plugin.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603
  1. (function () {
  2. var searchreplace = (function () {
  3. 'use strict';
  4. var Cell = function (initial) {
  5. var value = initial;
  6. var get = function () {
  7. return value;
  8. };
  9. var set = function (v) {
  10. value = v;
  11. };
  12. var clone = function () {
  13. return Cell(get());
  14. };
  15. return {
  16. get: get,
  17. set: set,
  18. clone: clone
  19. };
  20. };
  21. var global = tinymce.util.Tools.resolve('tinymce.PluginManager');
  22. var global$1 = tinymce.util.Tools.resolve('tinymce.util.Tools');
  23. function isContentEditableFalse(node) {
  24. return node && node.nodeType === 1 && node.contentEditable === 'false';
  25. }
  26. function findAndReplaceDOMText(regex, node, replacementNode, captureGroup, schema) {
  27. var m;
  28. var matches = [];
  29. var text, count = 0, doc;
  30. var blockElementsMap, hiddenTextElementsMap, shortEndedElementsMap;
  31. doc = node.ownerDocument;
  32. blockElementsMap = schema.getBlockElements();
  33. hiddenTextElementsMap = schema.getWhiteSpaceElements();
  34. shortEndedElementsMap = schema.getShortEndedElements();
  35. function getMatchIndexes(m, captureGroup) {
  36. captureGroup = captureGroup || 0;
  37. if (!m[0]) {
  38. throw new Error('findAndReplaceDOMText cannot handle zero-length matches');
  39. }
  40. var index = m.index;
  41. if (captureGroup > 0) {
  42. var cg = m[captureGroup];
  43. if (!cg) {
  44. throw new Error('Invalid capture group');
  45. }
  46. index += m[0].indexOf(cg);
  47. m[0] = cg;
  48. }
  49. return [
  50. index,
  51. index + m[0].length,
  52. [m[0]]
  53. ];
  54. }
  55. function getText(node) {
  56. var txt;
  57. if (node.nodeType === 3) {
  58. return node.data;
  59. }
  60. if (hiddenTextElementsMap[node.nodeName] && !blockElementsMap[node.nodeName]) {
  61. return '';
  62. }
  63. txt = '';
  64. if (isContentEditableFalse(node)) {
  65. return '\n';
  66. }
  67. if (blockElementsMap[node.nodeName] || shortEndedElementsMap[node.nodeName]) {
  68. txt += '\n';
  69. }
  70. if (node = node.firstChild) {
  71. do {
  72. txt += getText(node);
  73. } while (node = node.nextSibling);
  74. }
  75. return txt;
  76. }
  77. function stepThroughMatches(node, matches, replaceFn) {
  78. var startNode, endNode, startNodeIndex, endNodeIndex, innerNodes = [], atIndex = 0, curNode = node, matchLocation = matches.shift(), matchIndex = 0;
  79. out:
  80. while (true) {
  81. if (blockElementsMap[curNode.nodeName] || shortEndedElementsMap[curNode.nodeName] || isContentEditableFalse(curNode)) {
  82. atIndex++;
  83. }
  84. if (curNode.nodeType === 3) {
  85. if (!endNode && curNode.length + atIndex >= matchLocation[1]) {
  86. endNode = curNode;
  87. endNodeIndex = matchLocation[1] - atIndex;
  88. } else if (startNode) {
  89. innerNodes.push(curNode);
  90. }
  91. if (!startNode && curNode.length + atIndex > matchLocation[0]) {
  92. startNode = curNode;
  93. startNodeIndex = matchLocation[0] - atIndex;
  94. }
  95. atIndex += curNode.length;
  96. }
  97. if (startNode && endNode) {
  98. curNode = replaceFn({
  99. startNode: startNode,
  100. startNodeIndex: startNodeIndex,
  101. endNode: endNode,
  102. endNodeIndex: endNodeIndex,
  103. innerNodes: innerNodes,
  104. match: matchLocation[2],
  105. matchIndex: matchIndex
  106. });
  107. atIndex -= endNode.length - endNodeIndex;
  108. startNode = null;
  109. endNode = null;
  110. innerNodes = [];
  111. matchLocation = matches.shift();
  112. matchIndex++;
  113. if (!matchLocation) {
  114. break;
  115. }
  116. } else if ((!hiddenTextElementsMap[curNode.nodeName] || blockElementsMap[curNode.nodeName]) && curNode.firstChild) {
  117. if (!isContentEditableFalse(curNode)) {
  118. curNode = curNode.firstChild;
  119. continue;
  120. }
  121. } else if (curNode.nextSibling) {
  122. curNode = curNode.nextSibling;
  123. continue;
  124. }
  125. while (true) {
  126. if (curNode.nextSibling) {
  127. curNode = curNode.nextSibling;
  128. break;
  129. } else if (curNode.parentNode !== node) {
  130. curNode = curNode.parentNode;
  131. } else {
  132. break out;
  133. }
  134. }
  135. }
  136. }
  137. function genReplacer(nodeName) {
  138. var makeReplacementNode;
  139. if (typeof nodeName !== 'function') {
  140. var stencilNode_1 = nodeName.nodeType ? nodeName : doc.createElement(nodeName);
  141. makeReplacementNode = function (fill, matchIndex) {
  142. var clone = stencilNode_1.cloneNode(false);
  143. clone.setAttribute('data-mce-index', matchIndex);
  144. if (fill) {
  145. clone.appendChild(doc.createTextNode(fill));
  146. }
  147. return clone;
  148. };
  149. } else {
  150. makeReplacementNode = nodeName;
  151. }
  152. return function (range) {
  153. var before;
  154. var after;
  155. var parentNode;
  156. var startNode = range.startNode;
  157. var endNode = range.endNode;
  158. var matchIndex = range.matchIndex;
  159. if (startNode === endNode) {
  160. var node_1 = startNode;
  161. parentNode = node_1.parentNode;
  162. if (range.startNodeIndex > 0) {
  163. before = doc.createTextNode(node_1.data.substring(0, range.startNodeIndex));
  164. parentNode.insertBefore(before, node_1);
  165. }
  166. var el = makeReplacementNode(range.match[0], matchIndex);
  167. parentNode.insertBefore(el, node_1);
  168. if (range.endNodeIndex < node_1.length) {
  169. after = doc.createTextNode(node_1.data.substring(range.endNodeIndex));
  170. parentNode.insertBefore(after, node_1);
  171. }
  172. node_1.parentNode.removeChild(node_1);
  173. return el;
  174. }
  175. before = doc.createTextNode(startNode.data.substring(0, range.startNodeIndex));
  176. after = doc.createTextNode(endNode.data.substring(range.endNodeIndex));
  177. var elA = makeReplacementNode(startNode.data.substring(range.startNodeIndex), matchIndex);
  178. var innerEls = [];
  179. for (var i = 0, l = range.innerNodes.length; i < l; ++i) {
  180. var innerNode = range.innerNodes[i];
  181. var innerEl = makeReplacementNode(innerNode.data, matchIndex);
  182. innerNode.parentNode.replaceChild(innerEl, innerNode);
  183. innerEls.push(innerEl);
  184. }
  185. var elB = makeReplacementNode(endNode.data.substring(0, range.endNodeIndex), matchIndex);
  186. parentNode = startNode.parentNode;
  187. parentNode.insertBefore(before, startNode);
  188. parentNode.insertBefore(elA, startNode);
  189. parentNode.removeChild(startNode);
  190. parentNode = endNode.parentNode;
  191. parentNode.insertBefore(elB, endNode);
  192. parentNode.insertBefore(after, endNode);
  193. parentNode.removeChild(endNode);
  194. return elB;
  195. };
  196. }
  197. text = getText(node);
  198. if (!text) {
  199. return;
  200. }
  201. if (regex.global) {
  202. while (m = regex.exec(text)) {
  203. matches.push(getMatchIndexes(m, captureGroup));
  204. }
  205. } else {
  206. m = text.match(regex);
  207. matches.push(getMatchIndexes(m, captureGroup));
  208. }
  209. if (matches.length) {
  210. count = matches.length;
  211. stepThroughMatches(node, matches, genReplacer(replacementNode));
  212. }
  213. return count;
  214. }
  215. var $_2vfkuyk1jkmcwprj = { findAndReplaceDOMText: findAndReplaceDOMText };
  216. var getElmIndex = function (elm) {
  217. var value = elm.getAttribute('data-mce-index');
  218. if (typeof value === 'number') {
  219. return '' + value;
  220. }
  221. return value;
  222. };
  223. var markAllMatches = function (editor, currentIndexState, regex) {
  224. var node, marker;
  225. marker = editor.dom.create('span', { 'data-mce-bogus': 1 });
  226. marker.className = 'mce-match-marker';
  227. node = editor.getBody();
  228. done(editor, currentIndexState, false);
  229. return $_2vfkuyk1jkmcwprj.findAndReplaceDOMText(regex, node, marker, false, editor.schema);
  230. };
  231. var unwrap = function (node) {
  232. var parentNode = node.parentNode;
  233. if (node.firstChild) {
  234. parentNode.insertBefore(node.firstChild, node);
  235. }
  236. node.parentNode.removeChild(node);
  237. };
  238. var findSpansByIndex = function (editor, index) {
  239. var nodes;
  240. var spans = [];
  241. nodes = global$1.toArray(editor.getBody().getElementsByTagName('span'));
  242. if (nodes.length) {
  243. for (var i = 0; i < nodes.length; i++) {
  244. var nodeIndex = getElmIndex(nodes[i]);
  245. if (nodeIndex === null || !nodeIndex.length) {
  246. continue;
  247. }
  248. if (nodeIndex === index.toString()) {
  249. spans.push(nodes[i]);
  250. }
  251. }
  252. }
  253. return spans;
  254. };
  255. var moveSelection = function (editor, currentIndexState, forward) {
  256. var testIndex = currentIndexState.get();
  257. var dom = editor.dom;
  258. forward = forward !== false;
  259. if (forward) {
  260. testIndex++;
  261. } else {
  262. testIndex--;
  263. }
  264. dom.removeClass(findSpansByIndex(editor, currentIndexState.get()), 'mce-match-marker-selected');
  265. var spans = findSpansByIndex(editor, testIndex);
  266. if (spans.length) {
  267. dom.addClass(findSpansByIndex(editor, testIndex), 'mce-match-marker-selected');
  268. editor.selection.scrollIntoView(spans[0]);
  269. return testIndex;
  270. }
  271. return -1;
  272. };
  273. var removeNode = function (dom, node) {
  274. var parent = node.parentNode;
  275. dom.remove(node);
  276. if (dom.isEmpty(parent)) {
  277. dom.remove(parent);
  278. }
  279. };
  280. var find = function (editor, currentIndexState, text, matchCase, wholeWord) {
  281. text = text.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&');
  282. text = text.replace(/\s/g, '\\s');
  283. text = wholeWord ? '\\b' + text + '\\b' : text;
  284. var count = markAllMatches(editor, currentIndexState, new RegExp(text, matchCase ? 'g' : 'gi'));
  285. if (count) {
  286. currentIndexState.set(-1);
  287. currentIndexState.set(moveSelection(editor, currentIndexState, true));
  288. }
  289. return count;
  290. };
  291. var next = function (editor, currentIndexState) {
  292. var index = moveSelection(editor, currentIndexState, true);
  293. if (index !== -1) {
  294. currentIndexState.set(index);
  295. }
  296. };
  297. var prev = function (editor, currentIndexState) {
  298. var index = moveSelection(editor, currentIndexState, false);
  299. if (index !== -1) {
  300. currentIndexState.set(index);
  301. }
  302. };
  303. var isMatchSpan = function (node) {
  304. var matchIndex = getElmIndex(node);
  305. return matchIndex !== null && matchIndex.length > 0;
  306. };
  307. var replace = function (editor, currentIndexState, text, forward, all) {
  308. var i, nodes, node, matchIndex, currentMatchIndex, nextIndex = currentIndexState.get(), hasMore;
  309. forward = forward !== false;
  310. node = editor.getBody();
  311. nodes = global$1.grep(global$1.toArray(node.getElementsByTagName('span')), isMatchSpan);
  312. for (i = 0; i < nodes.length; i++) {
  313. var nodeIndex = getElmIndex(nodes[i]);
  314. matchIndex = currentMatchIndex = parseInt(nodeIndex, 10);
  315. if (all || matchIndex === currentIndexState.get()) {
  316. if (text.length) {
  317. nodes[i].firstChild.nodeValue = text;
  318. unwrap(nodes[i]);
  319. } else {
  320. removeNode(editor.dom, nodes[i]);
  321. }
  322. while (nodes[++i]) {
  323. matchIndex = parseInt(getElmIndex(nodes[i]), 10);
  324. if (matchIndex === currentMatchIndex) {
  325. removeNode(editor.dom, nodes[i]);
  326. } else {
  327. i--;
  328. break;
  329. }
  330. }
  331. if (forward) {
  332. nextIndex--;
  333. }
  334. } else if (currentMatchIndex > currentIndexState.get()) {
  335. nodes[i].setAttribute('data-mce-index', currentMatchIndex - 1);
  336. }
  337. }
  338. currentIndexState.set(nextIndex);
  339. if (forward) {
  340. hasMore = hasNext(editor, currentIndexState);
  341. next(editor, currentIndexState);
  342. } else {
  343. hasMore = hasPrev(editor, currentIndexState);
  344. prev(editor, currentIndexState);
  345. }
  346. return !all && hasMore;
  347. };
  348. var done = function (editor, currentIndexState, keepEditorSelection) {
  349. var i, nodes, startContainer, endContainer;
  350. nodes = global$1.toArray(editor.getBody().getElementsByTagName('span'));
  351. for (i = 0; i < nodes.length; i++) {
  352. var nodeIndex = getElmIndex(nodes[i]);
  353. if (nodeIndex !== null && nodeIndex.length) {
  354. if (nodeIndex === currentIndexState.get().toString()) {
  355. if (!startContainer) {
  356. startContainer = nodes[i].firstChild;
  357. }
  358. endContainer = nodes[i].firstChild;
  359. }
  360. unwrap(nodes[i]);
  361. }
  362. }
  363. if (startContainer && endContainer) {
  364. var rng = editor.dom.createRng();
  365. rng.setStart(startContainer, 0);
  366. rng.setEnd(endContainer, endContainer.data.length);
  367. if (keepEditorSelection !== false) {
  368. editor.selection.setRng(rng);
  369. }
  370. return rng;
  371. }
  372. };
  373. var hasNext = function (editor, currentIndexState) {
  374. return findSpansByIndex(editor, currentIndexState.get() + 1).length > 0;
  375. };
  376. var hasPrev = function (editor, currentIndexState) {
  377. return findSpansByIndex(editor, currentIndexState.get() - 1).length > 0;
  378. };
  379. var $_f6rasljzjkmcwprd = {
  380. done: done,
  381. find: find,
  382. next: next,
  383. prev: prev,
  384. replace: replace,
  385. hasNext: hasNext,
  386. hasPrev: hasPrev
  387. };
  388. var get = function (editor, currentIndexState) {
  389. var done = function (keepEditorSelection) {
  390. return $_f6rasljzjkmcwprd.done(editor, currentIndexState, keepEditorSelection);
  391. };
  392. var find = function (text, matchCase, wholeWord) {
  393. return $_f6rasljzjkmcwprd.find(editor, currentIndexState, text, matchCase, wholeWord);
  394. };
  395. var next = function () {
  396. return $_f6rasljzjkmcwprd.next(editor, currentIndexState);
  397. };
  398. var prev = function () {
  399. return $_f6rasljzjkmcwprd.prev(editor, currentIndexState);
  400. };
  401. var replace = function (text, forward, all) {
  402. return $_f6rasljzjkmcwprd.replace(editor, currentIndexState, text, forward, all);
  403. };
  404. return {
  405. done: done,
  406. find: find,
  407. next: next,
  408. prev: prev,
  409. replace: replace
  410. };
  411. };
  412. var $_aeho3jyjkmcwpra = { get: get };
  413. var open = function (editor, currentIndexState) {
  414. var last = {}, selectedText;
  415. editor.undoManager.add();
  416. selectedText = global$1.trim(editor.selection.getContent({ format: 'text' }));
  417. function updateButtonStates() {
  418. win.statusbar.find('#next').disabled($_f6rasljzjkmcwprd.hasNext(editor, currentIndexState) === false);
  419. win.statusbar.find('#prev').disabled($_f6rasljzjkmcwprd.hasPrev(editor, currentIndexState) === false);
  420. }
  421. function notFoundAlert() {
  422. editor.windowManager.alert('Could not find the specified string.', function () {
  423. win.find('#find')[0].focus();
  424. });
  425. }
  426. var win = editor.windowManager.open({
  427. layout: 'flex',
  428. pack: 'center',
  429. align: 'center',
  430. onClose: function () {
  431. editor.focus();
  432. $_f6rasljzjkmcwprd.done(editor, currentIndexState);
  433. editor.undoManager.add();
  434. },
  435. onSubmit: function (e) {
  436. var count, caseState, text, wholeWord;
  437. e.preventDefault();
  438. caseState = win.find('#case').checked();
  439. wholeWord = win.find('#words').checked();
  440. text = win.find('#find').value();
  441. if (!text.length) {
  442. $_f6rasljzjkmcwprd.done(editor, currentIndexState, false);
  443. win.statusbar.items().slice(1).disabled(true);
  444. return;
  445. }
  446. if (last.text === text && last.caseState === caseState && last.wholeWord === wholeWord) {
  447. if (!$_f6rasljzjkmcwprd.hasNext(editor, currentIndexState)) {
  448. notFoundAlert();
  449. return;
  450. }
  451. $_f6rasljzjkmcwprd.next(editor, currentIndexState);
  452. updateButtonStates();
  453. return;
  454. }
  455. count = $_f6rasljzjkmcwprd.find(editor, currentIndexState, text, caseState, wholeWord);
  456. if (!count) {
  457. notFoundAlert();
  458. }
  459. win.statusbar.items().slice(1).disabled(count === 0);
  460. updateButtonStates();
  461. last = {
  462. text: text,
  463. caseState: caseState,
  464. wholeWord: wholeWord
  465. };
  466. },
  467. buttons: [
  468. {
  469. text: 'Find',
  470. subtype: 'primary',
  471. onclick: function () {
  472. win.submit();
  473. }
  474. },
  475. {
  476. text: 'Replace',
  477. disabled: true,
  478. onclick: function () {
  479. if (!$_f6rasljzjkmcwprd.replace(editor, currentIndexState, win.find('#replace').value())) {
  480. win.statusbar.items().slice(1).disabled(true);
  481. currentIndexState.set(-1);
  482. last = {};
  483. }
  484. }
  485. },
  486. {
  487. text: 'Replace all',
  488. disabled: true,
  489. onclick: function () {
  490. $_f6rasljzjkmcwprd.replace(editor, currentIndexState, win.find('#replace').value(), true, true);
  491. win.statusbar.items().slice(1).disabled(true);
  492. last = {};
  493. }
  494. },
  495. {
  496. type: 'spacer',
  497. flex: 1
  498. },
  499. {
  500. text: 'Prev',
  501. name: 'prev',
  502. disabled: true,
  503. onclick: function () {
  504. $_f6rasljzjkmcwprd.prev(editor, currentIndexState);
  505. updateButtonStates();
  506. }
  507. },
  508. {
  509. text: 'Next',
  510. name: 'next',
  511. disabled: true,
  512. onclick: function () {
  513. $_f6rasljzjkmcwprd.next(editor, currentIndexState);
  514. updateButtonStates();
  515. }
  516. }
  517. ],
  518. title: 'Find and replace',
  519. items: {
  520. type: 'form',
  521. padding: 20,
  522. labelGap: 30,
  523. spacing: 10,
  524. items: [
  525. {
  526. type: 'textbox',
  527. name: 'find',
  528. size: 40,
  529. label: 'Find',
  530. value: selectedText
  531. },
  532. {
  533. type: 'textbox',
  534. name: 'replace',
  535. size: 40,
  536. label: 'Replace with'
  537. },
  538. {
  539. type: 'checkbox',
  540. name: 'case',
  541. text: 'Match case',
  542. label: ' '
  543. },
  544. {
  545. type: 'checkbox',
  546. name: 'words',
  547. text: 'Whole words',
  548. label: ' '
  549. }
  550. ]
  551. }
  552. });
  553. };
  554. var $_54e0m7k3jkmcwprp = { open: open };
  555. var register = function (editor, currentIndexState) {
  556. editor.addCommand('SearchReplace', function () {
  557. $_54e0m7k3jkmcwprp.open(editor, currentIndexState);
  558. });
  559. };
  560. var $_2k91tsk2jkmcwpro = { register: register };
  561. var showDialog = function (editor, currentIndexState) {
  562. return function () {
  563. $_54e0m7k3jkmcwprp.open(editor, currentIndexState);
  564. };
  565. };
  566. var register$1 = function (editor, currentIndexState) {
  567. editor.addMenuItem('searchreplace', {
  568. text: 'Find and replace',
  569. shortcut: 'Meta+F',
  570. onclick: showDialog(editor, currentIndexState),
  571. separator: 'before',
  572. context: 'edit'
  573. });
  574. editor.addButton('searchreplace', {
  575. tooltip: 'Find and replace',
  576. onclick: showDialog(editor, currentIndexState)
  577. });
  578. editor.shortcuts.add('Meta+F', '', showDialog(editor, currentIndexState));
  579. };
  580. var $_a57hmjk4jkmcwprx = { register: register$1 };
  581. global.add('searchreplace', function (editor) {
  582. var currentIndexState = Cell(-1);
  583. $_2k91tsk2jkmcwpro.register(editor, currentIndexState);
  584. $_a57hmjk4jkmcwprx.register(editor, currentIndexState);
  585. return $_aeho3jyjkmcwpra.get(editor, currentIndexState);
  586. });
  587. function Plugin () {
  588. }
  589. return Plugin;
  590. }());
  591. })();