plugin.js 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092
  1. /**
  2. * TinyMCE version 6.4.2 (2023-04-26)
  3. */
  4. (function () {
  5. 'use strict';
  6. const Cell = initial => {
  7. let value = initial;
  8. const get = () => {
  9. return value;
  10. };
  11. const set = v => {
  12. value = v;
  13. };
  14. return {
  15. get,
  16. set
  17. };
  18. };
  19. var global$3 = tinymce.util.Tools.resolve('tinymce.PluginManager');
  20. const hasProto = (v, constructor, predicate) => {
  21. var _a;
  22. if (predicate(v, constructor.prototype)) {
  23. return true;
  24. } else {
  25. return ((_a = v.constructor) === null || _a === void 0 ? void 0 : _a.name) === constructor.name;
  26. }
  27. };
  28. const typeOf = x => {
  29. const t = typeof x;
  30. if (x === null) {
  31. return 'null';
  32. } else if (t === 'object' && Array.isArray(x)) {
  33. return 'array';
  34. } else if (t === 'object' && hasProto(x, String, (o, proto) => proto.isPrototypeOf(o))) {
  35. return 'string';
  36. } else {
  37. return t;
  38. }
  39. };
  40. const isType$1 = type => value => typeOf(value) === type;
  41. const isSimpleType = type => value => typeof value === type;
  42. const isString = isType$1('string');
  43. const isArray = isType$1('array');
  44. const isBoolean = isSimpleType('boolean');
  45. const isNullable = a => a === null || a === undefined;
  46. const isNonNullable = a => !isNullable(a);
  47. const isNumber = isSimpleType('number');
  48. const noop = () => {
  49. };
  50. const constant = value => {
  51. return () => {
  52. return value;
  53. };
  54. };
  55. const always = constant(true);
  56. const punctuationStr = '[!-#%-*,-\\/:;?@\\[-\\]_{}\xA1\xAB\xB7\xBB\xBF;\xB7\u055A-\u055F\u0589\u058A\u05BE\u05C0\u05C3\u05C6\u05F3\u05F4\u0609\u060A\u060C\u060D\u061B\u061E\u061F\u066A-\u066D\u06D4\u0700-\u070D\u07F7-\u07F9\u0830-\u083E\u085E\u0964\u0965\u0970\u0DF4\u0E4F\u0E5A\u0E5B\u0F04-\u0F12\u0F3A-\u0F3D\u0F85\u0FD0-\u0FD4\u0FD9\u0FDA\u104A-\u104F\u10FB\u1361-\u1368\u1400\u166D\u166E\u169B\u169C\u16EB-\u16ED\u1735\u1736\u17D4-\u17D6\u17D8-\u17DA\u1800-\u180A\u1944\u1945\u1A1E\u1A1F\u1AA0-\u1AA6\u1AA8-\u1AAD\u1B5A-\u1B60\u1BFC-\u1BFF\u1C3B-\u1C3F\u1C7E\u1C7F\u1CD3\u2010-\u2027\u2030-\u2043\u2045-\u2051\u2053-\u205E\u207D\u207E\u208D\u208E\u3008\u3009\u2768-\u2775\u27C5\u27C6\u27E6-\u27EF\u2983-\u2998\u29D8-\u29DB\u29FC\u29FD\u2CF9-\u2CFC\u2CFE\u2CFF\u2D70\u2E00-\u2E2E\u2E30\u2E31\u3001-\u3003\u3008-\u3011\u3014-\u301F\u3030\u303D\u30A0\u30FB\uA4FE\uA4FF\uA60D-\uA60F\uA673\uA67E\uA6F2-\uA6F7\uA874-\uA877\uA8CE\uA8CF\uA8F8-\uA8FA\uA92E\uA92F\uA95F\uA9C1-\uA9CD\uA9DE\uA9DF\uAA5C-\uAA5F\uAADE\uAADF\uABEB\uFD3E\uFD3F\uFE10-\uFE19\uFE30-\uFE52\uFE54-\uFE61\uFE63\uFE68\uFE6A\uFE6B\uFF01-\uFF03\uFF05-\uFF0A\uFF0C-\uFF0F\uFF1A\uFF1B\uFF1F\uFF20\uFF3B-\uFF3D\uff3f\uFF5B\uFF5D\uFF5F-\uFF65]';
  57. const punctuation$1 = constant(punctuationStr);
  58. class Optional {
  59. constructor(tag, value) {
  60. this.tag = tag;
  61. this.value = value;
  62. }
  63. static some(value) {
  64. return new Optional(true, value);
  65. }
  66. static none() {
  67. return Optional.singletonNone;
  68. }
  69. fold(onNone, onSome) {
  70. if (this.tag) {
  71. return onSome(this.value);
  72. } else {
  73. return onNone();
  74. }
  75. }
  76. isSome() {
  77. return this.tag;
  78. }
  79. isNone() {
  80. return !this.tag;
  81. }
  82. map(mapper) {
  83. if (this.tag) {
  84. return Optional.some(mapper(this.value));
  85. } else {
  86. return Optional.none();
  87. }
  88. }
  89. bind(binder) {
  90. if (this.tag) {
  91. return binder(this.value);
  92. } else {
  93. return Optional.none();
  94. }
  95. }
  96. exists(predicate) {
  97. return this.tag && predicate(this.value);
  98. }
  99. forall(predicate) {
  100. return !this.tag || predicate(this.value);
  101. }
  102. filter(predicate) {
  103. if (!this.tag || predicate(this.value)) {
  104. return this;
  105. } else {
  106. return Optional.none();
  107. }
  108. }
  109. getOr(replacement) {
  110. return this.tag ? this.value : replacement;
  111. }
  112. or(replacement) {
  113. return this.tag ? this : replacement;
  114. }
  115. getOrThunk(thunk) {
  116. return this.tag ? this.value : thunk();
  117. }
  118. orThunk(thunk) {
  119. return this.tag ? this : thunk();
  120. }
  121. getOrDie(message) {
  122. if (!this.tag) {
  123. throw new Error(message !== null && message !== void 0 ? message : 'Called getOrDie on None');
  124. } else {
  125. return this.value;
  126. }
  127. }
  128. static from(value) {
  129. return isNonNullable(value) ? Optional.some(value) : Optional.none();
  130. }
  131. getOrNull() {
  132. return this.tag ? this.value : null;
  133. }
  134. getOrUndefined() {
  135. return this.value;
  136. }
  137. each(worker) {
  138. if (this.tag) {
  139. worker(this.value);
  140. }
  141. }
  142. toArray() {
  143. return this.tag ? [this.value] : [];
  144. }
  145. toString() {
  146. return this.tag ? `some(${ this.value })` : 'none()';
  147. }
  148. }
  149. Optional.singletonNone = new Optional(false);
  150. const punctuation = punctuation$1;
  151. var global$2 = tinymce.util.Tools.resolve('tinymce.Env');
  152. var global$1 = tinymce.util.Tools.resolve('tinymce.util.Tools');
  153. const nativeSlice = Array.prototype.slice;
  154. const nativePush = Array.prototype.push;
  155. const map = (xs, f) => {
  156. const len = xs.length;
  157. const r = new Array(len);
  158. for (let i = 0; i < len; i++) {
  159. const x = xs[i];
  160. r[i] = f(x, i);
  161. }
  162. return r;
  163. };
  164. const each = (xs, f) => {
  165. for (let i = 0, len = xs.length; i < len; i++) {
  166. const x = xs[i];
  167. f(x, i);
  168. }
  169. };
  170. const eachr = (xs, f) => {
  171. for (let i = xs.length - 1; i >= 0; i--) {
  172. const x = xs[i];
  173. f(x, i);
  174. }
  175. };
  176. const groupBy = (xs, f) => {
  177. if (xs.length === 0) {
  178. return [];
  179. } else {
  180. let wasType = f(xs[0]);
  181. const r = [];
  182. let group = [];
  183. for (let i = 0, len = xs.length; i < len; i++) {
  184. const x = xs[i];
  185. const type = f(x);
  186. if (type !== wasType) {
  187. r.push(group);
  188. group = [];
  189. }
  190. wasType = type;
  191. group.push(x);
  192. }
  193. if (group.length !== 0) {
  194. r.push(group);
  195. }
  196. return r;
  197. }
  198. };
  199. const foldl = (xs, f, acc) => {
  200. each(xs, (x, i) => {
  201. acc = f(acc, x, i);
  202. });
  203. return acc;
  204. };
  205. const flatten = xs => {
  206. const r = [];
  207. for (let i = 0, len = xs.length; i < len; ++i) {
  208. if (!isArray(xs[i])) {
  209. throw new Error('Arr.flatten item ' + i + ' was not an array, input: ' + xs);
  210. }
  211. nativePush.apply(r, xs[i]);
  212. }
  213. return r;
  214. };
  215. const bind = (xs, f) => flatten(map(xs, f));
  216. const sort = (xs, comparator) => {
  217. const copy = nativeSlice.call(xs, 0);
  218. copy.sort(comparator);
  219. return copy;
  220. };
  221. const hasOwnProperty = Object.hasOwnProperty;
  222. const has = (obj, key) => hasOwnProperty.call(obj, key);
  223. typeof window !== 'undefined' ? window : Function('return this;')();
  224. const DOCUMENT = 9;
  225. const DOCUMENT_FRAGMENT = 11;
  226. const ELEMENT = 1;
  227. const TEXT = 3;
  228. const type = element => element.dom.nodeType;
  229. const isType = t => element => type(element) === t;
  230. const isText$1 = isType(TEXT);
  231. const rawSet = (dom, key, value) => {
  232. if (isString(value) || isBoolean(value) || isNumber(value)) {
  233. dom.setAttribute(key, value + '');
  234. } else {
  235. console.error('Invalid call to Attribute.set. Key ', key, ':: Value ', value, ':: Element ', dom);
  236. throw new Error('Attribute value was not simple');
  237. }
  238. };
  239. const set = (element, key, value) => {
  240. rawSet(element.dom, key, value);
  241. };
  242. const fromHtml = (html, scope) => {
  243. const doc = scope || document;
  244. const div = doc.createElement('div');
  245. div.innerHTML = html;
  246. if (!div.hasChildNodes() || div.childNodes.length > 1) {
  247. const message = 'HTML does not have a single root node';
  248. console.error(message, html);
  249. throw new Error(message);
  250. }
  251. return fromDom(div.childNodes[0]);
  252. };
  253. const fromTag = (tag, scope) => {
  254. const doc = scope || document;
  255. const node = doc.createElement(tag);
  256. return fromDom(node);
  257. };
  258. const fromText = (text, scope) => {
  259. const doc = scope || document;
  260. const node = doc.createTextNode(text);
  261. return fromDom(node);
  262. };
  263. const fromDom = node => {
  264. if (node === null || node === undefined) {
  265. throw new Error('Node cannot be null or undefined');
  266. }
  267. return { dom: node };
  268. };
  269. const fromPoint = (docElm, x, y) => Optional.from(docElm.dom.elementFromPoint(x, y)).map(fromDom);
  270. const SugarElement = {
  271. fromHtml,
  272. fromTag,
  273. fromText,
  274. fromDom,
  275. fromPoint
  276. };
  277. const bypassSelector = dom => dom.nodeType !== ELEMENT && dom.nodeType !== DOCUMENT && dom.nodeType !== DOCUMENT_FRAGMENT || dom.childElementCount === 0;
  278. const all = (selector, scope) => {
  279. const base = scope === undefined ? document : scope.dom;
  280. return bypassSelector(base) ? [] : map(base.querySelectorAll(selector), SugarElement.fromDom);
  281. };
  282. const parent = element => Optional.from(element.dom.parentNode).map(SugarElement.fromDom);
  283. const children = element => map(element.dom.childNodes, SugarElement.fromDom);
  284. const spot = (element, offset) => ({
  285. element,
  286. offset
  287. });
  288. const leaf = (element, offset) => {
  289. const cs = children(element);
  290. return cs.length > 0 && offset < cs.length ? spot(cs[offset], 0) : spot(element, offset);
  291. };
  292. const before = (marker, element) => {
  293. const parent$1 = parent(marker);
  294. parent$1.each(v => {
  295. v.dom.insertBefore(element.dom, marker.dom);
  296. });
  297. };
  298. const append = (parent, element) => {
  299. parent.dom.appendChild(element.dom);
  300. };
  301. const wrap = (element, wrapper) => {
  302. before(element, wrapper);
  303. append(wrapper, element);
  304. };
  305. const NodeValue = (is, name) => {
  306. const get = element => {
  307. if (!is(element)) {
  308. throw new Error('Can only get ' + name + ' value of a ' + name + ' node');
  309. }
  310. return getOption(element).getOr('');
  311. };
  312. const getOption = element => is(element) ? Optional.from(element.dom.nodeValue) : Optional.none();
  313. const set = (element, value) => {
  314. if (!is(element)) {
  315. throw new Error('Can only set raw ' + name + ' value of a ' + name + ' node');
  316. }
  317. element.dom.nodeValue = value;
  318. };
  319. return {
  320. get,
  321. getOption,
  322. set
  323. };
  324. };
  325. const api = NodeValue(isText$1, 'text');
  326. const get$1 = element => api.get(element);
  327. const compareDocumentPosition = (a, b, match) => {
  328. return (a.compareDocumentPosition(b) & match) !== 0;
  329. };
  330. const documentPositionPreceding = (a, b) => {
  331. return compareDocumentPosition(a, b, Node.DOCUMENT_POSITION_PRECEDING);
  332. };
  333. const descendants = (scope, selector) => all(selector, scope);
  334. var global = tinymce.util.Tools.resolve('tinymce.dom.TreeWalker');
  335. const isSimpleBoundary = (dom, node) => dom.isBlock(node) || has(dom.schema.getVoidElements(), node.nodeName);
  336. const isContentEditableFalse = (dom, node) => dom.getContentEditable(node) === 'false';
  337. const isContentEditableTrueInCef = (dom, node) => dom.getContentEditable(node) === 'true' && node.parentNode && dom.getContentEditableParent(node.parentNode) === 'false';
  338. const isHidden = (dom, node) => !dom.isBlock(node) && has(dom.schema.getWhitespaceElements(), node.nodeName);
  339. const isBoundary = (dom, node) => isSimpleBoundary(dom, node) || isContentEditableFalse(dom, node) || isHidden(dom, node) || isContentEditableTrueInCef(dom, node);
  340. const isText = node => node.nodeType === 3;
  341. const nuSection = () => ({
  342. sOffset: 0,
  343. fOffset: 0,
  344. elements: []
  345. });
  346. const toLeaf = (node, offset) => leaf(SugarElement.fromDom(node), offset);
  347. const walk = (dom, walkerFn, startNode, callbacks, endNode, skipStart = true) => {
  348. let next = skipStart ? walkerFn(false) : startNode;
  349. while (next) {
  350. const isCefNode = isContentEditableFalse(dom, next);
  351. if (isCefNode || isHidden(dom, next)) {
  352. const stopWalking = isCefNode ? callbacks.cef(next) : callbacks.boundary(next);
  353. if (stopWalking) {
  354. break;
  355. } else {
  356. next = walkerFn(true);
  357. continue;
  358. }
  359. } else if (isSimpleBoundary(dom, next)) {
  360. if (callbacks.boundary(next)) {
  361. break;
  362. }
  363. } else if (isText(next)) {
  364. callbacks.text(next);
  365. }
  366. if (next === endNode) {
  367. break;
  368. } else {
  369. next = walkerFn(false);
  370. }
  371. }
  372. };
  373. const collectTextToBoundary = (dom, section, node, rootNode, forwards) => {
  374. var _a;
  375. if (isBoundary(dom, node)) {
  376. return;
  377. }
  378. const rootBlock = (_a = dom.getParent(rootNode, dom.isBlock)) !== null && _a !== void 0 ? _a : dom.getRoot();
  379. const walker = new global(node, rootBlock);
  380. const walkerFn = forwards ? walker.next.bind(walker) : walker.prev.bind(walker);
  381. walk(dom, walkerFn, node, {
  382. boundary: always,
  383. cef: always,
  384. text: next => {
  385. if (forwards) {
  386. section.fOffset += next.length;
  387. } else {
  388. section.sOffset += next.length;
  389. }
  390. section.elements.push(SugarElement.fromDom(next));
  391. }
  392. });
  393. };
  394. const collect = (dom, rootNode, startNode, endNode, callbacks, skipStart = true) => {
  395. const walker = new global(startNode, rootNode);
  396. const sections = [];
  397. let current = nuSection();
  398. collectTextToBoundary(dom, current, startNode, rootNode, false);
  399. const finishSection = () => {
  400. if (current.elements.length > 0) {
  401. sections.push(current);
  402. current = nuSection();
  403. }
  404. return false;
  405. };
  406. walk(dom, walker.next.bind(walker), startNode, {
  407. boundary: finishSection,
  408. cef: node => {
  409. finishSection();
  410. if (callbacks) {
  411. sections.push(...callbacks.cef(node));
  412. }
  413. return false;
  414. },
  415. text: next => {
  416. current.elements.push(SugarElement.fromDom(next));
  417. if (callbacks) {
  418. callbacks.text(next, current);
  419. }
  420. }
  421. }, endNode, skipStart);
  422. if (endNode) {
  423. collectTextToBoundary(dom, current, endNode, rootNode, true);
  424. }
  425. finishSection();
  426. return sections;
  427. };
  428. const collectRangeSections = (dom, rng) => {
  429. const start = toLeaf(rng.startContainer, rng.startOffset);
  430. const startNode = start.element.dom;
  431. const end = toLeaf(rng.endContainer, rng.endOffset);
  432. const endNode = end.element.dom;
  433. return collect(dom, rng.commonAncestorContainer, startNode, endNode, {
  434. text: (node, section) => {
  435. if (node === endNode) {
  436. section.fOffset += node.length - end.offset;
  437. } else if (node === startNode) {
  438. section.sOffset += start.offset;
  439. }
  440. },
  441. cef: node => {
  442. const sections = bind(descendants(SugarElement.fromDom(node), '*[contenteditable=true]'), e => {
  443. const ceTrueNode = e.dom;
  444. return collect(dom, ceTrueNode, ceTrueNode);
  445. });
  446. return sort(sections, (a, b) => documentPositionPreceding(a.elements[0].dom, b.elements[0].dom) ? 1 : -1);
  447. }
  448. }, false);
  449. };
  450. const fromRng = (dom, rng) => rng.collapsed ? [] : collectRangeSections(dom, rng);
  451. const fromNode = (dom, node) => {
  452. const rng = dom.createRng();
  453. rng.selectNode(node);
  454. return fromRng(dom, rng);
  455. };
  456. const fromNodes = (dom, nodes) => bind(nodes, node => fromNode(dom, node));
  457. const find$2 = (text, pattern, start = 0, finish = text.length) => {
  458. const regex = pattern.regex;
  459. regex.lastIndex = start;
  460. const results = [];
  461. let match;
  462. while (match = regex.exec(text)) {
  463. const matchedText = match[pattern.matchIndex];
  464. const matchStart = match.index + match[0].indexOf(matchedText);
  465. const matchFinish = matchStart + matchedText.length;
  466. if (matchFinish > finish) {
  467. break;
  468. }
  469. results.push({
  470. start: matchStart,
  471. finish: matchFinish
  472. });
  473. regex.lastIndex = matchFinish;
  474. }
  475. return results;
  476. };
  477. const extract = (elements, matches) => {
  478. const nodePositions = foldl(elements, (acc, element) => {
  479. const content = get$1(element);
  480. const start = acc.last;
  481. const finish = start + content.length;
  482. const positions = bind(matches, (match, matchIdx) => {
  483. if (match.start < finish && match.finish > start) {
  484. return [{
  485. element,
  486. start: Math.max(start, match.start) - start,
  487. finish: Math.min(finish, match.finish) - start,
  488. matchId: matchIdx
  489. }];
  490. } else {
  491. return [];
  492. }
  493. });
  494. return {
  495. results: acc.results.concat(positions),
  496. last: finish
  497. };
  498. }, {
  499. results: [],
  500. last: 0
  501. }).results;
  502. return groupBy(nodePositions, position => position.matchId);
  503. };
  504. const find$1 = (pattern, sections) => bind(sections, section => {
  505. const elements = section.elements;
  506. const content = map(elements, get$1).join('');
  507. const positions = find$2(content, pattern, section.sOffset, content.length - section.fOffset);
  508. return extract(elements, positions);
  509. });
  510. const mark = (matches, replacementNode) => {
  511. eachr(matches, (match, idx) => {
  512. eachr(match, pos => {
  513. const wrapper = SugarElement.fromDom(replacementNode.cloneNode(false));
  514. set(wrapper, 'data-mce-index', idx);
  515. const textNode = pos.element.dom;
  516. if (textNode.length === pos.finish && pos.start === 0) {
  517. wrap(pos.element, wrapper);
  518. } else {
  519. if (textNode.length !== pos.finish) {
  520. textNode.splitText(pos.finish);
  521. }
  522. const matchNode = textNode.splitText(pos.start);
  523. wrap(SugarElement.fromDom(matchNode), wrapper);
  524. }
  525. });
  526. });
  527. };
  528. const findAndMark = (dom, pattern, node, replacementNode) => {
  529. const textSections = fromNode(dom, node);
  530. const matches = find$1(pattern, textSections);
  531. mark(matches, replacementNode);
  532. return matches.length;
  533. };
  534. const findAndMarkInSelection = (dom, pattern, selection, replacementNode) => {
  535. const bookmark = selection.getBookmark();
  536. const nodes = dom.select('td[data-mce-selected],th[data-mce-selected]');
  537. const textSections = nodes.length > 0 ? fromNodes(dom, nodes) : fromRng(dom, selection.getRng());
  538. const matches = find$1(pattern, textSections);
  539. mark(matches, replacementNode);
  540. selection.moveToBookmark(bookmark);
  541. return matches.length;
  542. };
  543. const getElmIndex = elm => {
  544. return elm.getAttribute('data-mce-index');
  545. };
  546. const markAllMatches = (editor, currentSearchState, pattern, inSelection) => {
  547. const marker = editor.dom.create('span', { 'data-mce-bogus': 1 });
  548. marker.className = 'mce-match-marker';
  549. const node = editor.getBody();
  550. done(editor, currentSearchState, false);
  551. if (inSelection) {
  552. return findAndMarkInSelection(editor.dom, pattern, editor.selection, marker);
  553. } else {
  554. return findAndMark(editor.dom, pattern, node, marker);
  555. }
  556. };
  557. const unwrap = node => {
  558. var _a;
  559. const parentNode = node.parentNode;
  560. if (node.firstChild) {
  561. parentNode.insertBefore(node.firstChild, node);
  562. }
  563. (_a = node.parentNode) === null || _a === void 0 ? void 0 : _a.removeChild(node);
  564. };
  565. const findSpansByIndex = (editor, index) => {
  566. const spans = [];
  567. const nodes = global$1.toArray(editor.getBody().getElementsByTagName('span'));
  568. if (nodes.length) {
  569. for (let i = 0; i < nodes.length; i++) {
  570. const nodeIndex = getElmIndex(nodes[i]);
  571. if (nodeIndex === null || !nodeIndex.length) {
  572. continue;
  573. }
  574. if (nodeIndex === index.toString()) {
  575. spans.push(nodes[i]);
  576. }
  577. }
  578. }
  579. return spans;
  580. };
  581. const moveSelection = (editor, currentSearchState, forward) => {
  582. const searchState = currentSearchState.get();
  583. let testIndex = searchState.index;
  584. const dom = editor.dom;
  585. if (forward) {
  586. if (testIndex + 1 === searchState.count) {
  587. testIndex = 0;
  588. } else {
  589. testIndex++;
  590. }
  591. } else {
  592. if (testIndex - 1 === -1) {
  593. testIndex = searchState.count - 1;
  594. } else {
  595. testIndex--;
  596. }
  597. }
  598. dom.removeClass(findSpansByIndex(editor, searchState.index), 'mce-match-marker-selected');
  599. const spans = findSpansByIndex(editor, testIndex);
  600. if (spans.length) {
  601. dom.addClass(findSpansByIndex(editor, testIndex), 'mce-match-marker-selected');
  602. editor.selection.scrollIntoView(spans[0]);
  603. return testIndex;
  604. }
  605. return -1;
  606. };
  607. const removeNode = (dom, node) => {
  608. const parent = node.parentNode;
  609. dom.remove(node);
  610. if (parent && dom.isEmpty(parent)) {
  611. dom.remove(parent);
  612. }
  613. };
  614. const escapeSearchText = (text, wholeWord) => {
  615. const escapedText = text.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&').replace(/\s/g, '[^\\S\\r\\n\\uFEFF]');
  616. const wordRegex = '(' + escapedText + ')';
  617. return wholeWord ? `(?:^|\\s|${ punctuation() })` + wordRegex + `(?=$|\\s|${ punctuation() })` : wordRegex;
  618. };
  619. const find = (editor, currentSearchState, text, matchCase, wholeWord, inSelection) => {
  620. const selection = editor.selection;
  621. const escapedText = escapeSearchText(text, wholeWord);
  622. const isForwardSelection = selection.isForward();
  623. const pattern = {
  624. regex: new RegExp(escapedText, matchCase ? 'g' : 'gi'),
  625. matchIndex: 1
  626. };
  627. const count = markAllMatches(editor, currentSearchState, pattern, inSelection);
  628. if (global$2.browser.isSafari()) {
  629. selection.setRng(selection.getRng(), isForwardSelection);
  630. }
  631. if (count) {
  632. const newIndex = moveSelection(editor, currentSearchState, true);
  633. currentSearchState.set({
  634. index: newIndex,
  635. count,
  636. text,
  637. matchCase,
  638. wholeWord,
  639. inSelection
  640. });
  641. }
  642. return count;
  643. };
  644. const next = (editor, currentSearchState) => {
  645. const index = moveSelection(editor, currentSearchState, true);
  646. currentSearchState.set({
  647. ...currentSearchState.get(),
  648. index
  649. });
  650. };
  651. const prev = (editor, currentSearchState) => {
  652. const index = moveSelection(editor, currentSearchState, false);
  653. currentSearchState.set({
  654. ...currentSearchState.get(),
  655. index
  656. });
  657. };
  658. const isMatchSpan = node => {
  659. const matchIndex = getElmIndex(node);
  660. return matchIndex !== null && matchIndex.length > 0;
  661. };
  662. const replace = (editor, currentSearchState, text, forward, all) => {
  663. const searchState = currentSearchState.get();
  664. const currentIndex = searchState.index;
  665. let currentMatchIndex, nextIndex = currentIndex;
  666. forward = forward !== false;
  667. const node = editor.getBody();
  668. const nodes = global$1.grep(global$1.toArray(node.getElementsByTagName('span')), isMatchSpan);
  669. for (let i = 0; i < nodes.length; i++) {
  670. const nodeIndex = getElmIndex(nodes[i]);
  671. let matchIndex = currentMatchIndex = parseInt(nodeIndex, 10);
  672. if (all || matchIndex === searchState.index) {
  673. if (text.length) {
  674. nodes[i].innerText = text;
  675. unwrap(nodes[i]);
  676. } else {
  677. removeNode(editor.dom, nodes[i]);
  678. }
  679. while (nodes[++i]) {
  680. matchIndex = parseInt(getElmIndex(nodes[i]), 10);
  681. if (matchIndex === currentMatchIndex) {
  682. removeNode(editor.dom, nodes[i]);
  683. } else {
  684. i--;
  685. break;
  686. }
  687. }
  688. if (forward) {
  689. nextIndex--;
  690. }
  691. } else if (currentMatchIndex > currentIndex) {
  692. nodes[i].setAttribute('data-mce-index', String(currentMatchIndex - 1));
  693. }
  694. }
  695. currentSearchState.set({
  696. ...searchState,
  697. count: all ? 0 : searchState.count - 1,
  698. index: nextIndex
  699. });
  700. if (forward) {
  701. next(editor, currentSearchState);
  702. } else {
  703. prev(editor, currentSearchState);
  704. }
  705. return !all && currentSearchState.get().count > 0;
  706. };
  707. const done = (editor, currentSearchState, keepEditorSelection) => {
  708. let startContainer;
  709. let endContainer;
  710. const searchState = currentSearchState.get();
  711. const nodes = global$1.toArray(editor.getBody().getElementsByTagName('span'));
  712. for (let i = 0; i < nodes.length; i++) {
  713. const nodeIndex = getElmIndex(nodes[i]);
  714. if (nodeIndex !== null && nodeIndex.length) {
  715. if (nodeIndex === searchState.index.toString()) {
  716. if (!startContainer) {
  717. startContainer = nodes[i].firstChild;
  718. }
  719. endContainer = nodes[i].firstChild;
  720. }
  721. unwrap(nodes[i]);
  722. }
  723. }
  724. currentSearchState.set({
  725. ...searchState,
  726. index: -1,
  727. count: 0,
  728. text: ''
  729. });
  730. if (startContainer && endContainer) {
  731. const rng = editor.dom.createRng();
  732. rng.setStart(startContainer, 0);
  733. rng.setEnd(endContainer, endContainer.data.length);
  734. if (keepEditorSelection !== false) {
  735. editor.selection.setRng(rng);
  736. }
  737. return rng;
  738. } else {
  739. return undefined;
  740. }
  741. };
  742. const hasNext = (editor, currentSearchState) => currentSearchState.get().count > 1;
  743. const hasPrev = (editor, currentSearchState) => currentSearchState.get().count > 1;
  744. const get = (editor, currentState) => {
  745. const done$1 = keepEditorSelection => {
  746. return done(editor, currentState, keepEditorSelection);
  747. };
  748. const find$1 = (text, matchCase, wholeWord, inSelection = false) => {
  749. return find(editor, currentState, text, matchCase, wholeWord, inSelection);
  750. };
  751. const next$1 = () => {
  752. return next(editor, currentState);
  753. };
  754. const prev$1 = () => {
  755. return prev(editor, currentState);
  756. };
  757. const replace$1 = (text, forward, all) => {
  758. return replace(editor, currentState, text, forward, all);
  759. };
  760. return {
  761. done: done$1,
  762. find: find$1,
  763. next: next$1,
  764. prev: prev$1,
  765. replace: replace$1
  766. };
  767. };
  768. const singleton = doRevoke => {
  769. const subject = Cell(Optional.none());
  770. const revoke = () => subject.get().each(doRevoke);
  771. const clear = () => {
  772. revoke();
  773. subject.set(Optional.none());
  774. };
  775. const isSet = () => subject.get().isSome();
  776. const get = () => subject.get();
  777. const set = s => {
  778. revoke();
  779. subject.set(Optional.some(s));
  780. };
  781. return {
  782. clear,
  783. isSet,
  784. get,
  785. set
  786. };
  787. };
  788. const value = () => {
  789. const subject = singleton(noop);
  790. const on = f => subject.get().each(f);
  791. return {
  792. ...subject,
  793. on
  794. };
  795. };
  796. const open = (editor, currentSearchState) => {
  797. const dialogApi = value();
  798. editor.undoManager.add();
  799. const selectedText = global$1.trim(editor.selection.getContent({ format: 'text' }));
  800. const updateButtonStates = api => {
  801. api.setEnabled('next', hasNext(editor, currentSearchState));
  802. api.setEnabled('prev', hasPrev(editor, currentSearchState));
  803. };
  804. const updateSearchState = api => {
  805. const data = api.getData();
  806. const current = currentSearchState.get();
  807. currentSearchState.set({
  808. ...current,
  809. matchCase: data.matchcase,
  810. wholeWord: data.wholewords,
  811. inSelection: data.inselection
  812. });
  813. };
  814. const disableAll = (api, disable) => {
  815. const buttons = [
  816. 'replace',
  817. 'replaceall',
  818. 'prev',
  819. 'next'
  820. ];
  821. const toggle = name => api.setEnabled(name, !disable);
  822. each(buttons, toggle);
  823. };
  824. const notFoundAlert = api => {
  825. api.redial(getDialogSpec(true, api.getData()));
  826. };
  827. const focusButtonIfRequired = (api, name) => {
  828. if (global$2.browser.isSafari() && global$2.deviceType.isTouch() && (name === 'find' || name === 'replace' || name === 'replaceall')) {
  829. api.focus(name);
  830. }
  831. };
  832. const reset = api => {
  833. done(editor, currentSearchState, false);
  834. disableAll(api, true);
  835. updateButtonStates(api);
  836. };
  837. const doFind = api => {
  838. const data = api.getData();
  839. const last = currentSearchState.get();
  840. if (!data.findtext.length) {
  841. reset(api);
  842. return;
  843. }
  844. if (last.text === data.findtext && last.matchCase === data.matchcase && last.wholeWord === data.wholewords) {
  845. next(editor, currentSearchState);
  846. } else {
  847. const count = find(editor, currentSearchState, data.findtext, data.matchcase, data.wholewords, data.inselection);
  848. if (count <= 0) {
  849. notFoundAlert(api);
  850. }
  851. disableAll(api, count === 0);
  852. }
  853. updateButtonStates(api);
  854. };
  855. const initialState = currentSearchState.get();
  856. const initialData = {
  857. findtext: selectedText,
  858. replacetext: '',
  859. wholewords: initialState.wholeWord,
  860. matchcase: initialState.matchCase,
  861. inselection: initialState.inSelection
  862. };
  863. const getPanelItems = error => {
  864. const items = [
  865. {
  866. type: 'bar',
  867. items: [
  868. {
  869. type: 'input',
  870. name: 'findtext',
  871. placeholder: 'Find',
  872. maximized: true,
  873. inputMode: 'search'
  874. },
  875. {
  876. type: 'button',
  877. name: 'prev',
  878. text: 'Previous',
  879. icon: 'action-prev',
  880. enabled: false,
  881. borderless: true
  882. },
  883. {
  884. type: 'button',
  885. name: 'next',
  886. text: 'Next',
  887. icon: 'action-next',
  888. enabled: false,
  889. borderless: true
  890. }
  891. ]
  892. },
  893. {
  894. type: 'input',
  895. name: 'replacetext',
  896. placeholder: 'Replace with',
  897. inputMode: 'search'
  898. }
  899. ];
  900. if (error) {
  901. items.push({
  902. type: 'alertbanner',
  903. level: 'error',
  904. text: 'Could not find the specified string.',
  905. icon: 'warning'
  906. });
  907. }
  908. return items;
  909. };
  910. const getDialogSpec = (showNoMatchesAlertBanner, initialData) => ({
  911. title: 'Find and Replace',
  912. size: 'normal',
  913. body: {
  914. type: 'panel',
  915. items: getPanelItems(showNoMatchesAlertBanner)
  916. },
  917. buttons: [
  918. {
  919. type: 'menu',
  920. name: 'options',
  921. icon: 'preferences',
  922. tooltip: 'Preferences',
  923. align: 'start',
  924. items: [
  925. {
  926. type: 'togglemenuitem',
  927. name: 'matchcase',
  928. text: 'Match case'
  929. },
  930. {
  931. type: 'togglemenuitem',
  932. name: 'wholewords',
  933. text: 'Find whole words only'
  934. },
  935. {
  936. type: 'togglemenuitem',
  937. name: 'inselection',
  938. text: 'Find in selection'
  939. }
  940. ]
  941. },
  942. {
  943. type: 'custom',
  944. name: 'find',
  945. text: 'Find',
  946. primary: true
  947. },
  948. {
  949. type: 'custom',
  950. name: 'replace',
  951. text: 'Replace',
  952. enabled: false
  953. },
  954. {
  955. type: 'custom',
  956. name: 'replaceall',
  957. text: 'Replace all',
  958. enabled: false
  959. }
  960. ],
  961. initialData,
  962. onChange: (api, details) => {
  963. if (showNoMatchesAlertBanner) {
  964. api.redial(getDialogSpec(false, api.getData()));
  965. }
  966. if (details.name === 'findtext' && currentSearchState.get().count > 0) {
  967. reset(api);
  968. }
  969. },
  970. onAction: (api, details) => {
  971. const data = api.getData();
  972. switch (details.name) {
  973. case 'find':
  974. doFind(api);
  975. break;
  976. case 'replace':
  977. if (!replace(editor, currentSearchState, data.replacetext)) {
  978. reset(api);
  979. } else {
  980. updateButtonStates(api);
  981. }
  982. break;
  983. case 'replaceall':
  984. replace(editor, currentSearchState, data.replacetext, true, true);
  985. reset(api);
  986. break;
  987. case 'prev':
  988. prev(editor, currentSearchState);
  989. updateButtonStates(api);
  990. break;
  991. case 'next':
  992. next(editor, currentSearchState);
  993. updateButtonStates(api);
  994. break;
  995. case 'matchcase':
  996. case 'wholewords':
  997. case 'inselection':
  998. updateSearchState(api);
  999. reset(api);
  1000. break;
  1001. }
  1002. focusButtonIfRequired(api, details.name);
  1003. },
  1004. onSubmit: api => {
  1005. doFind(api);
  1006. focusButtonIfRequired(api, 'find');
  1007. },
  1008. onClose: () => {
  1009. editor.focus();
  1010. done(editor, currentSearchState);
  1011. editor.undoManager.add();
  1012. }
  1013. });
  1014. dialogApi.set(editor.windowManager.open(getDialogSpec(false, initialData), { inline: 'toolbar' }));
  1015. };
  1016. const register$1 = (editor, currentSearchState) => {
  1017. editor.addCommand('SearchReplace', () => {
  1018. open(editor, currentSearchState);
  1019. });
  1020. };
  1021. const showDialog = (editor, currentSearchState) => () => {
  1022. open(editor, currentSearchState);
  1023. };
  1024. const register = (editor, currentSearchState) => {
  1025. editor.ui.registry.addMenuItem('searchreplace', {
  1026. text: 'Find and replace...',
  1027. shortcut: 'Meta+F',
  1028. onAction: showDialog(editor, currentSearchState),
  1029. icon: 'search'
  1030. });
  1031. editor.ui.registry.addButton('searchreplace', {
  1032. tooltip: 'Find and replace',
  1033. onAction: showDialog(editor, currentSearchState),
  1034. icon: 'search'
  1035. });
  1036. editor.shortcuts.add('Meta+F', '', showDialog(editor, currentSearchState));
  1037. };
  1038. var Plugin = () => {
  1039. global$3.add('searchreplace', editor => {
  1040. const currentSearchState = Cell({
  1041. index: -1,
  1042. count: 0,
  1043. text: '',
  1044. matchCase: false,
  1045. wholeWord: false,
  1046. inSelection: false
  1047. });
  1048. register$1(editor, currentSearchState);
  1049. register(editor, currentSearchState);
  1050. return get(editor, currentSearchState);
  1051. });
  1052. };
  1053. Plugin();
  1054. })();