plugin.js 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872
  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.2.0 (2020-02-13)
  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. var clone = function () {
  20. return Cell(get());
  21. };
  22. return {
  23. get: get,
  24. set: set,
  25. clone: clone
  26. };
  27. };
  28. var global = tinymce.util.Tools.resolve('tinymce.PluginManager');
  29. var __assign = function () {
  30. __assign = Object.assign || function __assign(t) {
  31. for (var s, i = 1, n = arguments.length; i < n; i++) {
  32. s = arguments[i];
  33. for (var p in s)
  34. if (Object.prototype.hasOwnProperty.call(s, p))
  35. t[p] = s[p];
  36. }
  37. return t;
  38. };
  39. return __assign.apply(this, arguments);
  40. };
  41. var global$1 = tinymce.util.Tools.resolve('tinymce.util.Tools');
  42. function isContentEditableFalse(node) {
  43. return node && node.nodeType === 1 && node.contentEditable === 'false';
  44. }
  45. function findAndReplaceDOMText(regex, node, replacementNode, captureGroup, schema) {
  46. var m;
  47. var matches = [];
  48. var text, count = 0, doc;
  49. var blockElementsMap, hiddenTextElementsMap, shortEndedElementsMap;
  50. doc = node.ownerDocument;
  51. blockElementsMap = schema.getBlockElements();
  52. hiddenTextElementsMap = schema.getWhiteSpaceElements();
  53. shortEndedElementsMap = schema.getShortEndedElements();
  54. function getMatchIndexes(m, captureGroup) {
  55. captureGroup = captureGroup || 0;
  56. if (!m[0]) {
  57. throw new Error('findAndReplaceDOMText cannot handle zero-length matches');
  58. }
  59. var index = m.index;
  60. if (captureGroup > 0) {
  61. var cg = m[captureGroup];
  62. if (!cg) {
  63. throw new Error('Invalid capture group');
  64. }
  65. index += m[0].indexOf(cg);
  66. m[0] = cg;
  67. }
  68. return [
  69. index,
  70. index + m[0].length,
  71. [m[0]]
  72. ];
  73. }
  74. function getText(node) {
  75. var txt;
  76. if (node.nodeType === 3) {
  77. return node.data;
  78. }
  79. if (hiddenTextElementsMap[node.nodeName] && !blockElementsMap[node.nodeName]) {
  80. return '';
  81. }
  82. txt = '';
  83. if (isContentEditableFalse(node)) {
  84. return '\n';
  85. }
  86. if (blockElementsMap[node.nodeName] || shortEndedElementsMap[node.nodeName]) {
  87. txt += '\n';
  88. }
  89. if (node = node.firstChild) {
  90. do {
  91. txt += getText(node);
  92. } while (node = node.nextSibling);
  93. }
  94. return txt;
  95. }
  96. function stepThroughMatches(node, matches, replaceFn) {
  97. var startNode, endNode, startNodeIndex, endNodeIndex, innerNodes = [], atIndex = 0, curNode = node, matchLocation = matches.shift(), matchIndex = 0;
  98. out:
  99. while (true) {
  100. if (blockElementsMap[curNode.nodeName] || shortEndedElementsMap[curNode.nodeName] || isContentEditableFalse(curNode)) {
  101. atIndex++;
  102. }
  103. if (curNode.nodeType === 3) {
  104. if (!endNode && curNode.length + atIndex >= matchLocation[1]) {
  105. endNode = curNode;
  106. endNodeIndex = matchLocation[1] - atIndex;
  107. } else if (startNode) {
  108. innerNodes.push(curNode);
  109. }
  110. if (!startNode && curNode.length + atIndex > matchLocation[0]) {
  111. startNode = curNode;
  112. startNodeIndex = matchLocation[0] - atIndex;
  113. }
  114. atIndex += curNode.length;
  115. }
  116. if (startNode && endNode) {
  117. curNode = replaceFn({
  118. startNode: startNode,
  119. startNodeIndex: startNodeIndex,
  120. endNode: endNode,
  121. endNodeIndex: endNodeIndex,
  122. innerNodes: innerNodes,
  123. match: matchLocation[2],
  124. matchIndex: matchIndex
  125. });
  126. atIndex -= endNode.length - endNodeIndex;
  127. startNode = null;
  128. endNode = null;
  129. innerNodes = [];
  130. matchLocation = matches.shift();
  131. matchIndex++;
  132. if (!matchLocation) {
  133. break;
  134. }
  135. } else if ((!hiddenTextElementsMap[curNode.nodeName] || blockElementsMap[curNode.nodeName]) && curNode.firstChild) {
  136. if (!isContentEditableFalse(curNode)) {
  137. curNode = curNode.firstChild;
  138. continue;
  139. }
  140. } else if (curNode.nextSibling) {
  141. curNode = curNode.nextSibling;
  142. continue;
  143. }
  144. while (true) {
  145. if (curNode.nextSibling) {
  146. curNode = curNode.nextSibling;
  147. break;
  148. } else if (curNode.parentNode !== node) {
  149. curNode = curNode.parentNode;
  150. } else {
  151. break out;
  152. }
  153. }
  154. }
  155. }
  156. function genReplacer(nodeName) {
  157. var makeReplacementNode;
  158. if (typeof nodeName !== 'function') {
  159. var stencilNode_1 = nodeName.nodeType ? nodeName : doc.createElement(nodeName);
  160. makeReplacementNode = function (fill, matchIndex) {
  161. var clone = stencilNode_1.cloneNode(false);
  162. clone.setAttribute('data-mce-index', matchIndex);
  163. if (fill) {
  164. clone.appendChild(doc.createTextNode(fill));
  165. }
  166. return clone;
  167. };
  168. } else {
  169. makeReplacementNode = nodeName;
  170. }
  171. return function (range) {
  172. var before;
  173. var after;
  174. var parentNode;
  175. var startNode = range.startNode;
  176. var endNode = range.endNode;
  177. var matchIndex = range.matchIndex;
  178. if (startNode === endNode) {
  179. var node_1 = startNode;
  180. parentNode = node_1.parentNode;
  181. if (range.startNodeIndex > 0) {
  182. before = doc.createTextNode(node_1.data.substring(0, range.startNodeIndex));
  183. parentNode.insertBefore(before, node_1);
  184. }
  185. var el = makeReplacementNode(range.match[0], matchIndex);
  186. parentNode.insertBefore(el, node_1);
  187. if (range.endNodeIndex < node_1.length) {
  188. after = doc.createTextNode(node_1.data.substring(range.endNodeIndex));
  189. parentNode.insertBefore(after, node_1);
  190. }
  191. node_1.parentNode.removeChild(node_1);
  192. return el;
  193. }
  194. before = doc.createTextNode(startNode.data.substring(0, range.startNodeIndex));
  195. after = doc.createTextNode(endNode.data.substring(range.endNodeIndex));
  196. var elA = makeReplacementNode(startNode.data.substring(range.startNodeIndex), matchIndex);
  197. for (var i = 0, l = range.innerNodes.length; i < l; ++i) {
  198. var innerNode = range.innerNodes[i];
  199. var innerEl = makeReplacementNode(innerNode.data, matchIndex);
  200. innerNode.parentNode.replaceChild(innerEl, innerNode);
  201. }
  202. var elB = makeReplacementNode(endNode.data.substring(0, range.endNodeIndex), matchIndex);
  203. parentNode = startNode.parentNode;
  204. parentNode.insertBefore(before, startNode);
  205. parentNode.insertBefore(elA, startNode);
  206. parentNode.removeChild(startNode);
  207. parentNode = endNode.parentNode;
  208. parentNode.insertBefore(elB, endNode);
  209. parentNode.insertBefore(after, endNode);
  210. parentNode.removeChild(endNode);
  211. return elB;
  212. };
  213. }
  214. text = getText(node);
  215. if (!text) {
  216. return;
  217. }
  218. if (regex.global) {
  219. while (m = regex.exec(text)) {
  220. matches.push(getMatchIndexes(m, captureGroup));
  221. }
  222. } else {
  223. m = text.match(regex);
  224. matches.push(getMatchIndexes(m, captureGroup));
  225. }
  226. if (matches.length) {
  227. count = matches.length;
  228. stepThroughMatches(node, matches, genReplacer(replacementNode));
  229. }
  230. return count;
  231. }
  232. var FindReplaceText = { findAndReplaceDOMText: findAndReplaceDOMText };
  233. var getElmIndex = function (elm) {
  234. var value = elm.getAttribute('data-mce-index');
  235. if (typeof value === 'number') {
  236. return '' + value;
  237. }
  238. return value;
  239. };
  240. var markAllMatches = function (editor, currentSearchState, regex) {
  241. var node, marker;
  242. marker = editor.dom.create('span', { 'data-mce-bogus': 1 });
  243. marker.className = 'mce-match-marker';
  244. node = editor.getBody();
  245. done(editor, currentSearchState, false);
  246. return FindReplaceText.findAndReplaceDOMText(regex, node, marker, false, editor.schema);
  247. };
  248. var unwrap = function (node) {
  249. var parentNode = node.parentNode;
  250. if (node.firstChild) {
  251. parentNode.insertBefore(node.firstChild, node);
  252. }
  253. node.parentNode.removeChild(node);
  254. };
  255. var findSpansByIndex = function (editor, index) {
  256. var nodes;
  257. var spans = [];
  258. nodes = global$1.toArray(editor.getBody().getElementsByTagName('span'));
  259. if (nodes.length) {
  260. for (var i = 0; i < nodes.length; i++) {
  261. var nodeIndex = getElmIndex(nodes[i]);
  262. if (nodeIndex === null || !nodeIndex.length) {
  263. continue;
  264. }
  265. if (nodeIndex === index.toString()) {
  266. spans.push(nodes[i]);
  267. }
  268. }
  269. }
  270. return spans;
  271. };
  272. var moveSelection = function (editor, currentSearchState, forward) {
  273. var searchState = currentSearchState.get();
  274. var testIndex = searchState.index;
  275. var dom = editor.dom;
  276. forward = forward !== false;
  277. if (forward) {
  278. if (testIndex + 1 === searchState.count) {
  279. testIndex = 0;
  280. } else {
  281. testIndex++;
  282. }
  283. } else {
  284. if (testIndex - 1 === -1) {
  285. testIndex = searchState.count - 1;
  286. } else {
  287. testIndex--;
  288. }
  289. }
  290. dom.removeClass(findSpansByIndex(editor, searchState.index), 'mce-match-marker-selected');
  291. var spans = findSpansByIndex(editor, testIndex);
  292. if (spans.length) {
  293. dom.addClass(findSpansByIndex(editor, testIndex), 'mce-match-marker-selected');
  294. editor.selection.scrollIntoView(spans[0]);
  295. return testIndex;
  296. }
  297. return -1;
  298. };
  299. var removeNode = function (dom, node) {
  300. var parent = node.parentNode;
  301. dom.remove(node);
  302. if (dom.isEmpty(parent)) {
  303. dom.remove(parent);
  304. }
  305. };
  306. var escapeSearchText = function (text, wholeWord) {
  307. var escapedText = text.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&').replace(/\s/g, '[^\\S\\r\\n]');
  308. return wholeWord ? '\\b' + escapedText + '\\b' : escapedText;
  309. };
  310. var find = function (editor, currentSearchState, text, matchCase, wholeWord) {
  311. var escapedText = escapeSearchText(text, wholeWord);
  312. var count = markAllMatches(editor, currentSearchState, new RegExp(escapedText, matchCase ? 'g' : 'gi'));
  313. if (count) {
  314. var newIndex = moveSelection(editor, currentSearchState, true);
  315. currentSearchState.set({
  316. index: newIndex,
  317. count: count,
  318. text: text,
  319. matchCase: matchCase,
  320. wholeWord: wholeWord
  321. });
  322. }
  323. return count;
  324. };
  325. var next = function (editor, currentSearchState) {
  326. var index = moveSelection(editor, currentSearchState, true);
  327. currentSearchState.set(__assign(__assign({}, currentSearchState.get()), { index: index }));
  328. };
  329. var prev = function (editor, currentSearchState) {
  330. var index = moveSelection(editor, currentSearchState, false);
  331. currentSearchState.set(__assign(__assign({}, currentSearchState.get()), { index: index }));
  332. };
  333. var isMatchSpan = function (node) {
  334. var matchIndex = getElmIndex(node);
  335. return matchIndex !== null && matchIndex.length > 0;
  336. };
  337. var replace = function (editor, currentSearchState, text, forward, all) {
  338. var searchState = currentSearchState.get();
  339. var currentIndex = searchState.index;
  340. var i, nodes, node, matchIndex, currentMatchIndex, nextIndex = currentIndex;
  341. forward = forward !== false;
  342. node = editor.getBody();
  343. nodes = global$1.grep(global$1.toArray(node.getElementsByTagName('span')), isMatchSpan);
  344. for (i = 0; i < nodes.length; i++) {
  345. var nodeIndex = getElmIndex(nodes[i]);
  346. matchIndex = currentMatchIndex = parseInt(nodeIndex, 10);
  347. if (all || matchIndex === searchState.index) {
  348. if (text.length) {
  349. nodes[i].firstChild.nodeValue = text;
  350. unwrap(nodes[i]);
  351. } else {
  352. removeNode(editor.dom, nodes[i]);
  353. }
  354. while (nodes[++i]) {
  355. matchIndex = parseInt(getElmIndex(nodes[i]), 10);
  356. if (matchIndex === currentMatchIndex) {
  357. removeNode(editor.dom, nodes[i]);
  358. } else {
  359. i--;
  360. break;
  361. }
  362. }
  363. if (forward) {
  364. nextIndex--;
  365. }
  366. } else if (currentMatchIndex > currentIndex) {
  367. nodes[i].setAttribute('data-mce-index', String(currentMatchIndex - 1));
  368. }
  369. }
  370. currentSearchState.set(__assign(__assign({}, searchState), {
  371. count: all ? 0 : searchState.count - 1,
  372. index: nextIndex
  373. }));
  374. if (forward) {
  375. next(editor, currentSearchState);
  376. } else {
  377. prev(editor, currentSearchState);
  378. }
  379. return !all && currentSearchState.get().count > 0;
  380. };
  381. var done = function (editor, currentSearchState, keepEditorSelection) {
  382. var i, nodes, startContainer, endContainer;
  383. var searchState = currentSearchState.get();
  384. nodes = global$1.toArray(editor.getBody().getElementsByTagName('span'));
  385. for (i = 0; i < nodes.length; i++) {
  386. var nodeIndex = getElmIndex(nodes[i]);
  387. if (nodeIndex !== null && nodeIndex.length) {
  388. if (nodeIndex === searchState.index.toString()) {
  389. if (!startContainer) {
  390. startContainer = nodes[i].firstChild;
  391. }
  392. endContainer = nodes[i].firstChild;
  393. }
  394. unwrap(nodes[i]);
  395. }
  396. }
  397. currentSearchState.set(__assign(__assign({}, searchState), {
  398. index: -1,
  399. count: 0,
  400. text: ''
  401. }));
  402. if (startContainer && endContainer) {
  403. var rng = editor.dom.createRng();
  404. rng.setStart(startContainer, 0);
  405. rng.setEnd(endContainer, endContainer.data.length);
  406. if (keepEditorSelection !== false) {
  407. editor.selection.setRng(rng);
  408. }
  409. return rng;
  410. }
  411. };
  412. var hasNext = function (editor, currentSearchState) {
  413. return currentSearchState.get().count > 1;
  414. };
  415. var hasPrev = function (editor, currentSearchState) {
  416. return currentSearchState.get().count > 1;
  417. };
  418. var get = function (editor, currentState) {
  419. var done$1 = function (keepEditorSelection) {
  420. return done(editor, currentState, keepEditorSelection);
  421. };
  422. var find$1 = function (text, matchCase, wholeWord) {
  423. return find(editor, currentState, text, matchCase, wholeWord);
  424. };
  425. var next$1 = function () {
  426. return next(editor, currentState);
  427. };
  428. var prev$1 = function () {
  429. return prev(editor, currentState);
  430. };
  431. var replace$1 = function (text, forward, all) {
  432. return replace(editor, currentState, text, forward, all);
  433. };
  434. return {
  435. done: done$1,
  436. find: find$1,
  437. next: next$1,
  438. prev: prev$1,
  439. replace: replace$1
  440. };
  441. };
  442. var Api = { get: get };
  443. var noop = function () {
  444. };
  445. var constant = function (value) {
  446. return function () {
  447. return value;
  448. };
  449. };
  450. var never = constant(false);
  451. var always = constant(true);
  452. var none = function () {
  453. return NONE;
  454. };
  455. var NONE = function () {
  456. var eq = function (o) {
  457. return o.isNone();
  458. };
  459. var call = function (thunk) {
  460. return thunk();
  461. };
  462. var id = function (n) {
  463. return n;
  464. };
  465. var me = {
  466. fold: function (n, s) {
  467. return n();
  468. },
  469. is: never,
  470. isSome: never,
  471. isNone: always,
  472. getOr: id,
  473. getOrThunk: call,
  474. getOrDie: function (msg) {
  475. throw new Error(msg || 'error: getOrDie called on none.');
  476. },
  477. getOrNull: constant(null),
  478. getOrUndefined: constant(undefined),
  479. or: id,
  480. orThunk: call,
  481. map: none,
  482. each: noop,
  483. bind: none,
  484. exists: never,
  485. forall: always,
  486. filter: none,
  487. equals: eq,
  488. equals_: eq,
  489. toArray: function () {
  490. return [];
  491. },
  492. toString: constant('none()')
  493. };
  494. if (Object.freeze) {
  495. Object.freeze(me);
  496. }
  497. return me;
  498. }();
  499. var some = function (a) {
  500. var constant_a = constant(a);
  501. var self = function () {
  502. return me;
  503. };
  504. var bind = function (f) {
  505. return f(a);
  506. };
  507. var me = {
  508. fold: function (n, s) {
  509. return s(a);
  510. },
  511. is: function (v) {
  512. return a === v;
  513. },
  514. isSome: always,
  515. isNone: never,
  516. getOr: constant_a,
  517. getOrThunk: constant_a,
  518. getOrDie: constant_a,
  519. getOrNull: constant_a,
  520. getOrUndefined: constant_a,
  521. or: self,
  522. orThunk: self,
  523. map: function (f) {
  524. return some(f(a));
  525. },
  526. each: function (f) {
  527. f(a);
  528. },
  529. bind: bind,
  530. exists: bind,
  531. forall: bind,
  532. filter: function (f) {
  533. return f(a) ? me : NONE;
  534. },
  535. toArray: function () {
  536. return [a];
  537. },
  538. toString: function () {
  539. return 'some(' + a + ')';
  540. },
  541. equals: function (o) {
  542. return o.is(a);
  543. },
  544. equals_: function (o, elementEq) {
  545. return o.fold(never, function (b) {
  546. return elementEq(a, b);
  547. });
  548. }
  549. };
  550. return me;
  551. };
  552. var from = function (value) {
  553. return value === null || value === undefined ? NONE : some(value);
  554. };
  555. var Option = {
  556. some: some,
  557. none: none,
  558. from: from
  559. };
  560. var typeOf = function (x) {
  561. if (x === null) {
  562. return 'null';
  563. }
  564. var t = typeof x;
  565. if (t === 'object' && (Array.prototype.isPrototypeOf(x) || x.constructor && x.constructor.name === 'Array')) {
  566. return 'array';
  567. }
  568. if (t === 'object' && (String.prototype.isPrototypeOf(x) || x.constructor && x.constructor.name === 'String')) {
  569. return 'string';
  570. }
  571. return t;
  572. };
  573. var isType = function (type) {
  574. return function (value) {
  575. return typeOf(value) === type;
  576. };
  577. };
  578. var isFunction = isType('function');
  579. var nativeSlice = Array.prototype.slice;
  580. var each = function (xs, f) {
  581. for (var i = 0, len = xs.length; i < len; i++) {
  582. var x = xs[i];
  583. f(x, i);
  584. }
  585. };
  586. var from$1 = isFunction(Array.from) ? Array.from : function (x) {
  587. return nativeSlice.call(x);
  588. };
  589. var value = function () {
  590. var subject = Cell(Option.none());
  591. var clear = function () {
  592. subject.set(Option.none());
  593. };
  594. var set = function (s) {
  595. subject.set(Option.some(s));
  596. };
  597. var on = function (f) {
  598. subject.get().each(f);
  599. };
  600. var isSet = function () {
  601. return subject.get().isSome();
  602. };
  603. return {
  604. clear: clear,
  605. set: set,
  606. isSet: isSet,
  607. on: on
  608. };
  609. };
  610. var global$2 = tinymce.util.Tools.resolve('tinymce.Env');
  611. var open = function (editor, currentSearchState) {
  612. var dialogApi = value();
  613. editor.undoManager.add();
  614. var selectedText = global$1.trim(editor.selection.getContent({ format: 'text' }));
  615. function updateButtonStates(api) {
  616. var updateNext = hasNext(editor, currentSearchState) ? api.enable : api.disable;
  617. updateNext('next');
  618. var updatePrev = hasPrev(editor, currentSearchState) ? api.enable : api.disable;
  619. updatePrev('prev');
  620. }
  621. var updateSearchState = function (api) {
  622. var data = api.getData();
  623. var current = currentSearchState.get();
  624. currentSearchState.set(__assign(__assign({}, current), {
  625. matchCase: data.matchcase,
  626. wholeWord: data.wholewords
  627. }));
  628. };
  629. var disableAll = function (api, disable) {
  630. var buttons = [
  631. 'replace',
  632. 'replaceall',
  633. 'prev',
  634. 'next'
  635. ];
  636. var toggle = disable ? api.disable : api.enable;
  637. each(buttons, toggle);
  638. };
  639. function notFoundAlert(api) {
  640. editor.windowManager.alert('Could not find the specified string.', function () {
  641. api.focus('findtext');
  642. });
  643. }
  644. var focusButtonIfRequired = function (api, name) {
  645. if (global$2.browser.isSafari() && global$2.deviceType.isTouch() && (name === 'find' || name === 'replace' || name === 'replaceall')) {
  646. api.focus(name);
  647. }
  648. };
  649. var reset = function (api) {
  650. done(editor, currentSearchState, false);
  651. disableAll(api, true);
  652. updateButtonStates(api);
  653. };
  654. var doFind = function (api) {
  655. var data = api.getData();
  656. var last = currentSearchState.get();
  657. if (!data.findtext.length) {
  658. reset(api);
  659. return;
  660. }
  661. if (last.text === data.findtext && last.matchCase === data.matchcase && last.wholeWord === data.wholewords) {
  662. next(editor, currentSearchState);
  663. } else {
  664. var count = find(editor, currentSearchState, data.findtext, data.matchcase, data.wholewords);
  665. if (count <= 0) {
  666. notFoundAlert(api);
  667. }
  668. disableAll(api, count === 0);
  669. }
  670. updateButtonStates(api);
  671. };
  672. var initialState = currentSearchState.get();
  673. var initialData = {
  674. findtext: selectedText,
  675. replacetext: '',
  676. wholewords: initialState.wholeWord,
  677. matchcase: initialState.matchCase
  678. };
  679. var spec = {
  680. title: 'Find and Replace',
  681. size: 'normal',
  682. body: {
  683. type: 'panel',
  684. items: [
  685. {
  686. type: 'bar',
  687. items: [
  688. {
  689. type: 'input',
  690. name: 'findtext',
  691. placeholder: 'Find',
  692. maximized: true,
  693. inputMode: 'search'
  694. },
  695. {
  696. type: 'button',
  697. name: 'prev',
  698. text: 'Previous',
  699. icon: 'action-prev',
  700. disabled: true,
  701. borderless: true
  702. },
  703. {
  704. type: 'button',
  705. name: 'next',
  706. text: 'Next',
  707. icon: 'action-next',
  708. disabled: true,
  709. borderless: true
  710. }
  711. ]
  712. },
  713. {
  714. type: 'input',
  715. name: 'replacetext',
  716. placeholder: 'Replace with',
  717. inputMode: 'search'
  718. }
  719. ]
  720. },
  721. buttons: [
  722. {
  723. type: 'menu',
  724. name: 'options',
  725. icon: 'preferences',
  726. tooltip: 'Preferences',
  727. align: 'start',
  728. items: [
  729. {
  730. type: 'togglemenuitem',
  731. name: 'matchcase',
  732. text: 'Match case'
  733. },
  734. {
  735. type: 'togglemenuitem',
  736. name: 'wholewords',
  737. text: 'Find whole words only'
  738. }
  739. ]
  740. },
  741. {
  742. type: 'custom',
  743. name: 'find',
  744. text: 'Find',
  745. primary: true
  746. },
  747. {
  748. type: 'custom',
  749. name: 'replace',
  750. text: 'Replace',
  751. disabled: true
  752. },
  753. {
  754. type: 'custom',
  755. name: 'replaceall',
  756. text: 'Replace All',
  757. disabled: true
  758. }
  759. ],
  760. initialData: initialData,
  761. onChange: function (api, details) {
  762. if (details.name === 'findtext' && currentSearchState.get().count > 0) {
  763. reset(api);
  764. }
  765. },
  766. onAction: function (api, details) {
  767. var data = api.getData();
  768. switch (details.name) {
  769. case 'find':
  770. doFind(api);
  771. break;
  772. case 'replace':
  773. if (!replace(editor, currentSearchState, data.replacetext)) {
  774. reset(api);
  775. } else {
  776. updateButtonStates(api);
  777. }
  778. break;
  779. case 'replaceall':
  780. replace(editor, currentSearchState, data.replacetext, true, true);
  781. reset(api);
  782. break;
  783. case 'prev':
  784. prev(editor, currentSearchState);
  785. updateButtonStates(api);
  786. break;
  787. case 'next':
  788. next(editor, currentSearchState);
  789. updateButtonStates(api);
  790. break;
  791. case 'matchcase':
  792. case 'wholewords':
  793. updateSearchState(api);
  794. reset(api);
  795. break;
  796. }
  797. focusButtonIfRequired(api, details.name);
  798. },
  799. onSubmit: function (api) {
  800. doFind(api);
  801. focusButtonIfRequired(api, 'find');
  802. },
  803. onClose: function () {
  804. editor.focus();
  805. done(editor, currentSearchState);
  806. editor.undoManager.add();
  807. }
  808. };
  809. dialogApi.set(editor.windowManager.open(spec, { inline: 'toolbar' }));
  810. };
  811. var Dialog = { open: open };
  812. var register = function (editor, currentSearchState) {
  813. editor.addCommand('SearchReplace', function () {
  814. Dialog.open(editor, currentSearchState);
  815. });
  816. };
  817. var Commands = { register: register };
  818. var showDialog = function (editor, currentSearchState) {
  819. return function () {
  820. Dialog.open(editor, currentSearchState);
  821. };
  822. };
  823. var register$1 = function (editor, currentSearchState) {
  824. editor.ui.registry.addMenuItem('searchreplace', {
  825. text: 'Find and replace...',
  826. shortcut: 'Meta+F',
  827. onAction: showDialog(editor, currentSearchState),
  828. icon: 'search'
  829. });
  830. editor.ui.registry.addButton('searchreplace', {
  831. tooltip: 'Find and replace',
  832. onAction: showDialog(editor, currentSearchState),
  833. icon: 'search'
  834. });
  835. editor.shortcuts.add('Meta+F', '', showDialog(editor, currentSearchState));
  836. };
  837. var Buttons = { register: register$1 };
  838. function Plugin () {
  839. global.add('searchreplace', function (editor) {
  840. var currentSearchState = Cell({
  841. index: -1,
  842. count: 0,
  843. text: '',
  844. matchCase: false,
  845. wholeWord: false
  846. });
  847. Commands.register(editor, currentSearchState);
  848. Buttons.register(editor, currentSearchState);
  849. return Api.get(editor, currentSearchState);
  850. });
  851. }
  852. Plugin();
  853. }());