src/pyams_skin/resources/js/ext/tinymce/dev/plugins/searchreplace/plugin.js
changeset 69 a361355b55c7
--- /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:
+	// <p>text<span contentEditable="false">text<span contentEditable="true">text</span>text</span>text</p>
+	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);
+})();