/** * Copyright (c) Tiny Technologies, Inc. All rights reserved. * Licensed under the LGPL or a commercial license. * For LGPL see License.txt in the project root for license information. * For commercial licenses see https://www.tiny.cloud/ */ import { Node, Text } from '@ephox/dom-globals'; import { Adt, Option } from '@ephox/katamari'; import DOMUtils from 'tinymce/core/api/dom/DOMUtils'; import TreeWalker from 'tinymce/core/api/dom/TreeWalker'; // Note: This is duplicated with the TextPattern plugins `TextSearch` module, as there isn't really a nice way to share code across // plugins/themes. So if any changes are made here, be sure to keep changes synced with the textpattern plugin export interface OutcomeAdt extends Adt { fold: (aborted: () => R, edge: (edge: Node) => R, success: (info: T) => R) => R; match: (matches: {aborted: () => R, edge: (edge: Node) => R, success: (info: T) => R}) => R; } export interface Outcome { aborted: () => OutcomeAdt; edge: (elm: Node) => OutcomeAdt; success: (info: T) => OutcomeAdt; } export interface PhaseAdt extends Adt { fold: (abort: () => R, kontinue: () => R, finish: (info: T) => R) => R; match: (matches: {abort: () => R, kontinue: () => R, finish: (info: T) => R}) => R; } export interface Phase { abort: () => PhaseAdt; kontinue: () => PhaseAdt; finish: (info: T) => PhaseAdt; } export type ProcessCallback = (phase: Phase, element: Text, text: string, optOffset: Option) => PhaseAdt; const isText = (node: Node): node is Text => node.nodeType === Node.TEXT_NODE; const outcome = Adt.generate>([ { aborted: [ ] }, { edge: [ 'element' ] }, { success: [ 'info' ] } ]); const phase = Adt.generate>([ { abort: [ ] }, { kontinue: [ ] }, { finish: [ 'info' ] } ]); const repeat = (dom: DOMUtils, node: Node, offset: Option, process: ProcessCallback, walker: () => Node, recent: Option): OutcomeAdt => { const terminate = () => { return recent.fold(outcome.aborted, outcome.edge); }; const recurse = () => { const next = walker(); if (next) { return repeat(dom, next, Option.none(), process, walker, Option.some(node)); } else { return terminate(); } }; if (dom.isBlock(node)) { return terminate(); } else if (!isText(node)) { return recurse(); } else { const text = node.textContent; return process(phase, node, text, offset).fold(outcome.aborted, () => recurse(), outcome.success); } }; const repeatLeft = (dom: DOMUtils, node: Node, offset: number, process: ProcessCallback, rootNode?: Node): OutcomeAdt => { const walker = new TreeWalker(node, rootNode || dom.getRoot()); return repeat(dom, node, Option.some(offset), process, walker.prev, Option.none()); }; const repeatRight = (dom: DOMUtils, node: Node, offset: number, process: ProcessCallback, rootNode?: Node): OutcomeAdt => { const walker = new TreeWalker(node, rootNode || dom.getRoot()); return repeat(dom, node, Option.some(offset), process, walker.next, Option.none()); }; export { repeatLeft, repeatRight };