plugin.js 37 KB

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