plugin.js 37 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163
  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.5.1 (2020-10-01)
  8. */
  9. (function () {
  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 Optional = {
  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 window !== 'undefined' ? window : Function('return this;')();
  256. var DOCUMENT = 9;
  257. var DOCUMENT_FRAGMENT = 11;
  258. var ELEMENT = 1;
  259. var TEXT = 3;
  260. var type = function (element) {
  261. return element.dom.nodeType;
  262. };
  263. var isType$1 = function (t) {
  264. return function (element) {
  265. return type(element) === t;
  266. };
  267. };
  268. var isText = isType$1(TEXT);
  269. var rawSet = function (dom, key, value) {
  270. if (isString(value) || isBoolean(value) || isNumber(value)) {
  271. dom.setAttribute(key, value + '');
  272. } else {
  273. console.error('Invalid call to Attribute.set. Key ', key, ':: Value ', value, ':: Element ', dom);
  274. throw new Error('Attribute value was not simple');
  275. }
  276. };
  277. var set = function (element, key, value) {
  278. rawSet(element.dom, key, value);
  279. };
  280. var compareDocumentPosition = function (a, b, match) {
  281. return (a.compareDocumentPosition(b) & match) !== 0;
  282. };
  283. var documentPositionPreceding = function (a, b) {
  284. return compareDocumentPosition(a, b, Node.DOCUMENT_POSITION_PRECEDING);
  285. };
  286. var fromHtml = function (html, scope) {
  287. var doc = scope || document;
  288. var div = doc.createElement('div');
  289. div.innerHTML = html;
  290. if (!div.hasChildNodes() || div.childNodes.length > 1) {
  291. console.error('HTML does not have a single root node', html);
  292. throw new Error('HTML must have a single root node');
  293. }
  294. return fromDom(div.childNodes[0]);
  295. };
  296. var fromTag = function (tag, scope) {
  297. var doc = scope || document;
  298. var node = doc.createElement(tag);
  299. return fromDom(node);
  300. };
  301. var fromText = function (text, scope) {
  302. var doc = scope || document;
  303. var node = doc.createTextNode(text);
  304. return fromDom(node);
  305. };
  306. var fromDom = function (node) {
  307. if (node === null || node === undefined) {
  308. throw new Error('Node cannot be null or undefined');
  309. }
  310. return { dom: node };
  311. };
  312. var fromPoint = function (docElm, x, y) {
  313. return Optional.from(docElm.dom.elementFromPoint(x, y)).map(fromDom);
  314. };
  315. var SugarElement = {
  316. fromHtml: fromHtml,
  317. fromTag: fromTag,
  318. fromText: fromText,
  319. fromDom: fromDom,
  320. fromPoint: fromPoint
  321. };
  322. var bypassSelector = function (dom) {
  323. return dom.nodeType !== ELEMENT && dom.nodeType !== DOCUMENT && dom.nodeType !== DOCUMENT_FRAGMENT || dom.childElementCount === 0;
  324. };
  325. var all = function (selector, scope) {
  326. var base = scope === undefined ? document : scope.dom;
  327. return bypassSelector(base) ? [] : map(base.querySelectorAll(selector), SugarElement.fromDom);
  328. };
  329. var parent = function (element) {
  330. return Optional.from(element.dom.parentNode).map(SugarElement.fromDom);
  331. };
  332. var children = function (element) {
  333. return map(element.dom.childNodes, SugarElement.fromDom);
  334. };
  335. var spot = function (element, offset) {
  336. return {
  337. element: element,
  338. offset: offset
  339. };
  340. };
  341. var leaf = function (element, offset) {
  342. var cs = children(element);
  343. return cs.length > 0 && offset < cs.length ? spot(cs[offset], 0) : spot(element, offset);
  344. };
  345. var before = function (marker, element) {
  346. var parent$1 = parent(marker);
  347. parent$1.each(function (v) {
  348. v.dom.insertBefore(element.dom, marker.dom);
  349. });
  350. };
  351. var append = function (parent, element) {
  352. parent.dom.appendChild(element.dom);
  353. };
  354. var wrap = function (element, wrapper) {
  355. before(element, wrapper);
  356. append(wrapper, element);
  357. };
  358. function NodeValue (is, name) {
  359. var get = function (element) {
  360. if (!is(element)) {
  361. throw new Error('Can only get ' + name + ' value of a ' + name + ' node');
  362. }
  363. return getOption(element).getOr('');
  364. };
  365. var getOption = function (element) {
  366. return is(element) ? Optional.from(element.dom.nodeValue) : Optional.none();
  367. };
  368. var set = function (element, value) {
  369. if (!is(element)) {
  370. throw new Error('Can only set raw ' + name + ' value of a ' + name + ' node');
  371. }
  372. element.dom.nodeValue = value;
  373. };
  374. return {
  375. get: get,
  376. getOption: getOption,
  377. set: set
  378. };
  379. }
  380. var api = NodeValue(isText, 'text');
  381. var get = function (element) {
  382. return api.get(element);
  383. };
  384. var descendants = function (scope, selector) {
  385. return all(selector, scope);
  386. };
  387. var global$2 = tinymce.util.Tools.resolve('tinymce.dom.TreeWalker');
  388. var isSimpleBoundary = function (dom, node) {
  389. return dom.isBlock(node) || has(dom.schema.getShortEndedElements(), node.nodeName);
  390. };
  391. var isContentEditableFalse = function (dom, node) {
  392. return dom.getContentEditable(node) === 'false';
  393. };
  394. var isContentEditableTrueInCef = function (dom, node) {
  395. return dom.getContentEditable(node) === 'true' && dom.getContentEditableParent(node.parentNode) === 'false';
  396. };
  397. var isHidden = function (dom, node) {
  398. return !dom.isBlock(node) && has(dom.schema.getWhiteSpaceElements(), node.nodeName);
  399. };
  400. var isBoundary = function (dom, node) {
  401. return isSimpleBoundary(dom, node) || isContentEditableFalse(dom, node) || isHidden(dom, node) || isContentEditableTrueInCef(dom, node);
  402. };
  403. var isText$1 = function (node) {
  404. return node.nodeType === 3;
  405. };
  406. var nuSection = function () {
  407. return {
  408. sOffset: 0,
  409. fOffset: 0,
  410. elements: []
  411. };
  412. };
  413. var toLeaf = function (node, offset) {
  414. return leaf(SugarElement.fromDom(node), offset);
  415. };
  416. var walk = function (dom, walkerFn, startNode, callbacks, endNode, skipStart) {
  417. if (skipStart === void 0) {
  418. skipStart = true;
  419. }
  420. var next = skipStart ? walkerFn(false) : startNode;
  421. while (next) {
  422. var isCefNode = isContentEditableFalse(dom, next);
  423. if (isCefNode || isHidden(dom, next)) {
  424. var stopWalking = isCefNode ? callbacks.cef(next) : callbacks.boundary(next);
  425. if (stopWalking) {
  426. break;
  427. } else {
  428. next = walkerFn(true);
  429. continue;
  430. }
  431. } else if (isSimpleBoundary(dom, next)) {
  432. if (callbacks.boundary(next)) {
  433. break;
  434. }
  435. } else if (isText$1(next)) {
  436. callbacks.text(next);
  437. }
  438. if (next === endNode) {
  439. break;
  440. } else {
  441. next = walkerFn(false);
  442. }
  443. }
  444. };
  445. var collectTextToBoundary = function (dom, section, node, rootNode, forwards) {
  446. if (isBoundary(dom, node)) {
  447. return;
  448. }
  449. var rootBlock = dom.getParent(rootNode, dom.isBlock);
  450. var walker = new global$2(node, rootBlock);
  451. var walkerFn = forwards ? walker.next : walker.prev;
  452. walk(dom, walkerFn, node, {
  453. boundary: always,
  454. cef: always,
  455. text: function (next) {
  456. if (forwards) {
  457. section.fOffset += next.length;
  458. } else {
  459. section.sOffset += next.length;
  460. }
  461. section.elements.push(SugarElement.fromDom(next));
  462. }
  463. });
  464. };
  465. var collect = function (dom, rootNode, startNode, endNode, callbacks, skipStart) {
  466. if (skipStart === void 0) {
  467. skipStart = true;
  468. }
  469. var walker = new global$2(startNode, rootNode);
  470. var sections = [];
  471. var current = nuSection();
  472. collectTextToBoundary(dom, current, startNode, rootNode, false);
  473. var finishSection = function () {
  474. if (current.elements.length > 0) {
  475. sections.push(current);
  476. current = nuSection();
  477. }
  478. return false;
  479. };
  480. walk(dom, walker.next, startNode, {
  481. boundary: finishSection,
  482. cef: function (node) {
  483. finishSection();
  484. if (callbacks) {
  485. sections.push.apply(sections, callbacks.cef(node));
  486. }
  487. return false;
  488. },
  489. text: function (next) {
  490. current.elements.push(SugarElement.fromDom(next));
  491. if (callbacks) {
  492. callbacks.text(next, current);
  493. }
  494. }
  495. }, endNode, skipStart);
  496. if (endNode) {
  497. collectTextToBoundary(dom, current, endNode, rootNode, true);
  498. }
  499. finishSection();
  500. return sections;
  501. };
  502. var collectRangeSections = function (dom, rng) {
  503. var start = toLeaf(rng.startContainer, rng.startOffset);
  504. var startNode = start.element.dom;
  505. var end = toLeaf(rng.endContainer, rng.endOffset);
  506. var endNode = end.element.dom;
  507. return collect(dom, rng.commonAncestorContainer, startNode, endNode, {
  508. text: function (node, section) {
  509. if (node === endNode) {
  510. section.fOffset += node.length - end.offset;
  511. } else if (node === startNode) {
  512. section.sOffset += start.offset;
  513. }
  514. },
  515. cef: function (node) {
  516. var sections = bind(descendants(SugarElement.fromDom(node), '*[contenteditable=true]'), function (e) {
  517. var ceTrueNode = e.dom;
  518. return collect(dom, ceTrueNode, ceTrueNode);
  519. });
  520. return sort(sections, function (a, b) {
  521. return documentPositionPreceding(a.elements[0].dom, b.elements[0].dom) ? 1 : -1;
  522. });
  523. }
  524. }, false);
  525. };
  526. var fromRng = function (dom, rng) {
  527. return rng.collapsed ? [] : collectRangeSections(dom, rng);
  528. };
  529. var fromNode = function (dom, node) {
  530. var rng = dom.createRng();
  531. rng.selectNode(node);
  532. return fromRng(dom, rng);
  533. };
  534. var fromNodes = function (dom, nodes) {
  535. return bind(nodes, function (node) {
  536. return fromNode(dom, node);
  537. });
  538. };
  539. var find = function (text, pattern, start, finish) {
  540. if (start === void 0) {
  541. start = 0;
  542. }
  543. if (finish === void 0) {
  544. finish = text.length;
  545. }
  546. var regex = pattern.regex;
  547. regex.lastIndex = start;
  548. var results = [];
  549. var match;
  550. while (match = regex.exec(text)) {
  551. var matchedText = match[pattern.matchIndex];
  552. var matchStart = match.index + match[0].indexOf(matchedText);
  553. var matchFinish = matchStart + matchedText.length;
  554. if (matchFinish > finish) {
  555. break;
  556. }
  557. results.push({
  558. start: matchStart,
  559. finish: matchFinish
  560. });
  561. regex.lastIndex = matchFinish;
  562. }
  563. return results;
  564. };
  565. var extract = function (elements, matches) {
  566. var nodePositions = foldl(elements, function (acc, element) {
  567. var content = get(element);
  568. var start = acc.last;
  569. var finish = start + content.length;
  570. var positions = bind(matches, function (match, matchIdx) {
  571. if (match.start < finish && match.finish > start) {
  572. return [{
  573. element: element,
  574. start: Math.max(start, match.start) - start,
  575. finish: Math.min(finish, match.finish) - start,
  576. matchId: matchIdx
  577. }];
  578. } else {
  579. return [];
  580. }
  581. });
  582. return {
  583. results: acc.results.concat(positions),
  584. last: finish
  585. };
  586. }, {
  587. results: [],
  588. last: 0
  589. }).results;
  590. return groupBy(nodePositions, function (position) {
  591. return position.matchId;
  592. });
  593. };
  594. var find$1 = function (pattern, sections) {
  595. return bind(sections, function (section) {
  596. var elements = section.elements;
  597. var content = map(elements, get).join('');
  598. var positions = find(content, pattern, section.sOffset, content.length - section.fOffset);
  599. return extract(elements, positions);
  600. });
  601. };
  602. var mark = function (matches, replacementNode) {
  603. eachr(matches, function (match, idx) {
  604. eachr(match, function (pos) {
  605. var wrapper = SugarElement.fromDom(replacementNode.cloneNode(false));
  606. set(wrapper, 'data-mce-index', idx);
  607. var textNode = pos.element.dom;
  608. if (textNode.length === pos.finish && pos.start === 0) {
  609. wrap(pos.element, wrapper);
  610. } else {
  611. if (textNode.length !== pos.finish) {
  612. textNode.splitText(pos.finish);
  613. }
  614. var matchNode = textNode.splitText(pos.start);
  615. wrap(SugarElement.fromDom(matchNode), wrapper);
  616. }
  617. });
  618. });
  619. };
  620. var findAndMark = function (dom, pattern, node, replacementNode) {
  621. var textSections = fromNode(dom, node);
  622. var matches = find$1(pattern, textSections);
  623. mark(matches, replacementNode);
  624. return matches.length;
  625. };
  626. var findAndMarkInSelection = function (dom, pattern, selection, replacementNode) {
  627. var bookmark = selection.getBookmark();
  628. var nodes = dom.select('td[data-mce-selected],th[data-mce-selected]');
  629. var textSections = nodes.length > 0 ? fromNodes(dom, nodes) : fromRng(dom, selection.getRng());
  630. var matches = find$1(pattern, textSections);
  631. mark(matches, replacementNode);
  632. selection.moveToBookmark(bookmark);
  633. return matches.length;
  634. };
  635. var getElmIndex = function (elm) {
  636. var value = elm.getAttribute('data-mce-index');
  637. if (typeof value === 'number') {
  638. return '' + value;
  639. }
  640. return value;
  641. };
  642. var markAllMatches = function (editor, currentSearchState, pattern, inSelection) {
  643. var marker = editor.dom.create('span', { 'data-mce-bogus': 1 });
  644. marker.className = 'mce-match-marker';
  645. var node = editor.getBody();
  646. done(editor, currentSearchState, false);
  647. if (inSelection) {
  648. return findAndMarkInSelection(editor.dom, pattern, editor.selection, marker);
  649. } else {
  650. return findAndMark(editor.dom, pattern, node, marker);
  651. }
  652. };
  653. var unwrap = function (node) {
  654. var parentNode = node.parentNode;
  655. if (node.firstChild) {
  656. parentNode.insertBefore(node.firstChild, node);
  657. }
  658. node.parentNode.removeChild(node);
  659. };
  660. var findSpansByIndex = function (editor, index) {
  661. var spans = [];
  662. var nodes = global$1.toArray(editor.getBody().getElementsByTagName('span'));
  663. if (nodes.length) {
  664. for (var i = 0; i < nodes.length; i++) {
  665. var nodeIndex = getElmIndex(nodes[i]);
  666. if (nodeIndex === null || !nodeIndex.length) {
  667. continue;
  668. }
  669. if (nodeIndex === index.toString()) {
  670. spans.push(nodes[i]);
  671. }
  672. }
  673. }
  674. return spans;
  675. };
  676. var moveSelection = function (editor, currentSearchState, forward) {
  677. var searchState = currentSearchState.get();
  678. var testIndex = searchState.index;
  679. var dom = editor.dom;
  680. forward = forward !== false;
  681. if (forward) {
  682. if (testIndex + 1 === searchState.count) {
  683. testIndex = 0;
  684. } else {
  685. testIndex++;
  686. }
  687. } else {
  688. if (testIndex - 1 === -1) {
  689. testIndex = searchState.count - 1;
  690. } else {
  691. testIndex--;
  692. }
  693. }
  694. dom.removeClass(findSpansByIndex(editor, searchState.index), 'mce-match-marker-selected');
  695. var spans = findSpansByIndex(editor, testIndex);
  696. if (spans.length) {
  697. dom.addClass(findSpansByIndex(editor, testIndex), 'mce-match-marker-selected');
  698. editor.selection.scrollIntoView(spans[0]);
  699. return testIndex;
  700. }
  701. return -1;
  702. };
  703. var removeNode = function (dom, node) {
  704. var parent = node.parentNode;
  705. dom.remove(node);
  706. if (dom.isEmpty(parent)) {
  707. dom.remove(parent);
  708. }
  709. };
  710. var escapeSearchText = function (text, wholeWord) {
  711. var escapedText = text.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&').replace(/\s/g, '[^\\S\\r\\n\\uFEFF]');
  712. var wordRegex = '(' + escapedText + ')';
  713. return wholeWord ? '(?:^|\\s|' + punctuation$1() + ')' + wordRegex + ('(?=$|\\s|' + punctuation$1() + ')') : wordRegex;
  714. };
  715. var find$2 = function (editor, currentSearchState, text, matchCase, wholeWord, inSelection) {
  716. var escapedText = escapeSearchText(text, wholeWord);
  717. var pattern = {
  718. regex: new RegExp(escapedText, matchCase ? 'g' : 'gi'),
  719. matchIndex: 1
  720. };
  721. var count = markAllMatches(editor, currentSearchState, pattern, inSelection);
  722. if (count) {
  723. var newIndex = moveSelection(editor, currentSearchState, true);
  724. currentSearchState.set({
  725. index: newIndex,
  726. count: count,
  727. text: text,
  728. matchCase: matchCase,
  729. wholeWord: wholeWord,
  730. inSelection: inSelection
  731. });
  732. }
  733. return count;
  734. };
  735. var next = function (editor, currentSearchState) {
  736. var index = moveSelection(editor, currentSearchState, true);
  737. currentSearchState.set(__assign(__assign({}, currentSearchState.get()), { index: index }));
  738. };
  739. var prev = function (editor, currentSearchState) {
  740. var index = moveSelection(editor, currentSearchState, false);
  741. currentSearchState.set(__assign(__assign({}, currentSearchState.get()), { index: index }));
  742. };
  743. var isMatchSpan = function (node) {
  744. var matchIndex = getElmIndex(node);
  745. return matchIndex !== null && matchIndex.length > 0;
  746. };
  747. var replace = function (editor, currentSearchState, text, forward, all) {
  748. var searchState = currentSearchState.get();
  749. var currentIndex = searchState.index;
  750. var currentMatchIndex, nextIndex = currentIndex;
  751. forward = forward !== false;
  752. var node = editor.getBody();
  753. var nodes = global$1.grep(global$1.toArray(node.getElementsByTagName('span')), isMatchSpan);
  754. for (var i = 0; i < nodes.length; i++) {
  755. var nodeIndex = getElmIndex(nodes[i]);
  756. var matchIndex = currentMatchIndex = parseInt(nodeIndex, 10);
  757. if (all || matchIndex === searchState.index) {
  758. if (text.length) {
  759. nodes[i].firstChild.nodeValue = text;
  760. unwrap(nodes[i]);
  761. } else {
  762. removeNode(editor.dom, nodes[i]);
  763. }
  764. while (nodes[++i]) {
  765. matchIndex = parseInt(getElmIndex(nodes[i]), 10);
  766. if (matchIndex === currentMatchIndex) {
  767. removeNode(editor.dom, nodes[i]);
  768. } else {
  769. i--;
  770. break;
  771. }
  772. }
  773. if (forward) {
  774. nextIndex--;
  775. }
  776. } else if (currentMatchIndex > currentIndex) {
  777. nodes[i].setAttribute('data-mce-index', String(currentMatchIndex - 1));
  778. }
  779. }
  780. currentSearchState.set(__assign(__assign({}, searchState), {
  781. count: all ? 0 : searchState.count - 1,
  782. index: nextIndex
  783. }));
  784. if (forward) {
  785. next(editor, currentSearchState);
  786. } else {
  787. prev(editor, currentSearchState);
  788. }
  789. return !all && currentSearchState.get().count > 0;
  790. };
  791. var done = function (editor, currentSearchState, keepEditorSelection) {
  792. var i, startContainer, endContainer;
  793. var searchState = currentSearchState.get();
  794. var nodes = global$1.toArray(editor.getBody().getElementsByTagName('span'));
  795. for (i = 0; i < nodes.length; i++) {
  796. var nodeIndex = getElmIndex(nodes[i]);
  797. if (nodeIndex !== null && nodeIndex.length) {
  798. if (nodeIndex === searchState.index.toString()) {
  799. if (!startContainer) {
  800. startContainer = nodes[i].firstChild;
  801. }
  802. endContainer = nodes[i].firstChild;
  803. }
  804. unwrap(nodes[i]);
  805. }
  806. }
  807. currentSearchState.set(__assign(__assign({}, searchState), {
  808. index: -1,
  809. count: 0,
  810. text: ''
  811. }));
  812. if (startContainer && endContainer) {
  813. var rng = editor.dom.createRng();
  814. rng.setStart(startContainer, 0);
  815. rng.setEnd(endContainer, endContainer.data.length);
  816. if (keepEditorSelection !== false) {
  817. editor.selection.setRng(rng);
  818. }
  819. return rng;
  820. }
  821. };
  822. var hasNext = function (editor, currentSearchState) {
  823. return currentSearchState.get().count > 1;
  824. };
  825. var hasPrev = function (editor, currentSearchState) {
  826. return currentSearchState.get().count > 1;
  827. };
  828. var get$1 = function (editor, currentState) {
  829. var done$1 = function (keepEditorSelection) {
  830. return done(editor, currentState, keepEditorSelection);
  831. };
  832. var find = function (text, matchCase, wholeWord, inSelection) {
  833. if (inSelection === void 0) {
  834. inSelection = false;
  835. }
  836. return find$2(editor, currentState, text, matchCase, wholeWord, inSelection);
  837. };
  838. var next$1 = function () {
  839. return next(editor, currentState);
  840. };
  841. var prev$1 = function () {
  842. return prev(editor, currentState);
  843. };
  844. var replace$1 = function (text, forward, all) {
  845. return replace(editor, currentState, text, forward, all);
  846. };
  847. return {
  848. done: done$1,
  849. find: find,
  850. next: next$1,
  851. prev: prev$1,
  852. replace: replace$1
  853. };
  854. };
  855. var value = function () {
  856. var subject = Cell(Optional.none());
  857. var clear = function () {
  858. return subject.set(Optional.none());
  859. };
  860. var set = function (s) {
  861. return subject.set(Optional.some(s));
  862. };
  863. var isSet = function () {
  864. return subject.get().isSome();
  865. };
  866. var on = function (f) {
  867. return subject.get().each(f);
  868. };
  869. return {
  870. clear: clear,
  871. set: set,
  872. isSet: isSet,
  873. on: on
  874. };
  875. };
  876. var global$3 = tinymce.util.Tools.resolve('tinymce.Env');
  877. var open = function (editor, currentSearchState) {
  878. var dialogApi = value();
  879. editor.undoManager.add();
  880. var selectedText = global$1.trim(editor.selection.getContent({ format: 'text' }));
  881. function updateButtonStates(api) {
  882. var updateNext = hasNext(editor, currentSearchState) ? api.enable : api.disable;
  883. updateNext('next');
  884. var updatePrev = hasPrev(editor, currentSearchState) ? api.enable : api.disable;
  885. updatePrev('prev');
  886. }
  887. var updateSearchState = function (api) {
  888. var data = api.getData();
  889. var current = currentSearchState.get();
  890. currentSearchState.set(__assign(__assign({}, current), {
  891. matchCase: data.matchcase,
  892. wholeWord: data.wholewords,
  893. inSelection: data.inselection
  894. }));
  895. };
  896. var disableAll = function (api, disable) {
  897. var buttons = [
  898. 'replace',
  899. 'replaceall',
  900. 'prev',
  901. 'next'
  902. ];
  903. var toggle = disable ? api.disable : api.enable;
  904. each(buttons, toggle);
  905. };
  906. function notFoundAlert(api) {
  907. editor.windowManager.alert('Could not find the specified string.', function () {
  908. api.focus('findtext');
  909. });
  910. }
  911. var focusButtonIfRequired = function (api, name) {
  912. if (global$3.browser.isSafari() && global$3.deviceType.isTouch() && (name === 'find' || name === 'replace' || name === 'replaceall')) {
  913. api.focus(name);
  914. }
  915. };
  916. var reset = function (api) {
  917. done(editor, currentSearchState, false);
  918. disableAll(api, true);
  919. updateButtonStates(api);
  920. };
  921. var doFind = function (api) {
  922. var data = api.getData();
  923. var last = currentSearchState.get();
  924. if (!data.findtext.length) {
  925. reset(api);
  926. return;
  927. }
  928. if (last.text === data.findtext && last.matchCase === data.matchcase && last.wholeWord === data.wholewords) {
  929. next(editor, currentSearchState);
  930. } else {
  931. var count = find$2(editor, currentSearchState, data.findtext, data.matchcase, data.wholewords, data.inselection);
  932. if (count <= 0) {
  933. notFoundAlert(api);
  934. }
  935. disableAll(api, count === 0);
  936. }
  937. updateButtonStates(api);
  938. };
  939. var initialState = currentSearchState.get();
  940. var initialData = {
  941. findtext: selectedText,
  942. replacetext: '',
  943. wholewords: initialState.wholeWord,
  944. matchcase: initialState.matchCase,
  945. inselection: initialState.inSelection
  946. };
  947. var spec = {
  948. title: 'Find and Replace',
  949. size: 'normal',
  950. body: {
  951. type: 'panel',
  952. items: [
  953. {
  954. type: 'bar',
  955. items: [
  956. {
  957. type: 'input',
  958. name: 'findtext',
  959. placeholder: 'Find',
  960. maximized: true,
  961. inputMode: 'search'
  962. },
  963. {
  964. type: 'button',
  965. name: 'prev',
  966. text: 'Previous',
  967. icon: 'action-prev',
  968. disabled: true,
  969. borderless: true
  970. },
  971. {
  972. type: 'button',
  973. name: 'next',
  974. text: 'Next',
  975. icon: 'action-next',
  976. disabled: true,
  977. borderless: true
  978. }
  979. ]
  980. },
  981. {
  982. type: 'input',
  983. name: 'replacetext',
  984. placeholder: 'Replace with',
  985. inputMode: 'search'
  986. }
  987. ]
  988. },
  989. buttons: [
  990. {
  991. type: 'menu',
  992. name: 'options',
  993. icon: 'preferences',
  994. tooltip: 'Preferences',
  995. align: 'start',
  996. items: [
  997. {
  998. type: 'togglemenuitem',
  999. name: 'matchcase',
  1000. text: 'Match case'
  1001. },
  1002. {
  1003. type: 'togglemenuitem',
  1004. name: 'wholewords',
  1005. text: 'Find whole words only'
  1006. },
  1007. {
  1008. type: 'togglemenuitem',
  1009. name: 'inselection',
  1010. text: 'Find in selection'
  1011. }
  1012. ]
  1013. },
  1014. {
  1015. type: 'custom',
  1016. name: 'find',
  1017. text: 'Find',
  1018. primary: true
  1019. },
  1020. {
  1021. type: 'custom',
  1022. name: 'replace',
  1023. text: 'Replace',
  1024. disabled: true
  1025. },
  1026. {
  1027. type: 'custom',
  1028. name: 'replaceall',
  1029. text: 'Replace All',
  1030. disabled: true
  1031. }
  1032. ],
  1033. initialData: initialData,
  1034. onChange: function (api, details) {
  1035. if (details.name === 'findtext' && currentSearchState.get().count > 0) {
  1036. reset(api);
  1037. }
  1038. },
  1039. onAction: function (api, details) {
  1040. var data = api.getData();
  1041. switch (details.name) {
  1042. case 'find':
  1043. doFind(api);
  1044. break;
  1045. case 'replace':
  1046. if (!replace(editor, currentSearchState, data.replacetext)) {
  1047. reset(api);
  1048. } else {
  1049. updateButtonStates(api);
  1050. }
  1051. break;
  1052. case 'replaceall':
  1053. replace(editor, currentSearchState, data.replacetext, true, true);
  1054. reset(api);
  1055. break;
  1056. case 'prev':
  1057. prev(editor, currentSearchState);
  1058. updateButtonStates(api);
  1059. break;
  1060. case 'next':
  1061. next(editor, currentSearchState);
  1062. updateButtonStates(api);
  1063. break;
  1064. case 'matchcase':
  1065. case 'wholewords':
  1066. case 'inselection':
  1067. updateSearchState(api);
  1068. reset(api);
  1069. break;
  1070. }
  1071. focusButtonIfRequired(api, details.name);
  1072. },
  1073. onSubmit: function (api) {
  1074. doFind(api);
  1075. focusButtonIfRequired(api, 'find');
  1076. },
  1077. onClose: function () {
  1078. editor.focus();
  1079. done(editor, currentSearchState);
  1080. editor.undoManager.add();
  1081. }
  1082. };
  1083. dialogApi.set(editor.windowManager.open(spec, { inline: 'toolbar' }));
  1084. };
  1085. var register = function (editor, currentSearchState) {
  1086. editor.addCommand('SearchReplace', function () {
  1087. open(editor, currentSearchState);
  1088. });
  1089. };
  1090. var showDialog = function (editor, currentSearchState) {
  1091. return function () {
  1092. open(editor, currentSearchState);
  1093. };
  1094. };
  1095. var register$1 = function (editor, currentSearchState) {
  1096. editor.ui.registry.addMenuItem('searchreplace', {
  1097. text: 'Find and replace...',
  1098. shortcut: 'Meta+F',
  1099. onAction: showDialog(editor, currentSearchState),
  1100. icon: 'search'
  1101. });
  1102. editor.ui.registry.addButton('searchreplace', {
  1103. tooltip: 'Find and replace',
  1104. onAction: showDialog(editor, currentSearchState),
  1105. icon: 'search'
  1106. });
  1107. editor.shortcuts.add('Meta+F', '', showDialog(editor, currentSearchState));
  1108. };
  1109. function Plugin () {
  1110. global.add('searchreplace', function (editor) {
  1111. var currentSearchState = Cell({
  1112. index: -1,
  1113. count: 0,
  1114. text: '',
  1115. matchCase: false,
  1116. wholeWord: false,
  1117. inSelection: false
  1118. });
  1119. register(editor, currentSearchState);
  1120. register$1(editor, currentSearchState);
  1121. return get$1(editor, currentSearchState);
  1122. });
  1123. }
  1124. Plugin();
  1125. }());