diff -r fd8fb93e1b6a -r a361355b55c7 src/pyams_skin/resources/js/ext/tinymce/dev/plugins/searchreplace/plugin.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_skin/resources/js/ext/tinymce/dev/plugins/searchreplace/plugin.js Wed Jun 17 10:00:10 2015 +0200 @@ -0,0 +1,594 @@ +/** + * plugin.js + * + * Copyright, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://www.tinymce.com/license + * Contributing: http://www.tinymce.com/contributing + */ + +/*jshint smarttabs:true, undef:true, unused:true, latedef:true, curly:true, bitwise:true */ +/*eslint no-labels:0, no-constant-condition: 0 */ +/*global tinymce:true */ + +(function() { + // Based on work developed by: James Padolsey http://james.padolsey.com + // released under UNLICENSE that is compatible with LGPL + // TODO: Handle contentEditable edgecase: + //

texttexttexttexttext

+ function findAndReplaceDOMText(regex, node, replacementNode, captureGroup, schema) { + var m, matches = [], text, count = 0, doc; + var blockElementsMap, hiddenTextElementsMap, shortEndedElementsMap; + + doc = node.ownerDocument; + blockElementsMap = schema.getBlockElements(); // H1-H6, P, TD etc + hiddenTextElementsMap = schema.getWhiteSpaceElements(); // TEXTAREA, PRE, STYLE, SCRIPT + shortEndedElementsMap = schema.getShortEndedElements(); // BR, IMG, INPUT + + function getMatchIndexes(m, captureGroup) { + captureGroup = captureGroup || 0; + + if (!m[0]) { + throw 'findAndReplaceDOMText cannot handle zero-length matches'; + } + + var index = m.index; + + if (captureGroup > 0) { + var cg = m[captureGroup]; + + if (!cg) { + throw 'Invalid capture group'; + } + + index += m[0].indexOf(cg); + m[0] = cg; + } + + return [index, index + m[0].length, [m[0]]]; + } + + function getText(node) { + var txt; + + if (node.nodeType === 3) { + return node.data; + } + + if (hiddenTextElementsMap[node.nodeName] && !blockElementsMap[node.nodeName]) { + return ''; + } + + txt = ''; + + if (blockElementsMap[node.nodeName] || shortEndedElementsMap[node.nodeName]) { + txt += '\n'; + } + + if ((node = node.firstChild)) { + do { + txt += getText(node); + } while ((node = node.nextSibling)); + } + + return txt; + } + + function stepThroughMatches(node, matches, replaceFn) { + var startNode, endNode, startNodeIndex, + endNodeIndex, innerNodes = [], atIndex = 0, curNode = node, + matchLocation = matches.shift(), matchIndex = 0; + + out: while (true) { + if (blockElementsMap[curNode.nodeName] || shortEndedElementsMap[curNode.nodeName]) { + atIndex++; + } + + if (curNode.nodeType === 3) { + if (!endNode && curNode.length + atIndex >= matchLocation[1]) { + // We've found the ending + endNode = curNode; + endNodeIndex = matchLocation[1] - atIndex; + } else if (startNode) { + // Intersecting node + innerNodes.push(curNode); + } + + if (!startNode && curNode.length + atIndex > matchLocation[0]) { + // We've found the match start + startNode = curNode; + startNodeIndex = matchLocation[0] - atIndex; + } + + atIndex += curNode.length; + } + + if (startNode && endNode) { + curNode = replaceFn({ + startNode: startNode, + startNodeIndex: startNodeIndex, + endNode: endNode, + endNodeIndex: endNodeIndex, + innerNodes: innerNodes, + match: matchLocation[2], + matchIndex: matchIndex + }); + + // replaceFn has to return the node that replaced the endNode + // and then we step back so we can continue from the end of the + // match: + atIndex -= (endNode.length - endNodeIndex); + startNode = null; + endNode = null; + innerNodes = []; + matchLocation = matches.shift(); + matchIndex++; + + if (!matchLocation) { + break; // no more matches + } + } else if ((!hiddenTextElementsMap[curNode.nodeName] || blockElementsMap[curNode.nodeName]) && curNode.firstChild) { + // Move down + curNode = curNode.firstChild; + continue; + } else if (curNode.nextSibling) { + // Move forward: + curNode = curNode.nextSibling; + continue; + } + + // Move forward or up: + while (true) { + if (curNode.nextSibling) { + curNode = curNode.nextSibling; + break; + } else if (curNode.parentNode !== node) { + curNode = curNode.parentNode; + } else { + break out; + } + } + } + } + + /** + * Generates the actual replaceFn which splits up text nodes + * and inserts the replacement element. + */ + function genReplacer(nodeName) { + var makeReplacementNode; + + if (typeof nodeName != 'function') { + var stencilNode = nodeName.nodeType ? nodeName : doc.createElement(nodeName); + + makeReplacementNode = function(fill, matchIndex) { + var clone = stencilNode.cloneNode(false); + + clone.setAttribute('data-mce-index', matchIndex); + + if (fill) { + clone.appendChild(doc.createTextNode(fill)); + } + + return clone; + }; + } else { + makeReplacementNode = nodeName; + } + + return function(range) { + var before, after, parentNode, startNode = range.startNode, + endNode = range.endNode, matchIndex = range.matchIndex; + + if (startNode === endNode) { + var node = startNode; + + parentNode = node.parentNode; + if (range.startNodeIndex > 0) { + // Add `before` text node (before the match) + before = doc.createTextNode(node.data.substring(0, range.startNodeIndex)); + parentNode.insertBefore(before, node); + } + + // Create the replacement node: + var el = makeReplacementNode(range.match[0], matchIndex); + parentNode.insertBefore(el, node); + if (range.endNodeIndex < node.length) { + // Add `after` text node (after the match) + after = doc.createTextNode(node.data.substring(range.endNodeIndex)); + parentNode.insertBefore(after, node); + } + + node.parentNode.removeChild(node); + + return el; + } else { + // Replace startNode -> [innerNodes...] -> endNode (in that order) + before = doc.createTextNode(startNode.data.substring(0, range.startNodeIndex)); + after = doc.createTextNode(endNode.data.substring(range.endNodeIndex)); + var elA = makeReplacementNode(startNode.data.substring(range.startNodeIndex), matchIndex); + var innerEls = []; + + for (var i = 0, l = range.innerNodes.length; i < l; ++i) { + var innerNode = range.innerNodes[i]; + var innerEl = makeReplacementNode(innerNode.data, matchIndex); + innerNode.parentNode.replaceChild(innerEl, innerNode); + innerEls.push(innerEl); + } + + var elB = makeReplacementNode(endNode.data.substring(0, range.endNodeIndex), matchIndex); + + parentNode = startNode.parentNode; + parentNode.insertBefore(before, startNode); + parentNode.insertBefore(elA, startNode); + parentNode.removeChild(startNode); + + parentNode = endNode.parentNode; + parentNode.insertBefore(elB, endNode); + parentNode.insertBefore(after, endNode); + parentNode.removeChild(endNode); + + return elB; + } + }; + } + + text = getText(node); + if (!text) { + return; + } + + if (regex.global) { + while ((m = regex.exec(text))) { + matches.push(getMatchIndexes(m, captureGroup)); + } + } else { + m = text.match(regex); + matches.push(getMatchIndexes(m, captureGroup)); + } + + if (matches.length) { + count = matches.length; + stepThroughMatches(node, matches, genReplacer(replacementNode)); + } + + return count; + } + + function Plugin(editor) { + var self = this, currentIndex = -1; + + function showDialog() { + var last = {}; + + function updateButtonStates() { + win.statusbar.find('#next').disabled(!findSpansByIndex(currentIndex + 1).length); + win.statusbar.find('#prev').disabled(!findSpansByIndex(currentIndex - 1).length); + } + + function notFoundAlert() { + tinymce.ui.MessageBox.alert('Could not find the specified string.', function() { + win.find('#find')[0].focus(); + }); + } + + var win = tinymce.ui.Factory.create({ + type: 'window', + layout: "flex", + pack: "center", + align: "center", + onClose: function() { + editor.focus(); + self.done(); + }, + onSubmit: function(e) { + var count, caseState, text, wholeWord; + + e.preventDefault(); + + caseState = win.find('#case').checked(); + wholeWord = win.find('#words').checked(); + + text = win.find('#find').value(); + if (!text.length) { + self.done(false); + win.statusbar.items().slice(1).disabled(true); + return; + } + + if (last.text == text && last.caseState == caseState && last.wholeWord == wholeWord) { + if (findSpansByIndex(currentIndex + 1).length === 0) { + notFoundAlert(); + return; + } + + self.next(); + updateButtonStates(); + return; + } + + count = self.find(text, caseState, wholeWord); + if (!count) { + notFoundAlert(); + } + + win.statusbar.items().slice(1).disabled(count === 0); + updateButtonStates(); + + last = { + text: text, + caseState: caseState, + wholeWord: wholeWord + }; + }, + buttons: [ + {text: "Find", onclick: function() { + win.submit(); + }}, + {text: "Replace", disabled: true, onclick: function() { + if (!self.replace(win.find('#replace').value())) { + win.statusbar.items().slice(1).disabled(true); + currentIndex = -1; + last = {}; + } + }}, + {text: "Replace all", disabled: true, onclick: function() { + self.replace(win.find('#replace').value(), true, true); + win.statusbar.items().slice(1).disabled(true); + last = {}; + }}, + {type: "spacer", flex: 1}, + {text: "Prev", name: 'prev', disabled: true, onclick: function() { + self.prev(); + updateButtonStates(); + }}, + {text: "Next", name: 'next', disabled: true, onclick: function() { + self.next(); + updateButtonStates(); + }} + ], + title: "Find and replace", + items: { + type: "form", + padding: 20, + labelGap: 30, + spacing: 10, + items: [ + {type: 'textbox', name: 'find', size: 40, label: 'Find', value: editor.selection.getNode().src}, + {type: 'textbox', name: 'replace', size: 40, label: 'Replace with'}, + {type: 'checkbox', name: 'case', text: 'Match case', label: ' '}, + {type: 'checkbox', name: 'words', text: 'Whole words', label: ' '} + ] + } + }).renderTo().reflow(); + } + + self.init = function(ed) { + ed.addMenuItem('searchreplace', { + text: 'Find and replace', + shortcut: 'Meta+F', + onclick: showDialog, + separator: 'before', + context: 'edit' + }); + + ed.addButton('searchreplace', { + tooltip: 'Find and replace', + shortcut: 'Meta+F', + onclick: showDialog + }); + + ed.addCommand("SearchReplace", showDialog); + ed.shortcuts.add('Meta+F', '', showDialog); + }; + + function getElmIndex(elm) { + var value = elm.getAttribute('data-mce-index'); + + if (typeof value == "number") { + return "" + value; + } + + return value; + } + + function markAllMatches(regex) { + var node, marker; + + marker = editor.dom.create('span', { + "data-mce-bogus": 1 + }); + + marker.className = 'mce-match-marker'; // IE 7 adds class="mce-match-marker" and class=mce-match-marker + node = editor.getBody(); + + self.done(false); + + return findAndReplaceDOMText(regex, node, marker, false, editor.schema); + } + + function unwrap(node) { + var parentNode = node.parentNode; + + if (node.firstChild) { + parentNode.insertBefore(node.firstChild, node); + } + + node.parentNode.removeChild(node); + } + + function findSpansByIndex(index) { + var nodes, spans = []; + + nodes = tinymce.toArray(editor.getBody().getElementsByTagName('span')); + if (nodes.length) { + for (var i = 0; i < nodes.length; i++) { + var nodeIndex = getElmIndex(nodes[i]); + + if (nodeIndex === null || !nodeIndex.length) { + continue; + } + + if (nodeIndex === index.toString()) { + spans.push(nodes[i]); + } + } + } + + return spans; + } + + function moveSelection(forward) { + var testIndex = currentIndex, dom = editor.dom; + + forward = forward !== false; + + if (forward) { + testIndex++; + } else { + testIndex--; + } + + dom.removeClass(findSpansByIndex(currentIndex), 'mce-match-marker-selected'); + + var spans = findSpansByIndex(testIndex); + if (spans.length) { + dom.addClass(findSpansByIndex(testIndex), 'mce-match-marker-selected'); + editor.selection.scrollIntoView(spans[0]); + return testIndex; + } + + return -1; + } + + function removeNode(node) { + node.parentNode.removeChild(node); + } + + self.find = function(text, matchCase, wholeWord) { + text = text.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); + text = wholeWord ? '\\b' + text + '\\b' : text; + + var count = markAllMatches(new RegExp(text, matchCase ? 'g' : 'gi')); + + if (count) { + currentIndex = -1; + currentIndex = moveSelection(true); + } + + return count; + }; + + self.next = function() { + var index = moveSelection(true); + + if (index !== -1) { + currentIndex = index; + } + }; + + self.prev = function() { + var index = moveSelection(false); + + if (index !== -1) { + currentIndex = index; + } + }; + + self.replace = function(text, forward, all) { + var i, nodes, node, matchIndex, currentMatchIndex, nextIndex = currentIndex, hasMore; + + forward = forward !== false; + + node = editor.getBody(); + nodes = tinymce.toArray(node.getElementsByTagName('span')); + for (i = 0; i < nodes.length; i++) { + var nodeIndex = getElmIndex(nodes[i]); + + if (nodeIndex === null || !nodeIndex.length) { + continue; + } + + matchIndex = currentMatchIndex = parseInt(nodeIndex, 10); + if (all || matchIndex === currentIndex) { + if (text.length) { + nodes[i].firstChild.nodeValue = text; + unwrap(nodes[i]); + } else { + removeNode(nodes[i]); + } + + while (nodes[++i]) { + matchIndex = getElmIndex(nodes[i]); + + if (nodeIndex === null || !nodeIndex.length) { + continue; + } + + if (matchIndex === currentMatchIndex) { + removeNode(nodes[i]); + } else { + i--; + break; + } + } + + if (forward) { + nextIndex--; + } + } else if (currentMatchIndex > currentIndex) { + nodes[i].setAttribute('data-mce-index', currentMatchIndex - 1); + } + } + + editor.undoManager.add(); + currentIndex = nextIndex; + + if (forward) { + hasMore = findSpansByIndex(nextIndex + 1).length > 0; + self.next(); + } else { + hasMore = findSpansByIndex(nextIndex - 1).length > 0; + self.prev(); + } + + return !all && hasMore; + }; + + self.done = function(keepEditorSelection) { + var i, nodes, startContainer, endContainer; + + nodes = tinymce.toArray(editor.getBody().getElementsByTagName('span')); + for (i = 0; i < nodes.length; i++) { + var nodeIndex = getElmIndex(nodes[i]); + + if (nodeIndex !== null && nodeIndex.length) { + if (nodeIndex === currentIndex.toString()) { + if (!startContainer) { + startContainer = nodes[i].firstChild; + } + + endContainer = nodes[i].firstChild; + } + + unwrap(nodes[i]); + } + } + + if (startContainer && endContainer) { + var rng = editor.dom.createRng(); + rng.setStart(startContainer, 0); + rng.setEnd(endContainer, endContainer.data.length); + + if (keepEditorSelection !== false) { + editor.selection.setRng(rng); + } + + return rng; + } + }; + } + + tinymce.PluginManager.add('searchreplace', Plugin); +})();