]*>/gi, "[quote]");
- rep(/<\/blockquote>/gi, "[/quote]");
- rep(/
/gi, "\n");
- rep(/
/gi, "\n");
- rep(/
/gi, "\n");
- rep(//gi, "");
- rep(/<\/p>/gi, "\n");
- rep(/ |\u00a0/gi, " ");
- rep(/"/gi, "\"");
- rep(/</gi, "<");
- rep(/>/gi, ">");
- rep(/&/gi, "&");
+ container = rng[start ? 'startContainer' : 'endContainer'];
+ offset = rng[start ? 'startOffset' : 'endOffset'];
- return s;
- },
+ if (container.nodeType == 1) {
+ offsetNode = dom.create('span', {'data-mce-type': 'bookmark'});
- // BBCode -> HTML from PunBB dialect
- _punbb_bbcode2html: function(s) {
- s = tinymce.trim(s);
+ if (container.hasChildNodes()) {
+ offset = Math.min(offset, container.childNodes.length - 1);
- function rep(re, str) {
- s = s.replace(re, str);
+ if (start) {
+ container.insertBefore(offsetNode, container.childNodes[offset]);
+ } else {
+ dom.insertAfter(offsetNode, container.childNodes[offset]);
+ }
+ } else {
+ container.appendChild(offsetNode);
+ }
+
+ container = offsetNode;
+ offset = 0;
+ }
+
+ bookmark[start ? 'startContainer' : 'endContainer'] = container;
+ bookmark[start ? 'startOffset' : 'endOffset'] = offset;
}
- // example: [b] to
- rep(/\n/gi, "
");
- rep(/\[b\]/gi, "");
- rep(/\[\/b\]/gi, "");
- rep(/\[i\]/gi, "");
- rep(/\[\/i\]/gi, "");
- rep(/\[u\]/gi, "");
- rep(/\[\/u\]/gi, "");
- rep(/\[url=([^\]]+)\](.*?)\[\/url\]/gi, "$2");
- rep(/\[url\](.*?)\[\/url\]/gi, "$1");
- rep(/\[img\](.*?)\[\/img\]/gi, "");
- rep(/\[color=(.*?)\](.*?)\[\/color\]/gi, "$2");
- rep(/\[code\](.*?)\[\/code\]/gi, "$1 ");
- rep(/\[quote.*?\](.*?)\[\/quote\]/gi, "$1 ");
+ setupEndPoint(true);
- return s;
+ if (!rng.collapsed) {
+ setupEndPoint();
+ }
+
+ return bookmark;
}
- });
- // Register plugin
- tinymce.PluginManager.add('bbcode', tinymce.plugins.BBCodePlugin);
-})();
+ /**
+ * Moves the selection to the current bookmark and removes any selection container wrappers.
+ *
+ * @param {Object} bookmark Bookmark object to move selection to.
+ */
+ function moveToBookmark(bookmark) {
+ function restoreEndPoint(start) {
+ var container, offset, node;
- }).apply(root, arguments);
-});
-}(this));
+ function nodeIndex(container) {
+ var node = container.parentNode.firstChild, idx = 0;
-(function(root) {
-define("tinymce-charmap", ["tinymce"], function() {
- return (function() {
-/**
- * plugin.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
+ while (node) {
+ if (node == container) {
+ return idx;
+ }
-/*global tinymce:true */
+ // Skip data-mce-type=bookmark nodes
+ if (node.nodeType != 1 || node.getAttribute('data-mce-type') != 'bookmark') {
+ idx++;
+ }
-tinymce.PluginManager.add('charmap', function(editor) {
- var charmap = [
- ['160', 'no-break space'],
- ['173', 'soft hyphen'],
- ['34', 'quotation mark'],
- // finance
- ['162', 'cent sign'],
- ['8364', 'euro sign'],
- ['163', 'pound sign'],
- ['165', 'yen sign'],
- // signs
- ['169', 'copyright sign'],
- ['174', 'registered sign'],
- ['8482', 'trade mark sign'],
- ['8240', 'per mille sign'],
- ['181', 'micro sign'],
- ['183', 'middle dot'],
- ['8226', 'bullet'],
- ['8230', 'three dot leader'],
- ['8242', 'minutes / feet'],
- ['8243', 'seconds / inches'],
- ['167', 'section sign'],
- ['182', 'paragraph sign'],
- ['223', 'sharp s / ess-zed'],
- // quotations
- ['8249', 'single left-pointing angle quotation mark'],
- ['8250', 'single right-pointing angle quotation mark'],
- ['171', 'left pointing guillemet'],
- ['187', 'right pointing guillemet'],
- ['8216', 'left single quotation mark'],
- ['8217', 'right single quotation mark'],
- ['8220', 'left double quotation mark'],
- ['8221', 'right double quotation mark'],
- ['8218', 'single low-9 quotation mark'],
- ['8222', 'double low-9 quotation mark'],
- ['60', 'less-than sign'],
- ['62', 'greater-than sign'],
- ['8804', 'less-than or equal to'],
- ['8805', 'greater-than or equal to'],
- ['8211', 'en dash'],
- ['8212', 'em dash'],
- ['175', 'macron'],
- ['8254', 'overline'],
- ['164', 'currency sign'],
- ['166', 'broken bar'],
- ['168', 'diaeresis'],
- ['161', 'inverted exclamation mark'],
- ['191', 'turned question mark'],
- ['710', 'circumflex accent'],
- ['732', 'small tilde'],
- ['176', 'degree sign'],
- ['8722', 'minus sign'],
- ['177', 'plus-minus sign'],
- ['247', 'division sign'],
- ['8260', 'fraction slash'],
- ['215', 'multiplication sign'],
- ['185', 'superscript one'],
- ['178', 'superscript two'],
- ['179', 'superscript three'],
- ['188', 'fraction one quarter'],
- ['189', 'fraction one half'],
- ['190', 'fraction three quarters'],
- // math / logical
- ['402', 'function / florin'],
- ['8747', 'integral'],
- ['8721', 'n-ary sumation'],
- ['8734', 'infinity'],
- ['8730', 'square root'],
- ['8764', 'similar to'],
- ['8773', 'approximately equal to'],
- ['8776', 'almost equal to'],
- ['8800', 'not equal to'],
- ['8801', 'identical to'],
- ['8712', 'element of'],
- ['8713', 'not an element of'],
- ['8715', 'contains as member'],
- ['8719', 'n-ary product'],
- ['8743', 'logical and'],
- ['8744', 'logical or'],
- ['172', 'not sign'],
- ['8745', 'intersection'],
- ['8746', 'union'],
- ['8706', 'partial differential'],
- ['8704', 'for all'],
- ['8707', 'there exists'],
- ['8709', 'diameter'],
- ['8711', 'backward difference'],
- ['8727', 'asterisk operator'],
- ['8733', 'proportional to'],
- ['8736', 'angle'],
- // undefined
- ['180', 'acute accent'],
- ['184', 'cedilla'],
- ['170', 'feminine ordinal indicator'],
- ['186', 'masculine ordinal indicator'],
- ['8224', 'dagger'],
- ['8225', 'double dagger'],
- // alphabetical special chars
- ['192', 'A - grave'],
- ['193', 'A - acute'],
- ['194', 'A - circumflex'],
- ['195', 'A - tilde'],
- ['196', 'A - diaeresis'],
- ['197', 'A - ring above'],
- ['198', 'ligature AE'],
- ['199', 'C - cedilla'],
- ['200', 'E - grave'],
- ['201', 'E - acute'],
- ['202', 'E - circumflex'],
- ['203', 'E - diaeresis'],
- ['204', 'I - grave'],
- ['205', 'I - acute'],
- ['206', 'I - circumflex'],
- ['207', 'I - diaeresis'],
- ['208', 'ETH'],
- ['209', 'N - tilde'],
- ['210', 'O - grave'],
- ['211', 'O - acute'],
- ['212', 'O - circumflex'],
- ['213', 'O - tilde'],
- ['214', 'O - diaeresis'],
- ['216', 'O - slash'],
- ['338', 'ligature OE'],
- ['352', 'S - caron'],
- ['217', 'U - grave'],
- ['218', 'U - acute'],
- ['219', 'U - circumflex'],
- ['220', 'U - diaeresis'],
- ['221', 'Y - acute'],
- ['376', 'Y - diaeresis'],
- ['222', 'THORN'],
- ['224', 'a - grave'],
- ['225', 'a - acute'],
- ['226', 'a - circumflex'],
- ['227', 'a - tilde'],
- ['228', 'a - diaeresis'],
- ['229', 'a - ring above'],
- ['230', 'ligature ae'],
- ['231', 'c - cedilla'],
- ['232', 'e - grave'],
- ['233', 'e - acute'],
- ['234', 'e - circumflex'],
- ['235', 'e - diaeresis'],
- ['236', 'i - grave'],
- ['237', 'i - acute'],
- ['238', 'i - circumflex'],
- ['239', 'i - diaeresis'],
- ['240', 'eth'],
- ['241', 'n - tilde'],
- ['242', 'o - grave'],
- ['243', 'o - acute'],
- ['244', 'o - circumflex'],
- ['245', 'o - tilde'],
- ['246', 'o - diaeresis'],
- ['248', 'o slash'],
- ['339', 'ligature oe'],
- ['353', 's - caron'],
- ['249', 'u - grave'],
- ['250', 'u - acute'],
- ['251', 'u - circumflex'],
- ['252', 'u - diaeresis'],
- ['253', 'y - acute'],
- ['254', 'thorn'],
- ['255', 'y - diaeresis'],
- ['913', 'Alpha'],
- ['914', 'Beta'],
- ['915', 'Gamma'],
- ['916', 'Delta'],
- ['917', 'Epsilon'],
- ['918', 'Zeta'],
- ['919', 'Eta'],
- ['920', 'Theta'],
- ['921', 'Iota'],
- ['922', 'Kappa'],
- ['923', 'Lambda'],
- ['924', 'Mu'],
- ['925', 'Nu'],
- ['926', 'Xi'],
- ['927', 'Omicron'],
- ['928', 'Pi'],
- ['929', 'Rho'],
- ['931', 'Sigma'],
- ['932', 'Tau'],
- ['933', 'Upsilon'],
- ['934', 'Phi'],
- ['935', 'Chi'],
- ['936', 'Psi'],
- ['937', 'Omega'],
- ['945', 'alpha'],
- ['946', 'beta'],
- ['947', 'gamma'],
- ['948', 'delta'],
- ['949', 'epsilon'],
- ['950', 'zeta'],
- ['951', 'eta'],
- ['952', 'theta'],
- ['953', 'iota'],
- ['954', 'kappa'],
- ['955', 'lambda'],
- ['956', 'mu'],
- ['957', 'nu'],
- ['958', 'xi'],
- ['959', 'omicron'],
- ['960', 'pi'],
- ['961', 'rho'],
- ['962', 'final sigma'],
- ['963', 'sigma'],
- ['964', 'tau'],
- ['965', 'upsilon'],
- ['966', 'phi'],
- ['967', 'chi'],
- ['968', 'psi'],
- ['969', 'omega'],
- // symbols
- ['8501', 'alef symbol'],
- ['982', 'pi symbol'],
- ['8476', 'real part symbol'],
- ['978', 'upsilon - hook symbol'],
- ['8472', 'Weierstrass p'],
- ['8465', 'imaginary part'],
- // arrows
- ['8592', 'leftwards arrow'],
- ['8593', 'upwards arrow'],
- ['8594', 'rightwards arrow'],
- ['8595', 'downwards arrow'],
- ['8596', 'left right arrow'],
- ['8629', 'carriage return'],
- ['8656', 'leftwards double arrow'],
- ['8657', 'upwards double arrow'],
- ['8658', 'rightwards double arrow'],
- ['8659', 'downwards double arrow'],
- ['8660', 'left right double arrow'],
- ['8756', 'therefore'],
- ['8834', 'subset of'],
- ['8835', 'superset of'],
- ['8836', 'not a subset of'],
- ['8838', 'subset of or equal to'],
- ['8839', 'superset of or equal to'],
- ['8853', 'circled plus'],
- ['8855', 'circled times'],
- ['8869', 'perpendicular'],
- ['8901', 'dot operator'],
- ['8968', 'left ceiling'],
- ['8969', 'right ceiling'],
- ['8970', 'left floor'],
- ['8971', 'right floor'],
- ['9001', 'left-pointing angle bracket'],
- ['9002', 'right-pointing angle bracket'],
- ['9674', 'lozenge'],
- ['9824', 'black spade suit'],
- ['9827', 'black club suit'],
- ['9829', 'black heart suit'],
- ['9830', 'black diamond suit'],
- ['8194', 'en space'],
- ['8195', 'em space'],
- ['8201', 'thin space'],
- ['8204', 'zero width non-joiner'],
- ['8205', 'zero width joiner'],
- ['8206', 'left-to-right mark'],
- ['8207', 'right-to-left mark']
- ];
+ node = node.nextSibling;
+ }
+
+ return -1;
+ }
+
+ container = node = bookmark[start ? 'startContainer' : 'endContainer'];
+ offset = bookmark[start ? 'startOffset' : 'endOffset'];
+
+ if (!container) {
+ return;
+ }
+
+ if (container.nodeType == 1) {
+ offset = nodeIndex(container);
+ container = container.parentNode;
+ dom.remove(node);
+ }
+
+ bookmark[start ? 'startContainer' : 'endContainer'] = container;
+ bookmark[start ? 'startOffset' : 'endOffset'] = offset;
+ }
+
+ restoreEndPoint(true);
+ restoreEndPoint();
+
+ var rng = dom.createRng();
+
+ rng.setStart(bookmark.startContainer, bookmark.startOffset);
+
+ if (bookmark.endContainer) {
+ rng.setEnd(bookmark.endContainer, bookmark.endOffset);
+ }
- function showDialog() {
- var gridHtml, x, y, win;
+ selection.setRng(rng);
+ }
- function getParentTd(elm) {
- while (elm) {
- if (elm.nodeName == 'TD') {
- return elm;
+ function createNewTextBlock(contentNode, blockName) {
+ var node, textBlock, fragment = dom.createFragment(), hasContentNode;
+ var blockElements = editor.schema.getBlockElements();
+
+ if (editor.settings.forced_root_block) {
+ blockName = blockName || editor.settings.forced_root_block;
+ }
+
+ if (blockName) {
+ textBlock = dom.create(blockName);
+
+ if (textBlock.tagName === editor.settings.forced_root_block) {
+ dom.setAttribs(textBlock, editor.settings.forced_root_block_attrs);
}
- elm = elm.parentNode;
+ fragment.appendChild(textBlock);
+ }
+
+ if (contentNode) {
+ while ((node = contentNode.firstChild)) {
+ var nodeName = node.nodeName;
+
+ if (!hasContentNode && (nodeName != 'SPAN' || node.getAttribute('data-mce-type') != 'bookmark')) {
+ hasContentNode = true;
+ }
+
+ if (blockElements[nodeName]) {
+ fragment.appendChild(node);
+ textBlock = null;
+ } else {
+ if (blockName) {
+ if (!textBlock) {
+ textBlock = dom.create(blockName);
+ fragment.appendChild(textBlock);
+ }
+
+ textBlock.appendChild(node);
+ } else {
+ fragment.appendChild(node);
+ }
+ }
+ }
+ }
+
+ if (!editor.settings.forced_root_block) {
+ fragment.appendChild(dom.create('br'));
+ } else {
+ // BR is needed in empty blocks on non IE browsers
+ if (!hasContentNode && (!tinymce.Env.ie || tinymce.Env.ie > 10)) {
+ textBlock.appendChild(dom.create('br', {'data-mce-bogus': '1'}));
+ }
}
+
+ return fragment;
}
- gridHtml = '';
+ function getSelectedListItems() {
+ return tinymce.grep(selection.getSelectedBlocks(), function(block) {
+ return /^(LI|DT|DD)$/.test(block.nodeName);
+ });
+ }
- var width = 25;
- var height = Math.ceil(charmap.length / width);
- for (y = 0; y < height; y++) {
- gridHtml += '';
+ function splitList(ul, li, newBlock) {
+ var tmpRng, fragment, bookmarks, node;
- for (x = 0; x < width; x++) {
- var index = y * width + x;
- if (index < charmap.length) {
- var chr = charmap[index];
+ function removeAndKeepBookmarks(targetNode) {
+ tinymce.each(bookmarks, function(node) {
+ targetNode.parentNode.insertBefore(node, li.parentNode);
+ });
- gridHtml += '' +
- (chr ? String.fromCharCode(parseInt(chr[0], 10)) : ' ') + ' | ';
- } else {
- gridHtml += ' | ';
+ dom.remove(targetNode);
+ }
+
+ bookmarks = dom.select('span[data-mce-type="bookmark"]', ul);
+ newBlock = newBlock || createNewTextBlock(li);
+ tmpRng = dom.createRng();
+ tmpRng.setStartAfter(li);
+ tmpRng.setEndAfter(ul);
+ fragment = tmpRng.extractContents();
+
+ for (node = fragment.firstChild; node; node = node.firstChild) {
+ if (node.nodeName == 'LI' && dom.isEmpty(node)) {
+ dom.remove(node);
+ break;
}
}
- gridHtml += '
';
+ if (!dom.isEmpty(fragment)) {
+ dom.insertAfter(fragment, ul);
+ }
+
+ dom.insertAfter(newBlock, ul);
+
+ if (dom.isEmpty(li.parentNode)) {
+ removeAndKeepBookmarks(li.parentNode);
+ }
+
+ dom.remove(li);
+
+ if (dom.isEmpty(ul)) {
+ dom.remove(ul);
+ }
}
- gridHtml += '
';
+ function mergeWithAdjacentLists(listBlock) {
+ var sibling, node;
- var charMapPanel = {
- type: 'container',
- html: gridHtml,
- onclick: function(e) {
- var target = e.target;
- if (/^(TD|DIV)$/.test(target.nodeName)) {
- if (getParentTd(target).firstChild) {
- editor.execCommand('mceInsertContent', false, tinymce.trim(target.innerText || target.textContent));
+ sibling = listBlock.nextSibling;
+ if (sibling && isListNode(sibling) && sibling.nodeName == listBlock.nodeName) {
+ while ((node = sibling.firstChild)) {
+ listBlock.appendChild(node);
+ }
- if (!e.ctrlKey) {
- win.close();
+ dom.remove(sibling);
+ }
+
+ sibling = listBlock.previousSibling;
+ if (sibling && isListNode(sibling) && sibling.nodeName == listBlock.nodeName) {
+ while ((node = sibling.firstChild)) {
+ listBlock.insertBefore(node, listBlock.firstChild);
+ }
+
+ dom.remove(sibling);
+ }
+ }
+
+ /**
+ * Normalizes the all lists in the specified element.
+ */
+ function normalizeList(element) {
+ tinymce.each(tinymce.grep(dom.select('ol,ul', element)), function(ul) {
+ var sibling, parentNode = ul.parentNode;
+
+ // Move UL/OL to previous LI if it's the only child of a LI
+ if (parentNode.nodeName == 'LI' && parentNode.firstChild == ul) {
+ sibling = parentNode.previousSibling;
+ if (sibling && sibling.nodeName == 'LI') {
+ sibling.appendChild(ul);
+
+ if (dom.isEmpty(parentNode)) {
+ dom.remove(parentNode);
}
}
}
- },
- onmouseover: function(e) {
- var td = getParentTd(e.target);
- if (td && td.firstChild) {
- win.find('#preview').text(td.firstChild.firstChild.data);
- win.find('#previewTitle').text(td.title);
- } else {
- win.find('#preview').text(' ');
- win.find('#previewTitle').text(' ');
+ // Append OL/UL to previous LI if it's in a parent OL/UL i.e. old HTML4
+ if (isListNode(parentNode)) {
+ sibling = parentNode.previousSibling;
+ if (sibling && sibling.nodeName == 'LI') {
+ sibling.appendChild(ul);
+ }
+ }
+ });
+ }
+
+ function outdent(li) {
+ var ul = li.parentNode, ulParent = ul.parentNode, newBlock;
+
+ function removeEmptyLi(li) {
+ if (dom.isEmpty(li)) {
+ dom.remove(li);
}
}
- };
- win = editor.windowManager.open({
- title: "Special character",
- spacing: 10,
- padding: 10,
- items: [
- charMapPanel,
- {
- type: 'container',
- layout: 'flex',
- direction: 'column',
- align: 'center',
- spacing: 5,
- minWidth: 160,
- minHeight: 160,
- items: [
- {
- type: 'label',
- name: 'preview',
- text: ' ',
- style: 'font-size: 40px; text-align: center',
- border: 1,
- minWidth: 140,
- minHeight: 80
- },
- {
- type: 'label',
- name: 'previewTitle',
- text: ' ',
- style: 'text-align: center',
- border: 1,
- minWidth: 140,
- minHeight: 80
- }
- ]
+ if (li.nodeName == 'DD') {
+ dom.rename(li, 'DT');
+ return true;
+ }
+
+ if (isFirstChild(li) && isLastChild(li)) {
+ if (ulParent.nodeName == "LI") {
+ dom.insertAfter(li, ulParent);
+ removeEmptyLi(ulParent);
+ dom.remove(ul);
+ } else if (isListNode(ulParent)) {
+ dom.remove(ul, true);
+ } else {
+ ulParent.insertBefore(createNewTextBlock(li), ul);
+ dom.remove(ul);
}
- ],
- buttons: [
- {text: "Close", onclick: function() {
- win.close();
- }}
- ]
- });
- }
- editor.addCommand('mceShowCharmap', showDialog);
+ return true;
+ } else if (isFirstChild(li)) {
+ if (ulParent.nodeName == "LI") {
+ dom.insertAfter(li, ulParent);
+ li.appendChild(ul);
+ removeEmptyLi(ulParent);
+ } else if (isListNode(ulParent)) {
+ ulParent.insertBefore(li, ul);
+ } else {
+ ulParent.insertBefore(createNewTextBlock(li), ul);
+ dom.remove(li);
+ }
- editor.addButton('charmap', {
- icon: 'charmap',
- tooltip: 'Special character',
- cmd: 'mceShowCharmap'
- });
+ return true;
+ } else if (isLastChild(li)) {
+ if (ulParent.nodeName == "LI") {
+ dom.insertAfter(li, ulParent);
+ } else if (isListNode(ulParent)) {
+ dom.insertAfter(li, ul);
+ } else {
+ dom.insertAfter(createNewTextBlock(li), ul);
+ dom.remove(li);
+ }
- editor.addMenuItem('charmap', {
- icon: 'charmap',
- text: 'Special character',
- cmd: 'mceShowCharmap',
- context: 'insert'
- });
-});
+ return true;
+ }
- }).apply(root, arguments);
-});
-}(this));
+ if (ulParent.nodeName == 'LI') {
+ ul = ulParent;
+ newBlock = createNewTextBlock(li, 'LI');
+ } else if (isListNode(ulParent)) {
+ newBlock = createNewTextBlock(li, 'LI');
+ } else {
+ newBlock = createNewTextBlock(li);
+ }
-(function(root) {
-define("tinymce-code", ["tinymce"], function() {
- return (function() {
-/**
- * plugin.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
+ splitList(ul, li, newBlock);
+ normalizeList(ul.parentNode);
-/*global tinymce:true */
+ return true;
+ }
-tinymce.PluginManager.add('code', function(editor) {
- function showDialog() {
- var win = editor.windowManager.open({
- title: "Source code",
- body: {
- type: 'textbox',
- name: 'code',
- multiline: true,
- minWidth: editor.getParam("code_dialog_width", 600),
- minHeight: editor.getParam("code_dialog_height", Math.min(tinymce.DOM.getViewPort().h - 200, 500)),
- spellcheck: false,
- style: 'direction: ltr; text-align: left'
- },
- onSubmit: function(e) {
- // We get a lovely "Wrong document" error in IE 11 if we
- // don't move the focus to the editor before creating an undo
- // transation since it tries to make a bookmark for the current selection
- editor.focus();
+ function indent(li) {
+ var sibling, newList;
- editor.undoManager.transact(function() {
- editor.setContent(e.data.code);
- });
+ function mergeLists(from, to) {
+ var node;
- editor.selection.setCursorLocation();
- editor.nodeChanged();
+ if (isListNode(from)) {
+ while ((node = li.lastChild.firstChild)) {
+ to.appendChild(node);
+ }
+
+ dom.remove(from);
+ }
}
- });
- // Gecko has a major performance issue with textarea
- // contents so we need to set it when all reflows are done
- win.find('#code').value(editor.getContent({source_view: true}));
- }
+ if (li.nodeName == 'DT') {
+ dom.rename(li, 'DD');
+ return true;
+ }
- editor.addCommand("mceCodeEditor", showDialog);
+ sibling = li.previousSibling;
- editor.addButton('code', {
- icon: 'code',
- tooltip: 'Source code',
- onclick: showDialog
- });
+ if (sibling && isListNode(sibling)) {
+ sibling.appendChild(li);
+ return true;
+ }
- editor.addMenuItem('code', {
- icon: 'code',
- text: 'Source code',
- context: 'tools',
- onclick: showDialog
- });
-});
+ if (sibling && sibling.nodeName == 'LI' && isListNode(sibling.lastChild)) {
+ sibling.lastChild.appendChild(li);
+ mergeLists(li.lastChild, sibling.lastChild);
+ return true;
+ }
- }).apply(root, arguments);
-});
-}(this));
+ sibling = li.nextSibling;
-(function(root) {
-define("tinymce-colorpicker", ["tinymce"], function() {
- return (function() {
-/**
- * plugin.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
+ if (sibling && isListNode(sibling)) {
+ sibling.insertBefore(li, sibling.firstChild);
+ return true;
+ }
-/*global tinymce:true */
+ if (sibling && sibling.nodeName == 'LI' && isListNode(li.lastChild)) {
+ return false;
+ }
-tinymce.PluginManager.add('colorpicker', function(editor) {
- function colorPickerCallback(callback, value) {
- function setColor(value) {
- var color = new tinymce.util.Color(value), rgb = color.toRgb();
+ sibling = li.previousSibling;
+ if (sibling && sibling.nodeName == 'LI') {
+ newList = dom.create(li.parentNode.nodeName);
+ sibling.appendChild(newList);
+ newList.appendChild(li);
+ mergeLists(li.lastChild, newList);
+ return true;
+ }
- win.fromJSON({
- r: rgb.r,
- g: rgb.g,
- b: rgb.b,
- hex: color.toHex().substr(1)
- });
+ return false;
+ }
- showPreview(color.toHex());
+ function indentSelection() {
+ var listElements = getSelectedListItems();
+
+ if (listElements.length) {
+ var bookmark = createBookmark(selection.getRng(true));
+
+ for (var i = 0; i < listElements.length; i++) {
+ if (!indent(listElements[i]) && i === 0) {
+ break;
+ }
+ }
+
+ moveToBookmark(bookmark);
+ editor.nodeChanged();
+
+ return true;
+ }
}
- function showPreview(hexColor) {
- win.find('#preview')[0].getEl().style.background = hexColor;
+ function outdentSelection() {
+ var listElements = getSelectedListItems();
+
+ if (listElements.length) {
+ var bookmark = createBookmark(selection.getRng(true));
+ var i, y, root = editor.getBody();
+
+ i = listElements.length;
+ while (i--) {
+ var node = listElements[i].parentNode;
+
+ while (node && node != root) {
+ y = listElements.length;
+ while (y--) {
+ if (listElements[y] === node) {
+ listElements.splice(i, 1);
+ break;
+ }
+ }
+
+ node = node.parentNode;
+ }
+ }
+
+ for (i = 0; i < listElements.length; i++) {
+ if (!outdent(listElements[i]) && i === 0) {
+ break;
+ }
+ }
+
+ moveToBookmark(bookmark);
+ editor.nodeChanged();
+
+ return true;
+ }
}
- var win = editor.windowManager.open({
- title: 'Color',
- items: {
- type: 'container',
- layout: 'flex',
- direction: 'row',
- align: 'stretch',
- padding: 5,
- spacing: 10,
- items: [
- {
- type: 'colorpicker',
- value: value,
- onchange: function() {
- var rgb = this.rgb();
+ function applyList(listName) {
+ var rng = selection.getRng(true), bookmark = createBookmark(rng), listItemName = 'LI';
+
+ listName = listName.toUpperCase();
+
+ if (listName == 'DL') {
+ listItemName = 'DT';
+ }
+
+ function getSelectedTextBlocks() {
+ var textBlocks = [], root = editor.getBody();
+
+ function getEndPointNode(start) {
+ var container, offset;
+
+ container = rng[start ? 'startContainer' : 'endContainer'];
+ offset = rng[start ? 'startOffset' : 'endOffset'];
+
+ // Resolve node index
+ if (container.nodeType == 1) {
+ container = container.childNodes[Math.min(offset, container.childNodes.length - 1)] || container;
+ }
- if (win) {
- win.find('#r').value(rgb.r);
- win.find('#g').value(rgb.g);
- win.find('#b').value(rgb.b);
- win.find('#hex').value(this.value().substr(1));
- showPreview(this.value());
- }
+ while (container.parentNode != root) {
+ if (isTextBlock(container)) {
+ return container;
}
- },
- {
- type: 'form',
- padding: 0,
- labelGap: 5,
- defaults: {
- type: 'textbox',
- size: 7,
- value: '0',
- flex: 1,
- spellcheck: false,
- onchange: function() {
- var colorPickerCtrl = win.find('colorpicker')[0];
- var name, value;
- name = this.name();
- value = this.value();
+ if (/^(TD|TH)$/.test(container.parentNode.nodeName)) {
+ return container;
+ }
- if (name == "hex") {
- value = '#' + value;
- setColor(value);
- colorPickerCtrl.value(value);
- return;
- }
+ container = container.parentNode;
+ }
- value = {
- r: win.find('#r').value(),
- g: win.find('#g').value(),
- b: win.find('#b').value()
- };
+ return container;
+ }
- colorPickerCtrl.value(value);
- setColor(value);
- }
- },
- items: [
- {name: 'r', label: 'R', autofocus: 1},
- {name: 'g', label: 'G'},
- {name: 'b', label: 'B'},
- {name: 'hex', label: '#', value: '000000'},
- {name: 'preview', type: 'container', border: 1}
- ]
+ var startNode = getEndPointNode(true);
+ var endNode = getEndPointNode();
+ var block, siblings = [];
+
+ for (var node = startNode; node; node = node.nextSibling) {
+ siblings.push(node);
+
+ if (node == endNode) {
+ break;
}
- ]
- },
- onSubmit: function() {
- callback('#' + this.toJSON().hex);
- }
- });
+ }
- setColor(value);
- }
+ tinymce.each(siblings, function(node) {
+ if (isTextBlock(node)) {
+ textBlocks.push(node);
+ block = null;
+ return;
+ }
- if (!editor.settings.color_picker_callback) {
- editor.settings.color_picker_callback = colorPickerCallback;
- }
-});
+ if (dom.isBlock(node) || node.nodeName == 'BR') {
+ if (node.nodeName == 'BR') {
+ dom.remove(node);
+ }
- }).apply(root, arguments);
-});
-}(this));
+ block = null;
+ return;
+ }
-(function(root) {
-define("tinymce-contextmenu", ["tinymce"], function() {
- return (function() {
-/**
- * plugin.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
+ var nextSibling = node.nextSibling;
+ if (tinymce.dom.BookmarkManager.isBookmarkNode(node)) {
+ if (isTextBlock(nextSibling) || (!nextSibling && node.parentNode == root)) {
+ block = null;
+ return;
+ }
+ }
-/*global tinymce:true */
+ if (!block) {
+ block = dom.create('p');
+ node.parentNode.insertBefore(block, node);
+ textBlocks.push(block);
+ }
-tinymce.PluginManager.add('contextmenu', function(editor) {
- var menu, contextmenuNeverUseNative = editor.settings.contextmenu_never_use_native;
+ block.appendChild(node);
+ });
- editor.on('contextmenu', function(e) {
- var contextmenu, doc = editor.getDoc();
+ return textBlocks;
+ }
- // Block TinyMCE menu on ctrlKey
- if (e.ctrlKey && !contextmenuNeverUseNative) {
- return;
- }
+ tinymce.each(getSelectedTextBlocks(), function(block) {
+ var listBlock, sibling;
- e.preventDefault();
+ sibling = block.previousSibling;
+ if (sibling && isListNode(sibling) && sibling.nodeName == listName) {
+ listBlock = sibling;
+ block = dom.rename(block, listItemName);
+ sibling.appendChild(block);
+ } else {
+ listBlock = dom.create(listName);
+ block.parentNode.insertBefore(listBlock, block);
+ listBlock.appendChild(block);
+ block = dom.rename(block, listItemName);
+ }
- /**
- * WebKit/Blink on Mac has the odd behavior of selecting the target word or line this causes
- * issues when for example inserting images see: #7022
- */
- if (tinymce.Env.mac && tinymce.Env.webkit) {
- if (e.button == 2 && doc.caretRangeFromPoint) {
- editor.selection.setRng(doc.caretRangeFromPoint(e.x, e.y));
- }
- }
+ mergeWithAdjacentLists(listBlock);
+ });
- contextmenu = editor.settings.contextmenu || 'link image inserttable | cell row column deletetable';
+ moveToBookmark(bookmark);
+ }
- // Render menu
- if (!menu) {
- var items = [];
+ function removeList() {
+ var bookmark = createBookmark(selection.getRng(true)), root = editor.getBody();
- tinymce.each(contextmenu.split(/[ ,]/), function(name) {
- var item = editor.menuItems[name];
+ tinymce.each(getSelectedListItems(), function(li) {
+ var node, rootList;
- if (name == '|') {
- item = {text: name};
+ if (dom.isEmpty(li)) {
+ outdent(li);
+ return;
}
- if (item) {
- item.shortcut = ''; // Hide shortcuts
- items.push(item);
+ for (node = li; node && node != root; node = node.parentNode) {
+ if (isListNode(node)) {
+ rootList = node;
+ }
}
+
+ splitList(rootList, li);
});
- for (var i = 0; i < items.length; i++) {
- if (items[i].text == '|') {
- if (i === 0 || i == items.length - 1) {
- items.splice(i, 1);
- }
+ moveToBookmark(bookmark);
+ }
+
+ function toggleList(listName) {
+ var parentList = dom.getParent(selection.getStart(), 'OL,UL,DL');
+
+ if (parentList) {
+ if (parentList.nodeName == listName) {
+ removeList(listName);
+ } else {
+ var bookmark = createBookmark(selection.getRng(true));
+ mergeWithAdjacentLists(dom.rename(parentList, listName));
+ moveToBookmark(bookmark);
}
+ } else {
+ applyList(listName);
}
+ }
- menu = new tinymce.ui.Menu({
- items: items,
- context: 'contextmenu',
- classes: 'contextmenu'
- }).renderTo();
+ function queryListCommandState(listName) {
+ return function() {
+ var parentList = dom.getParent(editor.selection.getStart(), 'UL,OL,DL');
- editor.on('remove', function() {
- menu.remove();
- menu = null;
- });
- } else {
- menu.show();
+ return parentList && parentList.nodeName == listName;
+ };
}
- // Position menu
- var pos = {x: e.pageX, y: e.pageY};
+ self.backspaceDelete = function(isForward) {
+ function findNextCaretContainer(rng, isForward) {
+ var node = rng.startContainer, offset = rng.startOffset;
+ var nonEmptyBlocks, walker;
- if (!editor.inline) {
- pos = tinymce.DOM.getPos(editor.getContentAreaContainer());
- pos.x += e.clientX;
- pos.y += e.clientY;
- }
+ if (node.nodeType == 3 && (isForward ? offset < node.data.length : offset > 0)) {
+ return node;
+ }
- menu.moveTo(pos.x, pos.y);
- });
-});
+ nonEmptyBlocks = editor.schema.getNonEmptyElements();
+ walker = new tinymce.dom.TreeWalker(rng.startContainer);
- }).apply(root, arguments);
-});
-}(this));
+ while ((node = walker[isForward ? 'next' : 'prev']())) {
+ if (node.nodeName == 'LI' && !node.hasChildNodes()) {
+ return node;
+ }
-(function(root) {
-define("tinymce-directionality", ["tinymce"], function() {
- return (function() {
-/**
- * plugin.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
+ if (nonEmptyBlocks[node.nodeName]) {
+ return node;
+ }
-/*global tinymce:true */
+ if (node.nodeType == 3 && node.data.length > 0) {
+ return node;
+ }
+ }
+ }
-tinymce.PluginManager.add('directionality', function(editor) {
- function setDir(dir) {
- var dom = editor.dom, curDir, blocks = editor.selection.getSelectedBlocks();
+ function mergeLiElements(fromElm, toElm) {
+ var node, listNode, ul = fromElm.parentNode;
- if (blocks.length) {
- curDir = dom.getAttrib(blocks[0], "dir");
+ if (isListNode(toElm.lastChild)) {
+ listNode = toElm.lastChild;
+ }
- tinymce.each(blocks, function(block) {
- // Add dir to block if the parent block doesn't already have that dir
- if (!dom.getParent(block.parentNode, "*[dir='" + dir + "']", dom.getRoot())) {
- if (curDir != dir) {
- dom.setAttrib(block, "dir", dir);
- } else {
- dom.setAttrib(block, "dir", null);
+ node = toElm.lastChild;
+ if (node && node.nodeName == 'BR' && fromElm.hasChildNodes()) {
+ dom.remove(node);
+ }
+
+ if (dom.isEmpty(toElm)) {
+ dom.$(toElm).empty();
+ }
+
+ if (!dom.isEmpty(fromElm)) {
+ while ((node = fromElm.firstChild)) {
+ toElm.appendChild(node);
}
}
- });
- editor.nodeChanged();
- }
- }
+ if (listNode) {
+ toElm.appendChild(listNode);
+ }
- function generateSelector(dir) {
- var selector = [];
+ dom.remove(fromElm);
- tinymce.each('h1 h2 h3 h4 h5 h6 div p'.split(' '), function(name) {
- selector.push(name + '[dir=' + dir + ']');
- });
+ if (dom.isEmpty(ul)) {
+ dom.remove(ul);
+ }
+ }
- return selector.join(',');
- }
+ if (selection.isCollapsed()) {
+ var li = dom.getParent(selection.getStart(), 'LI');
- editor.addCommand('mceDirectionLTR', function() {
- setDir("ltr");
- });
+ if (li) {
+ var rng = selection.getRng(true);
+ var otherLi = dom.getParent(findNextCaretContainer(rng, isForward), 'LI');
- editor.addCommand('mceDirectionRTL', function() {
- setDir("rtl");
- });
+ if (otherLi && otherLi != li) {
+ var bookmark = createBookmark(rng);
- editor.addButton('ltr', {
- title: 'Left to right',
- cmd: 'mceDirectionLTR',
- stateSelector: generateSelector('ltr')
- });
+ if (isForward) {
+ mergeLiElements(otherLi, li);
+ } else {
+ mergeLiElements(li, otherLi);
+ }
- editor.addButton('rtl', {
- title: 'Right to left',
- cmd: 'mceDirectionRTL',
- stateSelector: generateSelector('rtl')
- });
-});
+ moveToBookmark(bookmark);
- }).apply(root, arguments);
-});
-}(this));
+ return true;
+ } else if (!otherLi) {
+ if (!isForward && removeList(li.parentNode.nodeName)) {
+ return true;
+ }
+ }
+ }
+ }
+ };
-(function(root) {
-define("tinymce-emoticons", ["tinymce"], function() {
- return (function() {
-/**
- * plugin.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
+ editor.on('BeforeExecCommand', function(e) {
+ var cmd = e.command.toLowerCase(), isHandled;
-/*global tinymce:true */
+ if (cmd == "indent") {
+ if (indentSelection()) {
+ isHandled = true;
+ }
+ } else if (cmd == "outdent") {
+ if (outdentSelection()) {
+ isHandled = true;
+ }
+ }
-tinymce.PluginManager.add('emoticons', function(editor, url) {
- var emoticons = [
- ["cool", "cry", "embarassed", "foot-in-mouth"],
- ["frown", "innocent", "kiss", "laughing"],
- ["money-mouth", "sealed", "smile", "surprised"],
- ["tongue-out", "undecided", "wink", "yell"]
- ];
+ if (isHandled) {
+ editor.fire('ExecCommand', {command: e.command});
+ e.preventDefault();
+ return true;
+ }
+ });
- function getHtml() {
- var emoticonsHtml;
+ editor.addCommand('InsertUnorderedList', function() {
+ toggleList('UL');
+ });
- emoticonsHtml = '';
+ editor.addCommand('InsertOrderedList', function() {
+ toggleList('OL');
+ });
- tinymce.each(emoticons, function(row) {
- emoticonsHtml += '';
+ editor.addCommand('InsertDefinitionList', function() {
+ toggleList('DL');
+ });
- tinymce.each(row, function(icon) {
- var emoticonUrl = url + '/img/smiley-' + icon + '.gif';
+ editor.addQueryStateHandler('InsertUnorderedList', queryListCommandState('UL'));
+ editor.addQueryStateHandler('InsertOrderedList', queryListCommandState('OL'));
+ editor.addQueryStateHandler('InsertDefinitionList', queryListCommandState('DL'));
- emoticonsHtml += ' | ';
- });
+ editor.on('keydown', function(e) {
+ // Check for tab but not ctrl/cmd+tab since it switches browser tabs
+ if (e.keyCode != 9 || tinymce.util.VK.metaKeyPressed(e)) {
+ return;
+ }
- emoticonsHtml += '
';
- });
+ if (editor.dom.getParent(editor.selection.getStart(), 'LI,DT,DD')) {
+ e.preventDefault();
- emoticonsHtml += '
';
+ if (e.shiftKey) {
+ outdentSelection();
+ } else {
+ indentSelection();
+ }
+ }
+ });
+ });
- return emoticonsHtml;
- }
+ editor.addButton('indent', {
+ icon: 'indent',
+ title: 'Increase indent',
+ cmd: 'Indent',
+ onPostRender: function() {
+ var ctrl = this;
- editor.addButton('emoticons', {
- type: 'panelbutton',
- panel: {
- role: 'application',
- autohide: true,
- html: getHtml,
- onclick: function(e) {
- var linkElm = editor.dom.getParent(e.target, 'a');
+ editor.on('nodechange', function() {
+ var blocks = editor.selection.getSelectedBlocks();
+ var disable = false;
- if (linkElm) {
- editor.insertContent(
- ''
- );
+ for (var i = 0, l = blocks.length; !disable && i < l; i++) {
+ var tag = blocks[i].nodeName;
- this.hide();
+ disable = (tag == 'LI' && isFirstChild(blocks[i]) || tag == 'UL' || tag == 'OL' || tag == 'DD');
}
+
+ ctrl.disabled(disable);
+ });
+ }
+ });
+
+ editor.on('keydown', function(e) {
+ if (e.keyCode == tinymce.util.VK.BACKSPACE) {
+ if (self.backspaceDelete()) {
+ e.preventDefault();
}
- },
- tooltip: 'Emoticons'
+ } else if (e.keyCode == tinymce.util.VK.DELETE) {
+ if (self.backspaceDelete(true)) {
+ e.preventDefault();
+ }
+ }
});
});
@@ -49964,7 +65587,7 @@ tinymce.PluginManager.add('emoticons', function(editor, url) {
}(this));
(function(root) {
-define("tinymce-fullpage", ["tinymce"], function() {
+define("tinymce-media", ["tinymce"], function() {
return (function() {
/**
* plugin.js
@@ -49976,638 +65599,797 @@ define("tinymce-fullpage", ["tinymce"], function() {
* Contributing: http://www.tinymce.com/contributing
*/
+/*jshint maxlen:255 */
+/*eslint max-len:0 */
/*global tinymce:true */
-tinymce.PluginManager.add('fullpage', function(editor) {
- var each = tinymce.each, Node = tinymce.html.Node;
- var head, foot;
-
- function showDialog() {
- var data = htmlToData();
+tinymce.PluginManager.add('media', function(editor, url) {
+ var urlPatterns = [
+ {regex: /youtu\.be\/([\w\-.]+)/, type: 'iframe', w: 425, h: 350, url: '//www.youtube.com/embed/$1', allowFullscreen: true},
+ {regex: /youtube\.com(.+)v=([^&]+)/, type: 'iframe', w: 425, h: 350, url: '//www.youtube.com/embed/$2', allowFullscreen: true},
+ {regex: /vimeo\.com\/([0-9]+)/, type: 'iframe', w: 425, h: 350, url: '//player.vimeo.com/video/$1?title=0&byline=0&portrait=0&color=8dc7dc', allowfullscreen: true},
+ {regex: /vimeo\.com\/(.*)\/([0-9]+)/, type: "iframe", w: 425, h: 350, url: "//player.vimeo.com/video/$2?title=0&byline=0", allowfullscreen: true},
+ {regex: /maps\.google\.([a-z]{2,3})\/maps\/(.+)msid=(.+)/, type: 'iframe', w: 425, h: 350, url: '//maps.google.com/maps/ms?msid=$2&output=embed"', allowFullscreen: false}
+ ];
- editor.windowManager.open({
- title: 'Document properties',
- data: data,
- defaults: {type: 'textbox', size: 40},
- body: [
- {name: 'title', label: 'Title'},
- {name: 'keywords', label: 'Keywords'},
- {name: 'description', label: 'Description'},
- {name: 'robots', label: 'Robots'},
- {name: 'author', label: 'Author'},
- {name: 'docencoding', label: 'Encoding'}
- ],
- onSubmit: function(e) {
- dataToHtml(tinymce.extend(data, e.data));
- }
- });
- }
+ var embedChange = (tinymce.Env.ie && tinymce.Env.ie <= 8) ? 'onChange' : 'onInput';
- function htmlToData() {
- var headerFragment = parseHeader(), data = {}, elm, matches;
+ function guessMime(url) {
+ url = url.toLowerCase();
- function getAttr(elm, name) {
- var value = elm.attr(name);
+ if (url.indexOf('.mp3') != -1) {
+ return 'audio/mpeg';
+ }
- return value || '';
+ if (url.indexOf('.wav') != -1) {
+ return 'audio/wav';
}
- // Default some values
- data.fontface = editor.getParam("fullpage_default_fontface", "");
- data.fontsize = editor.getParam("fullpage_default_fontsize", "");
+ if (url.indexOf('.mp4') != -1) {
+ return 'video/mp4';
+ }
- // Parse XML PI
- elm = headerFragment.firstChild;
- if (elm.type == 7) {
- data.xml_pi = true;
- matches = /encoding="([^"]+)"/.exec(elm.value);
- if (matches) {
- data.docencoding = matches[1];
- }
+ if (url.indexOf('.webm') != -1) {
+ return 'video/webm';
}
- // Parse doctype
- elm = headerFragment.getAll('#doctype')[0];
- if (elm) {
- data.doctype = '";
+ if (url.indexOf('.ogg') != -1) {
+ return 'video/ogg';
}
- // Parse title element
- elm = headerFragment.getAll('title')[0];
- if (elm && elm.firstChild) {
- data.title = elm.firstChild.value;
+ if (url.indexOf('.swf') != -1) {
+ return 'application/x-shockwave-flash';
}
- // Parse meta elements
- each(headerFragment.getAll('meta'), function(meta) {
- var name = meta.attr('name'), httpEquiv = meta.attr('http-equiv'), matches;
+ return '';
+ }
- if (name) {
- data[name.toLowerCase()] = meta.attr('content');
- } else if (httpEquiv == "Content-Type") {
- matches = /charset\s*=\s*(.*)\s*/gi.exec(meta.attr('content'));
+ function getVideoScriptMatch(src) {
+ var prefixes = editor.settings.media_scripts;
- if (matches) {
- data.docencoding = matches[1];
+ if (prefixes) {
+ for (var i = 0; i < prefixes.length; i++) {
+ if (src.indexOf(prefixes[i].filter) !== -1) {
+ return prefixes[i];
}
}
- });
-
- // Parse html attribs
- elm = headerFragment.getAll('html')[0];
- if (elm) {
- data.langcode = getAttr(elm, 'lang') || getAttr(elm, 'xml:lang');
}
+ }
- // Parse stylesheets
- data.stylesheets = [];
- tinymce.each(headerFragment.getAll('link'), function(link) {
- if (link.attr('rel') == 'stylesheet') {
- data.stylesheets.push(link.attr('href'));
+ function showDialog() {
+ var win, width, height, data;
+
+ var generalFormItems = [
+ {
+ name: 'source1',
+ type: 'filepicker',
+ filetype: 'media',
+ size: 40,
+ autofocus: true,
+ label: 'Source',
+ onchange: function(e) {
+ tinymce.each(e.meta, function(value, key) {
+ win.find('#' + key).value(value);
+ });
+ }
}
- });
+ ];
- // Parse body parts
- elm = headerFragment.getAll('body')[0];
- if (elm) {
- data.langdir = getAttr(elm, 'dir');
- data.style = getAttr(elm, 'style');
- data.visited_color = getAttr(elm, 'vlink');
- data.link_color = getAttr(elm, 'link');
- data.active_color = getAttr(elm, 'alink');
- }
+ function recalcSize(e) {
+ var widthCtrl, heightCtrl, newWidth, newHeight;
- return data;
- }
+ widthCtrl = win.find('#width')[0];
+ heightCtrl = win.find('#height')[0];
- function dataToHtml(data) {
- var headerFragment, headElement, html, elm, value, dom = editor.dom;
+ newWidth = widthCtrl.value();
+ newHeight = heightCtrl.value();
- function setAttr(elm, name, value) {
- elm.attr(name, value ? value : undefined);
- }
+ if (win.find('#constrain')[0].checked() && width && height && newWidth && newHeight) {
+ if (e.control == widthCtrl) {
+ newHeight = Math.round((newWidth / width) * newHeight);
- function addHeadNode(node) {
- if (headElement.firstChild) {
- headElement.insert(node, headElement.firstChild);
- } else {
- headElement.append(node);
+ if (!isNaN(newHeight)) {
+ heightCtrl.value(newHeight);
+ }
+ } else {
+ newWidth = Math.round((newHeight / height) * newWidth);
+
+ if (!isNaN(newWidth)) {
+ widthCtrl.value(newWidth);
+ }
+ }
}
+
+ width = newWidth;
+ height = newHeight;
}
- headerFragment = parseHeader();
- headElement = headerFragment.getAll('head')[0];
- if (!headElement) {
- elm = headerFragment.getAll('html')[0];
- headElement = new Node('head', 1);
+ if (editor.settings.media_alt_source !== false) {
+ generalFormItems.push({name: 'source2', type: 'filepicker', filetype: 'media', size: 40, label: 'Alternative source'});
+ }
- if (elm.firstChild) {
- elm.insert(headElement, elm.firstChild, true);
- } else {
- elm.append(headElement);
- }
+ if (editor.settings.media_poster !== false) {
+ generalFormItems.push({name: 'poster', type: 'filepicker', filetype: 'image', size: 40, label: 'Poster'});
}
- // Add/update/remove XML-PI
- elm = headerFragment.firstChild;
- if (data.xml_pi) {
- value = 'version="1.0"';
+ if (editor.settings.media_dimensions !== false) {
+ generalFormItems.push({
+ type: 'container',
+ label: 'Dimensions',
+ layout: 'flex',
+ align: 'center',
+ spacing: 5,
+ items: [
+ {name: 'width', type: 'textbox', maxLength: 5, size: 3, onchange: recalcSize, ariaLabel: 'Width'},
+ {type: 'label', text: 'x'},
+ {name: 'height', type: 'textbox', maxLength: 5, size: 3, onchange: recalcSize, ariaLabel: 'Height'},
+ {name: 'constrain', type: 'checkbox', checked: true, text: 'Constrain proportions'}
+ ]
+ });
+ }
- if (data.docencoding) {
- value += ' encoding="' + data.docencoding + '"';
- }
+ data = getData(editor.selection.getNode());
+ width = data.width;
+ height = data.height;
- if (elm.type != 7) {
- elm = new Node('xml', 7);
- headerFragment.insert(elm, headerFragment.firstChild, true);
- }
+ var embedTextBox = {
+ id: 'mcemediasource',
+ type: 'textbox',
+ flex: 1,
+ name: 'embed',
+ value: getSource(),
+ multiline: true,
+ label: 'Source'
+ };
- elm.value = value;
- } else if (elm && elm.type == 7) {
- elm.remove();
+ function updateValueOnChange() {
+ data = htmlToData(this.value());
+ this.parent().parent().fromJSON(data);
}
- // Add/update/remove doctype
- elm = headerFragment.getAll('#doctype')[0];
- if (data.doctype) {
- if (!elm) {
- elm = new Node('#doctype', 10);
+ embedTextBox[embedChange] = updateValueOnChange;
- if (data.xml_pi) {
- headerFragment.insert(elm, headerFragment.firstChild);
- } else {
- addHeadNode(elm);
+ win = editor.windowManager.open({
+ title: 'Insert/edit video',
+ data: data,
+ bodyType: 'tabpanel',
+ body: [
+ {
+ title: 'General',
+ type: "form",
+ onShowTab: function() {
+ data = htmlToData(this.next().find('#embed').value());
+ this.fromJSON(data);
+ },
+ items: generalFormItems
+ },
+
+ {
+ title: 'Embed',
+ type: "container",
+ layout: 'flex',
+ direction: 'column',
+ align: 'stretch',
+ padding: 10,
+ spacing: 10,
+ onShowTab: function() {
+ this.find('#embed').value(dataToHtml(this.parent().toJSON()));
+ },
+ items: [
+ {
+ type: 'label',
+ text: 'Paste your embed code below:',
+ forId: 'mcemediasource'
+ },
+ embedTextBox
+ ]
}
- }
+ ],
+ onSubmit: function() {
+ var beforeObjects, afterObjects, i, y;
- elm.value = data.doctype.substring(9, data.doctype.length - 1);
- } else if (elm) {
- elm.remove();
- }
+ beforeObjects = editor.dom.select('img[data-mce-object]');
+ editor.insertContent(dataToHtml(this.toJSON()));
+ afterObjects = editor.dom.select('img[data-mce-object]');
- // Add meta encoding
- elm = null;
- each(headerFragment.getAll('meta'), function(meta) {
- if (meta.attr('http-equiv') == 'Content-Type') {
- elm = meta;
+ // Find new image placeholder so we can select it
+ for (i = 0; i < beforeObjects.length; i++) {
+ for (y = afterObjects.length - 1; y >= 0; y--) {
+ if (beforeObjects[i] == afterObjects[y]) {
+ afterObjects.splice(y, 1);
+ }
+ }
+ }
+
+ editor.selection.select(afterObjects[0]);
+ editor.nodeChanged();
}
});
+ }
- if (data.docencoding) {
- if (!elm) {
- elm = new Node('meta', 1);
- elm.attr('http-equiv', 'Content-Type');
- elm.shortEnded = true;
- addHeadNode(elm);
- }
+ function getSource() {
+ var elm = editor.selection.getNode();
- elm.attr('content', 'text/html; charset=' + data.docencoding);
- } else if (elm) {
- elm.remove();
+ if (elm.getAttribute('data-mce-object')) {
+ return editor.selection.getContent();
}
+ }
- // Add/update/remove title
- elm = headerFragment.getAll('title')[0];
- if (data.title) {
- if (!elm) {
- elm = new Node('title', 1);
- addHeadNode(elm);
- } else {
- elm.empty();
+ function dataToHtml(data) {
+ var html = '';
+
+ if (!data.source1) {
+ tinymce.extend(data, htmlToData(data.embed));
+ if (!data.source1) {
+ return '';
}
+ }
- elm.append(new Node('#text', 3)).value = data.title;
- } else if (elm) {
- elm.remove();
+ if (!data.source2) {
+ data.source2 = '';
}
- // Add/update/remove meta
- each('keywords,description,author,copyright,robots'.split(','), function(name) {
- var nodes = headerFragment.getAll('meta'), i, meta, value = data[name];
+ if (!data.poster) {
+ data.poster = '';
+ }
- for (i = 0; i < nodes.length; i++) {
- meta = nodes[i];
+ data.source1 = editor.convertURL(data.source1, "source");
+ data.source2 = editor.convertURL(data.source2, "source");
+ data.source1mime = guessMime(data.source1);
+ data.source2mime = guessMime(data.source2);
+ data.poster = editor.convertURL(data.poster, "poster");
+ data.flashPlayerUrl = editor.convertURL(url + '/moxieplayer.swf', "movie");
- if (meta.attr('name') == name) {
- if (value) {
- meta.attr('content', value);
- } else {
- meta.remove();
- }
+ tinymce.each(urlPatterns, function(pattern) {
+ var match, i, url;
- return;
- }
- }
+ if ((match = pattern.regex.exec(data.source1))) {
+ url = pattern.url;
- if (value) {
- elm = new Node('meta', 1);
- elm.attr('name', name);
- elm.attr('content', value);
- elm.shortEnded = true;
+ for (i = 0; match[i]; i++) {
+ /*jshint loopfunc:true*/
+ /*eslint no-loop-func:0 */
+ url = url.replace('$' + i, function() {
+ return match[i];
+ });
+ }
- addHeadNode(elm);
+ data.source1 = url;
+ data.type = pattern.type;
+ data.allowFullscreen = pattern.allowFullscreen;
+ data.width = data.width || pattern.w;
+ data.height = data.height || pattern.h;
}
});
- var currentStyleSheetsMap = {};
- tinymce.each(headerFragment.getAll('link'), function(stylesheet) {
- if (stylesheet.attr('rel') == 'stylesheet') {
- currentStyleSheetsMap[stylesheet.attr('href')] = stylesheet;
+ if (data.embed) {
+ html = updateHtml(data.embed, data, true);
+ } else {
+ var videoScript = getVideoScriptMatch(data.source1);
+ if (videoScript) {
+ data.type = 'script';
+ data.width = videoScript.width;
+ data.height = videoScript.height;
}
- });
- // Add new
- tinymce.each(data.stylesheets, function(stylesheet) {
- if (!currentStyleSheetsMap[stylesheet]) {
- elm = new Node('link', 1);
- elm.attr({
- rel: 'stylesheet',
- text: 'text/css',
- href: stylesheet
- });
- elm.shortEnded = true;
- addHeadNode(elm);
- }
+ data.width = data.width || 300;
+ data.height = data.height || 150;
- delete currentStyleSheetsMap[stylesheet];
- });
+ tinymce.each(data, function(value, key) {
+ data[key] = editor.dom.encode(value);
+ });
- // Delete old
- tinymce.each(currentStyleSheetsMap, function(stylesheet) {
- stylesheet.remove();
- });
+ if (data.type == "iframe") {
+ var allowFullscreen = data.allowFullscreen ? ' allowFullscreen="1"' : '';
+ html += '';
+ } else if (data.source1mime == "application/x-shockwave-flash") {
+ html += '';
+ } else if (data.source1mime.indexOf('audio') != -1) {
+ if (editor.settings.audio_template_callback) {
+ html = editor.settings.audio_template_callback(data);
+ } else {
+ html += (
+ ''
+ );
+ }
+ } else if (data.type == "script") {
+ html += '';
+ } else {
+ if (editor.settings.video_template_callback) {
+ html = editor.settings.video_template_callback(data);
+ } else {
+ html = (
+ ''
+ );
+ }
+ }
}
- // Set html attributes
- elm = headerFragment.getAll('html')[0];
- if (elm) {
- setAttr(elm, 'lang', data.langcode);
- setAttr(elm, 'xml:lang', data.langcode);
- }
+ return html;
+ }
- // No need for a head element
- if (!headElement.firstChild) {
- headElement.remove();
- }
+ function htmlToData(html) {
+ var data = {};
- // Serialize header fragment and crop away body part
- html = new tinymce.html.Serializer({
+ new tinymce.html.SaxParser({
validate: false,
- indent: true,
- apply_source_formatting: true,
- indent_before: 'head,html,body,meta,title,script,link,style',
- indent_after: 'head,html,body,meta,title,script,link,style'
- }).serialize(headerFragment);
+ allow_conditional_comments: true,
+ special: 'script,noscript',
+ start: function(name, attrs) {
+ if (!data.source1 && name == "param") {
+ data.source1 = attrs.map.movie;
+ }
- head = html.substring(0, html.indexOf(''));
- }
+ if (name == "iframe" || name == "object" || name == "embed" || name == "video" || name == "audio") {
+ if (!data.type) {
+ data.type = name;
+ }
- function parseHeader() {
- // Parse the contents with a DOM parser
- return new tinymce.html.DomParser({
- validate: false,
- root_name: '#document'
- }).parse(head);
- }
+ data = tinymce.extend(attrs.map, data);
+ }
- function setContent(evt) {
- var startPos, endPos, content = evt.content, headerFragment, styles = '', dom = editor.dom, elm;
+ if (name == "script") {
+ var videoScript = getVideoScriptMatch(attrs.map.src);
+ if (!videoScript) {
+ return;
+ }
- if (evt.selection) {
- return;
+ data = {
+ type: "script",
+ source1: attrs.map.src,
+ width: videoScript.width,
+ height: videoScript.height
+ };
+ }
+
+ if (name == "source") {
+ if (!data.source1) {
+ data.source1 = attrs.map.src;
+ } else if (!data.source2) {
+ data.source2 = attrs.map.src;
+ }
+ }
+
+ if (name == "img" && !data.poster) {
+ data.poster = attrs.map.src;
+ }
+ }
+ }).parse(html);
+
+ data.source1 = data.source1 || data.src || data.data;
+ data.source2 = data.source2 || '';
+ data.poster = data.poster || '';
+
+ return data;
+ }
+
+ function getData(element) {
+ if (element.getAttribute('data-mce-object')) {
+ return htmlToData(editor.serializer.serialize(element, {selection: true}));
}
- function low(s) {
- return s.replace(/<\/?[A-Z]+/g, function(a) {
- return a.toLowerCase();
- });
- }
+ return {};
+ }
- // Ignore raw updated if we already have a head, this will fix issues with undo/redo keeping the head/foot separate
- if (evt.format == 'raw' && head) {
- return;
+ function sanitize(html) {
+ if (editor.settings.media_filter_html === false) {
+ return html;
}
- if (evt.source_view && editor.getParam('fullpage_hide_in_source_view')) {
- return;
- }
+ var writer = new tinymce.html.Writer(), blocked;
- // Fixed so new document/setContent('') doesn't remove existing header/footer except when it's in source code view
- if (content.length === 0 && !evt.source_view) {
- content = tinymce.trim(head) + '\n' + tinymce.trim(content) + '\n' + tinymce.trim(foot);
- }
+ new tinymce.html.SaxParser({
+ validate: false,
+ allow_conditional_comments: false,
+ special: 'script,noscript',
- // Parse out head, body and footer
- content = content.replace(/<(\/?)BODY/gi, '<$1body');
- startPos = content.indexOf('', startPos);
- head = low(content.substring(0, startPos + 1));
+ cdata: function(text) {
+ writer.cdata(text);
+ },
- endPos = content.indexOf('\n';
- }
+ start: function(name, attrs, empty) {
+ blocked = true;
- // Parse header and update iframe
- headerFragment = parseHeader();
- each(headerFragment.getAll('style'), function(node) {
- if (node.firstChild) {
- styles += node.firstChild.value;
- }
- });
+ if (name == 'script' || name == 'noscript') {
+ return;
+ }
- elm = headerFragment.getAll('body')[0];
- if (elm) {
- dom.setAttribs(editor.getBody(), {
- style: elm.attr('style') || '',
- dir: elm.attr('dir') || '',
- vLink: elm.attr('vlink') || '',
- link: elm.attr('link') || '',
- aLink: elm.attr('alink') || ''
- });
- }
+ for (var i = 0; i < attrs.length; i++) {
+ if (attrs[i].name.indexOf('on') === 0) {
+ return;
+ }
- dom.remove('fullpage_styles');
+ if (attrs[i].name == 'style') {
+ attrs[i].value = editor.dom.serializeStyle(editor.dom.parseStyle(attrs[i].value), name);
+ }
+ }
- var headElm = editor.getDoc().getElementsByTagName('head')[0];
+ writer.start(name, attrs, empty);
+ blocked = false;
+ },
- if (styles) {
- dom.add(headElm, 'style', {
- id: 'fullpage_styles'
- }, styles);
+ end: function(name) {
+ if (blocked) {
+ return;
+ }
- // Needed for IE 6/7
- elm = dom.get('fullpage_styles');
- if (elm.styleSheet) {
- elm.styleSheet.cssText = styles;
+ writer.end(name);
}
- }
+ }, new tinymce.html.Schema({})).parse(html);
- var currentStyleSheetsMap = {};
- tinymce.each(headElm.getElementsByTagName('link'), function(stylesheet) {
- if (stylesheet.rel == 'stylesheet' && stylesheet.getAttribute('data-mce-fullpage')) {
- currentStyleSheetsMap[stylesheet.href] = stylesheet;
- }
- });
+ return writer.getContent();
+ }
- // Add new
- tinymce.each(headerFragment.getAll('link'), function(stylesheet) {
- var href = stylesheet.attr('href');
+ function updateHtml(html, data, updateAll) {
+ var writer = new tinymce.html.Writer();
+ var sourceCount = 0, hasImage;
- if (!currentStyleSheetsMap[href] && stylesheet.attr('rel') == 'stylesheet') {
- dom.add(headElm, 'link', {
- rel: 'stylesheet',
- text: 'text/css',
- href: href,
- 'data-mce-fullpage': '1'
- });
- }
+ function setAttributes(attrs, updatedAttrs) {
+ var name, i, value, attr;
- delete currentStyleSheetsMap[href];
- });
+ for (name in updatedAttrs) {
+ value = "" + updatedAttrs[name];
- // Delete old
- tinymce.each(currentStyleSheetsMap, function(stylesheet) {
- stylesheet.parentNode.removeChild(stylesheet);
- });
- }
+ if (attrs.map[name]) {
+ i = attrs.length;
+ while (i--) {
+ attr = attrs[i];
- function getDefaultHeader() {
- var header = '', value, styles = '';
+ if (attr.name == name) {
+ if (value) {
+ attrs.map[name] = value;
+ attr.value = value;
+ } else {
+ delete attrs.map[name];
+ attrs.splice(i, 1);
+ }
+ }
+ }
+ } else if (value) {
+ attrs.push({
+ name: name,
+ value: value
+ });
- if (editor.getParam('fullpage_default_xml_pi')) {
- header += '\n';
+ attrs.map[name] = value;
+ }
+ }
}
- header += editor.getParam('fullpage_default_doctype', '');
- header += '\n\n\n';
+ new tinymce.html.SaxParser({
+ validate: false,
+ allow_conditional_comments: true,
+ special: 'script,noscript',
- if ((value = editor.getParam('fullpage_default_title'))) {
- header += '' + value + '\n';
- }
+ comment: function(text) {
+ writer.comment(text);
+ },
- if ((value = editor.getParam('fullpage_default_encoding'))) {
- header += '\n';
- }
+ cdata: function(text) {
+ writer.cdata(text);
+ },
- if ((value = editor.getParam('fullpage_default_font_family'))) {
- styles += 'font-family: ' + value + ';';
- }
+ text: function(text, raw) {
+ writer.text(text, raw);
+ },
- if ((value = editor.getParam('fullpage_default_font_size'))) {
- styles += 'font-size: ' + value + ';';
- }
+ start: function(name, attrs, empty) {
+ switch (name) {
+ case "video":
+ case "object":
+ case "embed":
+ case "img":
+ case "iframe":
+ setAttributes(attrs, {
+ width: data.width,
+ height: data.height
+ });
+ break;
+ }
- if ((value = editor.getParam('fullpage_default_text_color'))) {
- styles += 'color: ' + value + ';';
- }
+ if (updateAll) {
+ switch (name) {
+ case "video":
+ setAttributes(attrs, {
+ poster: data.poster,
+ src: ""
+ });
- header += '\n\n';
+ if (data.source2) {
+ setAttributes(attrs, {
+ src: ""
+ });
+ }
+ break;
- return header;
- }
+ case "iframe":
+ setAttributes(attrs, {
+ src: data.source1
+ });
+ break;
- function getContent(evt) {
- if (!evt.selection && (!evt.source_view || !editor.getParam('fullpage_hide_in_source_view'))) {
- evt.content = tinymce.trim(head) + '\n' + tinymce.trim(evt.content) + '\n' + tinymce.trim(foot);
- }
- }
+ case "source":
+ sourceCount++;
- editor.addCommand('mceFullPageProperties', showDialog);
+ if (sourceCount <= 2) {
+ setAttributes(attrs, {
+ src: data["source" + sourceCount],
+ type: data["source" + sourceCount + "mime"]
+ });
- editor.addButton('fullpage', {
- title: 'Document properties',
- cmd: 'mceFullPageProperties'
- });
+ if (!data["source" + sourceCount]) {
+ return;
+ }
+ }
+ break;
- editor.addMenuItem('fullpage', {
- text: 'Document properties',
- cmd: 'mceFullPageProperties',
- context: 'file'
- });
+ case "img":
+ if (!data.poster) {
+ return;
+ }
- editor.on('BeforeSetContent', setContent);
- editor.on('GetContent', getContent);
-});
+ hasImage = true;
+ break;
+ }
+ }
+ writer.start(name, attrs, empty);
+ },
- }).apply(root, arguments);
-});
-}(this));
+ end: function(name) {
+ if (name == "video" && updateAll) {
+ for (var index = 1; index <= 2; index++) {
+ if (data["source" + index]) {
+ var attrs = [];
+ attrs.map = {};
-(function(root) {
-define("tinymce-fullscreen", ["tinymce"], function() {
- return (function() {
-/**
- * plugin.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
+ if (sourceCount < index) {
+ setAttributes(attrs, {
+ src: data["source" + index],
+ type: data["source" + index + "mime"]
+ });
-/*global tinymce:true */
+ writer.start("source", attrs, true);
+ }
+ }
+ }
+ }
-tinymce.PluginManager.add('fullscreen', function(editor) {
- var fullscreenState = false, DOM = tinymce.DOM, iframeWidth, iframeHeight, resizeHandler;
- var containerWidth, containerHeight;
+ if (data.poster && name == "object" && updateAll && !hasImage) {
+ var imgAttrs = [];
+ imgAttrs.map = {};
- if (editor.settings.inline) {
- return;
+ setAttributes(imgAttrs, {
+ src: data.poster,
+ width: data.width,
+ height: data.height
+ });
+
+ writer.start("img", imgAttrs, true);
+ }
+
+ writer.end(name);
+ }
+ }, new tinymce.html.Schema({})).parse(html);
+
+ return writer.getContent();
}
- function getWindowSize() {
- var w, h, win = window, doc = document;
- var body = doc.body;
+ editor.on('ResolveName', function(e) {
+ var name;
- // Old IE
- if (body.offsetWidth) {
- w = body.offsetWidth;
- h = body.offsetHeight;
+ if (e.target.nodeType == 1 && (name = e.target.getAttribute("data-mce-object"))) {
+ e.name = name;
}
+ });
- // Modern browsers
- if (win.innerWidth && win.innerHeight) {
- w = win.innerWidth;
- h = win.innerHeight;
- }
+ editor.on('preInit', function() {
+ // Make sure that any messy HTML is retained inside these
+ var specialElements = editor.schema.getSpecialElements();
+ tinymce.each('video audio iframe object'.split(' '), function(name) {
+ specialElements[name] = new RegExp('<\/' + name + '[^>]*>', 'gi');
+ });
- return {w: w, h: h};
- }
+ // Allow elements
+ //editor.schema.addValidElements('object[id|style|width|height|classid|codebase|*],embed[id|style|width|height|type|src|*],video[*],audio[*]');
- function toggleFullscreen() {
- var body = document.body, documentElement = document.documentElement, editorContainerStyle;
- var editorContainer, iframe, iframeStyle;
+ // Set allowFullscreen attribs as boolean
+ var boolAttrs = editor.schema.getBoolAttrs();
+ tinymce.each('webkitallowfullscreen mozallowfullscreen allowfullscreen'.split(' '), function(name) {
+ boolAttrs[name] = {};
+ });
- function resize() {
- DOM.setStyle(iframe, 'height', getWindowSize().h - (editorContainer.clientHeight - iframe.clientHeight));
- }
+ // Converts iframe, video etc into placeholder images
+ editor.parser.addNodeFilter('iframe,video,audio,object,embed,script', function(nodes, name) {
+ var i = nodes.length, ai, node, placeHolder, attrName, attrValue, attribs, innerHtml;
+ var videoScript;
- fullscreenState = !fullscreenState;
+ while (i--) {
+ node = nodes[i];
+ if (!node.parent) {
+ continue;
+ }
- editorContainer = editor.getContainer();
- editorContainerStyle = editorContainer.style;
- iframe = editor.getContentAreaContainer().firstChild;
- iframeStyle = iframe.style;
+ if (node.name == 'script') {
+ videoScript = getVideoScriptMatch(node.attr('src'));
+ if (!videoScript) {
+ continue;
+ }
+ }
- if (fullscreenState) {
- iframeWidth = iframeStyle.width;
- iframeHeight = iframeStyle.height;
- iframeStyle.width = iframeStyle.height = '100%';
- containerWidth = editorContainerStyle.width;
- containerHeight = editorContainerStyle.height;
- editorContainerStyle.width = editorContainerStyle.height = '';
+ placeHolder = new tinymce.html.Node('img', 1);
+ placeHolder.shortEnded = true;
- DOM.addClass(body, 'mce-fullscreen');
- DOM.addClass(documentElement, 'mce-fullscreen');
- DOM.addClass(editorContainer, 'mce-fullscreen');
+ if (videoScript) {
+ if (videoScript.width) {
+ node.attr('width', videoScript.width.toString());
+ }
- DOM.bind(window, 'resize', resize);
- resize();
- resizeHandler = resize;
- } else {
- iframeStyle.width = iframeWidth;
- iframeStyle.height = iframeHeight;
+ if (videoScript.height) {
+ node.attr('height', videoScript.height.toString());
+ }
+ }
- if (containerWidth) {
- editorContainerStyle.width = containerWidth;
- }
+ // Prefix all attributes except width, height and style since we
+ // will add these to the placeholder
+ attribs = node.attributes;
+ ai = attribs.length;
+ while (ai--) {
+ attrName = attribs[ai].name;
+ attrValue = attribs[ai].value;
- if (containerHeight) {
- editorContainerStyle.height = containerHeight;
+ if (attrName !== "width" && attrName !== "height" && attrName !== "style") {
+ if (attrName == "data" || attrName == "src") {
+ attrValue = editor.convertURL(attrValue, attrName);
+ }
+
+ placeHolder.attr('data-mce-p-' + attrName, attrValue);
+ }
+ }
+
+ // Place the inner HTML contents inside an escaped attribute
+ // This enables us to copy/paste the fake object
+ innerHtml = node.firstChild && node.firstChild.value;
+ if (innerHtml) {
+ placeHolder.attr("data-mce-html", escape(innerHtml));
+ placeHolder.firstChild = null;
+ }
+
+ placeHolder.attr({
+ width: node.attr('width') || "300",
+ height: node.attr('height') || (name == "audio" ? "30" : "150"),
+ style: node.attr('style'),
+ src: tinymce.Env.transparentSrc,
+ "data-mce-object": name,
+ "class": "mce-object mce-object-" + name
+ });
+
+ node.replace(placeHolder);
}
+ });
- DOM.removeClass(body, 'mce-fullscreen');
- DOM.removeClass(documentElement, 'mce-fullscreen');
- DOM.removeClass(editorContainer, 'mce-fullscreen');
- DOM.unbind(window, 'resize', resizeHandler);
- }
+ // Replaces placeholder images with real elements for video, object, iframe etc
+ editor.serializer.addAttributeFilter('data-mce-object', function(nodes, name) {
+ var i = nodes.length, node, realElm, ai, attribs, innerHtml, innerNode, realElmName;
- editor.fire('FullscreenStateChanged', {state: fullscreenState});
- }
+ while (i--) {
+ node = nodes[i];
+ if (!node.parent) {
+ continue;
+ }
- editor.on('init', function() {
- editor.addShortcut('Meta+Alt+F', '', toggleFullscreen);
- });
+ realElmName = node.attr(name);
+ realElm = new tinymce.html.Node(realElmName, 1);
- editor.on('remove', function() {
- if (resizeHandler) {
- DOM.unbind(window, 'resize', resizeHandler);
- }
- });
+ // Add width/height to everything but audio
+ if (realElmName != "audio" && realElmName != "script") {
+ realElm.attr({
+ width: node.attr('width'),
+ height: node.attr('height')
+ });
+ }
- editor.addCommand('mceFullScreen', toggleFullscreen);
+ realElm.attr({
+ style: node.attr('style')
+ });
- editor.addMenuItem('fullscreen', {
- text: 'Fullscreen',
- shortcut: 'Meta+Alt+F',
- selectable: true,
- onClick: toggleFullscreen,
- onPostRender: function() {
- var self = this;
+ // Unprefix all placeholder attributes
+ attribs = node.attributes;
+ ai = attribs.length;
+ while (ai--) {
+ var attrName = attribs[ai].name;
- editor.on('FullscreenStateChanged', function(e) {
- self.active(e.state);
- });
- },
- context: 'view'
+ if (attrName.indexOf('data-mce-p-') === 0) {
+ realElm.attr(attrName.substr(11), attribs[ai].value);
+ }
+ }
+
+ if (realElmName == "script") {
+ realElm.attr('type', 'text/javascript');
+ }
+
+ // Inject innerhtml
+ innerHtml = node.attr('data-mce-html');
+ if (innerHtml) {
+ innerNode = new tinymce.html.Node('#text', 3);
+ innerNode.raw = true;
+ innerNode.value = sanitize(unescape(innerHtml));
+ realElm.append(innerNode);
+ }
+
+ node.replace(realElm);
+ }
+ });
});
- editor.addButton('fullscreen', {
- tooltip: 'Fullscreen',
- shortcut: 'Meta+Alt+F',
- onClick: toggleFullscreen,
- onPostRender: function() {
- var self = this;
+ editor.on('ObjectSelected', function(e) {
+ var objectType = e.target.getAttribute('data-mce-object');
- editor.on('FullscreenStateChanged', function(e) {
- self.active(e.state);
- });
+ if (objectType == "audio" || objectType == "script") {
+ e.preventDefault();
}
});
- return {
- isFullscreen: function() {
- return fullscreenState;
+ editor.on('objectResized', function(e) {
+ var target = e.target, html;
+
+ if (target.getAttribute('data-mce-object')) {
+ html = target.getAttribute('data-mce-html');
+ if (html) {
+ html = unescape(html);
+ target.setAttribute('data-mce-html', escape(
+ updateHtml(html, {
+ width: e.width,
+ height: e.height
+ })
+ ));
+ }
}
- };
+ });
+
+ editor.addButton('media', {
+ tooltip: 'Insert/edit video',
+ onclick: showDialog,
+ stateSelector: ['img[data-mce-object=video]', 'img[data-mce-object=iframe]']
+ });
+
+ editor.addMenuItem('media', {
+ icon: 'media',
+ text: 'Insert/edit video',
+ onclick: showDialog,
+ context: 'insert',
+ prependToContext: true
+ });
+
+ this.showDialog = showDialog;
});
+
}).apply(root, arguments);
});
}(this));
(function(root) {
-define("tinymce-hr", ["tinymce"], function() {
+define("tinymce-nonbreaking", ["tinymce"], function() {
return (function() {
/**
* plugin.js
@@ -50621,23 +66403,46 @@ define("tinymce-hr", ["tinymce"], function() {
/*global tinymce:true */
-tinymce.PluginManager.add('hr', function(editor) {
- editor.addCommand('InsertHorizontalRule', function() {
- editor.execCommand('mceInsertContent', false, '
');
+tinymce.PluginManager.add('nonbreaking', function(editor) {
+ var setting = editor.getParam('nonbreaking_force_tab');
+
+ editor.addCommand('mceNonBreaking', function() {
+ editor.insertContent(
+ (editor.plugins.visualchars && editor.plugins.visualchars.state) ?
+ ' ' : ' '
+ );
+
+ editor.dom.setAttrib(editor.dom.select('span.mce-nbsp'), 'data-mce-bogus', '1');
});
- editor.addButton('hr', {
- icon: 'hr',
- tooltip: 'Horizontal line',
- cmd: 'InsertHorizontalRule'
+ editor.addButton('nonbreaking', {
+ title: 'Nonbreaking space',
+ cmd: 'mceNonBreaking'
});
- editor.addMenuItem('hr', {
- icon: 'hr',
- text: 'Horizontal line',
- cmd: 'InsertHorizontalRule',
+ editor.addMenuItem('nonbreaking', {
+ text: 'Nonbreaking space',
+ cmd: 'mceNonBreaking',
context: 'insert'
});
+
+ if (setting) {
+ var spaces = +setting > 1 ? +setting : 3; // defaults to 3 spaces if setting is true (or 1)
+
+ editor.on('keydown', function(e) {
+ if (e.keyCode == 9) {
+
+ if (e.shiftKey) {
+ return;
+ }
+
+ e.preventDefault();
+ for (var i = 0; i < spaces; i++) {
+ editor.execCommand('mceNonBreaking');
+ }
+ }
+ });
+ }
});
@@ -50646,7 +66451,7 @@ tinymce.PluginManager.add('hr', function(editor) {
}(this));
(function(root) {
-define("tinymce-image", ["tinymce"], function() {
+define("tinymce-noneditable", ["tinymce"], function() {
return (function() {
/**
* plugin.js
@@ -50658,759 +66463,617 @@ define("tinymce-image", ["tinymce"], function() {
* Contributing: http://www.tinymce.com/contributing
*/
+/*jshint loopfunc:true */
+/*eslint no-loop-func:0 */
/*global tinymce:true */
-tinymce.PluginManager.add('image', function(editor) {
- function getImageSize(url, callback) {
- var img = document.createElement('img');
+tinymce.PluginManager.add('noneditable', function(editor) {
+ var TreeWalker = tinymce.dom.TreeWalker;
+ var externalName = 'contenteditable', internalName = 'data-mce-' + externalName;
+ var VK = tinymce.util.VK;
- function done(width, height) {
- if (img.parentNode) {
- img.parentNode.removeChild(img);
+ // Returns the content editable state of a node "true/false" or null
+ function getContentEditable(node) {
+ var contentEditable;
+
+ // Ignore non elements
+ if (node.nodeType === 1) {
+ // Check for fake content editable
+ contentEditable = node.getAttribute(internalName);
+ if (contentEditable && contentEditable !== "inherit") {
+ return contentEditable;
}
- callback({width: width, height: height});
+ // Check for real content editable
+ contentEditable = node.contentEditable;
+ if (contentEditable !== "inherit") {
+ return contentEditable;
+ }
}
- img.onload = function() {
- done(Math.max(img.width, img.clientWidth), Math.max(img.height, img.clientHeight));
- };
-
- img.onerror = function() {
- done();
- };
-
- var style = img.style;
- style.visibility = 'hidden';
- style.position = 'fixed';
- style.bottom = style.left = 0;
- style.width = style.height = 'auto';
-
- document.body.appendChild(img);
- img.src = url;
+ return null;
}
- function buildListItems(inputList, itemCallback, startItems) {
- function appendItems(values, output) {
- output = output || [];
-
- tinymce.each(values, function(item) {
- var menuItem = {text: item.text || item.title};
-
- if (item.menu) {
- menuItem.menu = appendItems(item.menu);
- } else {
- menuItem.value = item.value;
- itemCallback(menuItem);
- }
+ // Returns the noneditable parent or null if there is a editable before it or if it wasn't found
+ function getNonEditableParent(node) {
+ var state;
- output.push(menuItem);
- });
+ while (node) {
+ state = getContentEditable(node);
+ if (state) {
+ return state === "false" ? node : null;
+ }
- return output;
+ node = node.parentNode;
}
-
- return appendItems(inputList, startItems || []);
- }
-
- function createImageList(callback) {
- return function() {
- var imageList = editor.settings.image_list;
-
- if (typeof imageList == "string") {
- tinymce.util.XHR.send({
- url: imageList,
- success: function(text) {
- callback(tinymce.util.JSON.parse(text));
- }
- });
- } else if (typeof imageList == "function") {
- imageList(callback);
- } else {
- callback(imageList);
- }
- };
}
- function showDialog(imageList) {
- var win, data = {}, dom = editor.dom, imgElm = editor.selection.getNode();
- var width, height, imageListCtrl, classListCtrl, imageDimensions = editor.settings.image_dimensions !== false;
-
- function recalcSize() {
- var widthCtrl, heightCtrl, newWidth, newHeight;
+ function handleContentEditableSelection() {
+ var dom = editor.dom, selection = editor.selection, caretContainerId = 'mce_noneditablecaret', invisibleChar = '\uFEFF';
- widthCtrl = win.find('#width')[0];
- heightCtrl = win.find('#height')[0];
+ // Get caret container parent for the specified node
+ function getParentCaretContainer(node) {
+ while (node) {
+ if (node.id === caretContainerId) {
+ return node;
+ }
- if (!widthCtrl || !heightCtrl) {
- return;
+ node = node.parentNode;
}
+ }
- newWidth = widthCtrl.value();
- newHeight = heightCtrl.value();
-
- if (win.find('#constrain')[0].checked() && width && height && newWidth && newHeight) {
- if (width != newWidth) {
- newHeight = Math.round((newWidth / width) * newHeight);
+ // Finds the first text node in the specified node
+ function findFirstTextNode(node) {
+ var walker;
- if (!isNaN(newHeight)) {
- heightCtrl.value(newHeight);
- }
- } else {
- newWidth = Math.round((newHeight / height) * newWidth);
+ if (node) {
+ walker = new TreeWalker(node, node);
- if (!isNaN(newWidth)) {
- widthCtrl.value(newWidth);
+ for (node = walker.current(); node; node = walker.next()) {
+ if (node.nodeType === 3) {
+ return node;
}
}
}
-
- width = newWidth;
- height = newHeight;
}
- function onSubmitForm() {
- function waitLoad(imgElm) {
- function selectImage() {
- imgElm.onload = imgElm.onerror = null;
+ // Insert caret container before/after target or expand selection to include block
+ function insertCaretContainerOrExpandToBlock(target, before) {
+ var caretContainer, rng;
- if (editor.selection) {
- editor.selection.select(imgElm);
- editor.nodeChanged();
- }
+ // Select block
+ if (getContentEditable(target) === "false") {
+ if (dom.isBlock(target)) {
+ selection.select(target);
+ return;
}
-
- imgElm.onload = function() {
- if (!data.width && !data.height && imageDimensions) {
- dom.setAttribs(imgElm, {
- width: imgElm.clientWidth,
- height: imgElm.clientHeight
- });
- }
-
- selectImage();
- };
-
- imgElm.onerror = selectImage;
}
- updateStyle();
- recalcSize();
-
- data = tinymce.extend(data, win.toJSON());
+ rng = dom.createRng();
- if (!data.alt) {
- data.alt = '';
- }
+ if (getContentEditable(target) === "true") {
+ if (!target.firstChild) {
+ target.appendChild(editor.getDoc().createTextNode('\u00a0'));
+ }
- if (!data.title) {
- data.title = '';
+ target = target.firstChild;
+ before = true;
}
- if (data.width === '') {
- data.width = null;
- }
+ /*
+ caretContainer = dom.create('span', {
+ id: caretContainerId,
+ 'data-mce-bogus': true,
+ style:'border: 1px solid red'
+ }, invisibleChar);
+ */
- if (data.height === '') {
- data.height = null;
- }
+ caretContainer = dom.create('span', {id: caretContainerId, 'data-mce-bogus': true}, invisibleChar);
- if (!data.style) {
- data.style = null;
+ if (before) {
+ target.parentNode.insertBefore(caretContainer, target);
+ } else {
+ dom.insertAfter(caretContainer, target);
}
- // Setup new data excluding style properties
- /*eslint dot-notation: 0*/
- data = {
- src: data.src,
- alt: data.alt,
- title: data.title,
- width: data.width,
- height: data.height,
- style: data.style,
- "class": data["class"]
- };
-
- editor.undoManager.transact(function() {
- if (!data.src) {
- if (imgElm) {
- dom.remove(imgElm);
- editor.focus();
- editor.nodeChanged();
- }
-
- return;
- }
-
- if (data.title === "") {
- data.title = null;
- }
-
- if (!imgElm) {
- data.id = '__mcenew';
- editor.focus();
- editor.selection.setContent(dom.createHTML('img', data));
- imgElm = dom.get('__mcenew');
- dom.setAttrib(imgElm, 'id', null);
- } else {
- dom.setAttribs(imgElm, data);
- }
+ rng.setStart(caretContainer.firstChild, 1);
+ rng.collapse(true);
+ selection.setRng(rng);
- waitLoad(imgElm);
- });
+ return caretContainer;
}
- function removePixelSuffix(value) {
- if (value) {
- value = value.replace(/px$/, '');
- }
+ // Removes any caret container
+ function removeCaretContainer(caretContainer) {
+ var rng, child, lastContainer;
- return value;
- }
+ if (caretContainer) {
+ rng = selection.getRng(true);
+ rng.setStartBefore(caretContainer);
+ rng.setEndBefore(caretContainer);
- function srcChange(e) {
- var srcURL, prependURL, absoluteURLPattern, meta = e.meta || {};
+ child = findFirstTextNode(caretContainer);
+ if (child && child.nodeValue.charAt(0) == invisibleChar) {
+ child = child.deleteData(0, 1);
+ }
- if (imageListCtrl) {
- imageListCtrl.value(editor.convertURL(this.value(), 'src'));
- }
+ dom.remove(caretContainer, true);
- tinymce.each(meta, function(value, key) {
- win.find('#' + key).value(value);
- });
+ selection.setRng(rng);
+ } else {
+ while ((caretContainer = dom.get(caretContainerId)) && caretContainer !== lastContainer) {
+ child = findFirstTextNode(caretContainer);
+ if (child && child.nodeValue.charAt(0) == invisibleChar) {
+ child = child.deleteData(0, 1);
+ }
- if (!meta.width && !meta.height) {
- srcURL = editor.convertURL(this.value(), 'src');
+ dom.remove(caretContainer, true);
- // Pattern test the src url and make sure we haven't already prepended the url
- prependURL = editor.settings.image_prepend_url;
- absoluteURLPattern = new RegExp('^(?:[a-z]+:)?//', 'i');
- if (prependURL && !absoluteURLPattern.test(srcURL) && srcURL.substring(0, prependURL.length) !== prependURL) {
- srcURL = prependURL + srcURL;
+ lastContainer = caretContainer;
}
-
- this.value(srcURL);
-
- getImageSize(editor.documentBaseURI.toAbsolute(this.value()), function(data) {
- if (data.width && data.height && imageDimensions) {
- width = data.width;
- height = data.height;
-
- win.find('#width').value(width);
- win.find('#height').value(height);
- }
- });
}
}
- width = dom.getAttrib(imgElm, 'width');
- height = dom.getAttrib(imgElm, 'height');
+ // Modifies the selection to include contentEditable false elements or insert caret containers
+ function moveSelection() {
+ var nonEditableStart, nonEditableEnd, isCollapsed, rng, element;
- if (imgElm.nodeName == 'IMG' && !imgElm.getAttribute('data-mce-object') && !imgElm.getAttribute('data-mce-placeholder')) {
- data = {
- src: dom.getAttrib(imgElm, 'src'),
- alt: dom.getAttrib(imgElm, 'alt'),
- title: dom.getAttrib(imgElm, 'title'),
- "class": dom.getAttrib(imgElm, 'class'),
- width: width,
- height: height
- };
- } else {
- imgElm = null;
- }
+ // Checks if there is any contents to the left/right side of caret returns the noneditable element or
+ // any editable element if it finds one inside
+ function hasSideContent(element, left) {
+ var container, offset, walker, node, len;
- if (imageList) {
- imageListCtrl = {
- type: 'listbox',
- label: 'Image list',
- values: buildListItems(
- imageList,
- function(item) {
- item.value = editor.convertURL(item.value || item.url, 'src');
- },
- [{text: 'None', value: ''}]
- ),
- value: data.src && editor.convertURL(data.src, 'src'),
- onselect: function(e) {
- var altCtrl = win.find('#alt');
+ container = rng.startContainer;
+ offset = rng.startOffset;
- if (!altCtrl.value() || (e.lastControl && altCtrl.value() == e.lastControl.text())) {
- altCtrl.value(e.control.text());
+ // If endpoint is in middle of text node then expand to beginning/end of element
+ if (container.nodeType == 3) {
+ len = container.nodeValue.length;
+ if ((offset > 0 && offset < len) || (left ? offset == len : offset === 0)) {
+ return;
+ }
+ } else {
+ // Can we resolve the node by index
+ if (offset < container.childNodes.length) {
+ // Browser represents caret position as the offset at the start of an element. When moving right
+ // this is the element we are moving into so we consider our container to be child node at offset-1
+ var pos = !left && offset > 0 ? offset - 1 : offset;
+ container = container.childNodes[pos];
+ if (container.hasChildNodes()) {
+ container = container.firstChild;
+ }
+ } else {
+ // If not then the caret is at the last position in it's container and the caret container
+ // should be inserted after the noneditable element
+ return !left ? element : null;
}
-
- win.find('#src').value(e.control.value()).fire('change');
- },
- onPostRender: function() {
- /*eslint consistent-this: 0*/
- imageListCtrl = this;
}
- };
- }
- if (editor.settings.image_class_list) {
- classListCtrl = {
- name: 'class',
- type: 'listbox',
- label: 'Class',
- values: buildListItems(
- editor.settings.image_class_list,
- function(item) {
- if (item.value) {
- item.textStyle = function() {
- return editor.formatter.getCssText({inline: 'img', classes: [item.value]});
- };
- }
+ // Walk left/right to look for contents
+ walker = new TreeWalker(container, element);
+ while ((node = walker[left ? 'prev' : 'next']())) {
+ if (node.nodeType === 3 && node.nodeValue.length > 0) {
+ return;
+ } else if (getContentEditable(node) === "true") {
+ // Found contentEditable=true element return this one to we can move the caret inside it
+ return node;
}
- )
- };
- }
+ }
- // General settings shared between simple and advanced dialogs
- var generalFormItems = [
- {
- name: 'src',
- type: 'filepicker',
- filetype: 'image',
- label: 'Source',
- autofocus: true,
- onchange: srcChange
- },
- imageListCtrl
- ];
+ return element;
+ }
- if (editor.settings.image_description !== false) {
- generalFormItems.push({name: 'alt', type: 'textbox', label: 'Image description'});
- }
+ // Remove any existing caret containers
+ removeCaretContainer();
+
+ // Get noneditable start/end elements
+ isCollapsed = selection.isCollapsed();
+ nonEditableStart = getNonEditableParent(selection.getStart());
+ nonEditableEnd = getNonEditableParent(selection.getEnd());
- if (editor.settings.image_title) {
- generalFormItems.push({name: 'title', type: 'textbox', label: 'Image Title'});
- }
+ // Is any fo the range endpoints noneditable
+ if (nonEditableStart || nonEditableEnd) {
+ rng = selection.getRng(true);
- if (imageDimensions) {
- generalFormItems.push({
- type: 'container',
- label: 'Dimensions',
- layout: 'flex',
- direction: 'row',
- align: 'center',
- spacing: 5,
- items: [
- {name: 'width', type: 'textbox', maxLength: 5, size: 3, onchange: recalcSize, ariaLabel: 'Width'},
- {type: 'label', text: 'x'},
- {name: 'height', type: 'textbox', maxLength: 5, size: 3, onchange: recalcSize, ariaLabel: 'Height'},
- {name: 'constrain', type: 'checkbox', checked: true, text: 'Constrain proportions'}
- ]
- });
- }
+ // If it's a caret selection then look left/right to see if we need to move the caret out side or expand
+ if (isCollapsed) {
+ nonEditableStart = nonEditableStart || nonEditableEnd;
- generalFormItems.push(classListCtrl);
+ if ((element = hasSideContent(nonEditableStart, true))) {
+ // We have no contents to the left of the caret then insert a caret container before the noneditable element
+ insertCaretContainerOrExpandToBlock(element, true);
+ } else if ((element = hasSideContent(nonEditableStart, false))) {
+ // We have no contents to the right of the caret then insert a caret container after the noneditable element
+ insertCaretContainerOrExpandToBlock(element, false);
+ } else {
+ // We are in the middle of a noneditable so expand to select it
+ selection.select(nonEditableStart);
+ }
+ } else {
+ rng = selection.getRng(true);
- function mergeMargins(css) {
- if (css.margin) {
+ // Expand selection to include start non editable element
+ if (nonEditableStart) {
+ rng.setStartBefore(nonEditableStart);
+ }
- var splitMargin = css.margin.split(" ");
+ // Expand selection to include end non editable element
+ if (nonEditableEnd) {
+ rng.setEndAfter(nonEditableEnd);
+ }
- switch (splitMargin.length) {
- case 1: //margin: toprightbottomleft;
- css['margin-top'] = css['margin-top'] || splitMargin[0];
- css['margin-right'] = css['margin-right'] || splitMargin[0];
- css['margin-bottom'] = css['margin-bottom'] || splitMargin[0];
- css['margin-left'] = css['margin-left'] || splitMargin[0];
- break;
- case 2: //margin: topbottom rightleft;
- css['margin-top'] = css['margin-top'] || splitMargin[0];
- css['margin-right'] = css['margin-right'] || splitMargin[1];
- css['margin-bottom'] = css['margin-bottom'] || splitMargin[0];
- css['margin-left'] = css['margin-left'] || splitMargin[1];
- break;
- case 3: //margin: top rightleft bottom;
- css['margin-top'] = css['margin-top'] || splitMargin[0];
- css['margin-right'] = css['margin-right'] || splitMargin[1];
- css['margin-bottom'] = css['margin-bottom'] || splitMargin[2];
- css['margin-left'] = css['margin-left'] || splitMargin[1];
- break;
- case 4: //margin: top right bottom left;
- css['margin-top'] = css['margin-top'] || splitMargin[0];
- css['margin-right'] = css['margin-right'] || splitMargin[1];
- css['margin-bottom'] = css['margin-bottom'] || splitMargin[2];
- css['margin-left'] = css['margin-left'] || splitMargin[3];
+ selection.setRng(rng);
}
- delete css.margin;
}
- return css;
}
- function updateStyle() {
- function addPixelSuffix(value) {
- if (value.length > 0 && /^[0-9]+$/.test(value)) {
- value += 'px';
- }
+ function handleKey(e) {
+ var keyCode = e.keyCode, nonEditableParent, caretContainer, startElement, endElement;
- return value;
+ function getNonEmptyTextNodeSibling(node, prev) {
+ while ((node = node[prev ? 'previousSibling' : 'nextSibling'])) {
+ if (node.nodeType !== 3 || node.nodeValue.length > 0) {
+ return node;
+ }
+ }
}
- if (!editor.settings.image_advtab) {
- return;
+ function positionCaretOnElement(element, start) {
+ selection.select(element);
+ selection.collapse(start);
}
- var data = win.toJSON(),
- css = dom.parseStyle(data.style);
+ function canDelete(backspace) {
+ var rng, container, offset, nonEditableParent;
- css = mergeMargins(css);
+ function removeNodeIfNotParent(node) {
+ var parent = container;
- if (data.vspace) {
- css['margin-top'] = css['margin-bottom'] = addPixelSuffix(data.vspace);
- }
- if (data.hspace) {
- css['margin-left'] = css['margin-right'] = addPixelSuffix(data.hspace);
- }
- if (data.border) {
- css['border-width'] = addPixelSuffix(data.border);
- }
+ while (parent) {
+ if (parent === node) {
+ return;
+ }
- win.find('#style').value(dom.serializeStyle(dom.parseStyle(dom.serializeStyle(css))));
- }
+ parent = parent.parentNode;
+ }
- function updateVSpaceHSpaceBorder() {
- if (!editor.settings.image_advtab) {
- return;
- }
+ dom.remove(node);
+ moveSelection();
+ }
- var data = win.toJSON(),
- css = dom.parseStyle(data.style);
+ function isNextPrevTreeNodeNonEditable() {
+ var node, walker, nonEmptyElements = editor.schema.getNonEmptyElements();
- win.find('#vspace').value("");
- win.find('#hspace').value("");
+ walker = new tinymce.dom.TreeWalker(container, editor.getBody());
+ while ((node = (backspace ? walker.prev() : walker.next()))) {
+ // Found IMG/INPUT etc
+ if (nonEmptyElements[node.nodeName.toLowerCase()]) {
+ break;
+ }
- css = mergeMargins(css);
+ // Found text node with contents
+ if (node.nodeType === 3 && tinymce.trim(node.nodeValue).length > 0) {
+ break;
+ }
- //Move opposite equal margins to vspace/hspace field
- if ((css['margin-top'] && css['margin-bottom']) || (css['margin-right'] && css['margin-left'])) {
- if (css['margin-top'] === css['margin-bottom']) {
- win.find('#vspace').value(removePixelSuffix(css['margin-top']));
- } else {
- win.find('#vspace').value('');
- }
- if (css['margin-right'] === css['margin-left']) {
- win.find('#hspace').value(removePixelSuffix(css['margin-right']));
- } else {
- win.find('#hspace').value('');
+ // Found non editable node
+ if (getContentEditable(node) === "false") {
+ removeNodeIfNotParent(node);
+ return true;
+ }
+ }
+
+ // Check if the content node is within a non editable parent
+ if (getNonEditableParent(node)) {
+ return true;
+ }
+
+ return false;
}
- }
- //Move border-width
- if (css['border-width']) {
- win.find('#border').value(removePixelSuffix(css['border-width']));
- }
+ if (selection.isCollapsed()) {
+ rng = selection.getRng(true);
+ container = rng.startContainer;
+ offset = rng.startOffset;
+ container = getParentCaretContainer(container) || container;
- win.find('#style').value(dom.serializeStyle(dom.parseStyle(dom.serializeStyle(css))));
+ // Is in noneditable parent
+ if ((nonEditableParent = getNonEditableParent(container))) {
+ removeNodeIfNotParent(nonEditableParent);
+ return false;
+ }
- }
+ // Check if the caret is in the middle of a text node
+ if (container.nodeType == 3 && (backspace ? offset > 0 : offset < container.nodeValue.length)) {
+ return true;
+ }
- if (editor.settings.image_advtab) {
- // Parse styles from img
- if (imgElm) {
- if (imgElm.style.marginLeft && imgElm.style.marginRight && imgElm.style.marginLeft === imgElm.style.marginRight) {
- data.hspace = removePixelSuffix(imgElm.style.marginLeft);
- }
- if (imgElm.style.marginTop && imgElm.style.marginBottom && imgElm.style.marginTop === imgElm.style.marginBottom) {
- data.vspace = removePixelSuffix(imgElm.style.marginTop);
- }
- if (imgElm.style.borderWidth) {
- data.border = removePixelSuffix(imgElm.style.borderWidth);
+ // Resolve container index
+ if (container.nodeType == 1) {
+ container = container.childNodes[offset] || container;
+ }
+
+ // Check if previous or next tree node is non editable then block the event
+ if (isNextPrevTreeNodeNonEditable()) {
+ return false;
+ }
}
- data.style = editor.dom.serializeStyle(editor.dom.parseStyle(editor.dom.getAttrib(imgElm, 'style')));
+ return true;
}
- // Advanced dialog shows general+advanced tabs
- win = editor.windowManager.open({
- title: 'Insert/edit image',
- data: data,
- bodyType: 'tabpanel',
- body: [
- {
- title: 'General',
- type: 'form',
- items: generalFormItems
- },
+ moveSelection();
- {
- title: 'Advanced',
- type: 'form',
- pack: 'start',
- items: [
- {
- label: 'Style',
- name: 'style',
- type: 'textbox',
- onchange: updateVSpaceHSpaceBorder
- },
- {
- type: 'form',
- layout: 'grid',
- packV: 'start',
- columns: 2,
- padding: 0,
- alignH: ['left', 'right'],
- defaults: {
- type: 'textbox',
- maxWidth: 50,
- onchange: updateStyle
- },
- items: [
- {label: 'Vertical space', name: 'vspace'},
- {label: 'Horizontal space', name: 'hspace'},
- {label: 'Border', name: 'border'}
- ]
- }
- ]
- }
- ],
- onSubmit: onSubmitForm
- });
- } else {
- // Simple default dialog
- win = editor.windowManager.open({
- title: 'Insert/edit image',
- data: data,
- body: generalFormItems,
- onSubmit: onSubmitForm
- });
- }
- }
+ startElement = selection.getStart();
+ endElement = selection.getEnd();
- editor.addButton('image', {
- icon: 'image',
- tooltip: 'Insert/edit image',
- onclick: createImageList(showDialog),
- stateSelector: 'img:not([data-mce-object],[data-mce-placeholder])'
- });
+ // Disable all key presses in contentEditable=false except delete or backspace
+ nonEditableParent = getNonEditableParent(startElement) || getNonEditableParent(endElement);
+ var currentNode = editor.selection.getNode();
- editor.addMenuItem('image', {
- icon: 'image',
- text: 'Insert/edit image',
- onclick: createImageList(showDialog),
- context: 'insert',
- prependToContext: true
- });
+ var isDirectionKey = keyCode == VK.LEFT || keyCode == VK.RIGHT || keyCode == VK.UP || keyCode == VK.DOWN;
+ var left = keyCode == VK.LEFT || keyCode == VK.UP;
- editor.addCommand('mceImage', createImageList(showDialog));
-});
+ if (nonEditableParent && (keyCode < 112 || keyCode > 124) && keyCode != VK.DELETE && keyCode != VK.BACKSPACE) {
+ // Is Ctrl+c, Ctrl+v or Ctrl+x then use default browser behavior
+ if ((tinymce.isMac ? e.metaKey : e.ctrlKey) && (keyCode == 67 || keyCode == 88 || keyCode == 86)) {
+ return;
+ }
- }).apply(root, arguments);
-});
-}(this));
+ e.preventDefault();
-(function(root) {
-define("tinymce-importcss", ["tinymce"], function() {
- return (function() {
-/**
- * plugin.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
+ // Arrow left/right select the element and collapse left/right
+ if (isDirectionKey) {
-/*global tinymce:true */
+ // If a block element find previous or next element to position the caret
+ if (editor.dom.isBlock(nonEditableParent)) {
+ var targetElement = left ? nonEditableParent.previousSibling : nonEditableParent.nextSibling;
-tinymce.PluginManager.add('importcss', function(editor) {
- var self = this, each = tinymce.each;
+ // Handling for edge-cases:
+ // - two nonEditables in a row -> no way to get between them
+ // - nonEditable as the first/last element -> no way to get before/behind it
+ if (!targetElement || targetElement && getContentEditable(targetElement) === 'false') {
+ var p = dom.create('p', null, ' ');
+ p.className = 'mceTmpParagraph';
- function compileFilter(filter) {
- if (typeof filter == "string") {
- return function(value) {
- return value.indexOf(filter) !== -1;
- };
- } else if (filter instanceof RegExp) {
- return function(value) {
- return filter.test(value);
- };
- }
+ var insertElement = left ? nonEditableParent : targetElement;
- return filter;
- }
+ if (insertElement && insertElement.parentNode) {
+ insertElement.parentNode.insertBefore(p, insertElement);
+ } else if (!targetElement && !left) {
+ nonEditableParent.parentNode.appendChild(p);
+ }
- function getSelectors(doc, fileFilter) {
- var selectors = [], contentCSSUrls = {};
+ targetElement = p;
+ }
- function append(styleSheet, imported) {
- var href = styleSheet.href, rules;
+ var walker = new TreeWalker(targetElement, targetElement);
+ var caretElement = left ? walker.prev() : walker.next();
- if (!href || !fileFilter(href, imported)) {
- return;
- }
+ positionCaretOnElement(caretElement, !left);
+ } else {
+ positionCaretOnElement(nonEditableParent, left);
+ }
+ }
+ } else {
+ // Is arrow left/right, backspace or delete
+ if (isDirectionKey || keyCode == VK.BACKSPACE || keyCode == VK.DELETE) {
+ caretContainer = getParentCaretContainer(startElement);
- each(styleSheet.imports, function(styleSheet) {
- append(styleSheet, true);
- });
+ if (caretContainer) {
+ // Arrow left or backspace
+ if (keyCode == VK.LEFT || keyCode == VK.BACKSPACE) {
+ nonEditableParent = getNonEmptyTextNodeSibling(caretContainer, true);
- try {
- rules = styleSheet.cssRules || styleSheet.rules;
- } catch (e) {
- // Firefox fails on rules to remote domain for example:
- // @import url(//fonts.googleapis.com/css?family=Pathway+Gothic+One);
- }
+ if (nonEditableParent && getContentEditable(nonEditableParent) === "false") {
+ e.preventDefault();
- each(rules, function(cssRule) {
- if (cssRule.styleSheet) {
- append(cssRule.styleSheet, true);
- } else if (cssRule.selectorText) {
- each(cssRule.selectorText.split(','), function(selector) {
- selectors.push(tinymce.trim(selector));
- });
- }
- });
- }
+ if (keyCode == VK.LEFT) {
+ positionCaretOnElement(nonEditableParent, true);
+ } else {
+ dom.remove(nonEditableParent);
+ return;
+ }
+ } else {
+ removeCaretContainer(caretContainer);
+ }
+ }
- each(editor.contentCSS, function(url) {
- contentCSSUrls[url] = true;
- });
+ // Arrow right or delete
+ if (keyCode == VK.RIGHT || keyCode == VK.DELETE) {
+ nonEditableParent = getNonEmptyTextNodeSibling(caretContainer, true);
- if (!fileFilter) {
- fileFilter = function(href, imported) {
- return imported || contentCSSUrls[href];
- };
- }
+ if (nonEditableParent && getContentEditable(nonEditableParent) === "false") {
+ e.preventDefault();
- try {
- each(doc.styleSheets, function(styleSheet) {
- append(styleSheet);
- });
- } catch (e) {
- // Ignore
- }
+ if (keyCode == VK.RIGHT) {
+ positionCaretOnElement(nonEditableParent, false);
+ } else {
+ dom.remove(nonEditableParent);
+ return;
+ }
+ } else {
+ removeCaretContainer(caretContainer);
+ }
+ }
+ } else {
- return selectors;
- }
+ if (isDirectionKey) {
+ // Removal of separator paragraphs between two nonEditables
+ // and before/after a nonEditable as the first/last element
+ if (currentNode && currentNode.className.indexOf('mceTmpParagraph') !== -1 &&
+ currentNode[left ? 'previousSibling' : 'nextSibling']) {
+ var jumpTarget = currentNode[left ? 'previousSibling' : 'nextSibling'];
- function convertSelectorToFormat(selectorText) {
- var format;
+ // current node is still empty and a separator -> remove it
+ // else: remove the separator class, as it now includes content
+ if (currentNode.innerHTML === ' ' || currentNode.innerHTML === '' || currentNode.innerHTML === ' ') {
+ dom.remove(currentNode);
+ } else {
+ currentNode.className = currentNode.className.replace('mceTmpParagraph', '');
+ }
- // Parse simple element.class1, .class1
- var selector = /^(?:([a-z0-9\-_]+))?(\.[a-z0-9_\-\.]+)$/i.exec(selectorText);
- if (!selector) {
- return;
- }
+ positionCaretOnElement(jumpTarget, !left);
+ }
+ }
- var elementName = selector[1];
- var classes = selector[2].substr(1).split('.').join(' ');
- var inlineSelectorElements = tinymce.makeMap('a,img');
+ var rng = selection.getRng(true);
+ var container = rng.endContainer;
- // element.class - Produce block formats
- if (selector[1]) {
- format = {
- title: selectorText
- };
+ // FIX: If end of node is selected, check wether next sibling is nonEditable to correctly remove it
+ // (else would break for more complex nonEditables, their content would get moved to the current node)
+ if (dom.isBlock(container) && dom.isBlock(container.nextSibling) && rng.endOffset == 1 && keyCode == VK.DELETE) {
+ nonEditableParent = getNonEditableParent(container.nextSibling);
+ }
- if (editor.schema.getTextBlockElements()[elementName]) {
- // Text block format ex: h1.class1
- format.block = elementName;
- } else if (editor.schema.getBlockElements()[elementName] || inlineSelectorElements[elementName.toLowerCase()]) {
- // Block elements such as table.class and special inline elements such as a.class or img.class
- format.selector = elementName;
- } else {
- // Inline format strong.class1
- format.inline = elementName;
+ // correctly remove block-level nonEditable domNode on delete/backspace
+ if (nonEditableParent && (keyCode == VK.DELETE || keyCode == VK.BACKSPACE) && dom.isBlock(nonEditableParent)) {
+ e.preventDefault();
+ dom.remove(nonEditableParent);
+ return;
+ }
+ }
+
+ if ((keyCode == VK.BACKSPACE || keyCode == VK.DELETE) && !canDelete(keyCode == VK.BACKSPACE)) {
+ e.preventDefault();
+ return false;
+ }
+ }
}
- } else if (selector[2]) {
- // .class - Produce inline span with classes
- format = {
- inline: 'span',
- title: selectorText.substr(1),
- classes: classes
- };
}
- // Append to or override class attribute
- if (editor.settings.importcss_merge_classes !== false) {
- format.classes = classes;
- } else {
- format.attributes = {"class": classes};
- }
+ editor.on('mousedown', function(e) {
+ var node = editor.selection.getNode();
+
+ // Also remove separator lines when clicking on another node
+ if (node && node.className.indexOf('mceTmpParagraph') !== -1 && node !== e.target) {
+ // current node is still empty and a separator -> remove it
+ // else: remove the separator class, as it now includes content
+ if (node.innerHTML === ' ' || node.innerHTML === '' || node.innerHTML === ' ') {
+ dom.remove(node);
+ } else {
+ node.className = node.className.replace('mceTmpParagraph', '');
+ }
+ }
+
+ if (getContentEditable(node) === "false" && node == e.target) {
+ // Expand selection on mouse down we can't block the default event since it's used for drag/drop
+ moveSelection();
+ }
+ });
+
+ editor.on('mouseup', moveSelection);
- return format;
+ editor.on('keydown', handleKey);
}
- editor.on('renderFormatsMenu', function(e) {
- var settings = editor.settings, selectors = {};
- var selectorConverter = settings.importcss_selector_converter || convertSelectorToFormat;
- var selectorFilter = compileFilter(settings.importcss_selector_filter), ctrl = e.control;
+ var editClass, nonEditClass, nonEditableRegExps;
- if (!editor.settings.importcss_append) {
- ctrl.items().remove();
+ // Converts configured regexps to noneditable span items
+ function convertRegExpsToNonEditable(e) {
+ var i = nonEditableRegExps.length, content = e.content, cls = tinymce.trim(nonEditClass);
+
+ // Don't replace the variables when raw is used for example on undo/redo
+ if (e.format == "raw") {
+ return;
}
- // Setup new groups collection by cloning the configured one
- var groups = [];
- tinymce.each(settings.importcss_groups, function(group) {
- group = tinymce.extend({}, group);
- group.filter = compileFilter(group.filter);
- groups.push(group);
- });
+ while (i--) {
+ content = content.replace(nonEditableRegExps[i], function(match) {
+ var args = arguments, index = args[args.length - 2];
- each(getSelectors(e.doc || editor.getDoc(), compileFilter(settings.importcss_file_filter)), function(selector) {
- if (selector.indexOf('.mce-') === -1) {
- if (!selectors[selector] && (!selectorFilter || selectorFilter(selector))) {
- var format = selectorConverter.call(self, selector), menu;
+ // Is value inside an attribute then don't replace
+ if (index > 0 && content.charAt(index - 1) == '"') {
+ return match;
+ }
- if (format) {
- var formatName = format.name || tinymce.DOM.uniqueId();
+ return (
+ '' +
+ editor.dom.encode(typeof args[1] === "string" ? args[1] : args[0]) + ''
+ );
+ });
+ }
- if (groups) {
- for (var i = 0; i < groups.length; i++) {
- if (!groups[i].filter || groups[i].filter(selector)) {
- if (!groups[i].item) {
- groups[i].item = {text: groups[i].title, menu: []};
- }
+ e.content = content;
+ }
- menu = groups[i].item.menu;
- break;
- }
- }
- }
+ editClass = " " + tinymce.trim(editor.getParam("noneditable_editable_class", "mceEditable")) + " ";
+ nonEditClass = " " + tinymce.trim(editor.getParam("noneditable_noneditable_class", "mceNonEditable")) + " ";
- editor.formatter.register(formatName, format);
+ // Setup noneditable regexps array
+ nonEditableRegExps = editor.getParam("noneditable_regexp");
+ if (nonEditableRegExps && !nonEditableRegExps.length) {
+ nonEditableRegExps = [nonEditableRegExps];
+ }
- var menuItem = tinymce.extend({}, ctrl.settings.itemDefaults, {
- text: format.title,
- format: formatName
- });
+ editor.on('PreInit', function() {
+ handleContentEditableSelection();
- if (menu) {
- menu.push(menuItem);
- } else {
- ctrl.add(menuItem);
- }
- }
+ if (nonEditableRegExps) {
+ editor.on('BeforeSetContent', convertRegExpsToNonEditable);
+ }
- selectors[selector] = true;
+ // Apply contentEditable true/false on elements with the noneditable/editable classes
+ editor.parser.addAttributeFilter('class', function(nodes) {
+ var i = nodes.length, className, node;
+
+ while (i--) {
+ node = nodes[i];
+ className = " " + node.attr("class") + " ";
+
+ if (className.indexOf(editClass) !== -1) {
+ node.attr(internalName, "true");
+ } else if (className.indexOf(nonEditClass) !== -1) {
+ node.attr(internalName, "false");
}
}
});
- each(groups, function(group) {
- ctrl.add(group.item);
+ // Remove internal name
+ editor.serializer.addAttributeFilter(internalName, function(nodes) {
+ var i = nodes.length, node;
+
+ while (i--) {
+ node = nodes[i];
+
+ if (nonEditableRegExps && node.attr('data-mce-content')) {
+ node.name = "#text";
+ node.type = 3;
+ node.raw = true;
+ node.value = node.attr('data-mce-content');
+ } else {
+ node.attr(externalName, null);
+ node.attr(internalName, null);
+ }
+ }
});
- e.control.renderNew();
+ // Convert external name into internal name
+ editor.parser.addAttributeFilter(externalName, function(nodes) {
+ var i = nodes.length, node;
+
+ while (i--) {
+ node = nodes[i];
+ node.attr(internalName, node.attr(externalName));
+ node.attr(externalName, null);
+ }
+ });
});
- // Expose default convertSelectorToFormat implementation
- self.convertSelectorToFormat = convertSelectorToFormat;
+ editor.on('drop', function(e) {
+ if (getNonEditableParent(e.target)) {
+ e.preventDefault();
+ }
+ });
});
-
}).apply(root, arguments);
});
}(this));
(function(root) {
-define("tinymce-insertdatetime", ["tinymce"], function() {
+define("tinymce-pagebreak", ["tinymce"], function() {
return (function() {
/**
* plugin.js
@@ -51424,114 +67087,81 @@ define("tinymce-insertdatetime", ["tinymce"], function() {
/*global tinymce:true */
-tinymce.PluginManager.add('insertdatetime', function(editor) {
- var daysShort = "Sun Mon Tue Wed Thu Fri Sat Sun".split(' ');
- var daysLong = "Sunday Monday Tuesday Wednesday Thursday Friday Saturday Sunday".split(' ');
- var monthsShort = "Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec".split(' ');
- var monthsLong = "January February March April May June July August September October November December".split(' ');
- var menuItems = [], lastFormat, defaultButtonTimeFormat;
+tinymce.PluginManager.add('pagebreak', function(editor) {
+ var pageBreakClass = 'mce-pagebreak', separatorHtml = editor.getParam('pagebreak_separator', '');
- function getDateTime(fmt, date) {
- function addZeros(value, len) {
- value = "" + value;
+ var pageBreakSeparatorRegExp = new RegExp(separatorHtml.replace(/[\?\.\*\[\]\(\)\{\}\+\^\$\:]/g, function(a) {
+ return '\\' + a;
+ }), 'gi');
- if (value.length < len) {
- for (var i = 0; i < (len - value.length); i++) {
- value = "0" + value;
- }
- }
+ var pageBreakPlaceHolderHtml = '';
- return value;
+ // Register commands
+ editor.addCommand('mcePageBreak', function() {
+ if (editor.settings.pagebreak_split_block) {
+ editor.insertContent('' + pageBreakPlaceHolderHtml + '
');
+ } else {
+ editor.insertContent(pageBreakPlaceHolderHtml);
}
+ });
- date = date || new Date();
-
- fmt = fmt.replace("%D", "%m/%d/%Y");
- fmt = fmt.replace("%r", "%I:%M:%S %p");
- fmt = fmt.replace("%Y", "" + date.getFullYear());
- fmt = fmt.replace("%y", "" + date.getYear());
- fmt = fmt.replace("%m", addZeros(date.getMonth() + 1, 2));
- fmt = fmt.replace("%d", addZeros(date.getDate(), 2));
- fmt = fmt.replace("%H", "" + addZeros(date.getHours(), 2));
- fmt = fmt.replace("%M", "" + addZeros(date.getMinutes(), 2));
- fmt = fmt.replace("%S", "" + addZeros(date.getSeconds(), 2));
- fmt = fmt.replace("%I", "" + ((date.getHours() + 11) % 12 + 1));
- fmt = fmt.replace("%p", "" + (date.getHours() < 12 ? "AM" : "PM"));
- fmt = fmt.replace("%B", "" + editor.translate(monthsLong[date.getMonth()]));
- fmt = fmt.replace("%b", "" + editor.translate(monthsShort[date.getMonth()]));
- fmt = fmt.replace("%A", "" + editor.translate(daysLong[date.getDay()]));
- fmt = fmt.replace("%a", "" + editor.translate(daysShort[date.getDay()]));
- fmt = fmt.replace("%%", "%");
-
- return fmt;
- }
-
- function insertDateTime(format) {
- var html = getDateTime(format);
-
- if (editor.settings.insertdatetime_element) {
- var computerTime;
-
- if (/%[HMSIp]/.test(format)) {
- computerTime = getDateTime("%Y-%m-%dT%H:%M");
- } else {
- computerTime = getDateTime("%Y-%m-%d");
- }
+ // Register buttons
+ editor.addButton('pagebreak', {
+ title: 'Page break',
+ cmd: 'mcePageBreak'
+ });
- html = '';
+ editor.addMenuItem('pagebreak', {
+ text: 'Page break',
+ icon: 'pagebreak',
+ cmd: 'mcePageBreak',
+ context: 'insert'
+ });
- var timeElm = editor.dom.getParent(editor.selection.getStart(), 'time');
- if (timeElm) {
- editor.dom.setOuterHTML(timeElm, html);
- return;
- }
+ editor.on('ResolveName', function(e) {
+ if (e.target.nodeName == 'IMG' && editor.dom.hasClass(e.target, pageBreakClass)) {
+ e.name = 'pagebreak';
}
+ });
- editor.insertContent(html);
- }
+ editor.on('click', function(e) {
+ e = e.target;
- editor.addCommand('mceInsertDate', function() {
- insertDateTime(editor.getParam("insertdatetime_dateformat", editor.translate("%Y-%m-%d")));
+ if (e.nodeName === 'IMG' && editor.dom.hasClass(e, pageBreakClass)) {
+ editor.selection.select(e);
+ }
});
- editor.addCommand('mceInsertTime', function() {
- insertDateTime(editor.getParam("insertdatetime_timeformat", editor.translate('%H:%M:%S')));
+ editor.on('BeforeSetContent', function(e) {
+ e.content = e.content.replace(pageBreakSeparatorRegExp, pageBreakPlaceHolderHtml);
});
- editor.addButton('insertdatetime', {
- type: 'splitbutton',
- title: 'Insert date/time',
- onclick: function() {
- insertDateTime(lastFormat || defaultButtonTimeFormat);
- },
- menu: menuItems
- });
+ editor.on('PreInit', function() {
+ editor.serializer.addNodeFilter('img', function(nodes) {
+ var i = nodes.length, node, className;
- tinymce.each(editor.settings.insertdatetime_formats || [
- "%H:%M:%S",
- "%Y-%m-%d",
- "%I:%M:%S %p",
- "%D"
- ], function(fmt) {
- if (!defaultButtonTimeFormat) {
- defaultButtonTimeFormat = fmt;
- }
+ while (i--) {
+ node = nodes[i];
+ className = node.attr('class');
+ if (className && className.indexOf('mce-pagebreak') !== -1) {
+ // Replace parent block node if pagebreak_split_block is enabled
+ var parentNode = node.parent;
+ if (editor.schema.getBlockElements()[parentNode.name] && editor.settings.pagebreak_split_block) {
+ parentNode.type = 3;
+ parentNode.value = separatorHtml;
+ parentNode.raw = true;
+ node.remove();
+ continue;
+ }
- menuItems.push({
- text: getDateTime(fmt),
- onclick: function() {
- lastFormat = fmt;
- insertDateTime(fmt);
+ node.type = 3;
+ node.value = separatorHtml;
+ node.raw = true;
+ }
}
});
});
-
- editor.addMenuItem('insertdatetime', {
- icon: 'date',
- text: 'Insert date/time',
- menu: menuItems,
- context: 'insert'
- });
});
@@ -51540,876 +67170,878 @@ tinymce.PluginManager.add('insertdatetime', function(editor) {
}(this));
(function(root) {
-define("tinymce-layer", ["tinymce"], function() {
+define("tinymce-paste", ["tinymce"], function() {
return (function() {
/**
- * plugin.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
+ * Compiled inline version. (Library mode)
*/
-/*global tinymce:true */
+/*jshint smarttabs:true, undef:true, latedef:true, curly:true, bitwise:true, camelcase:true */
+/*globals $code */
-tinymce.PluginManager.add('layer', function(editor) {
- function getParentLayer(node) {
- do {
- if (node.className && node.className.indexOf('mceItemLayer') != -1) {
- return node;
+(function(exports, undefined) {
+ "use strict";
+
+ var modules = {};
+
+ function require(ids, callback) {
+ var module, defs = [];
+
+ for (var i = 0; i < ids.length; ++i) {
+ module = modules[ids[i]] || resolve(ids[i]);
+ if (!module) {
+ throw 'module definition dependecy not found: ' + ids[i];
}
- } while ((node = node.parentNode));
+
+ defs.push(module);
+ }
+
+ callback.apply(null, defs);
}
- function visualAid(e) {
- var dom = editor.dom;
+ function define(id, dependencies, definition) {
+ if (typeof id !== 'string') {
+ throw 'invalid module definition, module id must be defined and be a string';
+ }
- tinymce.each(dom.select('div,p', e), function(e) {
- if (/^(absolute|relative|fixed)$/i.test(e.style.position)) {
- if (e.hasVisual) {
- dom.addClass(e, 'mceItemVisualAid');
- } else {
- dom.removeClass(e, 'mceItemVisualAid');
- }
+ if (dependencies === undefined) {
+ throw 'invalid module definition, dependencies must be specified';
+ }
- dom.addClass(e, 'mceItemLayer');
- }
+ if (definition === undefined) {
+ throw 'invalid module definition, definition function must be specified';
+ }
+
+ require(dependencies, function() {
+ modules[id] = definition.apply(null, arguments);
});
}
- function move(d) {
- var i, z = [], le = getParentLayer(editor.selection.getNode()), ci = -1, fi = -1, nl;
-
- nl = [];
- tinymce.walk(editor.getBody(), function(n) {
- if (n.nodeType == 1 && /^(absolute|relative|static)$/i.test(n.style.position)) {
- nl.push(n);
- }
- }, 'childNodes');
+ function defined(id) {
+ return !!modules[id];
+ }
- // Find z-indexes
- for (i = 0; i < nl.length; i++) {
- z[i] = nl[i].style.zIndex ? parseInt(nl[i].style.zIndex, 10) : 0;
+ function resolve(id) {
+ var target = exports;
+ var fragments = id.split(/[.\/]/);
- if (ci < 0 && nl[i] == le) {
- ci = i;
+ for (var fi = 0; fi < fragments.length; ++fi) {
+ if (!target[fragments[fi]]) {
+ return;
}
+
+ target = target[fragments[fi]];
}
- if (d < 0) {
- // Move back
+ return target;
+ }
- // Try find a lower one
- for (i = 0; i < z.length; i++) {
- if (z[i] < z[ci]) {
- fi = i;
- break;
- }
- }
+ function expose(ids) {
+ var i, target, id, fragments, privateModules;
- if (fi > -1) {
- nl[ci].style.zIndex = z[fi];
- nl[fi].style.zIndex = z[ci];
- } else {
- if (z[ci] > 0) {
- nl[ci].style.zIndex = z[ci] - 1;
- }
- }
- } else {
- // Move forward
+ for (i = 0; i < ids.length; i++) {
+ target = exports;
+ id = ids[i];
+ fragments = id.split(/[.\/]/);
- // Try find a higher one
- for (i = 0; i < z.length; i++) {
- if (z[i] > z[ci]) {
- fi = i;
- break;
+ for (var fi = 0; fi < fragments.length - 1; ++fi) {
+ if (target[fragments[fi]] === undefined) {
+ target[fragments[fi]] = {};
}
- }
- if (fi > -1) {
- nl[ci].style.zIndex = z[fi];
- nl[fi].style.zIndex = z[ci];
- } else {
- nl[ci].style.zIndex = z[ci] + 1;
+ target = target[fragments[fi]];
}
- }
- editor.execCommand('mceRepaint');
- }
+ target[fragments[fragments.length - 1]] = modules[id];
+ }
+
+ // Expose private modules for unit tests
+ if (exports.AMDLC_TESTS) {
+ privateModules = exports.privateModules || {};
- function insertLayer() {
- var dom = editor.dom, p = dom.getPos(dom.getParent(editor.selection.getNode(), '*'));
- var body = editor.getBody();
+ for (id in modules) {
+ privateModules[id] = modules[id];
+ }
- editor.dom.add(body, 'div', {
- style: {
- position: 'absolute',
- left: p.x,
- top: (p.y > 20 ? p.y : 20),
- width: 100,
- height: 100
- },
- 'class': 'mceItemVisualAid mceItemLayer'
- }, editor.selection.getContent() || editor.getLang('layer.content'));
+ for (i = 0; i < ids.length; i++) {
+ delete privateModules[ids[i]];
+ }
- // Workaround for IE where it messes up the JS engine if you insert a layer on IE 6,7
- if (tinymce.Env.ie) {
- dom.setHTML(body, body.innerHTML);
+ exports.privateModules = privateModules;
}
}
- function toggleAbsolute() {
- var le = getParentLayer(editor.selection.getNode());
-
- if (!le) {
- le = editor.dom.getParent(editor.selection.getNode(), 'DIV,P,IMG');
- }
+// Included from: js/tinymce/plugins/paste/classes/Utils.js
- if (le) {
- if (le.style.position.toLowerCase() == "absolute") {
- editor.dom.setStyles(le, {
- position: '',
- left: '',
- top: '',
- width: '',
- height: ''
- });
+/**
+ * Utils.js
+ *
+ * Released under LGPL License.
+ * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
+ *
+ * License: http://www.tinymce.com/license
+ * Contributing: http://www.tinymce.com/contributing
+ */
- editor.dom.removeClass(le, 'mceItemVisualAid');
- editor.dom.removeClass(le, 'mceItemLayer');
+/**
+ * This class contails various utility functions for the paste plugin.
+ *
+ * @class tinymce.pasteplugin.Utils
+ */
+define("tinymce/pasteplugin/Utils", [
+ "tinymce/util/Tools",
+ "tinymce/html/DomParser",
+ "tinymce/html/Schema"
+], function(Tools, DomParser, Schema) {
+ function filter(content, items) {
+ Tools.each(items, function(v) {
+ if (v.constructor == RegExp) {
+ content = content.replace(v, '');
} else {
- if (!le.style.left) {
- le.style.left = 20 + 'px';
- }
-
- if (!le.style.top) {
- le.style.top = 20 + 'px';
- }
+ content = content.replace(v[0], v[1]);
+ }
+ });
- if (!le.style.width) {
- le.style.width = le.width ? (le.width + 'px') : '100px';
- }
+ return content;
+ }
- if (!le.style.height) {
- le.style.height = le.height ? (le.height + 'px') : '100px';
- }
+ /**
+ * Gets the innerText of the specified element. It will handle edge cases
+ * and works better than textContent on Gecko.
+ *
+ * @param {String} html HTML string to get text from.
+ * @return {String} String of text with line feeds.
+ */
+ function innerText(html) {
+ var schema = new Schema(), domParser = new DomParser({}, schema), text = '';
+ var shortEndedElements = schema.getShortEndedElements();
+ var ignoreElements = Tools.makeMap('script noscript style textarea video audio iframe object', ' ');
+ var blockElements = schema.getBlockElements();
- le.style.position = "absolute";
+ function walk(node) {
+ var name = node.name, currentNode = node;
- editor.dom.setAttrib(le, 'data-mce-style', '');
- editor.addVisual(editor.getBody());
+ if (name === 'br') {
+ text += '\n';
+ return;
}
- editor.execCommand('mceRepaint');
- editor.nodeChanged();
- }
- }
-
- // Register commands
- editor.addCommand('mceInsertLayer', insertLayer);
+ // img/input/hr
+ if (shortEndedElements[name]) {
+ text += ' ';
+ }
- editor.addCommand('mceMoveForward', function() {
- move(1);
- });
+ // Ingore script, video contents
+ if (ignoreElements[name]) {
+ text += ' ';
+ return;
+ }
- editor.addCommand('mceMoveBackward', function() {
- move(-1);
- });
+ if (node.type == 3) {
+ text += node.value;
+ }
- editor.addCommand('mceMakeAbsolute', function() {
- toggleAbsolute();
- });
+ // Walk all children
+ if (!node.shortEnded) {
+ if ((node = node.firstChild)) {
+ do {
+ walk(node);
+ } while ((node = node.next));
+ }
+ }
- // Register buttons
- editor.addButton('moveforward', {title: 'layer.forward_desc', cmd: 'mceMoveForward'});
- editor.addButton('movebackward', {title: 'layer.backward_desc', cmd: 'mceMoveBackward'});
- editor.addButton('absolute', {title: 'layer.absolute_desc', cmd: 'mceMakeAbsolute'});
- editor.addButton('insertlayer', {title: 'layer.insertlayer_desc', cmd: 'mceInsertLayer'});
+ // Add \n or \n\n for blocks or P
+ if (blockElements[name] && currentNode.next) {
+ text += '\n';
- editor.on('init', function() {
- if (tinymce.Env.ie) {
- editor.getDoc().execCommand('2D-Position', false, true);
+ if (name == 'p') {
+ text += '\n';
+ }
+ }
}
- });
-
- // Remove serialized styles when selecting a layer since it might be changed by a drag operation
- editor.on('mouseup', function(e) {
- var layer = getParentLayer(e.target);
- if (layer) {
- editor.dom.setAttrib(layer, 'data-mce-style', '');
- }
- });
+ html = filter(html, [
+ //g // Conditional comments
+ ]);
- // Fixes edit focus issues with layers on Gecko
- // This will enable designMode while inside a layer and disable it when outside
- editor.on('mousedown', function(e) {
- var node = e.target, doc = editor.getDoc(), parent;
+ walk(domParser.parse(html));
- if (tinymce.Env.gecko) {
- if (getParentLayer(node)) {
- if (doc.designMode !== 'on') {
- doc.designMode = 'on';
+ return text;
+ }
- // Repaint caret
- node = doc.body;
- parent = node.parentNode;
- parent.removeChild(node);
- parent.appendChild(node);
- }
- } else if (doc.designMode == 'on') {
- doc.designMode = 'off';
+ /**
+ * Trims the specified HTML by removing all WebKit fragments, all elements wrapping the body trailing BR elements etc.
+ *
+ * @param {String} html Html string to trim contents on.
+ * @return {String} Html contents that got trimmed.
+ */
+ function trimHtml(html) {
+ function trimSpaces(all, s1, s2) {
+ // WebKit meant to preserve multiple spaces but instead inserted around all inline tags,
+ // including the spans with inline styles created on paste
+ if (!s1 && !s2) {
+ return ' ';
}
+
+ return '\u00a0';
}
- });
- editor.on('NodeChange', visualAid);
-});
+ html = filter(html, [
+ /^[\s\S]*]*>\s*|\s*<\/body[^>]*>[\s\S]*$/g, // Remove anything but the contents within the BODY element
+ /|/g, // Inner fragments (tables from excel on mac)
+ [/( ?)\u00a0<\/span>( ?)/g, trimSpaces],
+ /
$/i // Trailing BR elements
+ ]);
+ return html;
+ }
- }).apply(root, arguments);
+ return {
+ filter: filter,
+ innerText: innerText,
+ trimHtml: trimHtml
+ };
});
-}(this));
-(function(root) {
-define("tinymce-legacyoutput", ["tinymce"], function() {
- return (function() {
+// Included from: js/tinymce/plugins/paste/classes/Clipboard.js
+
/**
- * plugin.js
+ * Clipboard.js
*
* Released under LGPL License.
* Copyright (c) 1999-2015 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
+ */
+
+/**
+ * This class contains logic for getting HTML contents out of the clipboard.
*
- * This plugin will force TinyMCE to produce deprecated legacy output such as font elements, u elements, align
- * attributes and so forth. There are a few cases where these old items might be needed for example in email applications or with Flash
+ * We need to make a lot of ugly hacks to get the contents out of the clipboard since
+ * the W3C Clipboard API is broken in all browsers that have it: Gecko/WebKit/Blink.
+ * We might rewrite this the way those API:s stabilize. Browsers doesn't handle pasting
+ * from applications like Word the same way as it does when pasting into a contentEditable area
+ * so we need to do lots of extra work to try to get to this clipboard data.
*
- * However you should NOT use this plugin if you are building some system that produces web contents such as a CMS. All these elements are
- * not apart of the newer specifications for HTML and XHTML.
+ * Current implementation steps:
+ * 1. On keydown with paste keys Ctrl+V or Shift+Insert create
+ * a paste bin element and move focus to that element.
+ * 2. Wait for the browser to fire a "paste" event and get the contents out of the paste bin.
+ * 3. Check if the paste was successful if true, process the HTML.
+ * (4). If the paste was unsuccessful use IE execCommand, Clipboard API, document.dataTransfer old WebKit API etc.
+ *
+ * @class tinymce.pasteplugin.Clipboard
+ * @private
*/
+define("tinymce/pasteplugin/Clipboard", [
+ "tinymce/Env",
+ "tinymce/dom/RangeUtils",
+ "tinymce/util/VK",
+ "tinymce/pasteplugin/Utils"
+], function(Env, RangeUtils, VK, Utils) {
+ return function(editor) {
+ var self = this, pasteBinElm, lastRng, keyboardPasteTimeStamp = 0, draggingInternally = false;
+ var pasteBinDefaultContent = '%MCEPASTEBIN%', keyboardPastePlainTextState;
+ var mceInternalUrlPrefix = 'data:text/mce-internal,';
-/*global tinymce:true */
+ /**
+ * Pastes the specified HTML. This means that the HTML is filtered and then
+ * inserted at the current selection in the editor. It will also fire paste events
+ * for custom user filtering.
+ *
+ * @param {String} html HTML code to paste into the current selection.
+ */
+ function pasteHtml(html) {
+ var args, dom = editor.dom;
-(function(tinymce) {
- // Override inline_styles setting to force TinyMCE to produce deprecated contents
- tinymce.on('AddEditor', function(e) {
- e.editor.settings.inline_styles = false;
- });
+ args = editor.fire('BeforePastePreProcess', {content: html}); // Internal event used by Quirks
+ args = editor.fire('PastePreProcess', args);
+ html = args.content;
- tinymce.PluginManager.add('legacyoutput', function(editor, url, $) {
- editor.on('init', function() {
- var alignElements = 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li,table,img',
- fontSizes = tinymce.explode(editor.settings.font_size_style_values),
- schema = editor.schema;
+ if (!args.isDefaultPrevented()) {
+ // User has bound PastePostProcess events then we need to pass it through a DOM node
+ // This is not ideal but we don't want to let the browser mess up the HTML for example
+ // some browsers add to P tags etc
+ if (editor.hasEventListeners('PastePostProcess') && !args.isDefaultPrevented()) {
+ // We need to attach the element to the DOM so Sizzle selectors work on the contents
+ var tempBody = dom.add(editor.getBody(), 'div', {style: 'display:none'}, html);
+ args = editor.fire('PastePostProcess', {node: tempBody});
+ dom.remove(tempBody);
+ html = args.node.innerHTML;
+ }
- // Override some internal formats to produce legacy elements and attributes
- editor.formatter.register({
- // Change alignment formats to use the deprecated align attribute
- alignleft: {selector: alignElements, attributes: {align: 'left'}},
- aligncenter: {selector: alignElements, attributes: {align: 'center'}},
- alignright: {selector: alignElements, attributes: {align: 'right'}},
- alignjustify: {selector: alignElements, attributes: {align: 'justify'}},
+ if (!args.isDefaultPrevented()) {
+ editor.insertContent(html, {merge: editor.settings.paste_merge_formats !== false, data: {paste: true}});
+ }
+ }
+ }
- // Change the basic formatting elements to use deprecated element types
- bold: [
- {inline: 'b', remove: 'all'},
- {inline: 'strong', remove: 'all'},
- {inline: 'span', styles: {fontWeight: 'bold'}}
- ],
- italic: [
- {inline: 'i', remove: 'all'},
- {inline: 'em', remove: 'all'},
- {inline: 'span', styles: {fontStyle: 'italic'}}
- ],
- underline: [
- {inline: 'u', remove: 'all'},
- {inline: 'span', styles: {textDecoration: 'underline'}, exact: true}
- ],
- strikethrough: [
- {inline: 'strike', remove: 'all'},
- {inline: 'span', styles: {textDecoration: 'line-through'}, exact: true}
- ],
+ /**
+ * Pastes the specified text. This means that the plain text is processed
+ * and converted into BR and P elements. It will fire paste events for custom filtering.
+ *
+ * @param {String} text Text to paste as the current selection location.
+ */
+ function pasteText(text) {
+ text = editor.dom.encode(text).replace(/\r\n/g, '\n');
- // Change font size and font family to use the deprecated font element
- fontname: {inline: 'font', attributes: {face: '%value'}},
- fontsize: {
- inline: 'font',
- attributes: {
- size: function(vars) {
- return tinymce.inArray(fontSizes, vars.value) + 1;
- }
- }
- },
+ var startBlock = editor.dom.getParent(editor.selection.getStart(), editor.dom.isBlock);
- // Setup font elements for colors as well
- forecolor: {inline: 'font', attributes: {color: '%value'}},
- hilitecolor: {inline: 'font', styles: {backgroundColor: '%value'}}
- });
+ // Create start block html for example
+ var forcedRootBlockName = editor.settings.forced_root_block;
+ var forcedRootBlockStartHtml;
+ if (forcedRootBlockName) {
+ forcedRootBlockStartHtml = editor.dom.createHTML(forcedRootBlockName, editor.settings.forced_root_block_attrs);
+ forcedRootBlockStartHtml = forcedRootBlockStartHtml.substr(0, forcedRootBlockStartHtml.length - 3) + '>';
+ }
- // Check that deprecated elements are allowed if not add them
- tinymce.each('b,i,u,strike'.split(','), function(name) {
- schema.addValidElements(name + '[*]');
- });
+ if ((startBlock && /^(PRE|DIV)$/.test(startBlock.nodeName)) || !forcedRootBlockName) {
+ text = Utils.filter(text, [
+ [/\n/g, "
"]
+ ]);
+ } else {
+ text = Utils.filter(text, [
+ [/\n\n/g, "
" + forcedRootBlockStartHtml],
+ [/^(.*<\/p>)()$/, forcedRootBlockStartHtml + '$1'],
+ [/\n/g, "
"]
+ ]);
- // Add font element if it's missing
- if (!schema.getElementRule("font")) {
- schema.addValidElements("font[face|size|color|style]");
+ if (text.indexOf('
') != -1) {
+ text = forcedRootBlockStartHtml + text;
+ }
}
- // Add the missing and depreacted align attribute for the serialization engine
- tinymce.each(alignElements.split(','), function(name) {
- var rule = schema.getElementRule(name);
+ pasteHtml(text);
+ }
- if (rule) {
- if (!rule.attributes.align) {
- rule.attributes.align = {};
- rule.attributesOrder.push('align');
- }
+ /**
+ * Creates a paste bin element as close as possible to the current caret location and places the focus inside that element
+ * so that when the real paste event occurs the contents gets inserted into this element
+ * instead of the current editor selection element.
+ */
+ function createPasteBin() {
+ var dom = editor.dom, body = editor.getBody();
+ var viewport = editor.dom.getViewPort(editor.getWin()), scrollTop = viewport.y, top = 20;
+ var scrollContainer;
+
+ lastRng = editor.selection.getRng();
+
+ if (editor.inline) {
+ scrollContainer = editor.selection.getScrollContainer();
+
+ // Can't always rely on scrollTop returning a useful value.
+ // It returns 0 if the browser doesn't support scrollTop for the element or is non-scrollable
+ if (scrollContainer && scrollContainer.scrollTop > 0) {
+ scrollTop = scrollContainer.scrollTop;
}
- });
- });
+ }
- editor.addButton('fontsizeselect', function() {
- var items = [], defaultFontsizeFormats = '8pt=1 10pt=2 12pt=3 14pt=4 18pt=5 24pt=6 36pt=7';
- var fontsize_formats = editor.settings.fontsize_formats || defaultFontsizeFormats;
+ /**
+ * Returns the rect of the current caret if the caret is in an empty block before a
+ * BR we insert a temporary invisible character that we get the rect this way we always get a proper rect.
+ *
+ * TODO: This might be useful in core.
+ */
+ function getCaretRect(rng) {
+ var rects, textNode, node, container = rng.startContainer;
- editor.$.each(fontsize_formats.split(' '), function(i, item) {
- var text = item, value = item;
- var values = item.split('=');
+ rects = rng.getClientRects();
+ if (rects.length) {
+ return rects[0];
+ }
- if (values.length > 1) {
- text = values[0];
- value = values[1];
+ if (!rng.collapsed || container.nodeType != 1) {
+ return;
}
- items.push({text: text, value: value});
- });
+ node = container.childNodes[lastRng.startOffset];
- return {
- type: 'listbox',
- text: 'Font Sizes',
- tooltip: 'Font Sizes',
- values: items,
- fixedWidth: true,
- onPostRender: function() {
- var self = this;
+ // Skip empty whitespace nodes
+ while (node && node.nodeType == 3 && !node.data.length) {
+ node = node.nextSibling;
+ }
- editor.on('NodeChange', function() {
- var fontElm;
+ if (!node) {
+ return;
+ }
- fontElm = editor.dom.getParent(editor.selection.getNode(), 'font');
- if (fontElm) {
- self.value(fontElm.size);
- } else {
- self.value('');
+ // Check if the location is |
+ // TODO: Might need to expand this to say |
+ if (node.tagName == 'BR') {
+ textNode = dom.doc.createTextNode('\uFEFF');
+ node.parentNode.insertBefore(textNode, node);
+
+ rng = dom.createRng();
+ rng.setStartBefore(textNode);
+ rng.setEndAfter(textNode);
+
+ rects = rng.getClientRects();
+ dom.remove(textNode);
+ }
+
+ if (rects.length) {
+ return rects[0];
+ }
+ }
+
+ // Calculate top cordinate this is needed to avoid scrolling to top of document
+ // We want the paste bin to be as close to the caret as possible to avoid scrolling
+ if (lastRng.getClientRects) {
+ var rect = getCaretRect(lastRng);
+
+ if (rect) {
+ // Client rects gets us closes to the actual
+ // caret location in for example a wrapped paragraph block
+ top = scrollTop + (rect.top - dom.getPos(body).y);
+ } else {
+ top = scrollTop;
+
+ // Check if we can find a closer location by checking the range element
+ var container = lastRng.startContainer;
+ if (container) {
+ if (container.nodeType == 3 && container.parentNode != body) {
+ container = container.parentNode;
+ }
+
+ if (container.nodeType == 1) {
+ top = dom.getPos(container, scrollContainer || body).y;
}
- });
- },
- onclick: function(e) {
- if (e.control.settings.value) {
- editor.execCommand('FontSize', false, e.control.settings.value);
}
}
- };
- });
+ }
- editor.addButton('fontselect', function() {
- function createFormats(formats) {
- formats = formats.replace(/;$/, '').split(';');
+ // Create a pastebin
+ pasteBinElm = dom.add(editor.getBody(), 'div', {
+ id: "mcepastebin",
+ contentEditable: true,
+ "data-mce-bogus": "all",
+ style: 'position: absolute; top: ' + top + 'px;' +
+ 'width: 10px; height: 10px; overflow: hidden; opacity: 0'
+ }, pasteBinDefaultContent);
- var i = formats.length;
- while (i--) {
- formats[i] = formats[i].split('=');
+ // Move paste bin out of sight since the controlSelection rect gets displayed otherwise on IE and Gecko
+ if (Env.ie || Env.gecko) {
+ dom.setStyle(pasteBinElm, 'left', dom.getStyle(body, 'direction', true) == 'rtl' ? 0xFFFF : -0xFFFF);
+ }
+
+ // Prevent focus events from bubbeling fixed FocusManager issues
+ dom.bind(pasteBinElm, 'beforedeactivate focusin focusout', function(e) {
+ e.stopPropagation();
+ });
+
+ pasteBinElm.focus();
+ editor.selection.select(pasteBinElm, true);
+ }
+
+ /**
+ * Removes the paste bin if it exists.
+ */
+ function removePasteBin() {
+ if (pasteBinElm) {
+ var pasteBinClone;
+
+ // WebKit/Blink might clone the div so
+ // lets make sure we remove all clones
+ // TODO: Man o man is this ugly. WebKit is the new IE! Remove this if they ever fix it!
+ while ((pasteBinClone = editor.dom.get('mcepastebin'))) {
+ editor.dom.remove(pasteBinClone);
+ editor.dom.unbind(pasteBinClone);
}
- return formats;
+ if (lastRng) {
+ editor.selection.setRng(lastRng);
+ }
}
- var defaultFontsFormats =
- 'Andale Mono=andale mono,monospace;' +
- 'Arial=arial,helvetica,sans-serif;' +
- 'Arial Black=arial black,sans-serif;' +
- 'Book Antiqua=book antiqua,palatino,serif;' +
- 'Comic Sans MS=comic sans ms,sans-serif;' +
- 'Courier New=courier new,courier,monospace;' +
- 'Georgia=georgia,palatino,serif;' +
- 'Helvetica=helvetica,arial,sans-serif;' +
- 'Impact=impact,sans-serif;' +
- 'Symbol=symbol;' +
- 'Tahoma=tahoma,arial,helvetica,sans-serif;' +
- 'Terminal=terminal,monaco,monospace;' +
- 'Times New Roman=times new roman,times,serif;' +
- 'Trebuchet MS=trebuchet ms,geneva,sans-serif;' +
- 'Verdana=verdana,geneva,sans-serif;' +
- 'Webdings=webdings;' +
- 'Wingdings=wingdings,zapf dingbats';
+ pasteBinElm = lastRng = null;
+ }
- var items = [], fonts = createFormats(editor.settings.font_formats || defaultFontsFormats);
+ /**
+ * Returns the contents of the paste bin as a HTML string.
+ *
+ * @return {String} Get the contents of the paste bin.
+ */
+ function getPasteBinHtml() {
+ var html = '', pasteBinClones, i, clone, cloneHtml;
- $.each(fonts, function(i, font) {
- items.push({
- text: {raw: font[0]},
- value: font[1],
- textStyle: font[1].indexOf('dings') == -1 ? 'font-family:' + font[1] : ''
- });
- });
+ // Since WebKit/Chrome might clone the paste bin when pasting
+ // for example: we need to check if any of them contains some useful html.
+ // TODO: Man o man is this ugly. WebKit is the new IE! Remove this if they ever fix it!
+ pasteBinClones = editor.dom.select('div[id=mcepastebin]');
+ for (i = 0; i < pasteBinClones.length; i++) {
+ clone = pasteBinClones[i];
- return {
- type: 'listbox',
- text: 'Font Family',
- tooltip: 'Font Family',
- values: items,
- fixedWidth: true,
- onPostRender: function() {
- var self = this;
+ // Pasting plain text produces pastebins in pastebinds makes sence right!?
+ if (clone.firstChild && clone.firstChild.id == 'mcepastebin') {
+ clone = clone.firstChild;
+ }
+
+ cloneHtml = clone.innerHTML;
+ if (html != pasteBinDefaultContent) {
+ html += cloneHtml;
+ }
+ }
- editor.on('NodeChange', function() {
- var fontElm;
+ return html;
+ }
- fontElm = editor.dom.getParent(editor.selection.getNode(), 'font');
- if (fontElm) {
- self.value(fontElm.face);
- } else {
- self.value('');
+ /**
+ * Gets various content types out of a datatransfer object.
+ *
+ * @param {DataTransfer} dataTransfer Event fired on paste.
+ * @return {Object} Object with mime types and data for those mime types.
+ */
+ function getDataTransferItems(dataTransfer) {
+ var data = {};
+
+ if (dataTransfer) {
+ // Use old WebKit/IE API
+ if (dataTransfer.getData) {
+ var legacyText = dataTransfer.getData('Text');
+ if (legacyText && legacyText.length > 0) {
+ if (legacyText.indexOf(mceInternalUrlPrefix) == -1) {
+ data['text/plain'] = legacyText;
}
- });
- },
- onselect: function(e) {
- if (e.control.settings.value) {
- editor.execCommand('FontName', false, e.control.settings.value);
}
}
- };
- });
- });
-})(tinymce);
+ if (dataTransfer.types) {
+ for (var i = 0; i < dataTransfer.types.length; i++) {
+ var contentType = dataTransfer.types[i];
+ data[contentType] = dataTransfer.getData(contentType);
+ }
+ }
+ }
- }).apply(root, arguments);
-});
-}(this));
+ return data;
+ }
-(function(root) {
-define("tinymce-link", ["tinymce"], function() {
- return (function() {
-/**
- * plugin.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
+ /**
+ * Gets various content types out of the Clipboard API. It will also get the
+ * plain text using older IE and WebKit API:s.
+ *
+ * @param {ClipboardEvent} clipboardEvent Event fired on paste.
+ * @return {Object} Object with mime types and data for those mime types.
+ */
+ function getClipboardContent(clipboardEvent) {
+ return getDataTransferItems(clipboardEvent.clipboardData || editor.getDoc().dataTransfer);
+ }
-/*global tinymce:true */
+ /**
+ * Checks if the clipboard contains image data if it does it will take that data
+ * and convert it into a data url image and paste that image at the caret location.
+ *
+ * @param {ClipboardEvent} e Paste/drop event object.
+ * @param {DOMRange} rng Optional rng object to move selection to.
+ * @return {Boolean} true/false if the image data was found or not.
+ */
+ function pasteImageData(e, rng) {
+ var dataTransfer = e.clipboardData || e.dataTransfer;
-tinymce.PluginManager.add('link', function(editor) {
- function createLinkList(callback) {
- return function() {
- var linkList = editor.settings.link_list;
+ function processItems(items) {
+ var i, item, reader, hadImage = false;
- if (typeof linkList == "string") {
- tinymce.util.XHR.send({
- url: linkList,
- success: function(text) {
- callback(tinymce.util.JSON.parse(text));
+ function pasteImage(reader) {
+ if (rng) {
+ editor.selection.setRng(rng);
+ rng = null;
}
- });
- } else if (typeof linkList == "function") {
- linkList(callback);
- } else {
- callback(linkList);
- }
- };
- }
- function buildListItems(inputList, itemCallback, startItems) {
- function appendItems(values, output) {
- output = output || [];
+ pasteHtml('');
+ }
- tinymce.each(values, function(item) {
- var menuItem = {text: item.text || item.title};
+ if (items) {
+ for (i = 0; i < items.length; i++) {
+ item = items[i];
- if (item.menu) {
- menuItem.menu = appendItems(item.menu);
- } else {
- menuItem.value = item.value;
+ if (/^image\/(jpeg|png|gif|bmp)$/.test(item.type)) {
+ reader = new FileReader();
+ reader.onload = pasteImage.bind(null, reader);
+ reader.readAsDataURL(item.getAsFile ? item.getAsFile() : item);
- if (itemCallback) {
- itemCallback(menuItem);
+ e.preventDefault();
+ hadImage = true;
+ }
}
}
- output.push(menuItem);
- });
+ return hadImage;
+ }
- return output;
+ if (editor.settings.paste_data_images && dataTransfer) {
+ return processItems(dataTransfer.items) || processItems(dataTransfer.files);
+ }
}
- return appendItems(inputList, startItems || []);
- }
-
- function showDialog(linkList) {
- var data = {}, selection = editor.selection, dom = editor.dom, selectedElm, anchorElm, initialText;
- var win, onlyText, textListCtrl, linkListCtrl, relListCtrl, targetListCtrl, classListCtrl, linkTitleCtrl, value;
-
- function linkListChangeHandler(e) {
- var textCtrl = win.find('#text');
+ /**
+ * Chrome on Android doesn't support proper clipboard access so we have no choice but to allow the browser default behavior.
+ *
+ * @param {Event} e Paste event object to check if it contains any data.
+ * @return {Boolean} true/false if the clipboard is empty or not.
+ */
+ function isBrokenAndroidClipboardEvent(e) {
+ var clipboardData = e.clipboardData;
- if (!textCtrl.value() || (e.lastControl && textCtrl.value() == e.lastControl.text())) {
- textCtrl.value(e.control.text());
- }
+ return navigator.userAgent.indexOf('Android') != -1 && clipboardData && clipboardData.items && clipboardData.items.length === 0;
+ }
- win.find('#href').value(e.control.value());
+ function getCaretRangeFromEvent(e) {
+ return RangeUtils.getCaretRangeFromPoint(e.clientX, e.clientY, editor.getDoc());
}
- function buildAnchorListControl(url) {
- var anchorList = [];
+ function hasContentType(clipboardContent, mimeType) {
+ return mimeType in clipboardContent && clipboardContent[mimeType].length > 0;
+ }
- tinymce.each(editor.dom.select('a:not([href])'), function(anchor) {
- var id = anchor.name || anchor.id;
+ function isKeyboardPasteEvent(e) {
+ return (VK.metaKeyPressed(e) && e.keyCode == 86) || (e.shiftKey && e.keyCode == 45);
+ }
- if (id) {
- anchorList.push({
- text: id,
- value: '#' + id,
- selected: url.indexOf('#' + id) != -1
- });
+ function registerEventHandlers() {
+ editor.on('keydown', function(e) {
+ function removePasteBinOnKeyUp(e) {
+ // Ctrl+V or Shift+Insert
+ if (isKeyboardPasteEvent(e) && !e.isDefaultPrevented()) {
+ removePasteBin();
+ }
}
- });
- if (anchorList.length) {
- anchorList.unshift({text: 'None', value: ''});
+ // Ctrl+V or Shift+Insert
+ if (isKeyboardPasteEvent(e) && !e.isDefaultPrevented()) {
+ keyboardPastePlainTextState = e.shiftKey && e.keyCode == 86;
- return {
- name: 'anchor',
- type: 'listbox',
- label: 'Anchors',
- values: anchorList,
- onselect: linkListChangeHandler
- };
- }
- }
+ // Edge case on Safari on Mac where it doesn't handle Cmd+Shift+V correctly
+ // it fires the keydown but no paste or keyup so we are left with a paste bin
+ if (keyboardPastePlainTextState && Env.webkit && navigator.userAgent.indexOf('Version/') != -1) {
+ return;
+ }
- function updateText() {
- if (!initialText && data.text.length === 0 && onlyText) {
- this.parent().parent().find('#text')[0].value(this.value());
- }
- }
+ // Prevent undoManager keydown handler from making an undo level with the pastebin in it
+ e.stopImmediatePropagation();
- function urlChange(e) {
- var meta = e.meta || {};
+ keyboardPasteTimeStamp = new Date().getTime();
- if (linkListCtrl) {
- linkListCtrl.value(editor.convertURL(this.value(), 'href'));
- }
+ // IE doesn't support Ctrl+Shift+V and it doesn't even produce a paste event
+ // so lets fake a paste event and let IE use the execCommand/dataTransfer methods
+ if (Env.ie && keyboardPastePlainTextState) {
+ e.preventDefault();
+ editor.fire('paste', {ieFake: true});
+ return;
+ }
- tinymce.each(e.meta, function(value, key) {
- win.find('#' + key).value(value);
- });
+ removePasteBin();
+ createPasteBin();
- if (!meta.text) {
- updateText.call(this);
- }
- }
+ // Remove pastebin if we get a keyup and no paste event
+ // For example pasting a file in IE 11 will not produce a paste event
+ editor.once('keyup', removePasteBinOnKeyUp);
+ editor.once('paste', function() {
+ editor.off('keyup', removePasteBinOnKeyUp);
+ });
+ }
+ });
- function isOnlyTextSelected(anchorElm) {
- var html = selection.getContent();
+ editor.on('paste', function(e) {
+ // Getting content from the Clipboard can take some time
+ var clipboardTimer = new Date().getTime();
+ var clipboardContent = getClipboardContent(e);
+ var clipboardDelay = new Date().getTime() - clipboardTimer;
- // Partial html and not a fully selected anchor element
- if (/]+>[^<]+<\/a>$/.test(html) || html.indexOf('href=') == -1)) {
- return false;
- }
+ var isKeyBoardPaste = (new Date().getTime() - keyboardPasteTimeStamp - clipboardDelay) < 1000;
+ var plainTextMode = self.pasteFormat == "text" || keyboardPastePlainTextState;
- if (anchorElm) {
- var nodes = anchorElm.childNodes, i;
+ keyboardPastePlainTextState = false;
- if (nodes.length === 0) {
- return false;
+ if (e.isDefaultPrevented() || isBrokenAndroidClipboardEvent(e)) {
+ removePasteBin();
+ return;
}
- for (i = nodes.length - 1; i >= 0; i--) {
- if (nodes[i].nodeType != 3) {
- return false;
- }
+ if (pasteImageData(e)) {
+ removePasteBin();
+ return;
}
- }
- return true;
- }
+ // Not a keyboard paste prevent default paste and try to grab the clipboard contents using different APIs
+ if (!isKeyBoardPaste) {
+ e.preventDefault();
+ }
- selectedElm = selection.getNode();
- anchorElm = dom.getParent(selectedElm, 'a[href]');
- onlyText = isOnlyTextSelected();
+ // Try IE only method if paste isn't a keyboard paste
+ if (Env.ie && (!isKeyBoardPaste || e.ieFake)) {
+ createPasteBin();
- data.text = initialText = anchorElm ? (anchorElm.innerText || anchorElm.textContent) : selection.getContent({format: 'text'});
- data.href = anchorElm ? dom.getAttrib(anchorElm, 'href') : '';
+ editor.dom.bind(pasteBinElm, 'paste', function(e) {
+ e.stopPropagation();
+ });
- if (anchorElm) {
- data.target = dom.getAttrib(anchorElm, 'target');
- } else if (editor.settings.default_link_target) {
- data.target = editor.settings.default_link_target;
- }
+ editor.getDoc().execCommand('Paste', false, null);
+ clipboardContent["text/html"] = getPasteBinHtml();
+ }
- if ((value = dom.getAttrib(anchorElm, 'rel'))) {
- data.rel = value;
- }
+ setTimeout(function() {
+ var content;
- if ((value = dom.getAttrib(anchorElm, 'class'))) {
- data['class'] = value;
- }
+ // Grab HTML from Clipboard API or paste bin as a fallback
+ if (hasContentType(clipboardContent, 'text/html')) {
+ content = clipboardContent['text/html'];
+ } else {
+ content = getPasteBinHtml();
- if ((value = dom.getAttrib(anchorElm, 'title'))) {
- data.title = value;
- }
+ // If paste bin is empty try using plain text mode
+ // since that is better than nothing right
+ if (content == pasteBinDefaultContent) {
+ plainTextMode = true;
+ }
+ }
- if (onlyText) {
- textListCtrl = {
- name: 'text',
- type: 'textbox',
- size: 40,
- label: 'Text to display',
- onchange: function() {
- data.text = this.value();
- }
- };
- }
+ content = Utils.trimHtml(content);
- if (linkList) {
- linkListCtrl = {
- type: 'listbox',
- label: 'Link list',
- values: buildListItems(
- linkList,
- function(item) {
- item.value = editor.convertURL(item.value || item.url, 'href');
- },
- [{text: 'None', value: ''}]
- ),
- onselect: linkListChangeHandler,
- value: editor.convertURL(data.href, 'href'),
- onPostRender: function() {
- /*eslint consistent-this:0*/
- linkListCtrl = this;
- }
- };
- }
+ // WebKit has a nice bug where it clones the paste bin if you paste from for example notepad
+ // so we need to force plain text mode in this case
+ if (pasteBinElm && pasteBinElm.firstChild && pasteBinElm.firstChild.id === 'mcepastebin') {
+ plainTextMode = true;
+ }
- if (editor.settings.target_list !== false) {
- if (!editor.settings.target_list) {
- editor.settings.target_list = [
- {text: 'None', value: ''},
- {text: 'New window', value: '_blank'}
- ];
- }
+ removePasteBin();
- targetListCtrl = {
- name: 'target',
- type: 'listbox',
- label: 'Target',
- values: buildListItems(editor.settings.target_list)
- };
- }
+ // If we got nothing from clipboard API and pastebin then we could try the last resort: plain/text
+ if (!content.length) {
+ plainTextMode = true;
+ }
- if (editor.settings.rel_list) {
- relListCtrl = {
- name: 'rel',
- type: 'listbox',
- label: 'Rel',
- values: buildListItems(editor.settings.rel_list)
- };
- }
+ // Grab plain text from Clipboard API or convert existing HTML to plain text
+ if (plainTextMode) {
+ // Use plain text contents from Clipboard API unless the HTML contains paragraphs then
+ // we should convert the HTML to plain text since works better when pasting HTML/Word contents as plain text
+ if (hasContentType(clipboardContent, 'text/plain') && content.indexOf('') == -1) {
+ content = clipboardContent['text/plain'];
+ } else {
+ content = Utils.innerText(content);
+ }
+ }
- if (editor.settings.link_class_list) {
- classListCtrl = {
- name: 'class',
- type: 'listbox',
- label: 'Class',
- values: buildListItems(
- editor.settings.link_class_list,
- function(item) {
- if (item.value) {
- item.textStyle = function() {
- return editor.formatter.getCssText({inline: 'a', classes: [item.value]});
- };
+ // If the content is the paste bin default HTML then it was
+ // impossible to get the cliboard data out.
+ if (content == pasteBinDefaultContent) {
+ if (!isKeyBoardPaste) {
+ editor.windowManager.alert('Please use Ctrl+V/Cmd+V keyboard shortcuts to paste contents.');
}
+
+ return;
}
- )
- };
- }
- if (editor.settings.link_title !== false) {
- linkTitleCtrl = {
- name: 'title',
- type: 'textbox',
- label: 'Title',
- value: data.title
- };
- }
+ if (plainTextMode) {
+ pasteText(content);
+ } else {
+ pasteHtml(content);
+ }
+ }, 0);
+ });
- win = editor.windowManager.open({
- title: 'Insert link',
- data: data,
- body: [
- {
- name: 'href',
- type: 'filepicker',
- filetype: 'file',
- size: 40,
- autofocus: true,
- label: 'Url',
- onchange: urlChange,
- onkeyup: updateText
- },
- textListCtrl,
- linkTitleCtrl,
- buildAnchorListControl(data.href),
- linkListCtrl,
- relListCtrl,
- targetListCtrl,
- classListCtrl
- ],
- onSubmit: function(e) {
- /*eslint dot-notation: 0*/
- var href;
+ editor.on('dragstart dragend', function(e) {
+ draggingInternally = e.type == 'dragstart';
+ });
- data = tinymce.extend(data, e.data);
- href = data.href;
+ editor.on('drop', function(e) {
+ var rng = getCaretRangeFromEvent(e);
- // Delay confirm since onSubmit will move focus
- function delayedConfirm(message, callback) {
- var rng = editor.selection.getRng();
+ if (e.isDefaultPrevented() || draggingInternally) {
+ return;
+ }
- window.setTimeout(function() {
- editor.windowManager.confirm(message, function(state) {
- editor.selection.setRng(rng);
- callback(state);
- });
- }, 0);
+ if (pasteImageData(e, rng)) {
+ return;
}
- function insertLink() {
- var linkAttrs = {
- href: href,
- target: data.target ? data.target : null,
- rel: data.rel ? data.rel : null,
- "class": data["class"] ? data["class"] : null,
- title: data.title ? data.title : null
- };
+ if (rng && editor.settings.paste_filter_drop !== false) {
+ var dropContent = getDataTransferItems(e.dataTransfer);
+ var content = dropContent['mce-internal'] || dropContent['text/html'] || dropContent['text/plain'];
- if (anchorElm) {
- editor.focus();
+ if (content) {
+ e.preventDefault();
- if (onlyText && data.text != initialText) {
- if ("innerText" in anchorElm) {
- anchorElm.innerText = data.text;
- } else {
- anchorElm.textContent = data.text;
+ editor.undoManager.transact(function() {
+ if (dropContent['mce-internal']) {
+ editor.execCommand('Delete');
}
- }
- dom.setAttribs(anchorElm, linkAttrs);
+ editor.selection.setRng(rng);
- selection.select(anchorElm);
- editor.undoManager.add();
- } else {
- if (onlyText) {
- editor.insertContent(dom.createHTML('a', linkAttrs, dom.encode(data.text)));
- } else {
- editor.execCommand('mceInsertLink', false, linkAttrs);
- }
+ content = Utils.trimHtml(content);
+
+ if (!dropContent['text/html']) {
+ pasteText(content);
+ } else {
+ pasteHtml(content);
+ }
+ });
}
}
+ });
- if (!href) {
- editor.execCommand('unlink');
- return;
+ editor.on('dragover dragend', function(e) {
+ if (editor.settings.paste_data_images) {
+ e.preventDefault();
}
+ });
+ }
- // Is email and not //user@domain.com
- if (href.indexOf('@') > 0 && href.indexOf('//') == -1 && href.indexOf('mailto:') == -1) {
- delayedConfirm(
- 'The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?',
- function(state) {
- if (state) {
- href = 'mailto:' + href;
- }
+ self.pasteHtml = pasteHtml;
+ self.pasteText = pasteText;
- insertLink();
- }
- );
+ editor.on('preInit', function() {
+ registerEventHandlers();
- return;
+ // Remove all data images from paste for example from Gecko
+ // except internal images like video elements
+ editor.parser.addNodeFilter('img', function(nodes, name, args) {
+ function isPasteInsert(args) {
+ return args.data && args.data.paste === true;
}
- // Is not protocol prefixed
- if ((editor.settings.link_assume_external_targets && !/^\w+:/i.test(href)) ||
- (!editor.settings.link_assume_external_targets && /^\s*www[\.|\d\.]/i.test(href))) {
- delayedConfirm(
- 'The URL you entered seems to be an external link. Do you want to add the required http:// prefix?',
- function(state) {
- if (state) {
- href = 'http://' + href;
- }
-
- insertLink();
- }
- );
-
- return;
+ function remove(node) {
+ if (!node.attr('data-mce-object') && src !== Env.transparentSrc) {
+ node.remove();
+ }
}
- insertLink();
- }
- });
- }
+ function isWebKitFakeUrl(src) {
+ return src.indexOf("webkit-fake-url") === 0;
+ }
- editor.addButton('link', {
- icon: 'link',
- tooltip: 'Insert/edit link',
- shortcut: 'Meta+K',
- onclick: createLinkList(showDialog),
- stateSelector: 'a[href]'
- });
+ function isDataUri(src) {
+ return src.indexOf("data:") === 0;
+ }
- editor.addButton('unlink', {
- icon: 'unlink',
- tooltip: 'Remove link',
- cmd: 'unlink',
- stateSelector: 'a[href]'
- });
+ if (!editor.settings.paste_data_images && isPasteInsert(args)) {
+ var i = nodes.length;
- editor.addShortcut('Meta+K', '', createLinkList(showDialog));
- editor.addCommand('mceLink', createLinkList(showDialog));
+ while (i--) {
+ var src = nodes[i].attributes.map.src;
- this.showDialog = showDialog;
+ if (!src) {
+ continue;
+ }
- editor.addMenuItem('link', {
- icon: 'link',
- text: 'Insert/edit link',
- shortcut: 'Meta+K',
- onclick: createLinkList(showDialog),
- stateSelector: 'a[href]',
- context: 'insert',
- prependToContext: true
- });
+ // Safari on Mac produces webkit-fake-url see: https://bugs.webkit.org/show_bug.cgi?id=49141
+ if (isWebKitFakeUrl(src)) {
+ remove(nodes[i]);
+ } else if (!editor.settings.allow_html_data_urls && isDataUri(src)) {
+ remove(nodes[i]);
+ }
+ }
+ }
+ });
+ });
+ };
});
+// Included from: js/tinymce/plugins/paste/classes/WordFilter.js
- }).apply(root, arguments);
-});
-}(this));
-
-(function(root) {
-define("tinymce-lists", ["tinymce"], function() {
- return (function() {
/**
- * plugin.js
+ * WordFilter.js
*
* Released under LGPL License.
* Copyright (c) 1999-2015 Ephox Corp. All rights reserved
@@ -52418,806 +68050,1011 @@ define("tinymce-lists", ["tinymce"], function() {
* Contributing: http://www.tinymce.com/contributing
*/
-/*global tinymce:true */
-/*eslint consistent-this:0 */
-
-tinymce.PluginManager.add('lists', function(editor) {
- var self = this;
-
- function isListNode(node) {
- return node && (/^(OL|UL|DL)$/).test(node.nodeName);
- }
-
- function isFirstChild(node) {
- return node.parentNode.firstChild == node;
- }
-
- function isLastChild(node) {
- return node.parentNode.lastChild == node;
+/**
+ * This class parses word HTML into proper TinyMCE markup.
+ *
+ * @class tinymce.pasteplugin.WordFilter
+ * @private
+ */
+define("tinymce/pasteplugin/WordFilter", [
+ "tinymce/util/Tools",
+ "tinymce/html/DomParser",
+ "tinymce/html/Schema",
+ "tinymce/html/Serializer",
+ "tinymce/html/Node",
+ "tinymce/pasteplugin/Utils"
+], function(Tools, DomParser, Schema, Serializer, Node, Utils) {
+ /**
+ * Checks if the specified content is from any of the following sources: MS Word/Office 365/Google docs.
+ */
+ function isWordContent(content) {
+ return (
+ (/||
- * becomes: ||
- *
- * @param {DOMRange} rng DOM Range to get bookmark on.
- * @return {Object} Bookmark object.
- */
- function createBookmark(rng) {
- var bookmark = {};
+ text = text.replace(/^[\u00a0 ]+/, '');
- function setupEndPoint(start) {
- var offsetNode, container, offset;
+ Tools.each(patterns, function(pattern) {
+ if (pattern.test(text)) {
+ found = true;
+ return false;
+ }
+ });
- container = rng[start ? 'startContainer' : 'endContainer'];
- offset = rng[start ? 'startOffset' : 'endOffset'];
+ return found;
+ }
- if (container.nodeType == 1) {
- offsetNode = dom.create('span', {'data-mce-type': 'bookmark'});
+ function isBulletList(text) {
+ return /^[\s\u00a0]*[\u2022\u00b7\u00a7\u25CF]\s*/.test(text);
+ }
- if (container.hasChildNodes()) {
- offset = Math.min(offset, container.childNodes.length - 1);
+ function WordFilter(editor) {
+ var settings = editor.settings;
- if (start) {
- container.insertBefore(offsetNode, container.childNodes[offset]);
- } else {
- dom.insertAfter(offsetNode, container.childNodes[offset]);
- }
- } else {
- container.appendChild(offsetNode);
- }
+ editor.on('BeforePastePreProcess', function(e) {
+ var content = e.content, retainStyleProperties, validStyles;
- container = offsetNode;
- offset = 0;
- }
+ // Remove google docs internal guid markers
+ content = content.replace(/]+id="?docs-internal-[^>]*>/gi, '');
+ content = content.replace(/
/gi, '');
- bookmark[start ? 'startContainer' : 'endContainer'] = container;
- bookmark[start ? 'startOffset' : 'endOffset'] = offset;
+ retainStyleProperties = settings.paste_retain_style_properties;
+ if (retainStyleProperties) {
+ validStyles = Tools.makeMap(retainStyleProperties.split(/[, ]/));
}
- setupEndPoint(true);
-
- if (!rng.collapsed) {
- setupEndPoint();
- }
+ /**
+ * Converts fake bullet and numbered lists to real semantic OL/UL.
+ *
+ * @param {tinymce.html.Node} node Root node to convert children of.
+ */
+ function convertFakeListsToProperLists(node) {
+ var currentListNode, prevListNode, lastLevel = 1;
- return bookmark;
- }
+ function getText(node) {
+ var txt = '';
- /**
- * Moves the selection to the current bookmark and removes any selection container wrappers.
- *
- * @param {Object} bookmark Bookmark object to move selection to.
- */
- function moveToBookmark(bookmark) {
- function restoreEndPoint(start) {
- var container, offset, node;
+ if (node.type === 3) {
+ return node.value;
+ }
- function nodeIndex(container) {
- var node = container.parentNode.firstChild, idx = 0;
+ if ((node = node.firstChild)) {
+ do {
+ txt += getText(node);
+ } while ((node = node.next));
+ }
- while (node) {
- if (node == container) {
- return idx;
- }
+ return txt;
+ }
- // Skip data-mce-type=bookmark nodes
- if (node.nodeType != 1 || node.getAttribute('data-mce-type') != 'bookmark') {
- idx++;
+ function trimListStart(node, regExp) {
+ if (node.type === 3) {
+ if (regExp.test(node.value)) {
+ node.value = node.value.replace(regExp, '');
+ return false;
}
+ }
- node = node.nextSibling;
+ if ((node = node.firstChild)) {
+ do {
+ if (!trimListStart(node, regExp)) {
+ return false;
+ }
+ } while ((node = node.next));
}
- return -1;
+ return true;
}
- container = node = bookmark[start ? 'startContainer' : 'endContainer'];
- offset = bookmark[start ? 'startOffset' : 'endOffset'];
-
- if (!container) {
- return;
- }
+ function removeIgnoredNodes(node) {
+ if (node._listIgnore) {
+ node.remove();
+ return;
+ }
- if (container.nodeType == 1) {
- offset = nodeIndex(container);
- container = container.parentNode;
- dom.remove(node);
+ if ((node = node.firstChild)) {
+ do {
+ removeIgnoredNodes(node);
+ } while ((node = node.next));
+ }
}
- bookmark[start ? 'startContainer' : 'endContainer'] = container;
- bookmark[start ? 'startOffset' : 'endOffset'] = offset;
- }
-
- restoreEndPoint(true);
- restoreEndPoint();
+ function convertParagraphToLi(paragraphNode, listName, start) {
+ var level = paragraphNode._listLevel || lastLevel;
- var rng = dom.createRng();
+ // Handle list nesting
+ if (level != lastLevel) {
+ if (level < lastLevel) {
+ // Move to parent list
+ if (currentListNode) {
+ currentListNode = currentListNode.parent.parent;
+ }
+ } else {
+ // Create new list
+ prevListNode = currentListNode;
+ currentListNode = null;
+ }
+ }
- rng.setStart(bookmark.startContainer, bookmark.startOffset);
+ if (!currentListNode || currentListNode.name != listName) {
+ prevListNode = prevListNode || currentListNode;
+ currentListNode = new Node(listName, 1);
- if (bookmark.endContainer) {
- rng.setEnd(bookmark.endContainer, bookmark.endOffset);
- }
+ if (start > 1) {
+ currentListNode.attr('start', '' + start);
+ }
- selection.setRng(rng);
- }
+ paragraphNode.wrap(currentListNode);
+ } else {
+ currentListNode.append(paragraphNode);
+ }
- function createNewTextBlock(contentNode, blockName) {
- var node, textBlock, fragment = dom.createFragment(), hasContentNode;
- var blockElements = editor.schema.getBlockElements();
+ paragraphNode.name = 'li';
- if (editor.settings.forced_root_block) {
- blockName = blockName || editor.settings.forced_root_block;
- }
+ // Append list to previous list if it exists
+ if (level > lastLevel && prevListNode) {
+ prevListNode.lastChild.append(currentListNode);
+ }
- if (blockName) {
- textBlock = dom.create(blockName);
+ lastLevel = level;
- if (textBlock.tagName === editor.settings.forced_root_block) {
- dom.setAttribs(textBlock, editor.settings.forced_root_block_attrs);
+ // Remove start of list item "1. " or "· " etc
+ removeIgnoredNodes(paragraphNode);
+ trimListStart(paragraphNode, /^\u00a0+/);
+ trimListStart(paragraphNode, /^\s*([\u2022\u00b7\u00a7\u25CF]|\w+\.)/);
+ trimListStart(paragraphNode, /^\u00a0+/);
}
- fragment.appendChild(textBlock);
- }
-
- if (contentNode) {
- while ((node = contentNode.firstChild)) {
- var nodeName = node.nodeName;
-
- if (!hasContentNode && (nodeName != 'SPAN' || node.getAttribute('data-mce-type') != 'bookmark')) {
- hasContentNode = true;
- }
-
- if (blockElements[nodeName]) {
- fragment.appendChild(node);
- textBlock = null;
- } else {
- if (blockName) {
- if (!textBlock) {
- textBlock = dom.create(blockName);
- fragment.appendChild(textBlock);
- }
+ // Build a list of all root level elements before we start
+ // altering them in the loop below.
+ var elements = [], child = node.firstChild;
+ while (typeof child !== 'undefined' && child !== null) {
+ elements.push(child);
- textBlock.appendChild(node);
- } else {
- fragment.appendChild(node);
+ child = child.walk();
+ if (child !== null) {
+ while (typeof child !== 'undefined' && child.parent !== node) {
+ child = child.walk();
}
}
}
- }
-
- if (!editor.settings.forced_root_block) {
- fragment.appendChild(dom.create('br'));
- } else {
- // BR is needed in empty blocks on non IE browsers
- if (!hasContentNode && (!tinymce.Env.ie || tinymce.Env.ie > 10)) {
- textBlock.appendChild(dom.create('br', {'data-mce-bogus': '1'}));
- }
- }
- return fragment;
- }
+ for (var i = 0; i < elements.length; i++) {
+ node = elements[i];
- function getSelectedListItems() {
- return tinymce.grep(selection.getSelectedBlocks(), function(block) {
- return /^(LI|DT|DD)$/.test(block.nodeName);
- });
- }
+ if (node.name == 'p' && node.firstChild) {
+ // Find first text node in paragraph
+ var nodeText = getText(node);
- function splitList(ul, li, newBlock) {
- var tmpRng, fragment, bookmarks, node;
+ // Detect unordered lists look for bullets
+ if (isBulletList(nodeText)) {
+ convertParagraphToLi(node, 'ul');
+ continue;
+ }
- function removeAndKeepBookmarks(targetNode) {
- tinymce.each(bookmarks, function(node) {
- targetNode.parentNode.insertBefore(node, li.parentNode);
- });
+ // Detect ordered lists 1., a. or ixv.
+ if (isNumericList(nodeText)) {
+ // Parse OL start number
+ var matches = /([0-9]+)\./.exec(nodeText);
+ var start = 1;
+ if (matches) {
+ start = parseInt(matches[1], 10);
+ }
- dom.remove(targetNode);
- }
+ convertParagraphToLi(node, 'ol', start);
+ continue;
+ }
- bookmarks = dom.select('span[data-mce-type="bookmark"]', ul);
- newBlock = newBlock || createNewTextBlock(li);
- tmpRng = dom.createRng();
- tmpRng.setStartAfter(li);
- tmpRng.setEndAfter(ul);
- fragment = tmpRng.extractContents();
+ // Convert paragraphs marked as lists but doesn't look like anything
+ if (node._listLevel) {
+ convertParagraphToLi(node, 'ul', 1);
+ continue;
+ }
- for (node = fragment.firstChild; node; node = node.firstChild) {
- if (node.nodeName == 'LI' && dom.isEmpty(node)) {
- dom.remove(node);
- break;
+ currentListNode = null;
+ } else {
+ // If the root level element isn't a p tag which can be
+ // processed by convertParagraphToLi, it interrupts the
+ // lists, causing a new list to start instead of having
+ // elements from the next list inserted above this tag.
+ prevListNode = currentListNode;
+ currentListNode = null;
+ }
}
}
- if (!dom.isEmpty(fragment)) {
- dom.insertAfter(fragment, ul);
- }
-
- dom.insertAfter(newBlock, ul);
-
- if (dom.isEmpty(li.parentNode)) {
- removeAndKeepBookmarks(li.parentNode);
- }
-
- dom.remove(li);
+ function filterStyles(node, styleValue) {
+ var outputStyles = {}, matches, styles = editor.dom.parseStyle(styleValue);
- if (dom.isEmpty(ul)) {
- dom.remove(ul);
- }
- }
+ Tools.each(styles, function(value, name) {
+ // Convert various MS styles to W3C styles
+ switch (name) {
+ case 'mso-list':
+ // Parse out list indent level for lists
+ matches = /\w+ \w+([0-9]+)/i.exec(styleValue);
+ if (matches) {
+ node._listLevel = parseInt(matches[1], 10);
+ }
- function mergeWithAdjacentLists(listBlock) {
- var sibling, node;
+ // Remove these nodes o
+ // Since the span gets removed we mark the text node and the span
+ if (/Ignore/i.test(value) && node.firstChild) {
+ node._listIgnore = true;
+ node.firstChild._listIgnore = true;
+ }
- sibling = listBlock.nextSibling;
- if (sibling && isListNode(sibling) && sibling.nodeName == listBlock.nodeName) {
- while ((node = sibling.firstChild)) {
- listBlock.appendChild(node);
- }
+ break;
- dom.remove(sibling);
- }
+ case "horiz-align":
+ name = "text-align";
+ break;
- sibling = listBlock.previousSibling;
- if (sibling && isListNode(sibling) && sibling.nodeName == listBlock.nodeName) {
- while ((node = sibling.firstChild)) {
- listBlock.insertBefore(node, listBlock.firstChild);
- }
+ case "vert-align":
+ name = "vertical-align";
+ break;
- dom.remove(sibling);
- }
- }
+ case "font-color":
+ case "mso-foreground":
+ name = "color";
+ break;
- /**
- * Normalizes the all lists in the specified element.
- */
- function normalizeList(element) {
- tinymce.each(tinymce.grep(dom.select('ol,ul', element)), function(ul) {
- var sibling, parentNode = ul.parentNode;
+ case "mso-background":
+ case "mso-highlight":
+ name = "background";
+ break;
- // Move UL/OL to previous LI if it's the only child of a LI
- if (parentNode.nodeName == 'LI' && parentNode.firstChild == ul) {
- sibling = parentNode.previousSibling;
- if (sibling && sibling.nodeName == 'LI') {
- sibling.appendChild(ul);
+ case "font-weight":
+ case "font-style":
+ if (value != "normal") {
+ outputStyles[name] = value;
+ }
+ return;
- if (dom.isEmpty(parentNode)) {
- dom.remove(parentNode);
- }
- }
- }
+ case "mso-element":
+ // Remove track changes code
+ if (/^(comment|comment-list)$/i.test(value)) {
+ node.remove();
+ return;
+ }
- // Append OL/UL to previous LI if it's in a parent OL/UL i.e. old HTML4
- if (isListNode(parentNode)) {
- sibling = parentNode.previousSibling;
- if (sibling && sibling.nodeName == 'LI') {
- sibling.appendChild(ul);
+ break;
}
- }
- });
- }
- function outdent(li) {
- var ul = li.parentNode, ulParent = ul.parentNode, newBlock;
+ if (name.indexOf('mso-comment') === 0) {
+ node.remove();
+ return;
+ }
- function removeEmptyLi(li) {
- if (dom.isEmpty(li)) {
- dom.remove(li);
- }
- }
+ // Never allow mso- prefixed names
+ if (name.indexOf('mso-') === 0) {
+ return;
+ }
- if (li.nodeName == 'DD') {
- dom.rename(li, 'DT');
- return true;
- }
+ // Output only valid styles
+ if (retainStyleProperties == "all" || (validStyles && validStyles[name])) {
+ outputStyles[name] = value;
+ }
+ });
- if (isFirstChild(li) && isLastChild(li)) {
- if (ulParent.nodeName == "LI") {
- dom.insertAfter(li, ulParent);
- removeEmptyLi(ulParent);
- dom.remove(ul);
- } else if (isListNode(ulParent)) {
- dom.remove(ul, true);
- } else {
- ulParent.insertBefore(createNewTextBlock(li), ul);
- dom.remove(ul);
+ // Convert bold style to "b" element
+ if (/(bold)/i.test(outputStyles["font-weight"])) {
+ delete outputStyles["font-weight"];
+ node.wrap(new Node("b", 1));
}
- return true;
- } else if (isFirstChild(li)) {
- if (ulParent.nodeName == "LI") {
- dom.insertAfter(li, ulParent);
- li.appendChild(ul);
- removeEmptyLi(ulParent);
- } else if (isListNode(ulParent)) {
- ulParent.insertBefore(li, ul);
- } else {
- ulParent.insertBefore(createNewTextBlock(li), ul);
- dom.remove(li);
+ // Convert italic style to "i" element
+ if (/(italic)/i.test(outputStyles["font-style"])) {
+ delete outputStyles["font-style"];
+ node.wrap(new Node("i", 1));
}
- return true;
- } else if (isLastChild(li)) {
- if (ulParent.nodeName == "LI") {
- dom.insertAfter(li, ulParent);
- } else if (isListNode(ulParent)) {
- dom.insertAfter(li, ul);
- } else {
- dom.insertAfter(createNewTextBlock(li), ul);
- dom.remove(li);
+ // Serialize the styles and see if there is something left to keep
+ outputStyles = editor.dom.serializeStyle(outputStyles, node.name);
+ if (outputStyles) {
+ return outputStyles;
}
- return true;
+ return null;
}
- if (ulParent.nodeName == 'LI') {
- ul = ulParent;
- newBlock = createNewTextBlock(li, 'LI');
- } else if (isListNode(ulParent)) {
- newBlock = createNewTextBlock(li, 'LI');
- } else {
- newBlock = createNewTextBlock(li);
+ if (settings.paste_enable_default_filters === false) {
+ return;
}
- splitList(ul, li, newBlock);
- normalizeList(ul.parentNode);
+ // Detect is the contents is Word junk HTML
+ if (isWordContent(e.content)) {
+ e.wordContent = true; // Mark it for other processors
- return true;
- }
+ // Remove basic Word junk
+ content = Utils.filter(content, [
+ // Word comments like conditional comments etc
+ //gi,
- function indent(li) {
- var sibling, newList;
+ // Remove comments, scripts (e.g., msoShowComment), XML tag, VML content,
+ // MS Office namespaced tags, and a few other tags
+ /<(!|script[^>]*>.*?<\/script(?=[>\s])|\/?(\?xml(:\w+)?|img|meta|link|style|\w:\w+)(?=[\s\/>]))[^>]*>/gi,
- function mergeLists(from, to) {
- var node;
+ // Convert into for line-though
+ [/<(\/?)s>/gi, "<$1strike>"],
- if (isListNode(from)) {
- while ((node = li.lastChild.firstChild)) {
- to.appendChild(node);
- }
+ // Replace nsbp entites to char since it's easier to handle
+ [/ /gi, "\u00a0"],
- dom.remove(from);
- }
- }
+ // Convert ___ to string of alternating
+ // breaking/non-breaking spaces of same length
+ [/([\s\u00a0]*)<\/span>/gi,
+ function(str, spaces) {
+ return (spaces.length > 0) ?
+ spaces.replace(/./, " ").slice(Math.floor(spaces.length / 2)).split("").join("\u00a0") : "";
+ }
+ ]
+ ]);
- if (li.nodeName == 'DT') {
- dom.rename(li, 'DD');
- return true;
- }
+ var validElements = settings.paste_word_valid_elements;
+ if (!validElements) {
+ validElements = (
+ '-strong/b,-em/i,-u,-span,-p,-ol,-ul,-li,-h1,-h2,-h3,-h4,-h5,-h6,' +
+ '-p/div,-a[href|name],sub,sup,strike,br,del,table[width],tr,' +
+ 'td[colspan|rowspan|width],th[colspan|rowspan|width],thead,tfoot,tbody'
+ );
+ }
- sibling = li.previousSibling;
+ // Setup strict schema
+ var schema = new Schema({
+ valid_elements: validElements,
+ valid_children: '-li[p]'
+ });
- if (sibling && isListNode(sibling)) {
- sibling.appendChild(li);
- return true;
- }
+ // Add style/class attribute to all element rules since the user might have removed them from
+ // paste_word_valid_elements config option and we need to check them for properties
+ Tools.each(schema.elements, function(rule) {
+ /*eslint dot-notation:0*/
+ if (!rule.attributes["class"]) {
+ rule.attributes["class"] = {};
+ rule.attributesOrder.push("class");
+ }
- if (sibling && sibling.nodeName == 'LI' && isListNode(sibling.lastChild)) {
- sibling.lastChild.appendChild(li);
- mergeLists(li.lastChild, sibling.lastChild);
- return true;
- }
+ if (!rule.attributes.style) {
+ rule.attributes.style = {};
+ rule.attributesOrder.push("style");
+ }
+ });
- sibling = li.nextSibling;
+ // Parse HTML into DOM structure
+ var domParser = new DomParser({}, schema);
- if (sibling && isListNode(sibling)) {
- sibling.insertBefore(li, sibling.firstChild);
- return true;
- }
+ // Filter styles to remove "mso" specific styles and convert some of them
+ domParser.addAttributeFilter('style', function(nodes) {
+ var i = nodes.length, node;
- if (sibling && sibling.nodeName == 'LI' && isListNode(li.lastChild)) {
- return false;
- }
+ while (i--) {
+ node = nodes[i];
+ node.attr('style', filterStyles(node, node.attr('style')));
- sibling = li.previousSibling;
- if (sibling && sibling.nodeName == 'LI') {
- newList = dom.create(li.parentNode.nodeName);
- sibling.appendChild(newList);
- newList.appendChild(li);
- mergeLists(li.lastChild, newList);
- return true;
- }
+ // Remove pointess spans
+ if (node.name == 'span' && node.parent && !node.attributes.length) {
+ node.unwrap();
+ }
+ }
+ });
- return false;
- }
+ // Check the class attribute for comments or del items and remove those
+ domParser.addAttributeFilter('class', function(nodes) {
+ var i = nodes.length, node, className;
- function indentSelection() {
- var listElements = getSelectedListItems();
+ while (i--) {
+ node = nodes[i];
- if (listElements.length) {
- var bookmark = createBookmark(selection.getRng(true));
+ className = node.attr('class');
+ if (/^(MsoCommentReference|MsoCommentText|msoDel)$/i.test(className)) {
+ node.remove();
+ }
- for (var i = 0; i < listElements.length; i++) {
- if (!indent(listElements[i]) && i === 0) {
- break;
+ node.attr('class', null);
}
- }
+ });
- moveToBookmark(bookmark);
- editor.nodeChanged();
+ // Remove all del elements since we don't want the track changes code in the editor
+ domParser.addNodeFilter('del', function(nodes) {
+ var i = nodes.length;
- return true;
- }
- }
+ while (i--) {
+ nodes[i].remove();
+ }
+ });
- function outdentSelection() {
- var listElements = getSelectedListItems();
+ // Keep some of the links and anchors
+ domParser.addNodeFilter('a', function(nodes) {
+ var i = nodes.length, node, href, name;
- if (listElements.length) {
- var bookmark = createBookmark(selection.getRng(true));
- var i, y, root = editor.getBody();
+ while (i--) {
+ node = nodes[i];
+ href = node.attr('href');
+ name = node.attr('name');
- i = listElements.length;
- while (i--) {
- var node = listElements[i].parentNode;
+ if (href && href.indexOf('#_msocom_') != -1) {
+ node.remove();
+ continue;
+ }
- while (node && node != root) {
- y = listElements.length;
- while (y--) {
- if (listElements[y] === node) {
- listElements.splice(i, 1);
- break;
+ if (href && href.indexOf('file://') === 0) {
+ href = href.split('#')[1];
+ if (href) {
+ href = '#' + href;
}
}
- node = node.parentNode;
- }
- }
+ if (!href && !name) {
+ node.unwrap();
+ } else {
+ // Remove all named anchors that aren't specific to TOC, Footnotes or Endnotes
+ if (name && !/^_?(?:toc|edn|ftn)/i.test(name)) {
+ node.unwrap();
+ continue;
+ }
- for (i = 0; i < listElements.length; i++) {
- if (!outdent(listElements[i]) && i === 0) {
- break;
+ node.attr({
+ href: href,
+ name: name
+ });
+ }
}
- }
+ });
- moveToBookmark(bookmark);
- editor.nodeChanged();
+ // Parse into DOM structure
+ var rootNode = domParser.parse(content);
- return true;
+ // Process DOM
+ if (settings.paste_convert_word_fake_lists !== false) {
+ convertFakeListsToProperLists(rootNode);
+ }
+
+ // Serialize DOM back to HTML
+ e.content = new Serializer({}, schema).serialize(rootNode);
}
- }
+ });
+ }
- function applyList(listName) {
- var rng = selection.getRng(true), bookmark = createBookmark(rng), listItemName = 'LI';
+ WordFilter.isWordContent = isWordContent;
- listName = listName.toUpperCase();
+ return WordFilter;
+});
- if (listName == 'DL') {
- listItemName = 'DT';
+// Included from: js/tinymce/plugins/paste/classes/Quirks.js
+
+/**
+ * Quirks.js
+ *
+ * Released under LGPL License.
+ * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
+ *
+ * License: http://www.tinymce.com/license
+ * Contributing: http://www.tinymce.com/contributing
+ */
+
+/**
+ * This class contains various fixes for browsers. These issues can not be feature
+ * detected since we have no direct control over the clipboard. However we might be able
+ * to remove some of these fixes once the browsers gets updated/fixed.
+ *
+ * @class tinymce.pasteplugin.Quirks
+ * @private
+ */
+define("tinymce/pasteplugin/Quirks", [
+ "tinymce/Env",
+ "tinymce/util/Tools",
+ "tinymce/pasteplugin/WordFilter",
+ "tinymce/pasteplugin/Utils"
+], function(Env, Tools, WordFilter, Utils) {
+ "use strict";
+
+ return function(editor) {
+ function addPreProcessFilter(filterFunc) {
+ editor.on('BeforePastePreProcess', function(e) {
+ e.content = filterFunc(e.content);
+ });
+ }
+
+ /**
+ * Removes BR elements after block elements. IE9 has a nasty bug where it puts a BR element after each
+ * block element when pasting from word. This removes those elements.
+ *
+ * This:
+ * a
b
+ *
+ * Becomes:
+ * a
b
+ */
+ function removeExplorerBrElementsAfterBlocks(html) {
+ // Only filter word specific content
+ if (!WordFilter.isWordContent(html)) {
+ return html;
}
- function getSelectedTextBlocks() {
- var textBlocks = [], root = editor.getBody();
+ // Produce block regexp based on the block elements in schema
+ var blockElements = [];
- function getEndPointNode(start) {
- var container, offset;
+ Tools.each(editor.schema.getBlockElements(), function(block, blockName) {
+ blockElements.push(blockName);
+ });
- container = rng[start ? 'startContainer' : 'endContainer'];
- offset = rng[start ? 'startOffset' : 'endOffset'];
+ var explorerBlocksRegExp = new RegExp(
+ '(?:
[\\s\\r\\n]+|
)*(<\\/?(' + blockElements.join('|') + ')[^>]*>)(?:
[\\s\\r\\n]+|
)*',
+ 'g'
+ );
- // Resolve node index
- if (container.nodeType == 1) {
- container = container.childNodes[Math.min(offset, container.childNodes.length - 1)] || container;
- }
+ // Remove BR:s from: X
+ html = Utils.filter(html, [
+ [explorerBlocksRegExp, '$1']
+ ]);
- while (container.parentNode != root) {
- if (isTextBlock(container)) {
- return container;
- }
+ // IE9 also adds an extra BR element for each soft-linefeed and it also adds a BR for each word wrap break
+ html = Utils.filter(html, [
+ [/
/g, '
'], // Replace multiple BR elements with uppercase BR to keep them intact
+ [/
/g, ' '], // Replace single br elements with space since they are word wrap BR:s
+ [/
/g, '
'] // Replace back the double brs but into a single BR
+ ]);
- if (/^(TD|TH)$/.test(container.parentNode.nodeName)) {
- return container;
- }
+ return html;
+ }
- container = container.parentNode;
- }
+ /**
+ * WebKit has a nasty bug where the all computed styles gets added to style attributes when copy/pasting contents.
+ * This fix solves that by simply removing the whole style attribute.
+ *
+ * The paste_webkit_styles option can be set to specify what to keep:
+ * paste_webkit_styles: "none" // Keep no styles
+ * paste_webkit_styles: "all", // Keep all of them
+ * paste_webkit_styles: "font-weight color" // Keep specific ones
+ *
+ * @param {String} content Content that needs to be processed.
+ * @return {String} Processed contents.
+ */
+ function removeWebKitStyles(content) {
+ // Passthrough all styles from Word and let the WordFilter handle that junk
+ if (WordFilter.isWordContent(content)) {
+ return content;
+ }
- return container;
- }
+ // Filter away styles that isn't matching the target node
+ var webKitStyles = editor.settings.paste_webkit_styles;
- var startNode = getEndPointNode(true);
- var endNode = getEndPointNode();
- var block, siblings = [];
+ if (editor.settings.paste_remove_styles_if_webkit === false || webKitStyles == "all") {
+ return content;
+ }
- for (var node = startNode; node; node = node.nextSibling) {
- siblings.push(node);
+ if (webKitStyles) {
+ webKitStyles = webKitStyles.split(/[, ]/);
+ }
- if (node == endNode) {
- break;
- }
- }
+ // Keep specific styles that doesn't match the current node computed style
+ if (webKitStyles) {
+ var dom = editor.dom, node = editor.selection.getNode();
- tinymce.each(siblings, function(node) {
- if (isTextBlock(node)) {
- textBlocks.push(node);
- block = null;
- return;
+ content = content.replace(/(<[^>]+) style="([^"]*)"([^>]*>)/gi, function(all, before, value, after) {
+ var inputStyles = dom.parseStyle(value, 'span'), outputStyles = {};
+
+ if (webKitStyles === "none") {
+ return before + after;
}
- if (dom.isBlock(node) || node.nodeName == 'BR') {
- if (node.nodeName == 'BR') {
- dom.remove(node);
- }
+ for (var i = 0; i < webKitStyles.length; i++) {
+ var inputValue = inputStyles[webKitStyles[i]], currentValue = dom.getStyle(node, webKitStyles[i], true);
- block = null;
- return;
- }
+ if (/color/.test(webKitStyles[i])) {
+ inputValue = dom.toHex(inputValue);
+ currentValue = dom.toHex(currentValue);
+ }
- var nextSibling = node.nextSibling;
- if (tinymce.dom.BookmarkManager.isBookmarkNode(node)) {
- if (isTextBlock(nextSibling) || (!nextSibling && node.parentNode == root)) {
- block = null;
- return;
+ if (currentValue != inputValue) {
+ outputStyles[webKitStyles[i]] = inputValue;
}
}
- if (!block) {
- block = dom.create('p');
- node.parentNode.insertBefore(block, node);
- textBlocks.push(block);
+ outputStyles = dom.serializeStyle(outputStyles, 'span');
+ if (outputStyles) {
+ return before + ' style="' + outputStyles + '"' + after;
}
- block.appendChild(node);
+ return before + after;
});
-
- return textBlocks;
+ } else {
+ // Remove all external styles
+ content = content.replace(/(<[^>]+) style="([^"]*)"([^>]*>)/gi, '$1$3');
}
- tinymce.each(getSelectedTextBlocks(), function(block) {
- var listBlock, sibling;
+ // Keep internal styles
+ content = content.replace(/(<[^>]+) data-mce-style="([^"]+)"([^>]*>)/gi, function(all, before, value, after) {
+ return before + ' style="' + value + '"' + after;
+ });
- sibling = block.previousSibling;
- if (sibling && isListNode(sibling) && sibling.nodeName == listName) {
- listBlock = sibling;
- block = dom.rename(block, listItemName);
- sibling.appendChild(block);
- } else {
- listBlock = dom.create(listName);
- block.parentNode.insertBefore(listBlock, block);
- listBlock.appendChild(block);
- block = dom.rename(block, listItemName);
- }
+ return content;
+ }
- mergeWithAdjacentLists(listBlock);
- });
+ // Sniff browsers and apply fixes since we can't feature detect
+ if (Env.webkit) {
+ addPreProcessFilter(removeWebKitStyles);
+ }
- moveToBookmark(bookmark);
+ if (Env.ie) {
+ addPreProcessFilter(removeExplorerBrElementsAfterBlocks);
}
+ };
+});
- function removeList() {
- var bookmark = createBookmark(selection.getRng(true)), root = editor.getBody();
+// Included from: js/tinymce/plugins/paste/classes/Plugin.js
- tinymce.each(getSelectedListItems(), function(li) {
- var node, rootList;
+/**
+ * Plugin.js
+ *
+ * Released under LGPL License.
+ * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
+ *
+ * License: http://www.tinymce.com/license
+ * Contributing: http://www.tinymce.com/contributing
+ */
- if (dom.isEmpty(li)) {
- outdent(li);
- return;
- }
+/**
+ * This class contains the tinymce plugin logic for the paste plugin.
+ *
+ * @class tinymce.pasteplugin.Plugin
+ * @private
+ */
+define("tinymce/pasteplugin/Plugin", [
+ "tinymce/PluginManager",
+ "tinymce/pasteplugin/Clipboard",
+ "tinymce/pasteplugin/WordFilter",
+ "tinymce/pasteplugin/Quirks"
+], function(PluginManager, Clipboard, WordFilter, Quirks) {
+ var userIsInformed;
- for (node = li; node && node != root; node = node.parentNode) {
- if (isListNode(node)) {
- rootList = node;
- }
+ PluginManager.add('paste', function(editor) {
+ var self = this, clipboard, settings = editor.settings;
+
+ function togglePlainTextPaste() {
+ if (clipboard.pasteFormat == "text") {
+ this.active(false);
+ clipboard.pasteFormat = "html";
+ } else {
+ clipboard.pasteFormat = "text";
+ this.active(true);
+
+ if (!userIsInformed) {
+ editor.windowManager.alert(
+ 'Paste is now in plain text mode. Contents will now ' +
+ 'be pasted as plain text until you toggle this option off.'
+ );
+
+ userIsInformed = true;
}
+ }
+ }
- splitList(rootList, li);
+ self.clipboard = clipboard = new Clipboard(editor);
+ self.quirks = new Quirks(editor);
+ self.wordFilter = new WordFilter(editor);
+
+ if (editor.settings.paste_as_text) {
+ self.clipboard.pasteFormat = "text";
+ }
+
+ if (settings.paste_preprocess) {
+ editor.on('PastePreProcess', function(e) {
+ settings.paste_preprocess.call(self, self, e);
});
+ }
- moveToBookmark(bookmark);
+ if (settings.paste_postprocess) {
+ editor.on('PastePostProcess', function(e) {
+ settings.paste_postprocess.call(self, self, e);
+ });
}
- function toggleList(listName) {
- var parentList = dom.getParent(selection.getStart(), 'OL,UL,DL');
+ editor.addCommand('mceInsertClipboardContent', function(ui, value) {
+ if (value.content) {
+ self.clipboard.pasteHtml(value.content);
+ }
- if (parentList) {
- if (parentList.nodeName == listName) {
- removeList(listName);
- } else {
- var bookmark = createBookmark(selection.getRng(true));
- mergeWithAdjacentLists(dom.rename(parentList, listName));
- moveToBookmark(bookmark);
- }
- } else {
- applyList(listName);
+ if (value.text) {
+ self.clipboard.pasteText(value.text);
}
+ });
+
+ // Block all drag/drop events
+ if (editor.paste_block_drop) {
+ editor.on('dragend dragover draggesture dragdrop drop drag', function(e) {
+ e.preventDefault();
+ e.stopPropagation();
+ });
}
- function queryListCommandState(listName) {
- return function() {
- var parentList = dom.getParent(editor.selection.getStart(), 'UL,OL,DL');
+ // Prevent users from dropping data images on Gecko
+ if (!editor.settings.paste_data_images) {
+ editor.on('drop', function(e) {
+ var dataTransfer = e.dataTransfer;
- return parentList && parentList.nodeName == listName;
- };
+ if (dataTransfer && dataTransfer.files && dataTransfer.files.length > 0) {
+ e.preventDefault();
+ }
+ });
}
- self.backspaceDelete = function(isForward) {
- function findNextCaretContainer(rng, isForward) {
- var node = rng.startContainer, offset = rng.startOffset;
- var nonEmptyBlocks, walker;
+ editor.addButton('pastetext', {
+ icon: 'pastetext',
+ tooltip: 'Paste as text',
+ onclick: togglePlainTextPaste,
+ active: self.clipboard.pasteFormat == "text"
+ });
- if (node.nodeType == 3 && (isForward ? offset < node.data.length : offset > 0)) {
- return node;
- }
+ editor.addMenuItem('pastetext', {
+ text: 'Paste as text',
+ selectable: true,
+ active: clipboard.pasteFormat,
+ onclick: togglePlainTextPaste
+ });
+ });
+});
- nonEmptyBlocks = editor.schema.getNonEmptyElements();
- walker = new tinymce.dom.TreeWalker(rng.startContainer);
+expose(["tinymce/pasteplugin/Utils"]);
+})(this);
- while ((node = walker[isForward ? 'next' : 'prev']())) {
- if (node.nodeName == 'LI' && !node.hasChildNodes()) {
- return node;
- }
+ }).apply(root, arguments);
+});
+}(this));
- if (nonEmptyBlocks[node.nodeName]) {
- return node;
- }
+(function(root) {
+define("tinymce-preview", ["tinymce"], function() {
+ return (function() {
+/**
+ * plugin.js
+ *
+ * Released under LGPL License.
+ * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
+ *
+ * License: http://www.tinymce.com/license
+ * Contributing: http://www.tinymce.com/contributing
+ */
- if (node.nodeType == 3 && node.data.length > 0) {
- return node;
- }
- }
- }
+/*global tinymce:true */
- function mergeLiElements(fromElm, toElm) {
- var node, listNode, ul = fromElm.parentNode;
+tinymce.PluginManager.add('preview', function(editor) {
+ var settings = editor.settings, sandbox = !tinymce.Env.ie;
- if (isListNode(toElm.lastChild)) {
- listNode = toElm.lastChild;
+ editor.addCommand('mcePreview', function() {
+ editor.windowManager.open({
+ title: 'Preview',
+ width: parseInt(editor.getParam("plugin_preview_width", "650"), 10),
+ height: parseInt(editor.getParam("plugin_preview_height", "500"), 10),
+ html: '',
+ buttons: {
+ text: 'Close',
+ onclick: function() {
+ this.parent().parent().close();
}
+ },
+ onPostRender: function() {
+ var previewHtml, headHtml = '';
- node = toElm.lastChild;
- if (node && node.nodeName == 'BR' && fromElm.hasChildNodes()) {
- dom.remove(node);
- }
+ headHtml += '';
- if (dom.isEmpty(toElm)) {
- dom.$(toElm).empty();
- }
+ tinymce.each(editor.contentCSS, function(url) {
+ headHtml += '';
+ });
- if (!dom.isEmpty(fromElm)) {
- while ((node = fromElm.firstChild)) {
- toElm.appendChild(node);
- }
+ var bodyId = settings.body_id || 'tinymce';
+ if (bodyId.indexOf('=') != -1) {
+ bodyId = editor.getParam('body_id', '', 'hash');
+ bodyId = bodyId[editor.id] || bodyId;
}
- if (listNode) {
- toElm.appendChild(listNode);
+ var bodyClass = settings.body_class || '';
+ if (bodyClass.indexOf('=') != -1) {
+ bodyClass = editor.getParam('body_class', '', 'hash');
+ bodyClass = bodyClass[editor.id] || '';
}
- dom.remove(fromElm);
+ var dirAttr = editor.settings.directionality ? ' dir="' + editor.settings.directionality + '"' : '';
- if (dom.isEmpty(ul)) {
- dom.remove(ul);
+ previewHtml = (
+ '' +
+ '' +
+ '' +
+ headHtml +
+ '' +
+ '' +
+ editor.getContent() +
+ '' +
+ ''
+ );
+
+ if (!sandbox) {
+ // IE 6-11 doesn't support data uris on iframes
+ // so I guess they will have to be less secure since we can't sandbox on those
+ // TODO: Use sandbox if future versions of IE supports iframes with data: uris.
+ var doc = this.getEl('body').firstChild.contentWindow.document;
+ doc.open();
+ doc.write(previewHtml);
+ doc.close();
+ } else {
+ this.getEl('body').firstChild.src = 'data:text/html;charset=utf-8,' + encodeURIComponent(previewHtml);
}
}
+ });
+ });
- if (selection.isCollapsed()) {
- var li = dom.getParent(selection.getStart(), 'LI');
+ editor.addButton('preview', {
+ title: 'Preview',
+ cmd: 'mcePreview'
+ });
- if (li) {
- var rng = selection.getRng(true);
- var otherLi = dom.getParent(findNextCaretContainer(rng, isForward), 'LI');
+ editor.addMenuItem('preview', {
+ text: 'Preview',
+ cmd: 'mcePreview',
+ context: 'view'
+ });
+});
- if (otherLi && otherLi != li) {
- var bookmark = createBookmark(rng);
- if (isForward) {
- mergeLiElements(otherLi, li);
- } else {
- mergeLiElements(li, otherLi);
- }
+ }).apply(root, arguments);
+});
+}(this));
- moveToBookmark(bookmark);
+(function(root) {
+define("tinymce-print", ["tinymce"], function() {
+ return (function() {
+/**
+ * plugin.js
+ *
+ * Released under LGPL License.
+ * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
+ *
+ * License: http://www.tinymce.com/license
+ * Contributing: http://www.tinymce.com/contributing
+ */
- return true;
- } else if (!otherLi) {
- if (!isForward && removeList(li.parentNode.nodeName)) {
- return true;
- }
- }
- }
- }
- };
+/*global tinymce:true */
- editor.on('BeforeExecCommand', function(e) {
- var cmd = e.command.toLowerCase(), isHandled;
+tinymce.PluginManager.add('print', function(editor) {
+ editor.addCommand('mcePrint', function() {
+ editor.getWin().print();
+ });
- if (cmd == "indent") {
- if (indentSelection()) {
- isHandled = true;
- }
- } else if (cmd == "outdent") {
- if (outdentSelection()) {
- isHandled = true;
- }
- }
+ editor.addButton('print', {
+ title: 'Print',
+ cmd: 'mcePrint'
+ });
- if (isHandled) {
- editor.fire('ExecCommand', {command: e.command});
- e.preventDefault();
- return true;
- }
- });
+ editor.addShortcut('Meta+P', '', 'mcePrint');
- editor.addCommand('InsertUnorderedList', function() {
- toggleList('UL');
- });
+ editor.addMenuItem('print', {
+ text: 'Print',
+ cmd: 'mcePrint',
+ icon: 'print',
+ shortcut: 'Meta+P',
+ context: 'file'
+ });
+});
- editor.addCommand('InsertOrderedList', function() {
- toggleList('OL');
- });
- editor.addCommand('InsertDefinitionList', function() {
- toggleList('DL');
- });
+ }).apply(root, arguments);
+});
+}(this));
- editor.addQueryStateHandler('InsertUnorderedList', queryListCommandState('UL'));
- editor.addQueryStateHandler('InsertOrderedList', queryListCommandState('OL'));
- editor.addQueryStateHandler('InsertDefinitionList', queryListCommandState('DL'));
+(function(root) {
+define("tinymce-save", ["tinymce"], function() {
+ return (function() {
+/**
+ * plugin.js
+ *
+ * Released under LGPL License.
+ * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
+ *
+ * License: http://www.tinymce.com/license
+ * Contributing: http://www.tinymce.com/contributing
+ */
- editor.on('keydown', function(e) {
- // Check for tab but not ctrl/cmd+tab since it switches browser tabs
- if (e.keyCode != 9 || tinymce.util.VK.metaKeyPressed(e)) {
- return;
+/*global tinymce:true */
+
+tinymce.PluginManager.add('save', function(editor) {
+ function save() {
+ var formObj;
+
+ formObj = tinymce.DOM.getParent(editor.id, 'form');
+
+ if (editor.getParam("save_enablewhendirty", true) && !editor.isDirty()) {
+ return;
+ }
+
+ tinymce.triggerSave();
+
+ // Use callback instead
+ if (editor.getParam("save_onsavecallback")) {
+ if (editor.execCallback('save_onsavecallback', editor)) {
+ editor.startContent = tinymce.trim(editor.getContent({format: 'raw'}));
+ editor.nodeChanged();
}
- if (editor.dom.getParent(editor.selection.getStart(), 'LI,DT,DD')) {
- e.preventDefault();
+ return;
+ }
- if (e.shiftKey) {
- outdentSelection();
+ if (formObj) {
+ editor.isNotDirty = true;
+
+ if (!formObj.onsubmit || formObj.onsubmit()) {
+ if (typeof formObj.submit == "function") {
+ formObj.submit();
} else {
- indentSelection();
+ editor.windowManager.alert("Error: Form submit field collision.");
}
}
- });
- });
- editor.addButton('indent', {
- icon: 'indent',
- title: 'Increase indent',
- cmd: 'Indent',
- onPostRender: function() {
- var ctrl = this;
+ editor.nodeChanged();
+ } else {
+ editor.windowManager.alert("Error: No form element found.");
+ }
+ }
- editor.on('nodechange', function() {
- var blocks = editor.selection.getSelectedBlocks();
- var disable = false;
+ function cancel() {
+ var h = tinymce.trim(editor.startContent);
- for (var i = 0, l = blocks.length; !disable && i < l; i++) {
- var tag = blocks[i].nodeName;
+ // Use callback instead
+ if (editor.getParam("save_oncancelcallback")) {
+ editor.execCallback('save_oncancelcallback', editor);
+ return;
+ }
- disable = (tag == 'LI' && isFirstChild(blocks[i]) || tag == 'UL' || tag == 'OL' || tag == 'DD');
- }
+ editor.setContent(h);
+ editor.undoManager.clear();
+ editor.nodeChanged();
+ }
- ctrl.disabled(disable);
- });
- }
+ function stateToggle() {
+ var self = this;
+
+ editor.on('nodeChange', function() {
+ self.disabled(editor.getParam("save_enablewhendirty", true) && !editor.isDirty());
+ });
+ }
+
+ editor.addCommand('mceSave', save);
+ editor.addCommand('mceCancel', cancel);
+
+ editor.addButton('save', {
+ icon: 'save',
+ text: 'Save',
+ cmd: 'mceSave',
+ disabled: true,
+ onPostRender: stateToggle
});
- editor.on('keydown', function(e) {
- if (e.keyCode == tinymce.util.VK.BACKSPACE) {
- if (self.backspaceDelete()) {
- e.preventDefault();
- }
- } else if (e.keyCode == tinymce.util.VK.DELETE) {
- if (self.backspaceDelete(true)) {
- e.preventDefault();
- }
- }
+ editor.addButton('cancel', {
+ text: 'Cancel',
+ icon: false,
+ cmd: 'mceCancel',
+ disabled: true,
+ onPostRender: stateToggle
});
+
+ editor.addShortcut('Meta+S', '', 'mceSave');
});
@@ -53226,7 +69063,7 @@ tinymce.PluginManager.add('lists', function(editor) {
}(this));
(function(root) {
-define("tinymce-media", ["tinymce"], function() {
+define("tinymce-searchreplace", ["tinymce"], function() {
return (function() {
/**
* plugin.js
@@ -53238,800 +69075,708 @@ define("tinymce-media", ["tinymce"], function() {
* Contributing: http://www.tinymce.com/contributing
*/
-/*jshint maxlen:255 */
-/*eslint max-len:0 */
+/*jshint smarttabs:true, undef:true, unused:true, latedef:true, curly:true, bitwise:true */
+/*eslint no-labels:0, no-constant-condition: 0 */
/*global tinymce:true */
-tinymce.PluginManager.add('media', function(editor, url) {
- var urlPatterns = [
- {regex: /youtu\.be\/([\w\-.]+)/, type: 'iframe', w: 425, h: 350, url: '//www.youtube.com/embed/$1', allowFullscreen: true},
- {regex: /youtube\.com(.+)v=([^&]+)/, type: 'iframe', w: 425, h: 350, url: '//www.youtube.com/embed/$2', allowFullscreen: true},
- {regex: /vimeo\.com\/([0-9]+)/, type: 'iframe', w: 425, h: 350, url: '//player.vimeo.com/video/$1?title=0&byline=0&portrait=0&color=8dc7dc', allowfullscreen: true},
- {regex: /vimeo\.com\/(.*)\/([0-9]+)/, type: "iframe", w: 425, h: 350, url: "//player.vimeo.com/video/$2?title=0&byline=0", allowfullscreen: true},
- {regex: /maps\.google\.([a-z]{2,3})\/maps\/(.+)msid=(.+)/, type: 'iframe', w: 425, h: 350, url: '//maps.google.com/maps/ms?msid=$2&output=embed"', allowFullscreen: false}
- ];
+(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 embedChange = (tinymce.Env.ie && tinymce.Env.ie <= 8) ? 'onChange' : 'onInput';
+ var index = m.index;
- function guessMime(url) {
- url = url.toLowerCase();
+ if (captureGroup > 0) {
+ var cg = m[captureGroup];
- if (url.indexOf('.mp3') != -1) {
- return 'audio/mpeg';
- }
+ if (!cg) {
+ throw 'Invalid capture group';
+ }
- if (url.indexOf('.wav') != -1) {
- return 'audio/wav';
- }
+ index += m[0].indexOf(cg);
+ m[0] = cg;
+ }
- if (url.indexOf('.mp4') != -1) {
- return 'video/mp4';
+ return [index, index + m[0].length, [m[0]]];
}
- if (url.indexOf('.webm') != -1) {
- return 'video/webm';
- }
+ function getText(node) {
+ var txt;
- if (url.indexOf('.ogg') != -1) {
- return 'video/ogg';
- }
+ if (node.nodeType === 3) {
+ return node.data;
+ }
- if (url.indexOf('.swf') != -1) {
- return 'application/x-shockwave-flash';
- }
+ if (hiddenTextElementsMap[node.nodeName] && !blockElementsMap[node.nodeName]) {
+ return '';
+ }
- return '';
- }
+ txt = '';
- function getVideoScriptMatch(src) {
- var prefixes = editor.settings.media_scripts;
+ if (blockElementsMap[node.nodeName] || shortEndedElementsMap[node.nodeName]) {
+ txt += '\n';
+ }
- if (prefixes) {
- for (var i = 0; i < prefixes.length; i++) {
- if (src.indexOf(prefixes[i].filter) !== -1) {
- return prefixes[i];
- }
+ if ((node = node.firstChild)) {
+ do {
+ txt += getText(node);
+ } while ((node = node.nextSibling));
}
+
+ return txt;
}
- }
- function showDialog() {
- var win, width, height, data;
+ function stepThroughMatches(node, matches, replaceFn) {
+ var startNode, endNode, startNodeIndex,
+ endNodeIndex, innerNodes = [], atIndex = 0, curNode = node,
+ matchLocation = matches.shift(), matchIndex = 0;
- var generalFormItems = [
- {
- name: 'source1',
- type: 'filepicker',
- filetype: 'media',
- size: 40,
- autofocus: true,
- label: 'Source',
- onchange: function(e) {
- tinymce.each(e.meta, function(value, key) {
- win.find('#' + key).value(value);
- });
+ out: while (true) {
+ if (blockElementsMap[curNode.nodeName] || shortEndedElementsMap[curNode.nodeName]) {
+ atIndex++;
}
- }
- ];
- function recalcSize(e) {
- var widthCtrl, heightCtrl, newWidth, newHeight;
+ 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);
+ }
- widthCtrl = win.find('#width')[0];
- heightCtrl = win.find('#height')[0];
+ if (!startNode && curNode.length + atIndex > matchLocation[0]) {
+ // We've found the match start
+ startNode = curNode;
+ startNodeIndex = matchLocation[0] - atIndex;
+ }
- newWidth = widthCtrl.value();
- newHeight = heightCtrl.value();
+ atIndex += curNode.length;
+ }
- if (win.find('#constrain')[0].checked() && width && height && newWidth && newHeight) {
- if (e.control == widthCtrl) {
- newHeight = Math.round((newWidth / width) * newHeight);
+ if (startNode && endNode) {
+ curNode = replaceFn({
+ startNode: startNode,
+ startNodeIndex: startNodeIndex,
+ endNode: endNode,
+ endNodeIndex: endNodeIndex,
+ innerNodes: innerNodes,
+ match: matchLocation[2],
+ matchIndex: matchIndex
+ });
- if (!isNaN(newHeight)) {
- heightCtrl.value(newHeight);
+ // 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 {
- newWidth = Math.round((newHeight / height) * newWidth);
+ } 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;
+ }
- if (!isNaN(newWidth)) {
- widthCtrl.value(newWidth);
+ // Move forward or up:
+ while (true) {
+ if (curNode.nextSibling) {
+ curNode = curNode.nextSibling;
+ break;
+ } else if (curNode.parentNode !== node) {
+ curNode = curNode.parentNode;
+ } else {
+ break out;
}
}
}
-
- width = newWidth;
- height = newHeight;
}
- if (editor.settings.media_alt_source !== false) {
- generalFormItems.push({name: 'source2', type: 'filepicker', filetype: 'media', size: 40, label: 'Alternative source'});
- }
+ /**
+ * Generates the actual replaceFn which splits up text nodes
+ * and inserts the replacement element.
+ */
+ function genReplacer(nodeName) {
+ var makeReplacementNode;
- if (editor.settings.media_poster !== false) {
- generalFormItems.push({name: 'poster', type: 'filepicker', filetype: 'image', size: 40, label: 'Poster'});
- }
+ if (typeof nodeName != 'function') {
+ var stencilNode = nodeName.nodeType ? nodeName : doc.createElement(nodeName);
- if (editor.settings.media_dimensions !== false) {
- generalFormItems.push({
- type: 'container',
- label: 'Dimensions',
- layout: 'flex',
- align: 'center',
- spacing: 5,
- items: [
- {name: 'width', type: 'textbox', maxLength: 5, size: 3, onchange: recalcSize, ariaLabel: 'Width'},
- {type: 'label', text: 'x'},
- {name: 'height', type: 'textbox', maxLength: 5, size: 3, onchange: recalcSize, ariaLabel: 'Height'},
- {name: 'constrain', type: 'checkbox', checked: true, text: 'Constrain proportions'}
- ]
- });
- }
+ makeReplacementNode = function(fill, matchIndex) {
+ var clone = stencilNode.cloneNode(false);
- data = getData(editor.selection.getNode());
- width = data.width;
- height = data.height;
+ clone.setAttribute('data-mce-index', matchIndex);
- var embedTextBox = {
- id: 'mcemediasource',
- type: 'textbox',
- flex: 1,
- name: 'embed',
- value: getSource(),
- multiline: true,
- label: 'Source'
- };
+ if (fill) {
+ clone.appendChild(doc.createTextNode(fill));
+ }
- function updateValueOnChange() {
- data = htmlToData(this.value());
- this.parent().parent().fromJSON(data);
- }
+ return clone;
+ };
+ } else {
+ makeReplacementNode = nodeName;
+ }
- embedTextBox[embedChange] = updateValueOnChange;
+ return function(range) {
+ var before, after, parentNode, startNode = range.startNode,
+ endNode = range.endNode, matchIndex = range.matchIndex;
- win = editor.windowManager.open({
- title: 'Insert/edit video',
- data: data,
- bodyType: 'tabpanel',
- body: [
- {
- title: 'General',
- type: "form",
- onShowTab: function() {
- data = htmlToData(this.next().find('#embed').value());
- this.fromJSON(data);
- },
- items: generalFormItems
- },
+ if (startNode === endNode) {
+ var node = startNode;
- {
- title: 'Embed',
- type: "container",
- layout: 'flex',
- direction: 'column',
- align: 'stretch',
- padding: 10,
- spacing: 10,
- onShowTab: function() {
- this.find('#embed').value(dataToHtml(this.parent().toJSON()));
- },
- items: [
- {
- type: 'label',
- text: 'Paste your embed code below:',
- forId: 'mcemediasource'
- },
- embedTextBox
- ]
+ 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;
}
- ],
- onSubmit: function() {
- var beforeObjects, afterObjects, i, y;
- beforeObjects = editor.dom.select('img[data-mce-object]');
- editor.insertContent(dataToHtml(this.toJSON()));
- afterObjects = editor.dom.select('img[data-mce-object]');
+ // 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 = [];
- // Find new image placeholder so we can select it
- for (i = 0; i < beforeObjects.length; i++) {
- for (y = afterObjects.length - 1; y >= 0; y--) {
- if (beforeObjects[i] == afterObjects[y]) {
- afterObjects.splice(y, 1);
- }
- }
+ 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);
}
- editor.selection.select(afterObjects[0]);
- editor.nodeChanged();
- }
- });
- }
+ var elB = makeReplacementNode(endNode.data.substring(0, range.endNodeIndex), matchIndex);
- function getSource() {
- var elm = editor.selection.getNode();
+ parentNode = startNode.parentNode;
+ parentNode.insertBefore(before, startNode);
+ parentNode.insertBefore(elA, startNode);
+ parentNode.removeChild(startNode);
- if (elm.getAttribute('data-mce-object')) {
- return editor.selection.getContent();
- }
- }
+ parentNode = endNode.parentNode;
+ parentNode.insertBefore(elB, endNode);
+ parentNode.insertBefore(after, endNode);
+ parentNode.removeChild(endNode);
- function dataToHtml(data) {
- var html = '';
+ return elB;
+ };
+ }
- if (!data.source1) {
- tinymce.extend(data, htmlToData(data.embed));
- if (!data.source1) {
- return '';
- }
+ text = getText(node);
+ if (!text) {
+ return;
}
- if (!data.source2) {
- data.source2 = '';
+ if (regex.global) {
+ while ((m = regex.exec(text))) {
+ matches.push(getMatchIndexes(m, captureGroup));
+ }
+ } else {
+ m = text.match(regex);
+ matches.push(getMatchIndexes(m, captureGroup));
}
- if (!data.poster) {
- data.poster = '';
+ if (matches.length) {
+ count = matches.length;
+ stepThroughMatches(node, matches, genReplacer(replacementNode));
}
- data.source1 = editor.convertURL(data.source1, "source");
- data.source2 = editor.convertURL(data.source2, "source");
- data.source1mime = guessMime(data.source1);
- data.source2mime = guessMime(data.source2);
- data.poster = editor.convertURL(data.poster, "poster");
- data.flashPlayerUrl = editor.convertURL(url + '/moxieplayer.swf', "movie");
+ return count;
+ }
- tinymce.each(urlPatterns, function(pattern) {
- var match, i, url;
+ function Plugin(editor) {
+ var self = this, currentIndex = -1;
- if ((match = pattern.regex.exec(data.source1))) {
- url = pattern.url;
+ function showDialog() {
+ var last = {}, selectedText;
- for (i = 0; match[i]; i++) {
- /*jshint loopfunc:true*/
- /*eslint no-loop-func:0 */
- url = url.replace('$' + i, function() {
- return match[i];
- });
- }
+ selectedText = tinymce.trim(editor.selection.getContent({format: 'text'}));
- data.source1 = url;
- data.type = pattern.type;
- data.allowFullscreen = pattern.allowFullscreen;
- data.width = data.width || pattern.w;
- data.height = data.height || pattern.h;
+ function updateButtonStates() {
+ win.statusbar.find('#next').disabled(!findSpansByIndex(currentIndex + 1).length);
+ win.statusbar.find('#prev').disabled(!findSpansByIndex(currentIndex - 1).length);
}
- });
- if (data.embed) {
- html = updateHtml(data.embed, data, true);
- } else {
- var videoScript = getVideoScriptMatch(data.source1);
- if (videoScript) {
- data.type = 'script';
- data.width = videoScript.width;
- data.height = videoScript.height;
+ function notFoundAlert() {
+ tinymce.ui.MessageBox.alert('Could not find the specified string.', function() {
+ win.find('#find')[0].focus();
+ });
}
- data.width = data.width || 300;
- data.height = data.height || 150;
+ 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;
- tinymce.each(data, function(value, key) {
- data[key] = editor.dom.encode(value);
- });
+ e.preventDefault();
- if (data.type == "iframe") {
- var allowFullscreen = data.allowFullscreen ? ' allowFullscreen="1"' : '';
- html += '';
- } else if (data.source1mime == "application/x-shockwave-flash") {
- html += '';
- } else if (data.source1mime.indexOf('audio') != -1) {
- if (editor.settings.audio_template_callback) {
- html = editor.settings.audio_template_callback(data);
- } else {
- html += (
- ''
- );
- }
- } else if (data.type == "script") {
- html += '';
- } else {
- if (editor.settings.video_template_callback) {
- html = editor.settings.video_template_callback(data);
- } else {
- html = (
- ''
- );
+ 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", subtype: 'primary', 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: selectedText},
+ {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();
}
- return html;
- }
-
- function htmlToData(html) {
- var data = {};
-
- new tinymce.html.SaxParser({
- validate: false,
- allow_conditional_comments: true,
- special: 'script,noscript',
- start: function(name, attrs) {
- if (!data.source1 && name == "param") {
- data.source1 = attrs.map.movie;
- }
-
- if (name == "iframe" || name == "object" || name == "embed" || name == "video" || name == "audio") {
- if (!data.type) {
- data.type = name;
- }
-
- data = tinymce.extend(attrs.map, data);
- }
+ self.init = function(ed) {
+ ed.addMenuItem('searchreplace', {
+ text: 'Find and replace',
+ shortcut: 'Meta+F',
+ onclick: showDialog,
+ separator: 'before',
+ context: 'edit'
+ });
- if (name == "script") {
- var videoScript = getVideoScriptMatch(attrs.map.src);
- if (!videoScript) {
- return;
- }
+ ed.addButton('searchreplace', {
+ tooltip: 'Find and replace',
+ shortcut: 'Meta+F',
+ onclick: showDialog
+ });
- data = {
- type: "script",
- source1: attrs.map.src,
- width: videoScript.width,
- height: videoScript.height
- };
- }
+ ed.addCommand("SearchReplace", showDialog);
+ ed.shortcuts.add('Meta+F', '', showDialog);
+ };
- if (name == "source") {
- if (!data.source1) {
- data.source1 = attrs.map.src;
- } else if (!data.source2) {
- data.source2 = attrs.map.src;
- }
- }
+ function getElmIndex(elm) {
+ var value = elm.getAttribute('data-mce-index');
- if (name == "img" && !data.poster) {
- data.poster = attrs.map.src;
- }
+ if (typeof value == "number") {
+ return "" + value;
}
- }).parse(html);
-
- data.source1 = data.source1 || data.src || data.data;
- data.source2 = data.source2 || '';
- data.poster = data.poster || '';
-
- return data;
- }
- function getData(element) {
- if (element.getAttribute('data-mce-object')) {
- return htmlToData(editor.serializer.serialize(element, {selection: true}));
+ return value;
}
- return {};
- }
+ function markAllMatches(regex) {
+ var node, marker;
- function sanitize(html) {
- if (editor.settings.media_filter_html === false) {
- return html;
- }
+ marker = editor.dom.create('span', {
+ "data-mce-bogus": 1
+ });
- var writer = new tinymce.html.Writer(), blocked;
+ marker.className = 'mce-match-marker'; // IE 7 adds class="mce-match-marker" and class=mce-match-marker
+ node = editor.getBody();
- new tinymce.html.SaxParser({
- validate: false,
- allow_conditional_comments: false,
- special: 'script,noscript',
+ self.done(false);
- comment: function(text) {
- writer.comment(text);
- },
+ return findAndReplaceDOMText(regex, node, marker, false, editor.schema);
+ }
- cdata: function(text) {
- writer.cdata(text);
- },
+ function unwrap(node) {
+ var parentNode = node.parentNode;
- text: function(text, raw) {
- writer.text(text, raw);
- },
+ if (node.firstChild) {
+ parentNode.insertBefore(node.firstChild, node);
+ }
- start: function(name, attrs, empty) {
- blocked = true;
+ node.parentNode.removeChild(node);
+ }
- if (name == 'script' || name == 'noscript') {
- return;
- }
+ function findSpansByIndex(index) {
+ var nodes, spans = [];
- for (var i = 0; i < attrs.length; i++) {
- if (attrs[i].name.indexOf('on') === 0) {
- return;
+ 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 (attrs[i].name == 'style') {
- attrs[i].value = editor.dom.serializeStyle(editor.dom.parseStyle(attrs[i].value), name);
+ if (nodeIndex === index.toString()) {
+ spans.push(nodes[i]);
}
}
+ }
- writer.start(name, attrs, empty);
- blocked = false;
- },
+ return spans;
+ }
- end: function(name) {
- if (blocked) {
- return;
- }
+ function moveSelection(forward) {
+ var testIndex = currentIndex, dom = editor.dom;
- writer.end(name);
- }
- }, new tinymce.html.Schema({})).parse(html);
+ forward = forward !== false;
- return writer.getContent();
- }
+ if (forward) {
+ testIndex++;
+ } else {
+ testIndex--;
+ }
- function updateHtml(html, data, updateAll) {
- var writer = new tinymce.html.Writer();
- var sourceCount = 0, hasImage;
+ dom.removeClass(findSpansByIndex(currentIndex), 'mce-match-marker-selected');
- function setAttributes(attrs, updatedAttrs) {
- var name, i, value, attr;
+ var spans = findSpansByIndex(testIndex);
+ if (spans.length) {
+ dom.addClass(findSpansByIndex(testIndex), 'mce-match-marker-selected');
+ editor.selection.scrollIntoView(spans[0]);
+ return testIndex;
+ }
- for (name in updatedAttrs) {
- value = "" + updatedAttrs[name];
+ return -1;
+ }
- if (attrs.map[name]) {
- i = attrs.length;
- while (i--) {
- attr = attrs[i];
+ function removeNode(node) {
+ var dom = editor.dom, parent = node.parentNode;
- if (attr.name == name) {
- if (value) {
- attrs.map[name] = value;
- attr.value = value;
- } else {
- delete attrs.map[name];
- attrs.splice(i, 1);
- }
- }
- }
- } else if (value) {
- attrs.push({
- name: name,
- value: value
- });
+ dom.remove(node);
- attrs.map[name] = value;
- }
+ if (dom.isEmpty(parent)) {
+ dom.remove(parent);
}
}
- new tinymce.html.SaxParser({
- validate: false,
- allow_conditional_comments: true,
- special: 'script,noscript',
+ self.find = function(text, matchCase, wholeWord) {
+ text = text.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
+ text = wholeWord ? '\\b' + text + '\\b' : text;
- comment: function(text) {
- writer.comment(text);
- },
+ var count = markAllMatches(new RegExp(text, matchCase ? 'g' : 'gi'));
- cdata: function(text) {
- writer.cdata(text);
- },
+ if (count) {
+ currentIndex = -1;
+ currentIndex = moveSelection(true);
+ }
- text: function(text, raw) {
- writer.text(text, raw);
- },
+ return count;
+ };
- start: function(name, attrs, empty) {
- switch (name) {
- case "video":
- case "object":
- case "embed":
- case "img":
- case "iframe":
- setAttributes(attrs, {
- width: data.width,
- height: data.height
- });
- break;
- }
+ self.next = function() {
+ var index = moveSelection(true);
- if (updateAll) {
- switch (name) {
- case "video":
- setAttributes(attrs, {
- poster: data.poster,
- src: ""
- });
+ if (index !== -1) {
+ currentIndex = index;
+ }
+ };
- if (data.source2) {
- setAttributes(attrs, {
- src: ""
- });
- }
- break;
+ self.prev = function() {
+ var index = moveSelection(false);
- case "iframe":
- setAttributes(attrs, {
- src: data.source1
- });
- break;
+ if (index !== -1) {
+ currentIndex = index;
+ }
+ };
- case "source":
- sourceCount++;
+ function isMatchSpan(node) {
+ var matchIndex = getElmIndex(node);
- if (sourceCount <= 2) {
- setAttributes(attrs, {
- src: data["source" + sourceCount],
- type: data["source" + sourceCount + "mime"]
- });
+ return matchIndex !== null && matchIndex.length > 0;
+ }
- if (!data["source" + sourceCount]) {
- return;
- }
- }
- break;
+ self.replace = function(text, forward, all) {
+ var i, nodes, node, matchIndex, currentMatchIndex, nextIndex = currentIndex, hasMore;
- case "img":
- if (!data.poster) {
- return;
- }
+ forward = forward !== false;
- hasImage = true;
+ node = editor.getBody();
+ nodes = tinymce.grep(tinymce.toArray(node.getElementsByTagName('span')), isMatchSpan);
+ for (i = 0; i < nodes.length; i++) {
+ var nodeIndex = getElmIndex(nodes[i]);
+
+ 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 = parseInt(getElmIndex(nodes[i]), 10);
+
+ if (matchIndex === currentMatchIndex) {
+ removeNode(nodes[i]);
+ } else {
+ i--;
break;
+ }
+ }
+
+ if (forward) {
+ nextIndex--;
}
+ } else if (currentMatchIndex > currentIndex) {
+ nodes[i].setAttribute('data-mce-index', currentMatchIndex - 1);
}
+ }
- writer.start(name, attrs, empty);
- },
+ editor.undoManager.add();
+ currentIndex = nextIndex;
- end: function(name) {
- if (name == "video" && updateAll) {
- for (var index = 1; index <= 2; index++) {
- if (data["source" + index]) {
- var attrs = [];
- attrs.map = {};
+ if (forward) {
+ hasMore = findSpansByIndex(nextIndex + 1).length > 0;
+ self.next();
+ } else {
+ hasMore = findSpansByIndex(nextIndex - 1).length > 0;
+ self.prev();
+ }
- if (sourceCount < index) {
- setAttributes(attrs, {
- src: data["source" + index],
- type: data["source" + index + "mime"]
- });
+ return !all && hasMore;
+ };
- writer.start("source", attrs, true);
- }
+ 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;
}
- }
- if (data.poster && name == "object" && updateAll && !hasImage) {
- var imgAttrs = [];
- imgAttrs.map = {};
+ unwrap(nodes[i]);
+ }
+ }
- setAttributes(imgAttrs, {
- src: data.poster,
- width: data.width,
- height: data.height
- });
+ if (startContainer && endContainer) {
+ var rng = editor.dom.createRng();
+ rng.setStart(startContainer, 0);
+ rng.setEnd(endContainer, endContainer.data.length);
- writer.start("img", imgAttrs, true);
+ if (keepEditorSelection !== false) {
+ editor.selection.setRng(rng);
}
- writer.end(name);
+ return rng;
}
- }, new tinymce.html.Schema({})).parse(html);
-
- return writer.getContent();
+ };
}
- editor.on('ResolveName', function(e) {
- var name;
-
- if (e.target.nodeType == 1 && (name = e.target.getAttribute("data-mce-object"))) {
- e.name = name;
- }
- });
-
- editor.on('preInit', function() {
- // Make sure that any messy HTML is retained inside these
- var specialElements = editor.schema.getSpecialElements();
- tinymce.each('video audio iframe object'.split(' '), function(name) {
- specialElements[name] = new RegExp('<\/' + name + '[^>]*>', 'gi');
- });
+ tinymce.PluginManager.add('searchreplace', Plugin);
+})();
- // Allow elements
- //editor.schema.addValidElements('object[id|style|width|height|classid|codebase|*],embed[id|style|width|height|type|src|*],video[*],audio[*]');
- // Set allowFullscreen attribs as boolean
- var boolAttrs = editor.schema.getBoolAttrs();
- tinymce.each('webkitallowfullscreen mozallowfullscreen allowfullscreen'.split(' '), function(name) {
- boolAttrs[name] = {};
- });
+ }).apply(root, arguments);
+});
+}(this));
- // Converts iframe, video etc into placeholder images
- editor.parser.addNodeFilter('iframe,video,audio,object,embed,script', function(nodes, name) {
- var i = nodes.length, ai, node, placeHolder, attrName, attrValue, attribs, innerHtml;
- var videoScript;
+(function(root) {
+define("tinymce-spellchecker", ["tinymce"], function() {
+ return (function() {
+/**
+ * Compiled inline version. (Library mode)
+ */
- while (i--) {
- node = nodes[i];
- if (!node.parent) {
- continue;
- }
+/*jshint smarttabs:true, undef:true, latedef:true, curly:true, bitwise:true, camelcase:true */
+/*globals $code */
- if (node.name == 'script') {
- videoScript = getVideoScriptMatch(node.attr('src'));
- if (!videoScript) {
- continue;
- }
- }
+(function(exports, undefined) {
+ "use strict";
- placeHolder = new tinymce.html.Node('img', 1);
- placeHolder.shortEnded = true;
+ var modules = {};
- if (videoScript) {
- if (videoScript.width) {
- node.attr('width', videoScript.width.toString());
- }
+ function require(ids, callback) {
+ var module, defs = [];
- if (videoScript.height) {
- node.attr('height', videoScript.height.toString());
- }
- }
+ for (var i = 0; i < ids.length; ++i) {
+ module = modules[ids[i]] || resolve(ids[i]);
+ if (!module) {
+ throw 'module definition dependecy not found: ' + ids[i];
+ }
- // Prefix all attributes except width, height and style since we
- // will add these to the placeholder
- attribs = node.attributes;
- ai = attribs.length;
- while (ai--) {
- attrName = attribs[ai].name;
- attrValue = attribs[ai].value;
+ defs.push(module);
+ }
- if (attrName !== "width" && attrName !== "height" && attrName !== "style") {
- if (attrName == "data" || attrName == "src") {
- attrValue = editor.convertURL(attrValue, attrName);
- }
+ callback.apply(null, defs);
+ }
- placeHolder.attr('data-mce-p-' + attrName, attrValue);
- }
- }
+ function define(id, dependencies, definition) {
+ if (typeof id !== 'string') {
+ throw 'invalid module definition, module id must be defined and be a string';
+ }
- // Place the inner HTML contents inside an escaped attribute
- // This enables us to copy/paste the fake object
- innerHtml = node.firstChild && node.firstChild.value;
- if (innerHtml) {
- placeHolder.attr("data-mce-html", escape(innerHtml));
- placeHolder.firstChild = null;
- }
+ if (dependencies === undefined) {
+ throw 'invalid module definition, dependencies must be specified';
+ }
- placeHolder.attr({
- width: node.attr('width') || "300",
- height: node.attr('height') || (name == "audio" ? "30" : "150"),
- style: node.attr('style'),
- src: tinymce.Env.transparentSrc,
- "data-mce-object": name,
- "class": "mce-object mce-object-" + name
- });
+ if (definition === undefined) {
+ throw 'invalid module definition, definition function must be specified';
+ }
- node.replace(placeHolder);
- }
+ require(dependencies, function() {
+ modules[id] = definition.apply(null, arguments);
});
+ }
- // Replaces placeholder images with real elements for video, object, iframe etc
- editor.serializer.addAttributeFilter('data-mce-object', function(nodes, name) {
- var i = nodes.length, node, realElm, ai, attribs, innerHtml, innerNode, realElmName;
-
- while (i--) {
- node = nodes[i];
- if (!node.parent) {
- continue;
- }
+ function defined(id) {
+ return !!modules[id];
+ }
- realElmName = node.attr(name);
- realElm = new tinymce.html.Node(realElmName, 1);
+ function resolve(id) {
+ var target = exports;
+ var fragments = id.split(/[.\/]/);
- // Add width/height to everything but audio
- if (realElmName != "audio" && realElmName != "script") {
- realElm.attr({
- width: node.attr('width'),
- height: node.attr('height')
- });
- }
+ for (var fi = 0; fi < fragments.length; ++fi) {
+ if (!target[fragments[fi]]) {
+ return;
+ }
- realElm.attr({
- style: node.attr('style')
- });
+ target = target[fragments[fi]];
+ }
- // Unprefix all placeholder attributes
- attribs = node.attributes;
- ai = attribs.length;
- while (ai--) {
- var attrName = attribs[ai].name;
+ return target;
+ }
- if (attrName.indexOf('data-mce-p-') === 0) {
- realElm.attr(attrName.substr(11), attribs[ai].value);
- }
- }
+ function expose(ids) {
+ var i, target, id, fragments, privateModules;
- if (realElmName == "script") {
- realElm.attr('type', 'text/javascript');
- }
+ for (i = 0; i < ids.length; i++) {
+ target = exports;
+ id = ids[i];
+ fragments = id.split(/[.\/]/);
- // Inject innerhtml
- innerHtml = node.attr('data-mce-html');
- if (innerHtml) {
- innerNode = new tinymce.html.Node('#text', 3);
- innerNode.raw = true;
- innerNode.value = sanitize(unescape(innerHtml));
- realElm.append(innerNode);
+ for (var fi = 0; fi < fragments.length - 1; ++fi) {
+ if (target[fragments[fi]] === undefined) {
+ target[fragments[fi]] = {};
}
- node.replace(realElm);
+ target = target[fragments[fi]];
}
- });
- });
-
- editor.on('ObjectSelected', function(e) {
- var objectType = e.target.getAttribute('data-mce-object');
- if (objectType == "audio" || objectType == "script") {
- e.preventDefault();
+ target[fragments[fragments.length - 1]] = modules[id];
}
- });
-
- editor.on('objectResized', function(e) {
- var target = e.target, html;
+
+ // Expose private modules for unit tests
+ if (exports.AMDLC_TESTS) {
+ privateModules = exports.privateModules || {};
- if (target.getAttribute('data-mce-object')) {
- html = target.getAttribute('data-mce-html');
- if (html) {
- html = unescape(html);
- target.setAttribute('data-mce-html', escape(
- updateHtml(html, {
- width: e.width,
- height: e.height
- })
- ));
+ for (id in modules) {
+ privateModules[id] = modules[id];
}
- }
- });
-
- editor.addButton('media', {
- tooltip: 'Insert/edit video',
- onclick: showDialog,
- stateSelector: ['img[data-mce-object=video]', 'img[data-mce-object=iframe]']
- });
-
- editor.addMenuItem('media', {
- icon: 'media',
- text: 'Insert/edit video',
- onclick: showDialog,
- context: 'insert',
- prependToContext: true
- });
- this.showDialog = showDialog;
-});
+ for (i = 0; i < ids.length; i++) {
+ delete privateModules[ids[i]];
+ }
+ exports.privateModules = privateModules;
+ }
+ }
- }).apply(root, arguments);
-});
-}(this));
+// Included from: js/tinymce/plugins/spellchecker/classes/DomTextMatcher.js
-(function(root) {
-define("tinymce-nonbreaking", ["tinymce"], function() {
- return (function() {
/**
- * plugin.js
+ * DomTextMatcher.js
*
* Released under LGPL License.
* Copyright (c) 1999-2015 Ephox Corp. All rights reserved
@@ -54040,679 +69785,914 @@ define("tinymce-nonbreaking", ["tinymce"], function() {
* Contributing: http://www.tinymce.com/contributing
*/
-/*global tinymce:true */
+/*eslint no-labels:0, no-constant-condition: 0 */
-tinymce.PluginManager.add('nonbreaking', function(editor) {
- var setting = editor.getParam('nonbreaking_force_tab');
+/**
+ * This class logic for filtering text and matching words.
+ *
+ * @class tinymce.spellcheckerplugin.TextFilter
+ * @private
+ */
+define("tinymce/spellcheckerplugin/DomTextMatcher", [], 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
+ return function(node, editor) {
+ var m, matches = [], text, dom = editor.dom;
+ var blockElementsMap, hiddenTextElementsMap, shortEndedElementsMap;
- editor.addCommand('mceNonBreaking', function() {
- editor.insertContent(
- (editor.plugins.visualchars && editor.plugins.visualchars.state) ?
- ' ' : ' '
- );
+ blockElementsMap = editor.schema.getBlockElements(); // H1-H6, P, TD etc
+ hiddenTextElementsMap = editor.schema.getWhiteSpaceElements(); // TEXTAREA, PRE, STYLE, SCRIPT
+ shortEndedElementsMap = editor.schema.getShortEndedElements(); // BR, IMG, INPUT
- editor.dom.setAttrib(editor.dom.select('span.mce-nbsp'), 'data-mce-bogus', '1');
- });
+ function createMatch(m, data) {
+ if (!m[0]) {
+ throw 'findAndReplaceDOMText cannot handle zero-length matches';
+ }
- editor.addButton('nonbreaking', {
- title: 'Nonbreaking space',
- cmd: 'mceNonBreaking'
- });
+ return {
+ start: m.index,
+ end: m.index + m[0].length,
+ text: m[0],
+ data: data
+ };
+ }
- editor.addMenuItem('nonbreaking', {
- text: 'Nonbreaking space',
- cmd: 'mceNonBreaking',
- context: 'insert'
- });
+ function getText(node) {
+ var txt;
- if (setting) {
- var spaces = +setting > 1 ? +setting : 3; // defaults to 3 spaces if setting is true (or 1)
+ if (node.nodeType === 3) {
+ return node.data;
+ }
- editor.on('keydown', function(e) {
- if (e.keyCode == 9) {
+ if (hiddenTextElementsMap[node.nodeName] && !blockElementsMap[node.nodeName]) {
+ return '';
+ }
- if (e.shiftKey) {
- return;
- }
+ txt = '';
- e.preventDefault();
- for (var i = 0; i < spaces; i++) {
- editor.execCommand('mceNonBreaking');
- }
+ if (blockElementsMap[node.nodeName] || shortEndedElementsMap[node.nodeName]) {
+ txt += '\n';
}
- });
- }
-});
+ if ((node = node.firstChild)) {
+ do {
+ txt += getText(node);
+ } while ((node = node.nextSibling));
+ }
- }).apply(root, arguments);
-});
-}(this));
+ return txt;
+ }
-(function(root) {
-define("tinymce-noneditable", ["tinymce"], function() {
- return (function() {
-/**
- * plugin.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
+ function stepThroughMatches(node, matches, replaceFn) {
+ var startNode, endNode, startNodeIndex,
+ endNodeIndex, innerNodes = [], atIndex = 0, curNode = node,
+ matchLocation, matchIndex = 0;
-/*jshint loopfunc:true */
-/*eslint no-loop-func:0 */
-/*global tinymce:true */
+ matches = matches.slice(0);
+ matches.sort(function(a, b) {
+ return a.start - b.start;
+ });
-tinymce.PluginManager.add('noneditable', function(editor) {
- var TreeWalker = tinymce.dom.TreeWalker;
- var externalName = 'contenteditable', internalName = 'data-mce-' + externalName;
- var VK = tinymce.util.VK;
+ matchLocation = matches.shift();
- // Returns the content editable state of a node "true/false" or null
- function getContentEditable(node) {
- var contentEditable;
+ out: while (true) {
+ if (blockElementsMap[curNode.nodeName] || shortEndedElementsMap[curNode.nodeName]) {
+ atIndex++;
+ }
- // Ignore non elements
- if (node.nodeType === 1) {
- // Check for fake content editable
- contentEditable = node.getAttribute(internalName);
- if (contentEditable && contentEditable !== "inherit") {
- return contentEditable;
- }
+ if (curNode.nodeType === 3) {
+ if (!endNode && curNode.length + atIndex >= matchLocation.end) {
+ // We've found the ending
+ endNode = curNode;
+ endNodeIndex = matchLocation.end - atIndex;
+ } else if (startNode) {
+ // Intersecting node
+ innerNodes.push(curNode);
+ }
- // Check for real content editable
- contentEditable = node.contentEditable;
- if (contentEditable !== "inherit") {
- return contentEditable;
+ if (!startNode && curNode.length + atIndex > matchLocation.start) {
+ // We've found the match start
+ startNode = curNode;
+ startNodeIndex = matchLocation.start - atIndex;
+ }
+
+ atIndex += curNode.length;
+ }
+
+ if (startNode && endNode) {
+ curNode = replaceFn({
+ startNode: startNode,
+ startNodeIndex: startNodeIndex,
+ endNode: endNode,
+ endNodeIndex: endNodeIndex,
+ innerNodes: innerNodes,
+ match: matchLocation.text,
+ 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;
+ }
+ }
}
}
- return null;
- }
+ /**
+ * Generates the actual replaceFn which splits up text nodes
+ * and inserts the replacement element.
+ */
+ function genReplacer(callback) {
+ function makeReplacementNode(fill, matchIndex) {
+ var match = matches[matchIndex];
- // Returns the noneditable parent or null if there is a editable before it or if it wasn't found
- function getNonEditableParent(node) {
- var state;
+ if (!match.stencil) {
+ match.stencil = callback(match);
+ }
- while (node) {
- state = getContentEditable(node);
- if (state) {
- return state === "false" ? node : null;
+ var clone = match.stencil.cloneNode(false);
+ clone.setAttribute('data-mce-index', matchIndex);
+
+ if (fill) {
+ clone.appendChild(dom.doc.createTextNode(fill));
+ }
+
+ return clone;
}
- node = node.parentNode;
- }
- }
+ return function(range) {
+ var before, after, parentNode, startNode = range.startNode,
+ endNode = range.endNode, matchIndex = range.matchIndex,
+ doc = dom.doc;
- function handleContentEditableSelection() {
- var dom = editor.dom, selection = editor.selection, caretContainerId = 'mce_noneditablecaret', invisibleChar = '\uFEFF';
+ if (startNode === endNode) {
+ var node = startNode;
- // Get caret container parent for the specified node
- function getParentCaretContainer(node) {
- while (node) {
- if (node.id === caretContainerId) {
- return node;
+ 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, 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;
}
- node = node.parentNode;
- }
+ // 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;
+ };
}
- // Finds the first text node in the specified node
- function findFirstTextNode(node) {
- var walker;
+ function unwrapElement(element) {
+ var parentNode = element.parentNode;
+ parentNode.insertBefore(element.firstChild, element);
+ element.parentNode.removeChild(element);
+ }
- if (node) {
- walker = new TreeWalker(node, node);
+ function getWrappersByIndex(index) {
+ var elements = node.getElementsByTagName('*'), wrappers = [];
- for (node = walker.current(); node; node = walker.next()) {
- if (node.nodeType === 3) {
- return node;
+ index = typeof index == "number" ? "" + index : null;
+
+ for (var i = 0; i < elements.length; i++) {
+ var element = elements[i], dataIndex = element.getAttribute('data-mce-index');
+
+ if (dataIndex !== null && dataIndex.length) {
+ if (dataIndex === index || index === null) {
+ wrappers.push(element);
}
}
}
+
+ return wrappers;
}
- // Insert caret container before/after target or expand selection to include block
- function insertCaretContainerOrExpandToBlock(target, before) {
- var caretContainer, rng;
+ /**
+ * Returns the index of a specific match object or -1 if it isn't found.
+ *
+ * @param {Match} match Text match object.
+ * @return {Number} Index of match or -1 if it isn't found.
+ */
+ function indexOf(match) {
+ var i = matches.length;
+ while (i--) {
+ if (matches[i] === match) {
+ return i;
+ }
+ }
- // Select block
- if (getContentEditable(target) === "false") {
- if (dom.isBlock(target)) {
- selection.select(target);
- return;
+ return -1;
+ }
+
+ /**
+ * Filters the matches. If the callback returns true it stays if not it gets removed.
+ *
+ * @param {Function} callback Callback to execute for each match.
+ * @return {DomTextMatcher} Current DomTextMatcher instance.
+ */
+ function filter(callback) {
+ var filteredMatches = [];
+
+ each(function(match, i) {
+ if (callback(match, i)) {
+ filteredMatches.push(match);
+ }
+ });
+
+ matches = filteredMatches;
+
+ /*jshint validthis:true*/
+ return this;
+ }
+
+ /**
+ * Executes the specified callback for each match.
+ *
+ * @param {Function} callback Callback to execute for each match.
+ * @return {DomTextMatcher} Current DomTextMatcher instance.
+ */
+ function each(callback) {
+ for (var i = 0, l = matches.length; i < l; i++) {
+ if (callback(matches[i], i) === false) {
+ break;
}
}
- rng = dom.createRng();
+ /*jshint validthis:true*/
+ return this;
+ }
- if (getContentEditable(target) === "true") {
- if (!target.firstChild) {
- target.appendChild(editor.getDoc().createTextNode('\u00a0'));
+ /**
+ * Wraps the current matches with nodes created by the specified callback.
+ * Multiple clones of these matches might occur on matches that are on multiple nodex.
+ *
+ * @param {Function} callback Callback to execute in order to create elements for matches.
+ * @return {DomTextMatcher} Current DomTextMatcher instance.
+ */
+ function wrap(callback) {
+ if (matches.length) {
+ stepThroughMatches(node, matches, genReplacer(callback));
+ }
+
+ /*jshint validthis:true*/
+ return this;
+ }
+
+ /**
+ * Finds the specified regexp and adds them to the matches collection.
+ *
+ * @param {RegExp} regex Global regexp to search the current node by.
+ * @param {Object} [data] Optional custom data element for the match.
+ * @return {DomTextMatcher} Current DomTextMatcher instance.
+ */
+ function find(regex, data) {
+ if (text && regex.global) {
+ while ((m = regex.exec(text))) {
+ matches.push(createMatch(m, data));
}
-
- target = target.firstChild;
- before = true;
}
- /*
- caretContainer = dom.create('span', {
- id: caretContainerId,
- 'data-mce-bogus': true,
- style:'border: 1px solid red'
- }, invisibleChar);
- */
+ return this;
+ }
- caretContainer = dom.create('span', {id: caretContainerId, 'data-mce-bogus': true}, invisibleChar);
+ /**
+ * Unwraps the specified match object or all matches if unspecified.
+ *
+ * @param {Object} [match] Optional match object.
+ * @return {DomTextMatcher} Current DomTextMatcher instance.
+ */
+ function unwrap(match) {
+ var i, elements = getWrappersByIndex(match ? indexOf(match) : null);
- if (before) {
- target.parentNode.insertBefore(caretContainer, target);
- } else {
- dom.insertAfter(caretContainer, target);
+ i = elements.length;
+ while (i--) {
+ unwrapElement(elements[i]);
}
- rng.setStart(caretContainer.firstChild, 1);
- rng.collapse(true);
- selection.setRng(rng);
+ return this;
+ }
- return caretContainer;
+ /**
+ * Returns a match object by the specified DOM element.
+ *
+ * @param {DOMElement} element Element to return match object for.
+ * @return {Object} Match object for the specified element.
+ */
+ function matchFromElement(element) {
+ return matches[element.getAttribute('data-mce-index')];
}
- // Removes any caret container
- function removeCaretContainer(caretContainer) {
- var rng, child, lastContainer;
+ /**
+ * Returns a DOM element from the specified match element. This will be the first element if it's split
+ * on multiple nodes.
+ *
+ * @param {Object} match Match element to get first element of.
+ * @return {DOMElement} DOM element for the specified match object.
+ */
+ function elementFromMatch(match) {
+ return getWrappersByIndex(indexOf(match))[0];
+ }
- if (caretContainer) {
- rng = selection.getRng(true);
- rng.setStartBefore(caretContainer);
- rng.setEndBefore(caretContainer);
+ /**
+ * Adds match the specified range for example a grammar line.
+ *
+ * @param {Number} start Start offset.
+ * @param {Number} length Length of the text.
+ * @param {Object} data Custom data object for match.
+ * @return {DomTextMatcher} Current DomTextMatcher instance.
+ */
+ function add(start, length, data) {
+ matches.push({
+ start: start,
+ end: start + length,
+ text: text.substr(start, length),
+ data: data
+ });
- child = findFirstTextNode(caretContainer);
- if (child && child.nodeValue.charAt(0) == invisibleChar) {
- child = child.deleteData(0, 1);
- }
+ return this;
+ }
- dom.remove(caretContainer, true);
+ /**
+ * Returns a DOM range for the specified match.
+ *
+ * @param {Object} match Match object to get range for.
+ * @return {DOMRange} DOM Range for the specified match.
+ */
+ function rangeFromMatch(match) {
+ var wrappers = getWrappersByIndex(indexOf(match));
- selection.setRng(rng);
- } else {
- while ((caretContainer = dom.get(caretContainerId)) && caretContainer !== lastContainer) {
- child = findFirstTextNode(caretContainer);
- if (child && child.nodeValue.charAt(0) == invisibleChar) {
- child = child.deleteData(0, 1);
- }
+ var rng = editor.dom.createRng();
+ rng.setStartBefore(wrappers[0]);
+ rng.setEndAfter(wrappers[wrappers.length - 1]);
- dom.remove(caretContainer, true);
+ return rng;
+ }
- lastContainer = caretContainer;
- }
+ /**
+ * Replaces the specified match with the specified text.
+ *
+ * @param {Object} match Match object to replace.
+ * @param {String} text Text to replace the match with.
+ * @return {DOMRange} DOM range produced after the replace.
+ */
+ function replace(match, text) {
+ var rng = rangeFromMatch(match);
+
+ rng.deleteContents();
+
+ if (text.length > 0) {
+ rng.insertNode(editor.dom.doc.createTextNode(text));
}
- }
- // Modifies the selection to include contentEditable false elements or insert caret containers
- function moveSelection() {
- var nonEditableStart, nonEditableEnd, isCollapsed, rng, element;
+ return rng;
+ }
- // Checks if there is any contents to the left/right side of caret returns the noneditable element or
- // any editable element if it finds one inside
- function hasSideContent(element, left) {
- var container, offset, walker, node, len;
+ /**
+ * Resets the DomTextMatcher instance. This will remove any wrapped nodes and remove any matches.
+ *
+ * @return {[type]} [description]
+ */
+ function reset() {
+ matches.splice(0, matches.length);
+ unwrap();
- container = rng.startContainer;
- offset = rng.startOffset;
+ return this;
+ }
- // If endpoint is in middle of text node then expand to beginning/end of element
- if (container.nodeType == 3) {
- len = container.nodeValue.length;
- if ((offset > 0 && offset < len) || (left ? offset == len : offset === 0)) {
- return;
- }
- } else {
- // Can we resolve the node by index
- if (offset < container.childNodes.length) {
- // Browser represents caret position as the offset at the start of an element. When moving right
- // this is the element we are moving into so we consider our container to be child node at offset-1
- var pos = !left && offset > 0 ? offset - 1 : offset;
- container = container.childNodes[pos];
- if (container.hasChildNodes()) {
- container = container.firstChild;
- }
- } else {
- // If not then the caret is at the last position in it's container and the caret container
- // should be inserted after the noneditable element
- return !left ? element : null;
- }
- }
+ text = getText(node);
- // Walk left/right to look for contents
- walker = new TreeWalker(container, element);
- while ((node = walker[left ? 'prev' : 'next']())) {
- if (node.nodeType === 3 && node.nodeValue.length > 0) {
- return;
- } else if (getContentEditable(node) === "true") {
- // Found contentEditable=true element return this one to we can move the caret inside it
- return node;
- }
- }
+ return {
+ text: text,
+ matches: matches,
+ each: each,
+ filter: filter,
+ reset: reset,
+ matchFromElement: matchFromElement,
+ elementFromMatch: elementFromMatch,
+ find: find,
+ add: add,
+ wrap: wrap,
+ unwrap: unwrap,
+ replace: replace,
+ rangeFromMatch: rangeFromMatch,
+ indexOf: indexOf
+ };
+ };
+});
- return element;
- }
+// Included from: js/tinymce/plugins/spellchecker/classes/Plugin.js
- // Remove any existing caret containers
- removeCaretContainer();
+/**
+ * Plugin.js
+ *
+ * Released under LGPL License.
+ * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
+ *
+ * License: http://www.tinymce.com/license
+ * Contributing: http://www.tinymce.com/contributing
+ */
- // Get noneditable start/end elements
- isCollapsed = selection.isCollapsed();
- nonEditableStart = getNonEditableParent(selection.getStart());
- nonEditableEnd = getNonEditableParent(selection.getEnd());
+/*jshint camelcase:false */
- // Is any fo the range endpoints noneditable
- if (nonEditableStart || nonEditableEnd) {
- rng = selection.getRng(true);
+/**
+ * This class contains all core logic for the spellchecker plugin.
+ *
+ * @class tinymce.spellcheckerplugin.Plugin
+ * @private
+ */
+define("tinymce/spellcheckerplugin/Plugin", [
+ "tinymce/spellcheckerplugin/DomTextMatcher",
+ "tinymce/PluginManager",
+ "tinymce/util/Tools",
+ "tinymce/ui/Menu",
+ "tinymce/dom/DOMUtils",
+ "tinymce/util/XHR",
+ "tinymce/util/URI",
+ "tinymce/util/JSON"
+], function(DomTextMatcher, PluginManager, Tools, Menu, DOMUtils, XHR, URI, JSON) {
+ PluginManager.add('spellchecker', function(editor, url) {
+ var languageMenuItems, self = this, lastSuggestions, started, suggestionsMenu, settings = editor.settings;
+ var hasDictionarySupport;
- // If it's a caret selection then look left/right to see if we need to move the caret out side or expand
- if (isCollapsed) {
- nonEditableStart = nonEditableStart || nonEditableEnd;
+ function getTextMatcher() {
+ if (!self.textMatcher) {
+ self.textMatcher = new DomTextMatcher(editor.getBody(), editor);
+ }
- if ((element = hasSideContent(nonEditableStart, true))) {
- // We have no contents to the left of the caret then insert a caret container before the noneditable element
- insertCaretContainerOrExpandToBlock(element, true);
- } else if ((element = hasSideContent(nonEditableStart, false))) {
- // We have no contents to the right of the caret then insert a caret container after the noneditable element
- insertCaretContainerOrExpandToBlock(element, false);
- } else {
- // We are in the middle of a noneditable so expand to select it
- selection.select(nonEditableStart);
- }
- } else {
- rng = selection.getRng(true);
+ return self.textMatcher;
+ }
- // Expand selection to include start non editable element
- if (nonEditableStart) {
- rng.setStartBefore(nonEditableStart);
- }
+ function buildMenuItems(listName, languageValues) {
+ var items = [];
- // Expand selection to include end non editable element
- if (nonEditableEnd) {
- rng.setEndAfter(nonEditableEnd);
- }
+ Tools.each(languageValues, function(languageValue) {
+ items.push({
+ selectable: true,
+ text: languageValue.name,
+ data: languageValue.value
+ });
+ });
- selection.setRng(rng);
- }
- }
+ return items;
}
- function handleKey(e) {
- var keyCode = e.keyCode, nonEditableParent, caretContainer, startElement, endElement;
+ var languagesString = settings.spellchecker_languages ||
+ 'English=en,Danish=da,Dutch=nl,Finnish=fi,French=fr_FR,' +
+ 'German=de,Italian=it,Polish=pl,Portuguese=pt_BR,' +
+ 'Spanish=es,Swedish=sv';
- function getNonEmptyTextNodeSibling(node, prev) {
- while ((node = node[prev ? 'previousSibling' : 'nextSibling'])) {
- if (node.nodeType !== 3 || node.nodeValue.length > 0) {
- return node;
- }
- }
- }
+ languageMenuItems = buildMenuItems('Language',
+ Tools.map(languagesString.split(','), function(langPair) {
+ langPair = langPair.split('=');
- function positionCaretOnElement(element, start) {
- selection.select(element);
- selection.collapse(start);
- }
+ return {
+ name: langPair[0],
+ value: langPair[1]
+ };
+ })
+ );
- function canDelete(backspace) {
- var rng, container, offset, nonEditableParent;
+ function isEmpty(obj) {
+ /*jshint unused:false*/
+ /*eslint no-unused-vars:0 */
+ for (var name in obj) {
+ return false;
+ }
- function removeNodeIfNotParent(node) {
- var parent = container;
+ return true;
+ }
- while (parent) {
- if (parent === node) {
- return;
- }
+ function showSuggestions(word, spans) {
+ var items = [], suggestions = lastSuggestions[word];
- parent = parent.parentNode;
+ Tools.each(suggestions, function(suggestion) {
+ items.push({
+ text: suggestion,
+ onclick: function() {
+ editor.insertContent(editor.dom.encode(suggestion));
+ editor.dom.remove(spans);
+ checkIfFinished();
}
+ });
+ });
- dom.remove(node);
- moveSelection();
- }
+ items.push({text: '-'});
- function isNextPrevTreeNodeNonEditable() {
- var node, walker, nonEmptyElements = editor.schema.getNonEmptyElements();
+ if (hasDictionarySupport) {
+ items.push({text: 'Add to Dictionary', onclick: function() {
+ addToDictionary(word, spans);
+ }});
+ }
- walker = new tinymce.dom.TreeWalker(container, editor.getBody());
- while ((node = (backspace ? walker.prev() : walker.next()))) {
- // Found IMG/INPUT etc
- if (nonEmptyElements[node.nodeName.toLowerCase()]) {
- break;
- }
+ items.push.apply(items, [
+ {text: 'Ignore', onclick: function() {
+ ignoreWord(word, spans);
+ }},
- // Found text node with contents
- if (node.nodeType === 3 && tinymce.trim(node.nodeValue).length > 0) {
- break;
- }
+ {text: 'Ignore all', onclick: function() {
+ ignoreWord(word, spans, true);
+ }}
+ ]);
- // Found non editable node
- if (getContentEditable(node) === "false") {
- removeNodeIfNotParent(node);
- return true;
- }
+ // Render menu
+ suggestionsMenu = new Menu({
+ items: items,
+ context: 'contextmenu',
+ onautohide: function(e) {
+ if (e.target.className.indexOf('spellchecker') != -1) {
+ e.preventDefault();
}
+ },
+ onhide: function() {
+ suggestionsMenu.remove();
+ suggestionsMenu = null;
+ }
+ });
- // Check if the content node is within a non editable parent
- if (getNonEditableParent(node)) {
- return true;
- }
+ suggestionsMenu.renderTo(document.body);
- return false;
- }
+ // Position menu
+ var pos = DOMUtils.DOM.getPos(editor.getContentAreaContainer());
+ var targetPos = editor.dom.getPos(spans[0]);
+ var root = editor.dom.getRoot();
- if (selection.isCollapsed()) {
- rng = selection.getRng(true);
- container = rng.startContainer;
- offset = rng.startOffset;
- container = getParentCaretContainer(container) || container;
+ // Adjust targetPos for scrolling in the editor
+ if (root.nodeName == 'BODY') {
+ targetPos.x -= root.ownerDocument.documentElement.scrollLeft || root.scrollLeft;
+ targetPos.y -= root.ownerDocument.documentElement.scrollTop || root.scrollTop;
+ } else {
+ targetPos.x -= root.scrollLeft;
+ targetPos.y -= root.scrollTop;
+ }
- // Is in noneditable parent
- if ((nonEditableParent = getNonEditableParent(container))) {
- removeNodeIfNotParent(nonEditableParent);
- return false;
- }
+ pos.x += targetPos.x;
+ pos.y += targetPos.y;
- // Check if the caret is in the middle of a text node
- if (container.nodeType == 3 && (backspace ? offset > 0 : offset < container.nodeValue.length)) {
- return true;
- }
+ suggestionsMenu.moveTo(pos.x, pos.y + spans[0].offsetHeight);
+ }
- // Resolve container index
- if (container.nodeType == 1) {
- container = container.childNodes[offset] || container;
- }
+ function getWordCharPattern() {
+ // Regexp for finding word specific characters this will split words by
+ // spaces, quotes, copy right characters etc. It's escaped with unicode characters
+ // to make it easier to output scripts on servers using different encodings
+ // so if you add any characters outside the 128 byte range make sure to escape it
+ return editor.getParam('spellchecker_wordchar_pattern') || new RegExp("[^" +
+ "\\s!\"#$%&()*+,-./:;<=>?@[\\]^_{|}`" +
+ "\u00a7\u00a9\u00ab\u00ae\u00b1\u00b6\u00b7\u00b8\u00bb" +
+ "\u00bc\u00bd\u00be\u00bf\u00d7\u00f7\u00a4\u201d\u201c\u201e\u00a0\u2002\u2003\u2009" +
+ "]+", "g");
+ }
- // Check if previous or next tree node is non editable then block the event
- if (isNextPrevTreeNodeNonEditable()) {
- return false;
- }
- }
+ function defaultSpellcheckCallback(method, text, doneCallback, errorCallback) {
+ var data = {method: method}, postData = '';
- return true;
+ if (method == "spellcheck") {
+ data.text = text;
+ data.lang = settings.spellchecker_language;
}
- moveSelection();
-
- startElement = selection.getStart();
- endElement = selection.getEnd();
+ if (method == "addToDictionary") {
+ data.word = text;
+ }
- // Disable all key presses in contentEditable=false except delete or backspace
- nonEditableParent = getNonEditableParent(startElement) || getNonEditableParent(endElement);
- var currentNode = editor.selection.getNode();
+ Tools.each(data, function(value, key) {
+ if (postData) {
+ postData += '&';
+ }
- var isDirectionKey = keyCode == VK.LEFT || keyCode == VK.RIGHT || keyCode == VK.UP || keyCode == VK.DOWN;
- var left = keyCode == VK.LEFT || keyCode == VK.UP;
+ postData += key + '=' + encodeURIComponent(value);
+ });
- if (nonEditableParent && (keyCode < 112 || keyCode > 124) && keyCode != VK.DELETE && keyCode != VK.BACKSPACE) {
+ XHR.send({
+ url: new URI(url).toAbsolute(settings.spellchecker_rpc_url),
+ type: "post",
+ content_type: 'application/x-www-form-urlencoded',
+ data: postData,
+ success: function(result) {
+ result = JSON.parse(result);
- // Is Ctrl+c, Ctrl+v or Ctrl+x then use default browser behavior
- if ((tinymce.isMac ? e.metaKey : e.ctrlKey) && (keyCode == 67 || keyCode == 88 || keyCode == 86)) {
- return;
+ if (!result) {
+ errorCallback("Sever response wasn't proper JSON.");
+ } else if (result.error) {
+ errorCallback(result.error);
+ } else {
+ doneCallback(result);
+ }
+ },
+ error: function(type, xhr) {
+ errorCallback("Spellchecker request error: " + xhr.status);
}
+ });
+ }
- e.preventDefault();
+ function sendRpcCall(name, data, successCallback, errorCallback) {
+ var spellCheckCallback = settings.spellchecker_callback || defaultSpellcheckCallback;
+ spellCheckCallback.call(self, name, data, successCallback, errorCallback);
+ }
- // Arrow left/right select the element and collapse left/right
- if (isDirectionKey) {
+ function spellcheck() {
+ finish();
- // If a block element find previous or next element to position the caret
- if (editor.dom.isBlock(nonEditableParent)) {
- var targetElement = left ? nonEditableParent.previousSibling : nonEditableParent.nextSibling;
+ if (started) {
+ return;
+ }
- // Handling for edge-cases:
- // - two nonEditables in a row -> no way to get between them
- // - nonEditable as the first/last element -> no way to get before/behind it
- if (!targetElement || targetElement && getContentEditable(targetElement) === 'false') {
- var p = dom.create('p', null, ' ');
- p.className = 'mceTmpParagraph';
+ function errorCallback(message) {
+ editor.windowManager.alert(message);
+ editor.setProgressState(false);
+ finish();
+ }
+
+ editor.setProgressState(true);
+ sendRpcCall("spellcheck", getTextMatcher().text, markErrors, errorCallback);
+ editor.focus();
+ }
- var insertElement = left ? nonEditableParent : targetElement;
+ function checkIfFinished() {
+ if (!editor.dom.select('span.mce-spellchecker-word').length) {
+ finish();
+ }
+ }
- if (insertElement && insertElement.parentNode) {
- insertElement.parentNode.insertBefore(p, insertElement);
- } else if (!targetElement && !left) {
- nonEditableParent.parentNode.appendChild(p);
- }
+ function addToDictionary(word, spans) {
+ editor.setProgressState(true);
- targetElement = p;
- }
+ sendRpcCall("addToDictionary", word, function() {
+ editor.setProgressState(false);
+ editor.dom.remove(spans, true);
+ checkIfFinished();
+ }, function(message) {
+ editor.windowManager.alert(message);
+ editor.setProgressState(false);
+ });
+ }
- var walker = new TreeWalker(targetElement, targetElement);
- var caretElement = left ? walker.prev() : walker.next();
+ function ignoreWord(word, spans, all) {
+ editor.selection.collapse();
- positionCaretOnElement(caretElement, !left);
- } else {
- positionCaretOnElement(nonEditableParent, left);
+ if (all) {
+ Tools.each(editor.dom.select('span.mce-spellchecker-word'), function(span) {
+ if (span.getAttribute('data-mce-word') == word) {
+ editor.dom.remove(span, true);
}
- }
+ });
} else {
- // Is arrow left/right, backspace or delete
- if (isDirectionKey || keyCode == VK.BACKSPACE || keyCode == VK.DELETE) {
- caretContainer = getParentCaretContainer(startElement);
-
- if (caretContainer) {
- // Arrow left or backspace
- if (keyCode == VK.LEFT || keyCode == VK.BACKSPACE) {
- nonEditableParent = getNonEmptyTextNodeSibling(caretContainer, true);
-
- if (nonEditableParent && getContentEditable(nonEditableParent) === "false") {
- e.preventDefault();
-
- if (keyCode == VK.LEFT) {
- positionCaretOnElement(nonEditableParent, true);
- } else {
- dom.remove(nonEditableParent);
- return;
- }
- } else {
- removeCaretContainer(caretContainer);
- }
- }
+ editor.dom.remove(spans, true);
+ }
- // Arrow right or delete
- if (keyCode == VK.RIGHT || keyCode == VK.DELETE) {
- nonEditableParent = getNonEmptyTextNodeSibling(caretContainer, true);
+ checkIfFinished();
+ }
- if (nonEditableParent && getContentEditable(nonEditableParent) === "false") {
- e.preventDefault();
+ function finish() {
+ getTextMatcher().reset();
+ self.textMatcher = null;
- if (keyCode == VK.RIGHT) {
- positionCaretOnElement(nonEditableParent, false);
- } else {
- dom.remove(nonEditableParent);
- return;
- }
- } else {
- removeCaretContainer(caretContainer);
- }
- }
- } else {
+ if (started) {
+ started = false;
+ editor.fire('SpellcheckEnd');
+ }
+ }
- if (isDirectionKey) {
- // Removal of separator paragraphs between two nonEditables
- // and before/after a nonEditable as the first/last element
- if (currentNode && currentNode.className.indexOf('mceTmpParagraph') !== -1 &&
- currentNode[left ? 'previousSibling' : 'nextSibling']) {
- var jumpTarget = currentNode[left ? 'previousSibling' : 'nextSibling'];
+ function getElmIndex(elm) {
+ var value = elm.getAttribute('data-mce-index');
- // current node is still empty and a separator -> remove it
- // else: remove the separator class, as it now includes content
- if (currentNode.innerHTML === ' ' || currentNode.innerHTML === '' || currentNode.innerHTML === ' ') {
- dom.remove(currentNode);
- } else {
- currentNode.className = currentNode.className.replace('mceTmpParagraph', '');
- }
+ if (typeof value == "number") {
+ return "" + value;
+ }
- positionCaretOnElement(jumpTarget, !left);
- }
- }
+ return value;
+ }
- var rng = selection.getRng(true);
- var container = rng.endContainer;
+ function findSpansByIndex(index) {
+ var nodes, spans = [];
- // FIX: If end of node is selected, check wether next sibling is nonEditable to correctly remove it
- // (else would break for more complex nonEditables, their content would get moved to the current node)
- if (dom.isBlock(container) && dom.isBlock(container.nextSibling) && rng.endOffset == 1 && keyCode == VK.DELETE) {
- nonEditableParent = getNonEditableParent(container.nextSibling);
- }
+ nodes = Tools.toArray(editor.getBody().getElementsByTagName('span'));
+ if (nodes.length) {
+ for (var i = 0; i < nodes.length; i++) {
+ var nodeIndex = getElmIndex(nodes[i]);
- // correctly remove block-level nonEditable domNode on delete/backspace
- if (nonEditableParent && (keyCode == VK.DELETE || keyCode == VK.BACKSPACE) && dom.isBlock(nonEditableParent)) {
- e.preventDefault();
- dom.remove(nonEditableParent);
- return;
- }
+ if (nodeIndex === null || !nodeIndex.length) {
+ continue;
}
- if ((keyCode == VK.BACKSPACE || keyCode == VK.DELETE) && !canDelete(keyCode == VK.BACKSPACE)) {
- e.preventDefault();
- return false;
+ if (nodeIndex === index.toString()) {
+ spans.push(nodes[i]);
}
}
}
+
+ return spans;
}
- editor.on('mousedown', function(e) {
- var node = editor.selection.getNode();
+ editor.on('click', function(e) {
+ var target = e.target;
- // Also remove separator lines when clicking on another node
- if (node && node.className.indexOf('mceTmpParagraph') !== -1 && node !== e.target) {
- // current node is still empty and a separator -> remove it
- // else: remove the separator class, as it now includes content
- if (node.innerHTML === ' ' || node.innerHTML === '' || node.innerHTML === ' ') {
- dom.remove(node);
- } else {
- node.className = node.className.replace('mceTmpParagraph', '');
- }
- }
+ if (target.className == "mce-spellchecker-word") {
+ e.preventDefault();
- if (getContentEditable(node) === "false" && node == e.target) {
- // Expand selection on mouse down we can't block the default event since it's used for drag/drop
- moveSelection();
+ var spans = findSpansByIndex(getElmIndex(target));
+
+ if (spans.length > 0) {
+ var rng = editor.dom.createRng();
+ rng.setStartBefore(spans[0]);
+ rng.setEndAfter(spans[spans.length - 1]);
+ editor.selection.setRng(rng);
+ showSuggestions(target.getAttribute('data-mce-word'), spans);
+ }
}
});
- editor.on('mouseup', moveSelection);
+ editor.addMenuItem('spellchecker', {
+ text: 'Spellcheck',
+ context: 'tools',
+ onclick: spellcheck,
+ selectable: true,
+ onPostRender: function() {
+ var self = this;
- editor.on('keydown', handleKey);
- }
+ self.active(started);
- var editClass, nonEditClass, nonEditableRegExps;
+ editor.on('SpellcheckStart SpellcheckEnd', function() {
+ self.active(started);
+ });
+ }
+ });
- // Converts configured regexps to noneditable span items
- function convertRegExpsToNonEditable(e) {
- var i = nonEditableRegExps.length, content = e.content, cls = tinymce.trim(nonEditClass);
+ function updateSelection(e) {
+ var selectedLanguage = settings.spellchecker_language;
- // Don't replace the variables when raw is used for example on undo/redo
- if (e.format == "raw") {
- return;
+ e.control.items().each(function(ctrl) {
+ ctrl.active(ctrl.settings.data === selectedLanguage);
+ });
}
- while (i--) {
- content = content.replace(nonEditableRegExps[i], function(match) {
- var args = arguments, index = args[args.length - 2];
-
- // Is value inside an attribute then don't replace
- if (index > 0 && content.charAt(index - 1) == '"') {
- return match;
- }
+ /**
+ * Find the specified words and marks them. It will also show suggestions for those words.
+ *
+ * @example
+ * editor.plugins.spellchecker.markErrors({
+ * dictionary: true,
+ * words: {
+ * "word1": ["suggestion 1", "Suggestion 2"]
+ * }
+ * });
+ * @param {Object} data Data object containing the words with suggestions.
+ */
+ function markErrors(data) {
+ var suggestions;
- return (
- '' +
- editor.dom.encode(typeof args[1] === "string" ? args[1] : args[0]) + ''
- );
- });
- }
+ if (data.words) {
+ hasDictionarySupport = !!data.dictionary;
+ suggestions = data.words;
+ } else {
+ // Fallback to old format
+ suggestions = data;
+ }
- e.content = content;
- }
+ editor.setProgressState(false);
- editClass = " " + tinymce.trim(editor.getParam("noneditable_editable_class", "mceEditable")) + " ";
- nonEditClass = " " + tinymce.trim(editor.getParam("noneditable_noneditable_class", "mceNonEditable")) + " ";
+ if (isEmpty(suggestions)) {
+ editor.windowManager.alert('No misspellings found');
+ started = false;
+ return;
+ }
- // Setup noneditable regexps array
- nonEditableRegExps = editor.getParam("noneditable_regexp");
- if (nonEditableRegExps && !nonEditableRegExps.length) {
- nonEditableRegExps = [nonEditableRegExps];
- }
+ lastSuggestions = suggestions;
- editor.on('PreInit', function() {
- handleContentEditableSelection();
+ getTextMatcher().find(getWordCharPattern()).filter(function(match) {
+ return !!suggestions[match.text];
+ }).wrap(function(match) {
+ return editor.dom.create('span', {
+ "class": 'mce-spellchecker-word',
+ "data-mce-bogus": 1,
+ "data-mce-word": match.text
+ });
+ });
- if (nonEditableRegExps) {
- editor.on('BeforeSetContent', convertRegExpsToNonEditable);
+ started = true;
+ editor.fire('SpellcheckStart');
}
- // Apply contentEditable true/false on elements with the noneditable/editable classes
- editor.parser.addAttributeFilter('class', function(nodes) {
- var i = nodes.length, className, node;
-
- while (i--) {
- node = nodes[i];
- className = " " + node.attr("class") + " ";
+ var buttonArgs = {
+ tooltip: 'Spellcheck',
+ onclick: spellcheck,
+ onPostRender: function() {
+ var self = this;
- if (className.indexOf(editClass) !== -1) {
- node.attr(internalName, "true");
- } else if (className.indexOf(nonEditClass) !== -1) {
- node.attr(internalName, "false");
- }
+ editor.on('SpellcheckStart SpellcheckEnd', function() {
+ self.active(started);
+ });
}
- });
+ };
- // Remove internal name
- editor.serializer.addAttributeFilter(internalName, function(nodes) {
- var i = nodes.length, node;
+ if (languageMenuItems.length > 1) {
+ buttonArgs.type = 'splitbutton';
+ buttonArgs.menu = languageMenuItems;
+ buttonArgs.onshow = updateSelection;
+ buttonArgs.onselect = function(e) {
+ settings.spellchecker_language = e.control.settings.data;
+ };
+ }
- while (i--) {
- node = nodes[i];
+ editor.addButton('spellchecker', buttonArgs);
+ editor.addCommand('mceSpellCheck', spellcheck);
- if (nonEditableRegExps && node.attr('data-mce-content')) {
- node.name = "#text";
- node.type = 3;
- node.raw = true;
- node.value = node.attr('data-mce-content');
- } else {
- node.attr(externalName, null);
- node.attr(internalName, null);
- }
+ editor.on('remove', function() {
+ if (suggestionsMenu) {
+ suggestionsMenu.remove();
+ suggestionsMenu = null;
}
});
- // Convert external name into internal name
- editor.parser.addAttributeFilter(externalName, function(nodes) {
- var i = nodes.length, node;
+ editor.on('change', checkIfFinished);
- while (i--) {
- node = nodes[i];
- node.attr(internalName, node.attr(externalName));
- node.attr(externalName, null);
- }
- });
- });
+ this.getTextMatcher = getTextMatcher;
+ this.getWordCharPattern = getWordCharPattern;
+ this.markErrors = markErrors;
+ this.getLanguage = function() {
+ return settings.spellchecker_language;
+ };
- editor.on('drop', function(e) {
- if (getNonEditableParent(e.target)) {
- e.preventDefault();
- }
+ // Set default spellchecker language if it's not specified
+ settings.spellchecker_language = settings.spellchecker_language || settings.language || 'en';
});
});
+expose(["tinymce/spellcheckerplugin/DomTextMatcher"]);
+})(this);
+
}).apply(root, arguments);
});
}(this));
(function(root) {
-define("tinymce-pagebreak", ["tinymce"], function() {
+define("tinymce-tabfocus", ["tinymce"], function() {
return (function() {
/**
* plugin.js
@@ -54726,80 +70706,112 @@ define("tinymce-pagebreak", ["tinymce"], function() {
/*global tinymce:true */
-tinymce.PluginManager.add('pagebreak', function(editor) {
- var pageBreakClass = 'mce-pagebreak', separatorHtml = editor.getParam('pagebreak_separator', '');
+tinymce.PluginManager.add('tabfocus', function(editor) {
+ var DOM = tinymce.DOM, each = tinymce.each, explode = tinymce.explode;
- var pageBreakSeparatorRegExp = new RegExp(separatorHtml.replace(/[\?\.\*\[\]\(\)\{\}\+\^\$\:]/g, function(a) {
- return '\\' + a;
- }), 'gi');
+ function tabCancel(e) {
+ if (e.keyCode === 9 && !e.ctrlKey && !e.altKey && !e.metaKey) {
+ e.preventDefault();
+ }
+ }
- var pageBreakPlaceHolderHtml = '';
+ function tabHandler(e) {
+ var x, el, v, i;
- // Register commands
- editor.addCommand('mcePageBreak', function() {
- if (editor.settings.pagebreak_split_block) {
- editor.insertContent('' + pageBreakPlaceHolderHtml + '
');
- } else {
- editor.insertContent(pageBreakPlaceHolderHtml);
+ if (e.keyCode !== 9 || e.ctrlKey || e.altKey || e.metaKey || e.isDefaultPrevented()) {
+ return;
}
- });
- // Register buttons
- editor.addButton('pagebreak', {
- title: 'Page break',
- cmd: 'mcePageBreak'
- });
+ function find(direction) {
+ el = DOM.select(':input:enabled,*[tabindex]:not(iframe)');
- editor.addMenuItem('pagebreak', {
- text: 'Page break',
- icon: 'pagebreak',
- cmd: 'mcePageBreak',
- context: 'insert'
- });
+ function canSelectRecursive(e) {
+ return e.nodeName === "BODY" || (e.type != 'hidden' &&
+ e.style.display != "none" &&
+ e.style.visibility != "hidden" && canSelectRecursive(e.parentNode));
+ }
- editor.on('ResolveName', function(e) {
- if (e.target.nodeName == 'IMG' && editor.dom.hasClass(e.target, pageBreakClass)) {
- e.name = 'pagebreak';
+ function canSelect(el) {
+ return /INPUT|TEXTAREA|BUTTON/.test(el.tagName) && tinymce.get(e.id) && el.tabIndex != -1 && canSelectRecursive(el);
+ }
+
+ each(el, function(e, i) {
+ if (e.id == editor.id) {
+ x = i;
+ return false;
+ }
+ });
+ if (direction > 0) {
+ for (i = x + 1; i < el.length; i++) {
+ if (canSelect(el[i])) {
+ return el[i];
+ }
+ }
+ } else {
+ for (i = x - 1; i >= 0; i--) {
+ if (canSelect(el[i])) {
+ return el[i];
+ }
+ }
+ }
+
+ return null;
}
- });
- editor.on('click', function(e) {
- e = e.target;
+ v = explode(editor.getParam('tab_focus', editor.getParam('tabfocus_elements', ':prev,:next')));
- if (e.nodeName === 'IMG' && editor.dom.hasClass(e, pageBreakClass)) {
- editor.selection.select(e);
+ if (v.length == 1) {
+ v[1] = v[0];
+ v[0] = ':prev';
}
- });
- editor.on('BeforeSetContent', function(e) {
- e.content = e.content.replace(pageBreakSeparatorRegExp, pageBreakPlaceHolderHtml);
- });
+ // Find element to focus
+ if (e.shiftKey) {
+ if (v[0] == ':prev') {
+ el = find(-1);
+ } else {
+ el = DOM.get(v[0]);
+ }
+ } else {
+ if (v[1] == ':next') {
+ el = find(1);
+ } else {
+ el = DOM.get(v[1]);
+ }
+ }
- editor.on('PreInit', function() {
- editor.serializer.addNodeFilter('img', function(nodes) {
- var i = nodes.length, node, className;
+ if (el) {
+ var focusEditor = tinymce.get(el.id || el.name);
- while (i--) {
- node = nodes[i];
- className = node.attr('class');
- if (className && className.indexOf('mce-pagebreak') !== -1) {
- // Replace parent block node if pagebreak_split_block is enabled
- var parentNode = node.parent;
- if (editor.schema.getBlockElements()[parentNode.name] && editor.settings.pagebreak_split_block) {
- parentNode.type = 3;
- parentNode.value = separatorHtml;
- parentNode.raw = true;
- node.remove();
- continue;
+ if (el.id && focusEditor) {
+ focusEditor.focus();
+ } else {
+ window.setTimeout(function() {
+ if (!tinymce.Env.webkit) {
+ window.focus();
}
- node.type = 3;
- node.value = separatorHtml;
- node.raw = true;
- }
+ el.focus();
+ }, 10);
}
- });
+
+ e.preventDefault();
+ }
+ }
+
+ editor.on('init', function() {
+ if (editor.inline) {
+ // Remove default tabIndex in inline mode
+ tinymce.DOM.setAttrib(editor.getBody(), 'tabIndex', null);
+ }
+
+ editor.on('keyup', tabCancel);
+
+ if (tinymce.Env.gecko) {
+ editor.on('keypress keydown', tabHandler);
+ } else {
+ editor.on('keydown', tabHandler);
+ }
});
});
@@ -54809,7 +70821,7 @@ tinymce.PluginManager.add('pagebreak', function(editor) {
}(this));
(function(root) {
-define("tinymce-paste", ["tinymce"], function() {
+define("tinymce-table", ["tinymce"], function() {
return (function() {
/**
* Compiled inline version. (Library mode)
@@ -54906,14 +70918,51 @@ define("tinymce-paste", ["tinymce"], function() {
delete privateModules[ids[i]];
}
- exports.privateModules = privateModules;
+ exports.privateModules = privateModules;
+ }
+ }
+
+// Included from: js/tinymce/plugins/table/classes/Utils.js
+
+/**
+ * Utils.js
+ *
+ * Released under LGPL License.
+ * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
+ *
+ * License: http://www.tinymce.com/license
+ * Contributing: http://www.tinymce.com/contributing
+ */
+
+/**
+ * Various utility functions.
+ *
+ * @class tinymce.tableplugin.Utils
+ * @private
+ */
+define("tinymce/tableplugin/Utils", [
+ "tinymce/Env"
+], function(Env) {
+ function getSpanVal(td, name) {
+ return parseInt(td.getAttribute(name) || 1, 10);
+ }
+
+ function paddCell(cell) {
+ if (!Env.ie || Env.ie > 10) {
+ cell.innerHTML = '
';
}
}
-// Included from: js/tinymce/plugins/paste/classes/Utils.js
+ return {
+ getSpanVal: getSpanVal,
+ paddCell: paddCell
+ };
+});
+
+// Included from: js/tinymce/plugins/table/classes/TableGrid.js
/**
- * Utils.js
+ * TableGrid.js
*
* Released under LGPL License.
* Copyright (c) 1999-2015 Ephox Corp. All rights reserved
@@ -54923,764 +70972,859 @@ define("tinymce-paste", ["tinymce"], function() {
*/
/**
- * This class contails various utility functions for the paste plugin.
+ * This class creates a grid out of a table element. This
+ * makes it a whole lot easier to handle complex tables with
+ * col/row spans.
*
- * @class tinymce.pasteplugin.Utils
+ * @class tinymce.tableplugin.TableGrid
+ * @private
*/
-define("tinymce/pasteplugin/Utils", [
+define("tinymce/tableplugin/TableGrid", [
"tinymce/util/Tools",
- "tinymce/html/DomParser",
- "tinymce/html/Schema"
-], function(Tools, DomParser, Schema) {
- function filter(content, items) {
- Tools.each(items, function(v) {
- if (v.constructor == RegExp) {
- content = content.replace(v, '');
- } else {
- content = content.replace(v[0], v[1]);
- }
- });
+ "tinymce/Env",
+ "tinymce/tableplugin/Utils"
+], function(Tools, Env, Utils) {
+ var each = Tools.each, getSpanVal = Utils.getSpanVal;
- return content;
- }
+ return function(editor, table) {
+ var grid, gridWidth, startPos, endPos, selectedCell, selection = editor.selection, dom = selection.dom;
- /**
- * Gets the innerText of the specified element. It will handle edge cases
- * and works better than textContent on Gecko.
- *
- * @param {String} html HTML string to get text from.
- * @return {String} String of text with line feeds.
- */
- function innerText(html) {
- var schema = new Schema(), domParser = new DomParser({}, schema), text = '';
- var shortEndedElements = schema.getShortEndedElements();
- var ignoreElements = Tools.makeMap('script noscript style textarea video audio iframe object', ' ');
- var blockElements = schema.getBlockElements();
+ function buildGrid() {
+ var startY = 0;
- function walk(node) {
- var name = node.name, currentNode = node;
+ grid = [];
+ gridWidth = 0;
- if (name === 'br') {
- text += '\n';
- return;
- }
+ each(['thead', 'tbody', 'tfoot'], function(part) {
+ var rows = dom.select('> ' + part + ' tr', table);
- // img/input/hr
- if (shortEndedElements[name]) {
- text += ' ';
- }
+ each(rows, function(tr, y) {
+ y += startY;
- // Ingore script, video contents
- if (ignoreElements[name]) {
- text += ' ';
- return;
- }
+ each(dom.select('> td, > th', tr), function(td, x) {
+ var x2, y2, rowspan, colspan;
- if (node.type == 3) {
- text += node.value;
- }
+ // Skip over existing cells produced by rowspan
+ if (grid[y]) {
+ while (grid[y][x]) {
+ x++;
+ }
+ }
- // Walk all children
- if (!node.shortEnded) {
- if ((node = node.firstChild)) {
- do {
- walk(node);
- } while ((node = node.next));
- }
+ // Get col/rowspan from cell
+ rowspan = getSpanVal(td, 'rowspan');
+ colspan = getSpanVal(td, 'colspan');
+
+ // Fill out rowspan/colspan right and down
+ for (y2 = y; y2 < y + rowspan; y2++) {
+ if (!grid[y2]) {
+ grid[y2] = [];
+ }
+
+ for (x2 = x; x2 < x + colspan; x2++) {
+ grid[y2][x2] = {
+ part: part,
+ real: y2 == y && x2 == x,
+ elm: td,
+ rowspan: rowspan,
+ colspan: colspan
+ };
+ }
+ }
+
+ gridWidth = Math.max(gridWidth, x + 1);
+ });
+ });
+
+ startY += rows.length;
+ });
+ }
+
+ function cloneNode(node, children) {
+ node = node.cloneNode(children);
+ node.removeAttribute('id');
+
+ return node;
+ }
+
+ function getCell(x, y) {
+ var row;
+
+ row = grid[y];
+ if (row) {
+ return row[x];
}
+ }
- // Add \n or \n\n for blocks or P
- if (blockElements[name] && currentNode.next) {
- text += '\n';
+ function setSpanVal(td, name, val) {
+ if (td) {
+ val = parseInt(val, 10);
- if (name == 'p') {
- text += '\n';
+ if (val === 1) {
+ td.removeAttribute(name, 1);
+ } else {
+ td.setAttribute(name, val, 1);
}
}
}
- html = filter(html, [
- //g // Conditional comments
- ]);
+ function isCellSelected(cell) {
+ return cell && (dom.hasClass(cell.elm, 'mce-item-selected') || cell == selectedCell);
+ }
- walk(domParser.parse(html));
+ function getSelectedRows() {
+ var rows = [];
- return text;
- }
+ each(table.rows, function(row) {
+ each(row.cells, function(cell) {
+ if (dom.hasClass(cell, 'mce-item-selected') || (selectedCell && cell == selectedCell.elm)) {
+ rows.push(row);
+ return false;
+ }
+ });
+ });
- /**
- * Trims the specified HTML by removing all WebKit fragments, all elements wrapping the body trailing BR elements etc.
- *
- * @param {String} html Html string to trim contents on.
- * @return {String} Html contents that got trimmed.
- */
- function trimHtml(html) {
- function trimSpaces(all, s1, s2) {
- // WebKit meant to preserve multiple spaces but instead inserted around all inline tags,
- // including the spans with inline styles created on paste
- if (!s1 && !s2) {
- return ' ';
- }
+ return rows;
+ }
- return '\u00a0';
+ function deleteTable() {
+ var rng = dom.createRng();
+
+ rng.setStartAfter(table);
+ rng.setEndAfter(table);
+
+ selection.setRng(rng);
+
+ dom.remove(table);
}
- html = filter(html, [
- /^[\s\S]*]*>\s*|\s*<\/body[^>]*>[\s\S]*$/g, // Remove anything but the contents within the BODY element
- /|/g, // Inner fragments (tables from excel on mac)
- [/( ?)\u00a0<\/span>( ?)/g, trimSpaces],
- /
$/i // Trailing BR elements
- ]);
+ function cloneCell(cell) {
+ var formatNode, cloneFormats = {};
- return html;
- }
+ if (editor.settings.table_clone_elements !== false) {
+ cloneFormats = Tools.makeMap(
+ (editor.settings.table_clone_elements || 'strong em b i span font h1 h2 h3 h4 h5 h6 p div').toUpperCase(),
+ /[ ,]/
+ );
+ }
- return {
- filter: filter,
- innerText: innerText,
- trimHtml: trimHtml
- };
-});
+ // Clone formats
+ Tools.walk(cell, function(node) {
+ var curNode;
-// Included from: js/tinymce/plugins/paste/classes/Clipboard.js
+ if (node.nodeType == 3) {
+ each(dom.getParents(node.parentNode, null, cell).reverse(), function(node) {
+ if (!cloneFormats[node.nodeName]) {
+ return;
+ }
-/**
- * Clipboard.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
+ node = cloneNode(node, false);
-/**
- * This class contains logic for getting HTML contents out of the clipboard.
- *
- * We need to make a lot of ugly hacks to get the contents out of the clipboard since
- * the W3C Clipboard API is broken in all browsers that have it: Gecko/WebKit/Blink.
- * We might rewrite this the way those API:s stabilize. Browsers doesn't handle pasting
- * from applications like Word the same way as it does when pasting into a contentEditable area
- * so we need to do lots of extra work to try to get to this clipboard data.
- *
- * Current implementation steps:
- * 1. On keydown with paste keys Ctrl+V or Shift+Insert create
- * a paste bin element and move focus to that element.
- * 2. Wait for the browser to fire a "paste" event and get the contents out of the paste bin.
- * 3. Check if the paste was successful if true, process the HTML.
- * (4). If the paste was unsuccessful use IE execCommand, Clipboard API, document.dataTransfer old WebKit API etc.
- *
- * @class tinymce.pasteplugin.Clipboard
- * @private
- */
-define("tinymce/pasteplugin/Clipboard", [
- "tinymce/Env",
- "tinymce/dom/RangeUtils",
- "tinymce/util/VK",
- "tinymce/pasteplugin/Utils"
-], function(Env, RangeUtils, VK, Utils) {
- return function(editor) {
- var self = this, pasteBinElm, lastRng, keyboardPasteTimeStamp = 0, draggingInternally = false;
- var pasteBinDefaultContent = '%MCEPASTEBIN%', keyboardPastePlainTextState;
- var mceInternalUrlPrefix = 'data:text/mce-internal,';
+ if (!formatNode) {
+ formatNode = curNode = node;
+ } else if (curNode) {
+ curNode.appendChild(node);
+ }
- /**
- * Pastes the specified HTML. This means that the HTML is filtered and then
- * inserted at the current selection in the editor. It will also fire paste events
- * for custom user filtering.
- *
- * @param {String} html HTML code to paste into the current selection.
- */
- function pasteHtml(html) {
- var args, dom = editor.dom;
+ curNode = node;
+ });
- args = editor.fire('BeforePastePreProcess', {content: html}); // Internal event used by Quirks
- args = editor.fire('PastePreProcess', args);
- html = args.content;
+ // Add something to the inner node
+ if (curNode) {
+ curNode.innerHTML = Env.ie ? ' ' : '
';
+ }
- if (!args.isDefaultPrevented()) {
- // User has bound PastePostProcess events then we need to pass it through a DOM node
- // This is not ideal but we don't want to let the browser mess up the HTML for example
- // some browsers add to P tags etc
- if (editor.hasEventListeners('PastePostProcess') && !args.isDefaultPrevented()) {
- // We need to attach the element to the DOM so Sizzle selectors work on the contents
- var tempBody = dom.add(editor.getBody(), 'div', {style: 'display:none'}, html);
- args = editor.fire('PastePostProcess', {node: tempBody});
- dom.remove(tempBody);
- html = args.node.innerHTML;
+ return false;
}
+ }, 'childNodes');
- if (!args.isDefaultPrevented()) {
- editor.insertContent(html, {merge: editor.settings.paste_merge_formats !== false, data: {paste: true}});
- }
+ cell = cloneNode(cell, false);
+ setSpanVal(cell, 'rowSpan', 1);
+ setSpanVal(cell, 'colSpan', 1);
+
+ if (formatNode) {
+ cell.appendChild(formatNode);
+ } else {
+ Utils.paddCell(cell);
}
+
+ return cell;
}
- /**
- * Pastes the specified text. This means that the plain text is processed
- * and converted into BR and P elements. It will fire paste events for custom filtering.
- *
- * @param {String} text Text to paste as the current selection location.
- */
- function pasteText(text) {
- text = editor.dom.encode(text).replace(/\r\n/g, '\n');
+ function cleanup() {
+ var rng = dom.createRng(), row;
- var startBlock = editor.dom.getParent(editor.selection.getStart(), editor.dom.isBlock);
+ // Empty rows
+ each(dom.select('tr', table), function(tr) {
+ if (tr.cells.length === 0) {
+ dom.remove(tr);
+ }
+ });
- // Create start block html for example
- var forcedRootBlockName = editor.settings.forced_root_block;
- var forcedRootBlockStartHtml;
- if (forcedRootBlockName) {
- forcedRootBlockStartHtml = editor.dom.createHTML(forcedRootBlockName, editor.settings.forced_root_block_attrs);
- forcedRootBlockStartHtml = forcedRootBlockStartHtml.substr(0, forcedRootBlockStartHtml.length - 3) + '>';
+ // Empty table
+ if (dom.select('tr', table).length === 0) {
+ rng.setStartBefore(table);
+ rng.setEndBefore(table);
+ selection.setRng(rng);
+ dom.remove(table);
+ return;
}
- if ((startBlock && /^(PRE|DIV)$/.test(startBlock.nodeName)) || !forcedRootBlockName) {
- text = Utils.filter(text, [
- [/\n/g, "
"]
- ]);
- } else {
- text = Utils.filter(text, [
- [/\n\n/g, "
" + forcedRootBlockStartHtml],
- [/^(.*<\/p>)()$/, forcedRootBlockStartHtml + '$1'],
- [/\n/g, "
"]
- ]);
+ // Empty header/body/footer
+ each(dom.select('thead,tbody,tfoot', table), function(part) {
+ if (part.rows.length === 0) {
+ dom.remove(part);
+ }
+ });
- if (text.indexOf('
') != -1) {
- text = forcedRootBlockStartHtml + text;
+ // Restore selection to start position if it still exists
+ buildGrid();
+
+ // If we have a valid startPos object
+ if (startPos) {
+ // Restore the selection to the closest table position
+ row = grid[Math.min(grid.length - 1, startPos.y)];
+ if (row) {
+ selection.select(row[Math.min(row.length - 1, startPos.x)].elm, true);
+ selection.collapse(true);
}
}
-
- pasteHtml(text);
}
- /**
- * Creates a paste bin element as close as possible to the current caret location and places the focus inside that element
- * so that when the real paste event occurs the contents gets inserted into this element
- * instead of the current editor selection element.
- */
- function createPasteBin() {
- var dom = editor.dom, body = editor.getBody();
- var viewport = editor.dom.getViewPort(editor.getWin()), scrollTop = viewport.y, top = 20;
- var scrollContainer;
+ function fillLeftDown(x, y, rows, cols) {
+ var tr, x2, r, c, cell;
- lastRng = editor.selection.getRng();
+ tr = grid[y][x].elm.parentNode;
+ for (r = 1; r <= rows; r++) {
+ tr = dom.getNext(tr, 'tr');
- if (editor.inline) {
- scrollContainer = editor.selection.getScrollContainer();
+ if (tr) {
+ // Loop left to find real cell
+ for (x2 = x; x2 >= 0; x2--) {
+ cell = grid[y + r][x2].elm;
- // Can't always rely on scrollTop returning a useful value.
- // It returns 0 if the browser doesn't support scrollTop for the element or is non-scrollable
- if (scrollContainer && scrollContainer.scrollTop > 0) {
- scrollTop = scrollContainer.scrollTop;
+ if (cell.parentNode == tr) {
+ // Append clones after
+ for (c = 1; c <= cols; c++) {
+ dom.insertAfter(cloneCell(cell), cell);
+ }
+
+ break;
+ }
+ }
+
+ if (x2 == -1) {
+ // Insert nodes before first cell
+ for (c = 1; c <= cols; c++) {
+ tr.insertBefore(cloneCell(tr.cells[0]), tr.cells[0]);
+ }
+ }
}
}
+ }
- /**
- * Returns the rect of the current caret if the caret is in an empty block before a
- * BR we insert a temporary invisible character that we get the rect this way we always get a proper rect.
- *
- * TODO: This might be useful in core.
- */
- function getCaretRect(rng) {
- var rects, textNode, node, container = rng.startContainer;
+ function split() {
+ each(grid, function(row, y) {
+ each(row, function(cell, x) {
+ var colSpan, rowSpan, i;
- rects = rng.getClientRects();
- if (rects.length) {
- return rects[0];
- }
+ if (isCellSelected(cell)) {
+ cell = cell.elm;
+ colSpan = getSpanVal(cell, 'colspan');
+ rowSpan = getSpanVal(cell, 'rowspan');
- if (!rng.collapsed || container.nodeType != 1) {
- return;
- }
+ if (colSpan > 1 || rowSpan > 1) {
+ setSpanVal(cell, 'rowSpan', 1);
+ setSpanVal(cell, 'colSpan', 1);
- node = container.childNodes[lastRng.startOffset];
+ // Insert cells right
+ for (i = 0; i < colSpan - 1; i++) {
+ dom.insertAfter(cloneCell(cell), cell);
+ }
- // Skip empty whitespace nodes
- while (node && node.nodeType == 3 && !node.data.length) {
- node = node.nextSibling;
+ fillLeftDown(x, y, rowSpan - 1, colSpan);
+ }
+ }
+ });
+ });
+ }
+
+ function merge(cell, cols, rows) {
+ var pos, startX, startY, endX, endY, x, y, startCell, endCell, children, count;
+
+ // Use specified cell and cols/rows
+ if (cell) {
+ pos = getPos(cell);
+ startX = pos.x;
+ startY = pos.y;
+ endX = startX + (cols - 1);
+ endY = startY + (rows - 1);
+ } else {
+ startPos = endPos = null;
+
+ // Calculate start/end pos by checking for selected cells in grid works better with context menu
+ each(grid, function(row, y) {
+ each(row, function(cell, x) {
+ if (isCellSelected(cell)) {
+ if (!startPos) {
+ startPos = {x: x, y: y};
+ }
+
+ endPos = {x: x, y: y};
+ }
+ });
+ });
+
+ // Use selection, but make sure startPos is valid before accessing
+ if (startPos) {
+ startX = startPos.x;
+ startY = startPos.y;
+ endX = endPos.x;
+ endY = endPos.y;
}
+ }
+
+ // Find start/end cells
+ startCell = getCell(startX, startY);
+ endCell = getCell(endX, endY);
+
+ // Check if the cells exists and if they are of the same part for example tbody = tbody
+ if (startCell && endCell && startCell.part == endCell.part) {
+ // Split and rebuild grid
+ split();
+ buildGrid();
+
+ // Set row/col span to start cell
+ startCell = getCell(startX, startY).elm;
+ setSpanVal(startCell, 'colSpan', (endX - startX) + 1);
+ setSpanVal(startCell, 'rowSpan', (endY - startY) + 1);
+
+ // Remove other cells and add it's contents to the start cell
+ for (y = startY; y <= endY; y++) {
+ for (x = startX; x <= endX; x++) {
+ if (!grid[y] || !grid[y][x]) {
+ continue;
+ }
- if (!node) {
- return;
- }
+ cell = grid[y][x].elm;
- // Check if the location is |
- // TODO: Might need to expand this to say |
- if (node.tagName == 'BR') {
- textNode = dom.doc.createTextNode('\uFEFF');
- node.parentNode.insertBefore(textNode, node);
+ /*jshint loopfunc:true */
+ /*eslint no-loop-func:0 */
+ if (cell != startCell) {
+ // Move children to startCell
+ children = Tools.grep(cell.childNodes);
+ each(children, function(node) {
+ startCell.appendChild(node);
+ });
- rng = dom.createRng();
- rng.setStartBefore(textNode);
- rng.setEndAfter(textNode);
+ // Remove bogus nodes if there is children in the target cell
+ if (children.length) {
+ children = Tools.grep(startCell.childNodes);
+ count = 0;
+ each(children, function(node) {
+ if (node.nodeName == 'BR' && dom.getAttrib(node, 'data-mce-bogus') && count++ < children.length - 1) {
+ startCell.removeChild(node);
+ }
+ });
+ }
- rects = rng.getClientRects();
- dom.remove(textNode);
+ dom.remove(cell);
+ }
+ }
}
- if (rects.length) {
- return rects[0];
- }
+ // Remove empty rows etc and restore caret location
+ cleanup();
}
+ }
- // Calculate top cordinate this is needed to avoid scrolling to top of document
- // We want the paste bin to be as close to the caret as possible to avoid scrolling
- if (lastRng.getClientRects) {
- var rect = getCaretRect(lastRng);
-
- if (rect) {
- // Client rects gets us closes to the actual
- // caret location in for example a wrapped paragraph block
- top = scrollTop + (rect.top - dom.getPos(body).y);
- } else {
- top = scrollTop;
+ function insertRow(before) {
+ var posY, cell, lastCell, x, rowElm, newRow, newCell, otherCell, rowSpan;
- // Check if we can find a closer location by checking the range element
- var container = lastRng.startContainer;
- if (container) {
- if (container.nodeType == 3 && container.parentNode != body) {
- container = container.parentNode;
- }
+ // Find first/last row
+ each(grid, function(row, y) {
+ each(row, function(cell) {
+ if (isCellSelected(cell)) {
+ cell = cell.elm;
+ rowElm = cell.parentNode;
+ newRow = cloneNode(rowElm, false);
+ posY = y;
- if (container.nodeType == 1) {
- top = dom.getPos(container, scrollContainer || body).y;
+ if (before) {
+ return false;
}
}
+ });
+
+ if (before) {
+ return !posY;
}
+ });
+
+ // If posY is undefined there is nothing for us to do here...just return to avoid crashing below
+ if (posY === undefined) {
+ return;
}
- // Create a pastebin
- pasteBinElm = dom.add(editor.getBody(), 'div', {
- id: "mcepastebin",
- contentEditable: true,
- "data-mce-bogus": "all",
- style: 'position: absolute; top: ' + top + 'px;' +
- 'width: 10px; height: 10px; overflow: hidden; opacity: 0'
- }, pasteBinDefaultContent);
+ for (x = 0; x < grid[0].length; x++) {
+ // Cell not found could be because of an invalid table structure
+ if (!grid[posY][x]) {
+ continue;
+ }
- // Move paste bin out of sight since the controlSelection rect gets displayed otherwise on IE and Gecko
- if (Env.ie || Env.gecko) {
- dom.setStyle(pasteBinElm, 'left', dom.getStyle(body, 'direction', true) == 'rtl' ? 0xFFFF : -0xFFFF);
- }
+ cell = grid[posY][x].elm;
- // Prevent focus events from bubbeling fixed FocusManager issues
- dom.bind(pasteBinElm, 'beforedeactivate focusin focusout', function(e) {
- e.stopPropagation();
- });
+ if (cell != lastCell) {
+ if (!before) {
+ rowSpan = getSpanVal(cell, 'rowspan');
+ if (rowSpan > 1) {
+ setSpanVal(cell, 'rowSpan', rowSpan + 1);
+ continue;
+ }
+ } else {
+ // Check if cell above can be expanded
+ if (posY > 0 && grid[posY - 1][x]) {
+ otherCell = grid[posY - 1][x].elm;
+ rowSpan = getSpanVal(otherCell, 'rowSpan');
+ if (rowSpan > 1) {
+ setSpanVal(otherCell, 'rowSpan', rowSpan + 1);
+ continue;
+ }
+ }
+ }
- pasteBinElm.focus();
- editor.selection.select(pasteBinElm, true);
- }
+ // Insert new cell into new row
+ newCell = cloneCell(cell);
+ setSpanVal(newCell, 'colSpan', cell.colSpan);
- /**
- * Removes the paste bin if it exists.
- */
- function removePasteBin() {
- if (pasteBinElm) {
- var pasteBinClone;
+ newRow.appendChild(newCell);
- // WebKit/Blink might clone the div so
- // lets make sure we remove all clones
- // TODO: Man o man is this ugly. WebKit is the new IE! Remove this if they ever fix it!
- while ((pasteBinClone = editor.dom.get('mcepastebin'))) {
- editor.dom.remove(pasteBinClone);
- editor.dom.unbind(pasteBinClone);
+ lastCell = cell;
}
+ }
- if (lastRng) {
- editor.selection.setRng(lastRng);
+ if (newRow.hasChildNodes()) {
+ if (!before) {
+ dom.insertAfter(newRow, rowElm);
+ } else {
+ rowElm.parentNode.insertBefore(newRow, rowElm);
}
}
-
- pasteBinElm = lastRng = null;
}
- /**
- * Returns the contents of the paste bin as a HTML string.
- *
- * @return {String} Get the contents of the paste bin.
- */
- function getPasteBinHtml() {
- var html = '', pasteBinClones, i, clone, cloneHtml;
+ function insertCol(before) {
+ var posX, lastCell;
- // Since WebKit/Chrome might clone the paste bin when pasting
- // for example: we need to check if any of them contains some useful html.
- // TODO: Man o man is this ugly. WebKit is the new IE! Remove this if they ever fix it!
- pasteBinClones = editor.dom.select('div[id=mcepastebin]');
- for (i = 0; i < pasteBinClones.length; i++) {
- clone = pasteBinClones[i];
+ // Find first/last column
+ each(grid, function(row) {
+ each(row, function(cell, x) {
+ if (isCellSelected(cell)) {
+ posX = x;
- // Pasting plain text produces pastebins in pastebinds makes sence right!?
- if (clone.firstChild && clone.firstChild.id == 'mcepastebin') {
- clone = clone.firstChild;
- }
+ if (before) {
+ return false;
+ }
+ }
+ });
- cloneHtml = clone.innerHTML;
- if (html != pasteBinDefaultContent) {
- html += cloneHtml;
+ if (before) {
+ return !posX;
}
- }
+ });
- return html;
- }
+ each(grid, function(row, y) {
+ var cell, rowSpan, colSpan;
- /**
- * Gets various content types out of a datatransfer object.
- *
- * @param {DataTransfer} dataTransfer Event fired on paste.
- * @return {Object} Object with mime types and data for those mime types.
- */
- function getDataTransferItems(dataTransfer) {
- var data = {};
+ if (!row[posX]) {
+ return;
+ }
- if (dataTransfer) {
- // Use old WebKit/IE API
- if (dataTransfer.getData) {
- var legacyText = dataTransfer.getData('Text');
- if (legacyText && legacyText.length > 0) {
- if (legacyText.indexOf(mceInternalUrlPrefix) == -1) {
- data['text/plain'] = legacyText;
+ cell = row[posX].elm;
+ if (cell != lastCell) {
+ colSpan = getSpanVal(cell, 'colspan');
+ rowSpan = getSpanVal(cell, 'rowspan');
+
+ if (colSpan == 1) {
+ if (!before) {
+ dom.insertAfter(cloneCell(cell), cell);
+ fillLeftDown(posX, y, rowSpan - 1, colSpan);
+ } else {
+ cell.parentNode.insertBefore(cloneCell(cell), cell);
+ fillLeftDown(posX, y, rowSpan - 1, colSpan);
}
+ } else {
+ setSpanVal(cell, 'colSpan', cell.colSpan + 1);
}
+
+ lastCell = cell;
}
+ });
+ }
- if (dataTransfer.types) {
- for (var i = 0; i < dataTransfer.types.length; i++) {
- var contentType = dataTransfer.types[i];
- data[contentType] = dataTransfer.getData(contentType);
+ function deleteCols() {
+ var cols = [];
+
+ // Get selected column indexes
+ each(grid, function(row) {
+ each(row, function(cell, x) {
+ if (isCellSelected(cell) && Tools.inArray(cols, x) === -1) {
+ each(grid, function(row) {
+ var cell = row[x].elm, colSpan;
+
+ colSpan = getSpanVal(cell, 'colSpan');
+
+ if (colSpan > 1) {
+ setSpanVal(cell, 'colSpan', colSpan - 1);
+ } else {
+ dom.remove(cell);
+ }
+ });
+
+ cols.push(x);
}
- }
- }
+ });
+ });
- return data;
+ cleanup();
}
- /**
- * Gets various content types out of the Clipboard API. It will also get the
- * plain text using older IE and WebKit API:s.
- *
- * @param {ClipboardEvent} clipboardEvent Event fired on paste.
- * @return {Object} Object with mime types and data for those mime types.
- */
- function getClipboardContent(clipboardEvent) {
- return getDataTransferItems(clipboardEvent.clipboardData || editor.getDoc().dataTransfer);
- }
+ function deleteRows() {
+ var rows;
- /**
- * Checks if the clipboard contains image data if it does it will take that data
- * and convert it into a data url image and paste that image at the caret location.
- *
- * @param {ClipboardEvent} e Paste/drop event object.
- * @param {DOMRange} rng Optional rng object to move selection to.
- * @return {Boolean} true/false if the image data was found or not.
- */
- function pasteImageData(e, rng) {
- var dataTransfer = e.clipboardData || e.dataTransfer;
+ function deleteRow(tr) {
+ var pos, lastCell;
- function processItems(items) {
- var i, item, reader, hadImage = false;
+ // Move down row spanned cells
+ each(tr.cells, function(cell) {
+ var rowSpan = getSpanVal(cell, 'rowSpan');
- function pasteImage(reader) {
- if (rng) {
- editor.selection.setRng(rng);
- rng = null;
+ if (rowSpan > 1) {
+ setSpanVal(cell, 'rowSpan', rowSpan - 1);
+ pos = getPos(cell);
+ fillLeftDown(pos.x, pos.y, 1, 1);
}
+ });
- pasteHtml('');
- }
+ // Delete cells
+ pos = getPos(tr.cells[0]);
+ each(grid[pos.y], function(cell) {
+ var rowSpan;
- if (items) {
- for (i = 0; i < items.length; i++) {
- item = items[i];
+ cell = cell.elm;
- if (/^image\/(jpeg|png|gif|bmp)$/.test(item.type)) {
- reader = new FileReader();
- reader.onload = pasteImage.bind(null, reader);
- reader.readAsDataURL(item.getAsFile ? item.getAsFile() : item);
+ if (cell != lastCell) {
+ rowSpan = getSpanVal(cell, 'rowSpan');
- e.preventDefault();
- hadImage = true;
+ if (rowSpan <= 1) {
+ dom.remove(cell);
+ } else {
+ setSpanVal(cell, 'rowSpan', rowSpan - 1);
}
- }
- }
- return hadImage;
+ lastCell = cell;
+ }
+ });
}
- if (editor.settings.paste_data_images && dataTransfer) {
- return processItems(dataTransfer.items) || processItems(dataTransfer.files);
- }
- }
+ // Get selected rows and move selection out of scope
+ rows = getSelectedRows();
- /**
- * Chrome on Android doesn't support proper clipboard access so we have no choice but to allow the browser default behavior.
- *
- * @param {Event} e Paste event object to check if it contains any data.
- * @return {Boolean} true/false if the clipboard is empty or not.
- */
- function isBrokenAndroidClipboardEvent(e) {
- var clipboardData = e.clipboardData;
+ // Delete all selected rows
+ each(rows.reverse(), function(tr) {
+ deleteRow(tr);
+ });
- return navigator.userAgent.indexOf('Android') != -1 && clipboardData && clipboardData.items && clipboardData.items.length === 0;
+ cleanup();
}
- function getCaretRangeFromEvent(e) {
- return RangeUtils.getCaretRangeFromPoint(e.clientX, e.clientY, editor.getDoc());
- }
+ function cutRows() {
+ var rows = getSelectedRows();
- function hasContentType(clipboardContent, mimeType) {
- return mimeType in clipboardContent && clipboardContent[mimeType].length > 0;
- }
+ dom.remove(rows);
+ cleanup();
- function isKeyboardPasteEvent(e) {
- return (VK.metaKeyPressed(e) && e.keyCode == 86) || (e.shiftKey && e.keyCode == 45);
+ return rows;
}
- function registerEventHandlers() {
- editor.on('keydown', function(e) {
- function removePasteBinOnKeyUp(e) {
- // Ctrl+V or Shift+Insert
- if (isKeyboardPasteEvent(e) && !e.isDefaultPrevented()) {
- removePasteBin();
- }
- }
+ function copyRows() {
+ var rows = getSelectedRows();
- // Ctrl+V or Shift+Insert
- if (isKeyboardPasteEvent(e) && !e.isDefaultPrevented()) {
- keyboardPastePlainTextState = e.shiftKey && e.keyCode == 86;
+ each(rows, function(row, i) {
+ rows[i] = cloneNode(row, true);
+ });
- // Edge case on Safari on Mac where it doesn't handle Cmd+Shift+V correctly
- // it fires the keydown but no paste or keyup so we are left with a paste bin
- if (keyboardPastePlainTextState && Env.webkit && navigator.userAgent.indexOf('Version/') != -1) {
- return;
- }
+ return rows;
+ }
- // Prevent undoManager keydown handler from making an undo level with the pastebin in it
- e.stopImmediatePropagation();
+ function pasteRows(rows, before) {
+ var selectedRows = getSelectedRows(),
+ targetRow = selectedRows[before ? 0 : selectedRows.length - 1],
+ targetCellCount = targetRow.cells.length;
- keyboardPasteTimeStamp = new Date().getTime();
+ // Nothing to paste
+ if (!rows) {
+ return;
+ }
- // IE doesn't support Ctrl+Shift+V and it doesn't even produce a paste event
- // so lets fake a paste event and let IE use the execCommand/dataTransfer methods
- if (Env.ie && keyboardPastePlainTextState) {
- e.preventDefault();
- editor.fire('paste', {ieFake: true});
- return;
+ // Calc target cell count
+ each(grid, function(row) {
+ var match;
+
+ targetCellCount = 0;
+ each(row, function(cell) {
+ if (cell.real) {
+ targetCellCount += cell.colspan;
}
- removePasteBin();
- createPasteBin();
+ if (cell.elm.parentNode == targetRow) {
+ match = 1;
+ }
+ });
- // Remove pastebin if we get a keyup and no paste event
- // For example pasting a file in IE 11 will not produce a paste event
- editor.once('keyup', removePasteBinOnKeyUp);
- editor.once('paste', function() {
- editor.off('keyup', removePasteBinOnKeyUp);
- });
+ if (match) {
+ return false;
}
});
- editor.on('paste', function(e) {
- // Getting content from the Clipboard can take some time
- var clipboardTimer = new Date().getTime();
- var clipboardContent = getClipboardContent(e);
- var clipboardDelay = new Date().getTime() - clipboardTimer;
+ if (!before) {
+ rows.reverse();
+ }
- var isKeyBoardPaste = (new Date().getTime() - keyboardPasteTimeStamp - clipboardDelay) < 1000;
- var plainTextMode = self.pasteFormat == "text" || keyboardPastePlainTextState;
+ each(rows, function(row) {
+ var i, cellCount = row.cells.length, cell;
- keyboardPastePlainTextState = false;
+ // Remove col/rowspans
+ for (i = 0; i < cellCount; i++) {
+ cell = row.cells[i];
+ setSpanVal(cell, 'colSpan', 1);
+ setSpanVal(cell, 'rowSpan', 1);
+ }
- if (e.isDefaultPrevented() || isBrokenAndroidClipboardEvent(e)) {
- removePasteBin();
- return;
+ // Needs more cells
+ for (i = cellCount; i < targetCellCount; i++) {
+ row.appendChild(cloneCell(row.cells[cellCount - 1]));
}
- if (pasteImageData(e)) {
- removePasteBin();
- return;
+ // Needs less cells
+ for (i = targetCellCount; i < cellCount; i++) {
+ dom.remove(row.cells[i]);
}
- // Not a keyboard paste prevent default paste and try to grab the clipboard contents using different APIs
- if (!isKeyBoardPaste) {
- e.preventDefault();
+ // Add before/after
+ if (before) {
+ targetRow.parentNode.insertBefore(row, targetRow);
+ } else {
+ dom.insertAfter(row, targetRow);
}
+ });
- // Try IE only method if paste isn't a keyboard paste
- if (Env.ie && (!isKeyBoardPaste || e.ieFake)) {
- createPasteBin();
+ // Remove current selection
+ dom.removeClass(dom.select('td.mce-item-selected,th.mce-item-selected'), 'mce-item-selected');
+ }
- editor.dom.bind(pasteBinElm, 'paste', function(e) {
- e.stopPropagation();
- });
+ function getPos(target) {
+ var pos;
- editor.getDoc().execCommand('Paste', false, null);
- clipboardContent["text/html"] = getPasteBinHtml();
- }
+ each(grid, function(row, y) {
+ each(row, function(cell, x) {
+ if (cell.elm == target) {
+ pos = {x: x, y: y};
+ return false;
+ }
+ });
- setTimeout(function() {
- var content;
+ return !pos;
+ });
- // Grab HTML from Clipboard API or paste bin as a fallback
- if (hasContentType(clipboardContent, 'text/html')) {
- content = clipboardContent['text/html'];
- } else {
- content = getPasteBinHtml();
+ return pos;
+ }
- // If paste bin is empty try using plain text mode
- // since that is better than nothing right
- if (content == pasteBinDefaultContent) {
- plainTextMode = true;
- }
- }
+ function setStartCell(cell) {
+ startPos = getPos(cell);
+ }
- content = Utils.trimHtml(content);
+ function findEndPos() {
+ var maxX, maxY;
- // WebKit has a nice bug where it clones the paste bin if you paste from for example notepad
- // so we need to force plain text mode in this case
- if (pasteBinElm && pasteBinElm.firstChild && pasteBinElm.firstChild.id === 'mcepastebin') {
- plainTextMode = true;
- }
+ maxX = maxY = 0;
- removePasteBin();
+ each(grid, function(row, y) {
+ each(row, function(cell, x) {
+ var colSpan, rowSpan;
- // If we got nothing from clipboard API and pastebin then we could try the last resort: plain/text
- if (!content.length) {
- plainTextMode = true;
- }
+ if (isCellSelected(cell)) {
+ cell = grid[y][x];
- // Grab plain text from Clipboard API or convert existing HTML to plain text
- if (plainTextMode) {
- // Use plain text contents from Clipboard API unless the HTML contains paragraphs then
- // we should convert the HTML to plain text since works better when pasting HTML/Word contents as plain text
- if (hasContentType(clipboardContent, 'text/plain') && content.indexOf('') == -1) {
- content = clipboardContent['text/plain'];
- } else {
- content = Utils.innerText(content);
+ if (x > maxX) {
+ maxX = x;
}
- }
- // If the content is the paste bin default HTML then it was
- // impossible to get the cliboard data out.
- if (content == pasteBinDefaultContent) {
- if (!isKeyBoardPaste) {
- editor.windowManager.alert('Please use Ctrl+V/Cmd+V keyboard shortcuts to paste contents.');
+ if (y > maxY) {
+ maxY = y;
}
- return;
- }
+ if (cell.real) {
+ colSpan = cell.colspan - 1;
+ rowSpan = cell.rowspan - 1;
- if (plainTextMode) {
- pasteText(content);
- } else {
- pasteHtml(content);
+ if (colSpan) {
+ if (x + colSpan > maxX) {
+ maxX = x + colSpan;
+ }
+ }
+
+ if (rowSpan) {
+ if (y + rowSpan > maxY) {
+ maxY = y + rowSpan;
+ }
+ }
+ }
}
- }, 0);
+ });
});
- editor.on('dragstart dragend', function(e) {
- draggingInternally = e.type == 'dragstart';
- });
+ return {x: maxX, y: maxY};
+ }
- editor.on('drop', function(e) {
- var rng = getCaretRangeFromEvent(e);
+ function setEndCell(cell) {
+ var startX, startY, endX, endY, maxX, maxY, colSpan, rowSpan, x, y;
- if (e.isDefaultPrevented() || draggingInternally) {
- return;
- }
+ endPos = getPos(cell);
- if (pasteImageData(e, rng)) {
- return;
+ if (startPos && endPos) {
+ // Get start/end positions
+ startX = Math.min(startPos.x, endPos.x);
+ startY = Math.min(startPos.y, endPos.y);
+ endX = Math.max(startPos.x, endPos.x);
+ endY = Math.max(startPos.y, endPos.y);
+
+ // Expand end positon to include spans
+ maxX = endX;
+ maxY = endY;
+
+ // Expand startX
+ for (y = startY; y <= maxY; y++) {
+ cell = grid[y][startX];
+
+ if (!cell.real) {
+ if (startX - (cell.colspan - 1) < startX) {
+ startX -= cell.colspan - 1;
+ }
+ }
}
- if (rng && editor.settings.paste_filter_drop !== false) {
- var dropContent = getDataTransferItems(e.dataTransfer);
- var content = dropContent['mce-internal'] || dropContent['text/html'] || dropContent['text/plain'];
+ // Expand startY
+ for (x = startX; x <= maxX; x++) {
+ cell = grid[startY][x];
- if (content) {
- e.preventDefault();
+ if (!cell.real) {
+ if (startY - (cell.rowspan - 1) < startY) {
+ startY -= cell.rowspan - 1;
+ }
+ }
+ }
- editor.undoManager.transact(function() {
- if (dropContent['mce-internal']) {
- editor.execCommand('Delete');
- }
+ // Find max X, Y
+ for (y = startY; y <= endY; y++) {
+ for (x = startX; x <= endX; x++) {
+ cell = grid[y][x];
- editor.selection.setRng(rng);
+ if (cell.real) {
+ colSpan = cell.colspan - 1;
+ rowSpan = cell.rowspan - 1;
- content = Utils.trimHtml(content);
+ if (colSpan) {
+ if (x + colSpan > maxX) {
+ maxX = x + colSpan;
+ }
+ }
- if (!dropContent['text/html']) {
- pasteText(content);
- } else {
- pasteHtml(content);
+ if (rowSpan) {
+ if (y + rowSpan > maxY) {
+ maxY = y + rowSpan;
+ }
}
- });
+ }
}
}
- });
- editor.on('dragover dragend', function(e) {
- if (editor.settings.paste_data_images) {
- e.preventDefault();
+ // Remove current selection
+ dom.removeClass(dom.select('td.mce-item-selected,th.mce-item-selected'), 'mce-item-selected');
+
+ // Add new selection
+ for (y = startY; y <= maxY; y++) {
+ for (x = startX; x <= maxX; x++) {
+ if (grid[y][x]) {
+ dom.addClass(grid[y][x].elm, 'mce-item-selected');
+ }
+ }
}
- });
+ }
}
- self.pasteHtml = pasteHtml;
- self.pasteText = pasteText;
+ function moveRelIdx(cellElm, delta) {
+ var pos, index, cell;
- editor.on('preInit', function() {
- registerEventHandlers();
+ pos = getPos(cellElm);
+ index = pos.y * gridWidth + pos.x;
- // Remove all data images from paste for example from Gecko
- // except internal images like video elements
- editor.parser.addNodeFilter('img', function(nodes, name, args) {
- function isPasteInsert(args) {
- return args.data && args.data.paste === true;
+ do {
+ index += delta;
+ cell = getCell(index % gridWidth, Math.floor(index / gridWidth));
+
+ if (!cell) {
+ break;
}
- function remove(node) {
- if (!node.attr('data-mce-object') && src !== Env.transparentSrc) {
- node.remove();
+ if (cell.elm != cellElm) {
+ selection.select(cell.elm, true);
+
+ if (dom.isEmpty(cell.elm)) {
+ selection.collapse(true);
}
- }
- function isWebKitFakeUrl(src) {
- return src.indexOf("webkit-fake-url") === 0;
+ return true;
}
+ } while (cell.elm == cellElm);
- function isDataUri(src) {
- return src.indexOf("data:") === 0;
- }
+ return false;
+ }
- if (!editor.settings.paste_data_images && isPasteInsert(args)) {
- var i = nodes.length;
+ table = table || dom.getParent(selection.getStart(), 'table');
- while (i--) {
- var src = nodes[i].attributes.map.src;
+ buildGrid();
- if (!src) {
- continue;
- }
+ selectedCell = dom.getParent(selection.getStart(), 'th,td');
+ if (selectedCell) {
+ startPos = getPos(selectedCell);
+ endPos = findEndPos();
+ selectedCell = getCell(startPos.x, startPos.y);
+ }
- // Safari on Mac produces webkit-fake-url see: https://bugs.webkit.org/show_bug.cgi?id=49141
- if (isWebKitFakeUrl(src)) {
- remove(nodes[i]);
- } else if (!editor.settings.allow_html_data_urls && isDataUri(src)) {
- remove(nodes[i]);
- }
- }
- }
- });
+ Tools.extend(this, {
+ deleteTable: deleteTable,
+ split: split,
+ merge: merge,
+ insertRow: insertRow,
+ insertCol: insertCol,
+ deleteCols: deleteCols,
+ deleteRows: deleteRows,
+ cutRows: cutRows,
+ copyRows: copyRows,
+ pasteRows: pasteRows,
+ getPos: getPos,
+ setStartCell: setStartCell,
+ setEndCell: setEndCell,
+ moveRelIdx: moveRelIdx,
+ refresh: buildGrid
});
};
});
-// Included from: js/tinymce/plugins/paste/classes/WordFilter.js
+// Included from: js/tinymce/plugins/table/classes/Quirks.js
/**
- * WordFilter.js
+ * Quirks.js
*
* Released under LGPL License.
* Copyright (c) 1999-2015 Ephox Corp. All rights reserved
@@ -55690,498 +71834,397 @@ define("tinymce/pasteplugin/Clipboard", [
*/
/**
- * This class parses word HTML into proper TinyMCE markup.
+ * This class includes fixes for various browser quirks.
*
- * @class tinymce.pasteplugin.WordFilter
+ * @class tinymce.tableplugin.Quirks
* @private
*/
-define("tinymce/pasteplugin/WordFilter", [
+define("tinymce/tableplugin/Quirks", [
+ "tinymce/util/VK",
+ "tinymce/Env",
"tinymce/util/Tools",
- "tinymce/html/DomParser",
- "tinymce/html/Schema",
- "tinymce/html/Serializer",
- "tinymce/html/Node",
- "tinymce/pasteplugin/Utils"
-], function(Tools, DomParser, Schema, Serializer, Node, Utils) {
- /**
- * Checks if the specified content is from any of the following sources: MS Word/Office 365/Google docs.
- */
- function isWordContent(content) {
- return (
- (/]+id="?docs-internal-[^>]*>/gi, '');
- content = content.replace(/
/gi, '');
-
- retainStyleProperties = settings.paste_retain_style_properties;
- if (retainStyleProperties) {
- validStyles = Tools.makeMap(retainStyleProperties.split(/[, ]/));
- }
-
- /**
- * Converts fake bullet and numbered lists to real semantic OL/UL.
- *
- * @param {tinymce.html.Node} node Root node to convert children of.
- */
- function convertFakeListsToProperLists(node) {
- var currentListNode, prevListNode, lastLevel = 1;
+ "tinymce/tableplugin/Utils"
+], function(VK, Env, Tools, Utils) {
+ var each = Tools.each, getSpanVal = Utils.getSpanVal;
- function getText(node) {
- var txt = '';
+ return function(editor) {
+ /**
+ * Fixed caret movement around tables on WebKit.
+ */
+ function moveWebKitSelection() {
+ function eventHandler(e) {
+ var key = e.keyCode;
- if (node.type === 3) {
- return node.value;
- }
+ function handle(upBool, sourceNode) {
+ var siblingDirection = upBool ? 'previousSibling' : 'nextSibling';
+ var currentRow = editor.dom.getParent(sourceNode, 'tr');
+ var siblingRow = currentRow[siblingDirection];
- if ((node = node.firstChild)) {
- do {
- txt += getText(node);
- } while ((node = node.next));
+ if (siblingRow) {
+ moveCursorToRow(editor, sourceNode, siblingRow, upBool);
+ e.preventDefault();
+ return true;
}
- return txt;
- }
-
- function trimListStart(node, regExp) {
- if (node.type === 3) {
- if (regExp.test(node.value)) {
- node.value = node.value.replace(regExp, '');
- return false;
+ var tableNode = editor.dom.getParent(currentRow, 'table');
+ var middleNode = currentRow.parentNode;
+ var parentNodeName = middleNode.nodeName.toLowerCase();
+ if (parentNodeName === 'tbody' || parentNodeName === (upBool ? 'tfoot' : 'thead')) {
+ var targetParent = getTargetParent(upBool, tableNode, middleNode, 'tbody');
+ if (targetParent !== null) {
+ return moveToRowInTarget(upBool, targetParent, sourceNode);
}
}
- if ((node = node.firstChild)) {
- do {
- if (!trimListStart(node, regExp)) {
- return false;
- }
- } while ((node = node.next));
- }
-
- return true;
+ return escapeTable(upBool, currentRow, siblingDirection, tableNode);
}
- function removeIgnoredNodes(node) {
- if (node._listIgnore) {
- node.remove();
- return;
+ function getTargetParent(upBool, topNode, secondNode, nodeName) {
+ var tbodies = editor.dom.select('>' + nodeName, topNode);
+ var position = tbodies.indexOf(secondNode);
+ if (upBool && position === 0 || !upBool && position === tbodies.length - 1) {
+ return getFirstHeadOrFoot(upBool, topNode);
+ } else if (position === -1) {
+ var topOrBottom = secondNode.tagName.toLowerCase() === 'thead' ? 0 : tbodies.length - 1;
+ return tbodies[topOrBottom];
}
- if ((node = node.firstChild)) {
- do {
- removeIgnoredNodes(node);
- } while ((node = node.next));
- }
+ return tbodies[position + (upBool ? -1 : 1)];
}
- function convertParagraphToLi(paragraphNode, listName, start) {
- var level = paragraphNode._listLevel || lastLevel;
-
- // Handle list nesting
- if (level != lastLevel) {
- if (level < lastLevel) {
- // Move to parent list
- if (currentListNode) {
- currentListNode = currentListNode.parent.parent;
- }
- } else {
- // Create new list
- prevListNode = currentListNode;
- currentListNode = null;
- }
- }
-
- if (!currentListNode || currentListNode.name != listName) {
- prevListNode = prevListNode || currentListNode;
- currentListNode = new Node(listName, 1);
-
- if (start > 1) {
- currentListNode.attr('start', '' + start);
- }
-
- paragraphNode.wrap(currentListNode);
- } else {
- currentListNode.append(paragraphNode);
- }
-
- paragraphNode.name = 'li';
-
- // Append list to previous list if it exists
- if (level > lastLevel && prevListNode) {
- prevListNode.lastChild.append(currentListNode);
- }
-
- lastLevel = level;
-
- // Remove start of list item "1. " or "· " etc
- removeIgnoredNodes(paragraphNode);
- trimListStart(paragraphNode, /^\u00a0+/);
- trimListStart(paragraphNode, /^\s*([\u2022\u00b7\u00a7\u25CF]|\w+\.)/);
- trimListStart(paragraphNode, /^\u00a0+/);
+ function getFirstHeadOrFoot(upBool, parent) {
+ var tagName = upBool ? 'thead' : 'tfoot';
+ var headOrFoot = editor.dom.select('>' + tagName, parent);
+ return headOrFoot.length !== 0 ? headOrFoot[0] : null;
}
- // Build a list of all root level elements before we start
- // altering them in the loop below.
- var elements = [], child = node.firstChild;
- while (typeof child !== 'undefined' && child !== null) {
- elements.push(child);
+ function moveToRowInTarget(upBool, targetParent, sourceNode) {
+ var targetRow = getChildForDirection(targetParent, upBool);
- child = child.walk();
- if (child !== null) {
- while (typeof child !== 'undefined' && child.parent !== node) {
- child = child.walk();
- }
+ if (targetRow) {
+ moveCursorToRow(editor, sourceNode, targetRow, upBool);
}
- }
-
- for (var i = 0; i < elements.length; i++) {
- node = elements[i];
-
- if (node.name == 'p' && node.firstChild) {
- // Find first text node in paragraph
- var nodeText = getText(node);
-
- // Detect unordered lists look for bullets
- if (isBulletList(nodeText)) {
- convertParagraphToLi(node, 'ul');
- continue;
- }
-
- // Detect ordered lists 1., a. or ixv.
- if (isNumericList(nodeText)) {
- // Parse OL start number
- var matches = /([0-9]+)\./.exec(nodeText);
- var start = 1;
- if (matches) {
- start = parseInt(matches[1], 10);
- }
-
- convertParagraphToLi(node, 'ol', start);
- continue;
- }
-
- // Convert paragraphs marked as lists but doesn't look like anything
- if (node._listLevel) {
- convertParagraphToLi(node, 'ul', 1);
- continue;
- }
- currentListNode = null;
- } else {
- // If the root level element isn't a p tag which can be
- // processed by convertParagraphToLi, it interrupts the
- // lists, causing a new list to start instead of having
- // elements from the next list inserted above this tag.
- prevListNode = currentListNode;
- currentListNode = null;
- }
+ e.preventDefault();
+ return true;
}
- }
-
- function filterStyles(node, styleValue) {
- var outputStyles = {}, matches, styles = editor.dom.parseStyle(styleValue);
-
- Tools.each(styles, function(value, name) {
- // Convert various MS styles to W3C styles
- switch (name) {
- case 'mso-list':
- // Parse out list indent level for lists
- matches = /\w+ \w+([0-9]+)/i.exec(styleValue);
- if (matches) {
- node._listLevel = parseInt(matches[1], 10);
- }
-
- // Remove these nodes o
- // Since the span gets removed we mark the text node and the span
- if (/Ignore/i.test(value) && node.firstChild) {
- node._listIgnore = true;
- node.firstChild._listIgnore = true;
- }
- break;
+ function escapeTable(upBool, currentRow, siblingDirection, table) {
+ var tableSibling = table[siblingDirection];
- case "horiz-align":
- name = "text-align";
- break;
+ if (tableSibling) {
+ moveCursorToStartOfElement(tableSibling);
+ return true;
+ }
- case "vert-align":
- name = "vertical-align";
- break;
+ var parentCell = editor.dom.getParent(table, 'td,th');
+ if (parentCell) {
+ return handle(upBool, parentCell, e);
+ }
- case "font-color":
- case "mso-foreground":
- name = "color";
- break;
+ var backUpSibling = getChildForDirection(currentRow, !upBool);
+ moveCursorToStartOfElement(backUpSibling);
+ e.preventDefault();
+ return false;
+ }
- case "mso-background":
- case "mso-highlight":
- name = "background";
- break;
+ function getChildForDirection(parent, up) {
+ var child = parent && parent[up ? 'lastChild' : 'firstChild'];
+ // BR is not a valid table child to return in this case we return the table cell
+ return child && child.nodeName === 'BR' ? editor.dom.getParent(child, 'td,th') : child;
+ }
- case "font-weight":
- case "font-style":
- if (value != "normal") {
- outputStyles[name] = value;
- }
- return;
+ function moveCursorToStartOfElement(n) {
+ editor.selection.setCursorLocation(n, 0);
+ }
- case "mso-element":
- // Remove track changes code
- if (/^(comment|comment-list)$/i.test(value)) {
- node.remove();
- return;
- }
+ function isVerticalMovement() {
+ return key == VK.UP || key == VK.DOWN;
+ }
- break;
- }
+ function isInTable(editor) {
+ var node = editor.selection.getNode();
+ var currentRow = editor.dom.getParent(node, 'tr');
+ return currentRow !== null;
+ }
- if (name.indexOf('mso-comment') === 0) {
- node.remove();
- return;
+ function columnIndex(column) {
+ var colIndex = 0;
+ var c = column;
+ while (c.previousSibling) {
+ c = c.previousSibling;
+ colIndex = colIndex + getSpanVal(c, "colspan");
}
+ return colIndex;
+ }
- // Never allow mso- prefixed names
- if (name.indexOf('mso-') === 0) {
- return;
- }
+ function findColumn(rowElement, columnIndex) {
+ var c = 0, r = 0;
- // Output only valid styles
- if (retainStyleProperties == "all" || (validStyles && validStyles[name])) {
- outputStyles[name] = value;
- }
- });
+ each(rowElement.children, function(cell, i) {
+ c = c + getSpanVal(cell, "colspan");
+ r = i;
+ if (c > columnIndex) {
+ return false;
+ }
+ });
+ return r;
+ }
- // Convert bold style to "b" element
- if (/(bold)/i.test(outputStyles["font-weight"])) {
- delete outputStyles["font-weight"];
- node.wrap(new Node("b", 1));
+ function moveCursorToRow(ed, node, row, upBool) {
+ var srcColumnIndex = columnIndex(editor.dom.getParent(node, 'td,th'));
+ var tgtColumnIndex = findColumn(row, srcColumnIndex);
+ var tgtNode = row.childNodes[tgtColumnIndex];
+ var rowCellTarget = getChildForDirection(tgtNode, upBool);
+ moveCursorToStartOfElement(rowCellTarget || tgtNode);
}
- // Convert italic style to "i" element
- if (/(italic)/i.test(outputStyles["font-style"])) {
- delete outputStyles["font-style"];
- node.wrap(new Node("i", 1));
+ function shouldFixCaret(preBrowserNode) {
+ var newNode = editor.selection.getNode();
+ var newParent = editor.dom.getParent(newNode, 'td,th');
+ var oldParent = editor.dom.getParent(preBrowserNode, 'td,th');
+
+ return newParent && newParent !== oldParent && checkSameParentTable(newParent, oldParent);
}
- // Serialize the styles and see if there is something left to keep
- outputStyles = editor.dom.serializeStyle(outputStyles, node.name);
- if (outputStyles) {
- return outputStyles;
+ function checkSameParentTable(nodeOne, NodeTwo) {
+ return editor.dom.getParent(nodeOne, 'TABLE') === editor.dom.getParent(NodeTwo, 'TABLE');
}
- return null;
+ if (isVerticalMovement() && isInTable(editor)) {
+ var preBrowserNode = editor.selection.getNode();
+ setTimeout(function() {
+ if (shouldFixCaret(preBrowserNode)) {
+ handle(!e.shiftKey && key === VK.UP, preBrowserNode, e);
+ }
+ }, 0);
+ }
}
- if (settings.paste_enable_default_filters === false) {
- return;
+ editor.on('KeyDown', function(e) {
+ eventHandler(e);
+ });
+ }
+
+ function fixBeforeTableCaretBug() {
+ // Checks if the selection/caret is at the start of the specified block element
+ function isAtStart(rng, par) {
+ var doc = par.ownerDocument, rng2 = doc.createRange(), elm;
+
+ rng2.setStartBefore(par);
+ rng2.setEnd(rng.endContainer, rng.endOffset);
+
+ elm = doc.createElement('body');
+ elm.appendChild(rng2.cloneContents());
+
+ // Check for text characters of other elements that should be treated as content
+ return elm.innerHTML.replace(/<(br|img|object|embed|input|textarea)[^>]*>/gi, '-').replace(/<[^>]+>/g, '').length === 0;
}
- // Detect is the contents is Word junk HTML
- if (isWordContent(e.content)) {
- e.wordContent = true; // Mark it for other processors
+ // Fixes an bug where it's impossible to place the caret before a table in Gecko
+ // this fix solves it by detecting when the caret is at the beginning of such a table
+ // and then manually moves the caret infront of the table
+ editor.on('KeyDown', function(e) {
+ var rng, table, dom = editor.dom;
- // Remove basic Word junk
- content = Utils.filter(content, [
- // Word comments like conditional comments etc
- //gi,
+ // On gecko it's not possible to place the caret before a table
+ if (e.keyCode == 37 || e.keyCode == 38) {
+ rng = editor.selection.getRng();
+ table = dom.getParent(rng.startContainer, 'table');
- // Remove comments, scripts (e.g., msoShowComment), XML tag, VML content,
- // MS Office namespaced tags, and a few other tags
- /<(!|script[^>]*>.*?<\/script(?=[>\s])|\/?(\?xml(:\w+)?|img|meta|link|style|\w:\w+)(?=[\s\/>]))[^>]*>/gi,
+ if (table && editor.getBody().firstChild == table) {
+ if (isAtStart(rng, table)) {
+ rng = dom.createRng();
- // Convert into for line-though
- [/<(\/?)s>/gi, "<$1strike>"],
+ rng.setStartBefore(table);
+ rng.setEndBefore(table);
- // Replace nsbp entites to char since it's easier to handle
- [/ /gi, "\u00a0"],
+ editor.selection.setRng(rng);
- // Convert ___ to string of alternating
- // breaking/non-breaking spaces of same length
- [/([\s\u00a0]*)<\/span>/gi,
- function(str, spaces) {
- return (spaces.length > 0) ?
- spaces.replace(/./, " ").slice(Math.floor(spaces.length / 2)).split("").join("\u00a0") : "";
+ e.preventDefault();
}
- ]
- ]);
-
- var validElements = settings.paste_word_valid_elements;
- if (!validElements) {
- validElements = (
- '-strong/b,-em/i,-u,-span,-p,-ol,-ul,-li,-h1,-h2,-h3,-h4,-h5,-h6,' +
- '-p/div,-a[href|name],sub,sup,strike,br,del,table[width],tr,' +
- 'td[colspan|rowspan|width],th[colspan|rowspan|width],thead,tfoot,tbody'
- );
+ }
}
+ });
+ }
- // Setup strict schema
- var schema = new Schema({
- valid_elements: validElements,
- valid_children: '-li[p]'
- });
+ // Fixes an issue on Gecko where it's impossible to place the caret behind a table
+ // This fix will force a paragraph element after the table but only when the forced_root_block setting is enabled
+ function fixTableCaretPos() {
+ editor.on('KeyDown SetContent VisualAid', function() {
+ var last;
- // Add style/class attribute to all element rules since the user might have removed them from
- // paste_word_valid_elements config option and we need to check them for properties
- Tools.each(schema.elements, function(rule) {
- /*eslint dot-notation:0*/
- if (!rule.attributes["class"]) {
- rule.attributes["class"] = {};
- rule.attributesOrder.push("class");
+ // Skip empty text nodes from the end
+ for (last = editor.getBody().lastChild; last; last = last.previousSibling) {
+ if (last.nodeType == 3) {
+ if (last.nodeValue.length > 0) {
+ break;
+ }
+ } else if (last.nodeType == 1 && (last.tagName == 'BR' || !last.getAttribute('data-mce-bogus'))) {
+ break;
}
+ }
- if (!rule.attributes.style) {
- rule.attributes.style = {};
- rule.attributesOrder.push("style");
+ if (last && last.nodeName == 'TABLE') {
+ if (editor.settings.forced_root_block) {
+ editor.dom.add(
+ editor.getBody(),
+ editor.settings.forced_root_block,
+ editor.settings.forced_root_block_attrs,
+ Env.ie && Env.ie < 11 ? ' ' : '
'
+ );
+ } else {
+ editor.dom.add(editor.getBody(), 'br', {'data-mce-bogus': '1'});
}
- });
+ }
+ });
- // Parse HTML into DOM structure
- var domParser = new DomParser({}, schema);
+ editor.on('PreProcess', function(o) {
+ var last = o.node.lastChild;
- // Filter styles to remove "mso" specific styles and convert some of them
- domParser.addAttributeFilter('style', function(nodes) {
- var i = nodes.length, node;
+ if (last && (last.nodeName == "BR" || (last.childNodes.length == 1 &&
+ (last.firstChild.nodeName == 'BR' || last.firstChild.nodeValue == '\u00a0'))) &&
+ last.previousSibling && last.previousSibling.nodeName == "TABLE") {
+ editor.dom.remove(last);
+ }
+ });
+ }
- while (i--) {
- node = nodes[i];
- node.attr('style', filterStyles(node, node.attr('style')));
+ // this nasty hack is here to work around some WebKit selection bugs.
+ function fixTableCellSelection() {
+ function tableCellSelected(ed, rng, n, currentCell) {
+ // The decision of when a table cell is selected is somewhat involved. The fact that this code is
+ // required is actually a pointer to the root cause of this bug. A cell is selected when the start
+ // and end offsets are 0, the start container is a text, and the selection node is either a TR (most cases)
+ // or the parent of the table (in the case of the selection containing the last cell of a table).
+ var TEXT_NODE = 3, table = ed.dom.getParent(rng.startContainer, 'TABLE');
+ var tableParent, allOfCellSelected, tableCellSelection;
- // Remove pointess spans
- if (node.name == 'span' && node.parent && !node.attributes.length) {
- node.unwrap();
- }
- }
- });
+ if (table) {
+ tableParent = table.parentNode;
+ }
- // Check the class attribute for comments or del items and remove those
- domParser.addAttributeFilter('class', function(nodes) {
- var i = nodes.length, node, className;
+ allOfCellSelected = rng.startContainer.nodeType == TEXT_NODE &&
+ rng.startOffset === 0 &&
+ rng.endOffset === 0 &&
+ currentCell &&
+ (n.nodeName == "TR" || n == tableParent);
- while (i--) {
- node = nodes[i];
+ tableCellSelection = (n.nodeName == "TD" || n.nodeName == "TH") && !currentCell;
- className = node.attr('class');
- if (/^(MsoCommentReference|MsoCommentText|msoDel)$/i.test(className)) {
- node.remove();
- }
+ return allOfCellSelected || tableCellSelection;
+ }
- node.attr('class', null);
- }
- });
+ function fixSelection() {
+ var rng = editor.selection.getRng();
+ var n = editor.selection.getNode();
+ var currentCell = editor.dom.getParent(rng.startContainer, 'TD,TH');
- // Remove all del elements since we don't want the track changes code in the editor
- domParser.addNodeFilter('del', function(nodes) {
- var i = nodes.length;
+ if (!tableCellSelected(editor, rng, n, currentCell)) {
+ return;
+ }
- while (i--) {
- nodes[i].remove();
- }
- });
+ if (!currentCell) {
+ currentCell = n;
+ }
- // Keep some of the links and anchors
- domParser.addNodeFilter('a', function(nodes) {
- var i = nodes.length, node, href, name;
+ // Get the very last node inside the table cell
+ var end = currentCell.lastChild;
+ while (end.lastChild) {
+ end = end.lastChild;
+ }
- while (i--) {
- node = nodes[i];
- href = node.attr('href');
- name = node.attr('name');
+ // Select the entire table cell. Nothing outside of the table cell should be selected.
+ if (end.nodeType == 3) {
+ rng.setEnd(end, end.data.length);
+ editor.selection.setRng(rng);
+ }
+ }
- if (href && href.indexOf('#_msocom_') != -1) {
- node.remove();
- continue;
- }
+ editor.on('KeyDown', function() {
+ fixSelection();
+ });
- if (href && href.indexOf('file://') === 0) {
- href = href.split('#')[1];
- if (href) {
- href = '#' + href;
- }
- }
+ editor.on('MouseDown', function(e) {
+ if (e.button != 2) {
+ fixSelection();
+ }
+ });
+ }
- if (!href && !name) {
- node.unwrap();
- } else {
- // Remove all named anchors that aren't specific to TOC, Footnotes or Endnotes
- if (name && !/^_?(?:toc|edn|ftn)/i.test(name)) {
- node.unwrap();
- continue;
+ /**
+ * Delete table if all cells are selected.
+ */
+ function deleteTable() {
+ function placeCaretInCell(cell) {
+ editor.selection.select(cell, true);
+ editor.selection.collapse(true);
+ }
+
+ function clearCell(cell) {
+ editor.$(cell).empty();
+ Utils.paddCell(cell);
+ }
+
+ editor.on('keydown', function(e) {
+ if ((e.keyCode == VK.DELETE || e.keyCode == VK.BACKSPACE) && !e.isDefaultPrevented()) {
+ var table, tableCells, selectedTableCells, cell;
+
+ table = editor.dom.getParent(editor.selection.getStart(), 'table');
+ if (table) {
+ tableCells = editor.dom.select('td,th', table);
+ selectedTableCells = Tools.grep(tableCells, function(cell) {
+ return editor.dom.hasClass(cell, 'mce-item-selected');
+ });
+
+ if (selectedTableCells.length === 0) {
+ // If caret is within an empty table cell then empty it for real
+ cell = editor.dom.getParent(editor.selection.getStart(), 'td,th');
+ if (editor.selection.isCollapsed() && cell && editor.dom.isEmpty(cell)) {
+ e.preventDefault();
+ clearCell(cell);
+ placeCaretInCell(cell);
}
- node.attr({
- href: href,
- name: name
- });
+ return;
}
- }
- });
- // Parse into DOM structure
- var rootNode = domParser.parse(content);
+ e.preventDefault();
- // Process DOM
- if (settings.paste_convert_word_fake_lists !== false) {
- convertFakeListsToProperLists(rootNode);
+ if (tableCells.length == selectedTableCells.length) {
+ editor.execCommand('mceTableDelete');
+ } else {
+ Tools.each(selectedTableCells, clearCell);
+ placeCaretInCell(selectedTableCells[0]);
+ }
+ }
}
+ });
+ }
- // Serialize DOM back to HTML
- e.content = new Serializer({}, schema).serialize(rootNode);
- }
- });
- }
+ deleteTable();
- WordFilter.isWordContent = isWordContent;
+ if (Env.webkit) {
+ moveWebKitSelection();
+ fixTableCellSelection();
+ }
- return WordFilter;
+ if (Env.gecko) {
+ fixBeforeTableCaretBug();
+ fixTableCaretPos();
+ }
+
+ if (Env.ie > 10) {
+ fixBeforeTableCaretBug();
+ fixTableCaretPos();
+ }
+ };
});
-// Included from: js/tinymce/plugins/paste/classes/Quirks.js
+// Included from: js/tinymce/plugins/table/classes/CellSelection.js
/**
- * Quirks.js
+ * CellSelection.js
*
* Released under LGPL License.
* Copyright (c) 1999-2015 Ephox Corp. All rights reserved
@@ -56191,159 +72234,176 @@ define("tinymce/pasteplugin/WordFilter", [
*/
/**
- * This class contains various fixes for browsers. These issues can not be feature
- * detected since we have no direct control over the clipboard. However we might be able
- * to remove some of these fixes once the browsers gets updated/fixed.
+ * This class handles table cell selection by faking it using a css class that gets applied
+ * to cells when dragging the mouse from one cell to another.
*
- * @class tinymce.pasteplugin.Quirks
+ * @class tinymce.tableplugin.CellSelection
* @private
*/
-define("tinymce/pasteplugin/Quirks", [
- "tinymce/Env",
- "tinymce/util/Tools",
- "tinymce/pasteplugin/WordFilter",
- "tinymce/pasteplugin/Utils"
-], function(Env, Tools, WordFilter, Utils) {
- "use strict";
-
+define("tinymce/tableplugin/CellSelection", [
+ "tinymce/tableplugin/TableGrid",
+ "tinymce/dom/TreeWalker",
+ "tinymce/util/Tools"
+], function(TableGrid, TreeWalker, Tools) {
return function(editor) {
- function addPreProcessFilter(filterFunc) {
- editor.on('BeforePastePreProcess', function(e) {
- e.content = filterFunc(e.content);
- });
+ var dom = editor.dom, tableGrid, startCell, startTable, hasCellSelection = true, resizing;
+
+ function clear(force) {
+ // Restore selection possibilities
+ editor.getBody().style.webkitUserSelect = '';
+
+ if (force || hasCellSelection) {
+ editor.dom.removeClass(
+ editor.dom.select('td.mce-item-selected,th.mce-item-selected'),
+ 'mce-item-selected'
+ );
+
+ hasCellSelection = false;
+ }
}
- /**
- * Removes BR elements after block elements. IE9 has a nasty bug where it puts a BR element after each
- * block element when pasting from word. This removes those elements.
- *
- * This:
- * a
b
- *
- * Becomes:
- * a
b
- */
- function removeExplorerBrElementsAfterBlocks(html) {
- // Only filter word specific content
- if (!WordFilter.isWordContent(html)) {
- return html;
+ function cellSelectionHandler(e) {
+ var sel, table, target = e.target;
+
+ if (resizing) {
+ return;
}
- // Produce block regexp based on the block elements in schema
- var blockElements = [];
+ if (startCell && (tableGrid || target != startCell) && (target.nodeName == 'TD' || target.nodeName == 'TH')) {
+ table = dom.getParent(target, 'table');
+ if (table == startTable) {
+ if (!tableGrid) {
+ tableGrid = new TableGrid(editor, table);
+ tableGrid.setStartCell(startCell);
+
+ editor.getBody().style.webkitUserSelect = 'none';
+ }
+
+ tableGrid.setEndCell(target);
+ hasCellSelection = true;
+ }
+
+ // Remove current selection
+ sel = editor.selection.getSel();
+
+ try {
+ if (sel.removeAllRanges) {
+ sel.removeAllRanges();
+ } else {
+ sel.empty();
+ }
+ } catch (ex) {
+ // IE9 might throw errors here
+ }
+
+ e.preventDefault();
+ }
+ }
+
+ // Add cell selection logic
+ editor.on('MouseDown', function(e) {
+ if (e.button != 2 && !resizing) {
+ clear();
- Tools.each(editor.schema.getBlockElements(), function(block, blockName) {
- blockElements.push(blockName);
- });
+ startCell = dom.getParent(e.target, 'td,th');
+ startTable = dom.getParent(startCell, 'table');
+ }
+ });
- var explorerBlocksRegExp = new RegExp(
- '(?:
[\\s\\r\\n]+|
)*(<\\/?(' + blockElements.join('|') + ')[^>]*>)(?:
[\\s\\r\\n]+|
)*',
- 'g'
- );
+ editor.on('mouseover', cellSelectionHandler);
- // Remove BR:s from: X
- html = Utils.filter(html, [
- [explorerBlocksRegExp, '$1']
- ]);
+ editor.on('remove', function() {
+ dom.unbind(editor.getDoc(), 'mouseover', cellSelectionHandler);
+ });
- // IE9 also adds an extra BR element for each soft-linefeed and it also adds a BR for each word wrap break
- html = Utils.filter(html, [
- [/
/g, '
'], // Replace multiple BR elements with uppercase BR to keep them intact
- [/
/g, ' '], // Replace single br elements with space since they are word wrap BR:s
- [/
/g, '
'] // Replace back the double brs but into a single BR
- ]);
+ editor.on('MouseUp', function() {
+ var rng, sel = editor.selection, selectedCells, walker, node, lastNode;
- return html;
- }
+ function setPoint(node, start) {
+ var walker = new TreeWalker(node, node);
- /**
- * WebKit has a nasty bug where the all computed styles gets added to style attributes when copy/pasting contents.
- * This fix solves that by simply removing the whole style attribute.
- *
- * The paste_webkit_styles option can be set to specify what to keep:
- * paste_webkit_styles: "none" // Keep no styles
- * paste_webkit_styles: "all", // Keep all of them
- * paste_webkit_styles: "font-weight color" // Keep specific ones
- *
- * @param {String} content Content that needs to be processed.
- * @return {String} Processed contents.
- */
- function removeWebKitStyles(content) {
- // Passthrough all styles from Word and let the WordFilter handle that junk
- if (WordFilter.isWordContent(content)) {
- return content;
- }
+ do {
+ // Text node
+ if (node.nodeType == 3 && Tools.trim(node.nodeValue).length !== 0) {
+ if (start) {
+ rng.setStart(node, 0);
+ } else {
+ rng.setEnd(node, node.nodeValue.length);
+ }
- // Filter away styles that isn't matching the target node
- var webKitStyles = editor.settings.paste_webkit_styles;
+ return;
+ }
- if (editor.settings.paste_remove_styles_if_webkit === false || webKitStyles == "all") {
- return content;
- }
+ // BR element
+ if (node.nodeName == 'BR') {
+ if (start) {
+ rng.setStartBefore(node);
+ } else {
+ rng.setEndBefore(node);
+ }
- if (webKitStyles) {
- webKitStyles = webKitStyles.split(/[, ]/);
+ return;
+ }
+ } while ((node = (start ? walker.next() : walker.prev())));
}
- // Keep specific styles that doesn't match the current node computed style
- if (webKitStyles) {
- var dom = editor.dom, node = editor.selection.getNode();
+ // Move selection to startCell
+ if (startCell) {
+ if (tableGrid) {
+ editor.getBody().style.webkitUserSelect = '';
+ }
- content = content.replace(/(<[^>]+) style="([^"]*)"([^>]*>)/gi, function(all, before, value, after) {
- var inputStyles = dom.parseStyle(value, 'span'), outputStyles = {};
+ // Try to expand text selection as much as we can only Gecko supports cell selection
+ selectedCells = dom.select('td.mce-item-selected,th.mce-item-selected');
+ if (selectedCells.length > 0) {
+ rng = dom.createRng();
+ node = selectedCells[0];
+ rng.setStartBefore(node);
+ rng.setEndAfter(node);
- if (webKitStyles === "none") {
- return before + after;
- }
+ setPoint(node, 1);
+ walker = new TreeWalker(node, dom.getParent(selectedCells[0], 'table'));
- for (var i = 0; i < webKitStyles.length; i++) {
- var inputValue = inputStyles[webKitStyles[i]], currentValue = dom.getStyle(node, webKitStyles[i], true);
+ do {
+ if (node.nodeName == 'TD' || node.nodeName == 'TH') {
+ if (!dom.hasClass(node, 'mce-item-selected')) {
+ break;
+ }
- if (/color/.test(webKitStyles[i])) {
- inputValue = dom.toHex(inputValue);
- currentValue = dom.toHex(currentValue);
+ lastNode = node;
}
+ } while ((node = walker.next()));
- if (currentValue != inputValue) {
- outputStyles[webKitStyles[i]] = inputValue;
- }
- }
+ setPoint(lastNode);
- outputStyles = dom.serializeStyle(outputStyles, 'span');
- if (outputStyles) {
- return before + ' style="' + outputStyles + '"' + after;
- }
+ sel.setRng(rng);
+ }
- return before + after;
- });
- } else {
- // Remove all external styles
- content = content.replace(/(<[^>]+) style="([^"]*)"([^>]*>)/gi, '$1$3');
+ editor.nodeChanged();
+ startCell = tableGrid = startTable = null;
}
+ });
- // Keep internal styles
- content = content.replace(/(<[^>]+) data-mce-style="([^"]+)"([^>]*>)/gi, function(all, before, value, after) {
- return before + ' style="' + value + '"' + after;
- });
-
- return content;
- }
+ editor.on('KeyUp Drop SetContent', function(e) {
+ clear(e.type == 'setcontent');
+ startCell = tableGrid = startTable = null;
+ resizing = false;
+ });
- // Sniff browsers and apply fixes since we can't feature detect
- if (Env.webkit) {
- addPreProcessFilter(removeWebKitStyles);
- }
+ editor.on('ObjectResizeStart ObjectResized', function(e) {
+ resizing = e.type != 'objectresized';
+ });
- if (Env.ie) {
- addPreProcessFilter(removeExplorerBrElementsAfterBlocks);
- }
+ return {
+ clear: clear
+ };
};
});
-// Included from: js/tinymce/plugins/paste/classes/Plugin.js
+// Included from: js/tinymce/plugins/table/classes/Dialogs.js
/**
- * Plugin.js
+ * Dialogs.js
*
* Released under LGPL License.
* Copyright (c) 1999-2015 Ephox Corp. All rights reserved
@@ -56352,1070 +72412,1270 @@ define("tinymce/pasteplugin/Quirks", [
* Contributing: http://www.tinymce.com/contributing
*/
+/*eslint dot-notation:0*/
+
/**
- * This class contains the tinymce plugin logic for the paste plugin.
+ * ...
*
- * @class tinymce.pasteplugin.Plugin
+ * @class tinymce.tableplugin.Dialogs
* @private
*/
-define("tinymce/pasteplugin/Plugin", [
- "tinymce/PluginManager",
- "tinymce/pasteplugin/Clipboard",
- "tinymce/pasteplugin/WordFilter",
- "tinymce/pasteplugin/Quirks"
-], function(PluginManager, Clipboard, WordFilter, Quirks) {
- var userIsInformed;
+define("tinymce/tableplugin/Dialogs", [
+ "tinymce/util/Tools",
+ "tinymce/Env"
+], function(Tools, Env) {
+ var each = Tools.each;
- PluginManager.add('paste', function(editor) {
- var self = this, clipboard, settings = editor.settings;
+ return function(editor) {
+ var self = this;
- function togglePlainTextPaste() {
- if (clipboard.pasteFormat == "text") {
- this.active(false);
- clipboard.pasteFormat = "html";
- } else {
- clipboard.pasteFormat = "text";
- this.active(true);
+ function createColorPickAction() {
+ var colorPickerCallback = editor.settings.color_picker_callback;
- if (!userIsInformed) {
- editor.windowManager.alert(
- 'Paste is now in plain text mode. Contents will now ' +
- 'be pasted as plain text until you toggle this option off.'
- );
+ if (colorPickerCallback) {
+ return function() {
+ var self = this;
- userIsInformed = true;
- }
+ colorPickerCallback.call(
+ editor,
+ function(value) {
+ self.value(value).fire('change');
+ },
+ self.value()
+ );
+ };
}
}
- self.clipboard = clipboard = new Clipboard(editor);
- self.quirks = new Quirks(editor);
- self.wordFilter = new WordFilter(editor);
+ function createStyleForm(dom) {
+ return {
+ title: 'Advanced',
+ type: 'form',
+ defaults: {
+ onchange: function() {
+ updateStyle(dom, this.parents().reverse()[0], this.name() == "style");
+ }
+ },
+ items: [
+ {
+ label: 'Style',
+ name: 'style',
+ type: 'textbox'
+ },
- if (editor.settings.paste_as_text) {
- self.clipboard.pasteFormat = "text";
- }
+ {
+ type: 'form',
+ padding: 0,
+ formItemDefaults: {
+ layout: 'grid',
+ alignH: ['start', 'right']
+ },
+ defaults: {
+ size: 7
+ },
+ items: [
+ {
+ label: 'Border color',
+ type: 'colorbox',
+ name: 'borderColor',
+ onaction: createColorPickAction()
+ },
- if (settings.paste_preprocess) {
- editor.on('PastePreProcess', function(e) {
- settings.paste_preprocess.call(self, self, e);
- });
+ {
+ label: 'Background color',
+ type: 'colorbox',
+ name: 'backgroundColor',
+ onaction: createColorPickAction()
+ }
+ ]
+ }
+ ]
+ };
}
- if (settings.paste_postprocess) {
- editor.on('PastePostProcess', function(e) {
- settings.paste_postprocess.call(self, self, e);
- });
+ function removePxSuffix(size) {
+ return size ? size.replace(/px$/, '') : "";
}
- editor.addCommand('mceInsertClipboardContent', function(ui, value) {
- if (value.content) {
- self.clipboard.pasteHtml(value.content);
+ function addSizeSuffix(size) {
+ if (/^[0-9]+$/.test(size)) {
+ size += "px";
}
- if (value.text) {
- self.clipboard.pasteText(value.text);
- }
- });
+ return size;
+ }
- // Block all drag/drop events
- if (editor.paste_block_drop) {
- editor.on('dragend dragover draggesture dragdrop drop drag', function(e) {
- e.preventDefault();
- e.stopPropagation();
+ function unApplyAlign(elm) {
+ each('left center right'.split(' '), function(name) {
+ editor.formatter.remove('align' + name, {}, elm);
});
}
- // Prevent users from dropping data images on Gecko
- if (!editor.settings.paste_data_images) {
- editor.on('drop', function(e) {
- var dataTransfer = e.dataTransfer;
-
- if (dataTransfer && dataTransfer.files && dataTransfer.files.length > 0) {
- e.preventDefault();
- }
+ function unApplyVAlign(elm) {
+ each('top middle bottom'.split(' '), function(name) {
+ editor.formatter.remove('valign' + name, {}, elm);
});
}
- editor.addButton('pastetext', {
- icon: 'pastetext',
- tooltip: 'Paste as text',
- onclick: togglePlainTextPaste,
- active: self.clipboard.pasteFormat == "text"
- });
-
- editor.addMenuItem('pastetext', {
- text: 'Paste as text',
- selectable: true,
- active: clipboard.pasteFormat,
- onclick: togglePlainTextPaste
- });
- });
-});
-
-expose(["tinymce/pasteplugin/Utils"]);
-})(this);
-
- }).apply(root, arguments);
-});
-}(this));
-
-(function(root) {
-define("tinymce-preview", ["tinymce"], function() {
- return (function() {
-/**
- * plugin.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
-
-/*global tinymce:true */
+ function buildListItems(inputList, itemCallback, startItems) {
+ function appendItems(values, output) {
+ output = output || [];
-tinymce.PluginManager.add('preview', function(editor) {
- var settings = editor.settings, sandbox = !tinymce.Env.ie;
+ Tools.each(values, function(item) {
+ var menuItem = {text: item.text || item.title};
- editor.addCommand('mcePreview', function() {
- editor.windowManager.open({
- title: 'Preview',
- width: parseInt(editor.getParam("plugin_preview_width", "650"), 10),
- height: parseInt(editor.getParam("plugin_preview_height", "500"), 10),
- html: '',
- buttons: {
- text: 'Close',
- onclick: function() {
- this.parent().parent().close();
- }
- },
- onPostRender: function() {
- var previewHtml, headHtml = '';
+ if (item.menu) {
+ menuItem.menu = appendItems(item.menu);
+ } else {
+ menuItem.value = item.value;
- headHtml += '';
+ if (itemCallback) {
+ itemCallback(menuItem);
+ }
+ }
- tinymce.each(editor.contentCSS, function(url) {
- headHtml += '';
+ output.push(menuItem);
});
- var bodyId = settings.body_id || 'tinymce';
- if (bodyId.indexOf('=') != -1) {
- bodyId = editor.getParam('body_id', '', 'hash');
- bodyId = bodyId[editor.id] || bodyId;
- }
-
- var bodyClass = settings.body_class || '';
- if (bodyClass.indexOf('=') != -1) {
- bodyClass = editor.getParam('body_class', '', 'hash');
- bodyClass = bodyClass[editor.id] || '';
- }
+ return output;
+ }
- var dirAttr = editor.settings.directionality ? ' dir="' + editor.settings.directionality + '"' : '';
+ return appendItems(inputList, startItems || []);
+ }
- previewHtml = (
- '' +
- '' +
- '' +
- headHtml +
- '' +
- '' +
- editor.getContent() +
- '' +
- ''
- );
+ function updateStyle(dom, win, isStyleCtrl) {
+ var data = win.toJSON();
+ var css = dom.parseStyle(data.style);
- if (!sandbox) {
- // IE 6-11 doesn't support data uris on iframes
- // so I guess they will have to be less secure since we can't sandbox on those
- // TODO: Use sandbox if future versions of IE supports iframes with data: uris.
- var doc = this.getEl('body').firstChild.contentWindow.document;
- doc.open();
- doc.write(previewHtml);
- doc.close();
- } else {
- this.getEl('body').firstChild.src = 'data:text/html;charset=utf-8,' + encodeURIComponent(previewHtml);
- }
+ if (isStyleCtrl) {
+ win.find('#borderColor').value(css["border-color"] || '')[0].fire('change');
+ win.find('#backgroundColor').value(css["background-color"] || '')[0].fire('change');
+ } else {
+ css["border-color"] = data.borderColor;
+ css["background-color"] = data.backgroundColor;
}
- });
- });
- editor.addButton('preview', {
- title: 'Preview',
- cmd: 'mcePreview'
- });
+ win.find('#style').value(dom.serializeStyle(dom.parseStyle(dom.serializeStyle(css))));
+ }
- editor.addMenuItem('preview', {
- text: 'Preview',
- cmd: 'mcePreview',
- context: 'view'
- });
-});
+ function appendStylesToData(dom, data, elm) {
+ var css = dom.parseStyle(dom.getAttrib(elm, 'style'));
+ if (css["border-color"]) {
+ data.borderColor = css["border-color"];
+ }
- }).apply(root, arguments);
-});
-}(this));
+ if (css["background-color"]) {
+ data.backgroundColor = css["background-color"];
+ }
-(function(root) {
-define("tinymce-print", ["tinymce"], function() {
- return (function() {
-/**
- * plugin.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
+ data.style = dom.serializeStyle(css);
+ }
-/*global tinymce:true */
+ function mergeStyles(dom, elm, styles) {
+ var css = dom.parseStyle(dom.getAttrib(elm, 'style'));
-tinymce.PluginManager.add('print', function(editor) {
- editor.addCommand('mcePrint', function() {
- editor.getWin().print();
- });
+ each(styles, function(style) {
+ css[style.name] = style.value;
+ });
- editor.addButton('print', {
- title: 'Print',
- cmd: 'mcePrint'
- });
+ dom.setAttrib(elm, 'style', dom.serializeStyle(dom.parseStyle(dom.serializeStyle(css))));
+ }
- editor.addShortcut('Meta+P', '', 'mcePrint');
+ self.tableProps = function() {
+ self.table(true);
+ };
- editor.addMenuItem('print', {
- text: 'Print',
- cmd: 'mcePrint',
- icon: 'print',
- shortcut: 'Meta+P',
- context: 'file'
- });
-});
+ self.table = function(isProps) {
+ var dom = editor.dom, tableElm, colsCtrl, rowsCtrl, classListCtrl, data = {}, generalTableForm, stylesToMerge;
+ function onSubmitTableForm() {
- }).apply(root, arguments);
-});
-}(this));
+ //Explore the layers of the table till we find the first layer of tds or ths
+ function styleTDTH(elm, name, value) {
+ if (elm.tagName === "TD" || elm.tagName === "TH") {
+ dom.setStyle(elm, name, value);
+ } else {
+ if (elm.children) {
+ for (var i = 0; i < elm.children.length; i++) {
+ styleTDTH(elm.children[i], name, value);
+ }
+ }
+ }
+ }
-(function(root) {
-define("tinymce-save", ["tinymce"], function() {
- return (function() {
-/**
- * plugin.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
+ var captionElm;
+
+ updateStyle(dom, this);
+ data = Tools.extend(data, this.toJSON());
-/*global tinymce:true */
+ if (data["class"] === false) {
+ delete data["class"];
+ }
-tinymce.PluginManager.add('save', function(editor) {
- function save() {
- var formObj;
+ editor.undoManager.transact(function() {
+ if (!tableElm) {
+ tableElm = editor.plugins.table.insertTable(data.cols || 1, data.rows || 1);
+ }
- formObj = tinymce.DOM.getParent(editor.id, 'form');
+ editor.dom.setAttribs(tableElm, {
+ style: data.style,
+ 'class': data['class']
+ });
- if (editor.getParam("save_enablewhendirty", true) && !editor.isDirty()) {
- return;
- }
+ if (editor.settings.table_style_by_css) {
+ stylesToMerge = [];
+ stylesToMerge.push({name: 'border', value: data.border});
+ stylesToMerge.push({name: 'border-spacing', value: addSizeSuffix(data.cellspacing)});
+ mergeStyles(dom, tableElm, stylesToMerge);
+ dom.setAttribs(tableElm, {
+ 'data-mce-border-color': data.borderColor,
+ 'data-mce-cell-padding': data.cellpadding,
+ 'data-mce-border': data.border
+ });
+ if (tableElm.children) {
+ for (var i = 0; i < tableElm.children.length; i++) {
+ styleTDTH(tableElm.children[i], 'border', data.border);
+ styleTDTH(tableElm.children[i], 'padding', addSizeSuffix(data.cellpadding));
+ }
+ }
+ } else {
+ editor.dom.setAttribs(tableElm, {
+ border: data.border,
+ cellpadding: data.cellpadding,
+ cellspacing: data.cellspacing
+ });
+ }
- tinymce.triggerSave();
+ if (dom.getAttrib(tableElm, 'width') && !editor.settings.table_style_by_css) {
+ dom.setAttrib(tableElm, 'width', removePxSuffix(data.width));
+ } else {
+ dom.setStyle(tableElm, 'width', addSizeSuffix(data.width));
+ }
- // Use callback instead
- if (editor.getParam("save_onsavecallback")) {
- if (editor.execCallback('save_onsavecallback', editor)) {
- editor.startContent = tinymce.trim(editor.getContent({format: 'raw'}));
- editor.nodeChanged();
- }
+ dom.setStyle(tableElm, 'height', addSizeSuffix(data.height));
- return;
- }
+ // Toggle caption on/off
+ captionElm = dom.select('caption', tableElm)[0];
- if (formObj) {
- editor.isNotDirty = true;
+ if (captionElm && !data.caption) {
+ dom.remove(captionElm);
+ }
- if (!formObj.onsubmit || formObj.onsubmit()) {
- if (typeof formObj.submit == "function") {
- formObj.submit();
- } else {
- editor.windowManager.alert("Error: Form submit field collision.");
- }
+ if (!captionElm && data.caption) {
+ captionElm = dom.create('caption');
+ captionElm.innerHTML = !Env.ie ? '
' : '\u00a0';
+ tableElm.insertBefore(captionElm, tableElm.firstChild);
+ }
+ unApplyAlign(tableElm);
+ if (data.align) {
+ editor.formatter.apply('align' + data.align, {}, tableElm);
+ }
+
+ editor.focus();
+ editor.addVisual();
+ });
}
- editor.nodeChanged();
- } else {
- editor.windowManager.alert("Error: No form element found.");
- }
- }
+ function getTDTHOverallStyle(elm, name) {
+ var cells = editor.dom.select("td,th", elm), firstChildStyle;
- function cancel() {
- var h = tinymce.trim(editor.startContent);
+ function checkChildren(firstChildStyle, elms) {
- // Use callback instead
- if (editor.getParam("save_oncancelcallback")) {
- editor.execCallback('save_oncancelcallback', editor);
- return;
- }
+ for (var i = 0; i < elms.length; i++) {
+ var currentStyle = dom.getStyle(elms[i], name);
+ if (typeof firstChildStyle === "undefined") {
+ firstChildStyle = currentStyle;
+ }
+ if (firstChildStyle != currentStyle) {
+ return "";
+ }
+ }
- editor.setContent(h);
- editor.undoManager.clear();
- editor.nodeChanged();
- }
+ return firstChildStyle;
- function stateToggle() {
- var self = this;
+ }
- editor.on('nodeChange', function() {
- self.disabled(editor.getParam("save_enablewhendirty", true) && !editor.isDirty());
- });
- }
+ firstChildStyle = checkChildren(firstChildStyle, cells);
- editor.addCommand('mceSave', save);
- editor.addCommand('mceCancel', cancel);
+ return firstChildStyle;
+ }
- editor.addButton('save', {
- icon: 'save',
- text: 'Save',
- cmd: 'mceSave',
- disabled: true,
- onPostRender: stateToggle
- });
+ if (isProps === true) {
+ tableElm = dom.getParent(editor.selection.getStart(), 'table');
- editor.addButton('cancel', {
- text: 'Cancel',
- icon: false,
- cmd: 'mceCancel',
- disabled: true,
- onPostRender: stateToggle
- });
+ if (tableElm) {
+ data = {
+ width: removePxSuffix(dom.getStyle(tableElm, 'width') || dom.getAttrib(tableElm, 'width')),
+ height: removePxSuffix(dom.getStyle(tableElm, 'height') || dom.getAttrib(tableElm, 'height')),
+ cellspacing: removePxSuffix(dom.getStyle(tableElm, 'border-spacing') ||
+ dom.getAttrib(tableElm, 'cellspacing')),
+ cellpadding: dom.getAttrib(tableElm, 'data-mce-cell-padding') || dom.getAttrib(tableElm, 'cellpadding') ||
+ getTDTHOverallStyle(tableElm, 'padding'),
+ border: dom.getAttrib(tableElm, 'data-mce-border') || dom.getAttrib(tableElm, 'border') ||
+ getTDTHOverallStyle(tableElm, 'border'),
+ borderColor: dom.getAttrib(tableElm, 'data-mce-border-color'),
+ caption: !!dom.select('caption', tableElm)[0],
+ 'class': dom.getAttrib(tableElm, 'class')
+ };
- editor.addShortcut('Meta+S', '', 'mceSave');
-});
+ each('left center right'.split(' '), function(name) {
+ if (editor.formatter.matchNode(tableElm, 'align' + name)) {
+ data.align = name;
+ }
+ });
+ }
+ } else {
+ colsCtrl = {label: 'Cols', name: 'cols'};
+ rowsCtrl = {label: 'Rows', name: 'rows'};
+ }
+ if (editor.settings.table_class_list) {
+ if (data["class"]) {
+ data["class"] = data["class"].replace(/\s*mce\-item\-table\s*/g, '');
+ }
- }).apply(root, arguments);
-});
-}(this));
+ classListCtrl = {
+ name: 'class',
+ type: 'listbox',
+ label: 'Class',
+ values: buildListItems(
+ editor.settings.table_class_list,
+ function(item) {
+ if (item.value) {
+ item.textStyle = function() {
+ return editor.formatter.getCssText({block: 'table', classes: [item.value]});
+ };
+ }
+ }
+ )
+ };
+ }
-(function(root) {
-define("tinymce-searchreplace", ["tinymce"], function() {
- return (function() {
-/**
- * plugin.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
+ generalTableForm = {
+ type: 'form',
+ layout: 'flex',
+ direction: 'column',
+ labelGapCalc: 'children',
+ padding: 0,
+ items: [
+ {
+ type: 'form',
+ labelGapCalc: false,
+ padding: 0,
+ layout: 'grid',
+ columns: 2,
+ defaults: {
+ type: 'textbox',
+ maxWidth: 50
+ },
+ items: (editor.settings.table_appearance_options !== false) ? [
+ colsCtrl,
+ rowsCtrl,
+ {label: 'Width', name: 'width'},
+ {label: 'Height', name: 'height'},
+ {label: 'Cell spacing', name: 'cellspacing'},
+ {label: 'Cell padding', name: 'cellpadding'},
+ {label: 'Border', name: 'border'},
+ {label: 'Caption', name: 'caption', type: 'checkbox'}
+ ] : [
+ colsCtrl,
+ rowsCtrl,
+ {label: 'Width', name: 'width'},
+ {label: 'Height', name: 'height'}
+ ]
+ },
-/*jshint smarttabs:true, undef:true, unused:true, latedef:true, curly:true, bitwise:true */
-/*eslint no-labels:0, no-constant-condition: 0 */
-/*global tinymce:true */
+ {
+ label: 'Alignment',
+ name: 'align',
+ type: 'listbox',
+ text: 'None',
+ values: [
+ {text: 'None', value: ''},
+ {text: 'Left', value: 'left'},
+ {text: 'Center', value: 'center'},
+ {text: 'Right', value: 'right'}
+ ]
+ },
-(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;
+ classListCtrl
+ ]
+ };
- doc = node.ownerDocument;
- blockElementsMap = schema.getBlockElements(); // H1-H6, P, TD etc
- hiddenTextElementsMap = schema.getWhiteSpaceElements(); // TEXTAREA, PRE, STYLE, SCRIPT
- shortEndedElementsMap = schema.getShortEndedElements(); // BR, IMG, INPUT
+ if (editor.settings.table_advtab !== false) {
+ appendStylesToData(dom, data, tableElm);
- function getMatchIndexes(m, captureGroup) {
- captureGroup = captureGroup || 0;
+ editor.windowManager.open({
+ title: "Table properties",
+ data: data,
+ bodyType: 'tabpanel',
+ body: [
+ {
+ title: 'General',
+ type: 'form',
+ items: generalTableForm
+ },
+ createStyleForm(dom)
+ ],
- if (!m[0]) {
- throw 'findAndReplaceDOMText cannot handle zero-length matches';
+ onsubmit: onSubmitTableForm
+ });
+ } else {
+ editor.windowManager.open({
+ title: "Table properties",
+ data: data,
+ body: generalTableForm,
+ onsubmit: onSubmitTableForm
+ });
}
+ };
- var index = m.index;
-
- if (captureGroup > 0) {
- var cg = m[captureGroup];
+ self.merge = function(grid, cell) {
+ editor.windowManager.open({
+ title: "Merge cells",
+ body: [
+ {label: 'Cols', name: 'cols', type: 'textbox', value: '1', size: 10},
+ {label: 'Rows', name: 'rows', type: 'textbox', value: '1', size: 10}
+ ],
+ onsubmit: function() {
+ var data = this.toJSON();
- if (!cg) {
- throw 'Invalid capture group';
+ editor.undoManager.transact(function() {
+ grid.merge(cell, data.cols, data.rows);
+ });
}
+ });
+ };
- index += m[0].indexOf(cg);
- m[0] = cg;
- }
+ self.cell = function() {
+ var dom = editor.dom, cellElm, data, classListCtrl, cells = [];
- return [index, index + m[0].length, [m[0]]];
- }
+ function onSubmitCellForm() {
+ updateStyle(dom, this);
+ data = Tools.extend(data, this.toJSON());
- function getText(node) {
- var txt;
+ editor.undoManager.transact(function() {
+ each(cells, function(cellElm) {
+ editor.dom.setAttribs(cellElm, {
+ scope: data.scope,
+ style: data.style,
+ 'class': data['class']
+ });
- if (node.nodeType === 3) {
- return node.data;
- }
+ editor.dom.setStyles(cellElm, {
+ width: addSizeSuffix(data.width),
+ height: addSizeSuffix(data.height)
+ });
- if (hiddenTextElementsMap[node.nodeName] && !blockElementsMap[node.nodeName]) {
- return '';
- }
+ // Switch cell type
+ if (data.type && cellElm.nodeName.toLowerCase() != data.type) {
+ cellElm = dom.rename(cellElm, data.type);
+ }
- txt = '';
+ // Apply/remove alignment
+ unApplyAlign(cellElm);
+ if (data.align) {
+ editor.formatter.apply('align' + data.align, {}, cellElm);
+ }
- if (blockElementsMap[node.nodeName] || shortEndedElementsMap[node.nodeName]) {
- txt += '\n';
- }
+ // Apply/remove vertical alignment
+ unApplyVAlign(cellElm);
+ if (data.valign) {
+ editor.formatter.apply('valign' + data.valign, {}, cellElm);
+ }
+ });
- if ((node = node.firstChild)) {
- do {
- txt += getText(node);
- } while ((node = node.nextSibling));
+ editor.focus();
+ });
}
- return txt;
- }
+ // Get selected cells or the current cell
+ cells = editor.dom.select('td.mce-item-selected,th.mce-item-selected');
+ cellElm = editor.dom.getParent(editor.selection.getStart(), 'td,th');
+ if (!cells.length && cellElm) {
+ cells.push(cellElm);
+ }
- function stepThroughMatches(node, matches, replaceFn) {
- var startNode, endNode, startNodeIndex,
- endNodeIndex, innerNodes = [], atIndex = 0, curNode = node,
- matchLocation = matches.shift(), matchIndex = 0;
+ cellElm = cellElm || cells[0];
- out: while (true) {
- if (blockElementsMap[curNode.nodeName] || shortEndedElementsMap[curNode.nodeName]) {
- atIndex++;
- }
+ if (!cellElm) {
+ // If this element is null, return now to avoid crashing.
+ return;
+ }
- 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);
- }
+ data = {
+ width: removePxSuffix(dom.getStyle(cellElm, 'width') || dom.getAttrib(cellElm, 'width')),
+ height: removePxSuffix(dom.getStyle(cellElm, 'height') || dom.getAttrib(cellElm, 'height')),
+ scope: dom.getAttrib(cellElm, 'scope'),
+ 'class': dom.getAttrib(cellElm, 'class')
+ };
- if (!startNode && curNode.length + atIndex > matchLocation[0]) {
- // We've found the match start
- startNode = curNode;
- startNodeIndex = matchLocation[0] - atIndex;
- }
+ data.type = cellElm.nodeName.toLowerCase();
- atIndex += curNode.length;
+ each('left center right'.split(' '), function(name) {
+ if (editor.formatter.matchNode(cellElm, 'align' + name)) {
+ data.align = name;
}
+ });
- 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;
+ each('top middle bottom'.split(' '), function(name) {
+ if (editor.formatter.matchNode(cellElm, 'valign' + name)) {
+ data.valign = name;
}
+ });
- // Move forward or up:
- while (true) {
- if (curNode.nextSibling) {
- curNode = curNode.nextSibling;
- break;
- } else if (curNode.parentNode !== node) {
- curNode = curNode.parentNode;
- } else {
- break out;
- }
- }
+ if (editor.settings.table_cell_class_list) {
+ classListCtrl = {
+ name: 'class',
+ type: 'listbox',
+ label: 'Class',
+ values: buildListItems(
+ editor.settings.table_cell_class_list,
+ function(item) {
+ if (item.value) {
+ item.textStyle = function() {
+ return editor.formatter.getCssText({block: 'td', classes: [item.value]});
+ };
+ }
+ }
+ )
+ };
}
- }
- /**
- * Generates the actual replaceFn which splits up text nodes
- * and inserts the replacement element.
- */
- function genReplacer(nodeName) {
- var makeReplacementNode;
+ var generalCellForm = {
+ type: 'form',
+ layout: 'flex',
+ direction: 'column',
+ labelGapCalc: 'children',
+ padding: 0,
+ items: [
+ {
+ type: 'form',
+ layout: 'grid',
+ columns: 2,
+ labelGapCalc: false,
+ padding: 0,
+ defaults: {
+ type: 'textbox',
+ maxWidth: 50
+ },
+ items: [
+ {label: 'Width', name: 'width'},
+ {label: 'Height', name: 'height'},
+ {
+ label: 'Cell type',
+ name: 'type',
+ type: 'listbox',
+ text: 'None',
+ minWidth: 90,
+ maxWidth: null,
+ values: [
+ {text: 'Cell', value: 'td'},
+ {text: 'Header cell', value: 'th'}
+ ]
+ },
+ {
+ label: 'Scope',
+ name: 'scope',
+ type: 'listbox',
+ text: 'None',
+ minWidth: 90,
+ maxWidth: null,
+ values: [
+ {text: 'None', value: ''},
+ {text: 'Row', value: 'row'},
+ {text: 'Column', value: 'col'},
+ {text: 'Row group', value: 'rowgroup'},
+ {text: 'Column group', value: 'colgroup'}
+ ]
+ },
+ {
+ label: 'H Align',
+ name: 'align',
+ type: 'listbox',
+ text: 'None',
+ minWidth: 90,
+ maxWidth: null,
+ values: [
+ {text: 'None', value: ''},
+ {text: 'Left', value: 'left'},
+ {text: 'Center', value: 'center'},
+ {text: 'Right', value: 'right'}
+ ]
+ },
+ {
+ label: 'V Align',
+ name: 'valign',
+ type: 'listbox',
+ text: 'None',
+ minWidth: 90,
+ maxWidth: null,
+ values: [
+ {text: 'None', value: ''},
+ {text: 'Top', value: 'top'},
+ {text: 'Middle', value: 'middle'},
+ {text: 'Bottom', value: 'bottom'}
+ ]
+ }
+ ]
+ },
- if (typeof nodeName != 'function') {
- var stencilNode = nodeName.nodeType ? nodeName : doc.createElement(nodeName);
+ classListCtrl
+ ]
+ };
- makeReplacementNode = function(fill, matchIndex) {
- var clone = stencilNode.cloneNode(false);
+ if (editor.settings.table_cell_advtab !== false) {
+ appendStylesToData(dom, data, cellElm);
- clone.setAttribute('data-mce-index', matchIndex);
+ editor.windowManager.open({
+ title: "Cell properties",
+ bodyType: 'tabpanel',
+ data: data,
+ body: [
+ {
+ title: 'General',
+ type: 'form',
+ items: generalCellForm
+ },
- if (fill) {
- clone.appendChild(doc.createTextNode(fill));
- }
+ createStyleForm(dom)
+ ],
- return clone;
- };
+ onsubmit: onSubmitCellForm
+ });
} else {
- makeReplacementNode = nodeName;
+ editor.windowManager.open({
+ title: "Cell properties",
+ data: data,
+ body: generalCellForm,
+ onsubmit: onSubmitCellForm
+ });
}
+ };
- 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);
- }
+ self.row = function() {
+ var dom = editor.dom, tableElm, cellElm, rowElm, classListCtrl, data, rows = [], generalRowForm;
- // 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);
- }
+ function onSubmitRowForm() {
+ var tableElm, oldParentElm, parentElm;
- node.parentNode.removeChild(node);
+ updateStyle(dom, this);
+ data = Tools.extend(data, this.toJSON());
- return el;
- }
+ editor.undoManager.transact(function() {
+ var toType = data.type;
- // 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 = [];
+ each(rows, function(rowElm) {
+ editor.dom.setAttribs(rowElm, {
+ scope: data.scope,
+ style: data.style,
+ 'class': data['class']
+ });
- 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);
- }
+ editor.dom.setStyles(rowElm, {
+ height: addSizeSuffix(data.height)
+ });
- var elB = makeReplacementNode(endNode.data.substring(0, range.endNodeIndex), matchIndex);
+ if (toType != rowElm.parentNode.nodeName.toLowerCase()) {
+ tableElm = dom.getParent(rowElm, 'table');
- parentNode = startNode.parentNode;
- parentNode.insertBefore(before, startNode);
- parentNode.insertBefore(elA, startNode);
- parentNode.removeChild(startNode);
+ oldParentElm = rowElm.parentNode;
+ parentElm = dom.select(toType, tableElm)[0];
+ if (!parentElm) {
+ parentElm = dom.create(toType);
+ if (tableElm.firstChild) {
+ tableElm.insertBefore(parentElm, tableElm.firstChild);
+ } else {
+ tableElm.appendChild(parentElm);
+ }
+ }
- parentNode = endNode.parentNode;
- parentNode.insertBefore(elB, endNode);
- parentNode.insertBefore(after, endNode);
- parentNode.removeChild(endNode);
+ parentElm.appendChild(rowElm);
- return elB;
- };
- }
+ if (!oldParentElm.hasChildNodes()) {
+ dom.remove(oldParentElm);
+ }
+ }
- text = getText(node);
- if (!text) {
- return;
- }
+ // Apply/remove alignment
+ unApplyAlign(rowElm);
+ if (data.align) {
+ editor.formatter.apply('align' + data.align, {}, rowElm);
+ }
+ });
- if (regex.global) {
- while ((m = regex.exec(text))) {
- matches.push(getMatchIndexes(m, captureGroup));
+ editor.focus();
+ });
}
- } else {
- m = text.match(regex);
- matches.push(getMatchIndexes(m, captureGroup));
- }
- if (matches.length) {
- count = matches.length;
- stepThroughMatches(node, matches, genReplacer(replacementNode));
- }
+ tableElm = editor.dom.getParent(editor.selection.getStart(), 'table');
+ cellElm = editor.dom.getParent(editor.selection.getStart(), 'td,th');
- return count;
- }
+ each(tableElm.rows, function(row) {
+ each(row.cells, function(cell) {
+ if (dom.hasClass(cell, 'mce-item-selected') || cell == cellElm) {
+ rows.push(row);
+ return false;
+ }
+ });
+ });
- function Plugin(editor) {
- var self = this, currentIndex = -1;
+ rowElm = rows[0];
+ if (!rowElm) {
+ // If this element is null, return now to avoid crashing.
+ return;
+ }
- function showDialog() {
- var last = {}, selectedText;
+ data = {
+ height: removePxSuffix(dom.getStyle(rowElm, 'height') || dom.getAttrib(rowElm, 'height')),
+ scope: dom.getAttrib(rowElm, 'scope'),
+ 'class': dom.getAttrib(rowElm, 'class')
+ };
- selectedText = tinymce.trim(editor.selection.getContent({format: 'text'}));
+ data.type = rowElm.parentNode.nodeName.toLowerCase();
- function updateButtonStates() {
- win.statusbar.find('#next').disabled(!findSpansByIndex(currentIndex + 1).length);
- win.statusbar.find('#prev').disabled(!findSpansByIndex(currentIndex - 1).length);
- }
+ each('left center right'.split(' '), function(name) {
+ if (editor.formatter.matchNode(rowElm, 'align' + name)) {
+ data.align = name;
+ }
+ });
- function notFoundAlert() {
- tinymce.ui.MessageBox.alert('Could not find the specified string.', function() {
- win.find('#find')[0].focus();
- });
+ if (editor.settings.table_row_class_list) {
+ classListCtrl = {
+ name: 'class',
+ type: 'listbox',
+ label: 'Class',
+ values: buildListItems(
+ editor.settings.table_row_class_list,
+ function(item) {
+ if (item.value) {
+ item.textStyle = function() {
+ return editor.formatter.getCssText({block: 'tr', classes: [item.value]});
+ };
+ }
+ }
+ )
+ };
}
- var win = tinymce.ui.Factory.create({
- type: 'window',
- layout: "flex",
- pack: "center",
- align: "center",
- onClose: function() {
- editor.focus();
- self.done();
+ generalRowForm = {
+ type: 'form',
+ columns: 2,
+ padding: 0,
+ defaults: {
+ type: 'textbox'
},
- onSubmit: function(e) {
- var count, caseState, text, wholeWord;
+ items: [
+ {
+ type: 'listbox',
+ name: 'type',
+ label: 'Row type',
+ text: 'None',
+ maxWidth: null,
+ values: [
+ {text: 'Header', value: 'thead'},
+ {text: 'Body', value: 'tbody'},
+ {text: 'Footer', value: 'tfoot'}
+ ]
+ },
+ {
+ type: 'listbox',
+ name: 'align',
+ label: 'Alignment',
+ text: 'None',
+ maxWidth: null,
+ values: [
+ {text: 'None', value: ''},
+ {text: 'Left', value: 'left'},
+ {text: 'Center', value: 'center'},
+ {text: 'Right', value: 'right'}
+ ]
+ },
+ {label: 'Height', name: 'height'},
+ classListCtrl
+ ]
+ };
- e.preventDefault();
+ if (editor.settings.table_row_advtab !== false) {
+ appendStylesToData(dom, data, rowElm);
- caseState = win.find('#case').checked();
- wholeWord = win.find('#words').checked();
+ editor.windowManager.open({
+ title: "Row properties",
+ data: data,
+ bodyType: 'tabpanel',
+ body: [
+ {
+ title: 'General',
+ type: 'form',
+ items: generalRowForm
+ },
+ createStyleForm(dom)
+ ],
- text = win.find('#find').value();
- if (!text.length) {
- self.done(false);
- win.statusbar.items().slice(1).disabled(true);
- return;
- }
+ onsubmit: onSubmitRowForm
+ });
+ } else {
+ editor.windowManager.open({
+ title: "Row properties",
+ data: data,
+ body: generalRowForm,
+ onsubmit: onSubmitRowForm
+ });
+ }
+ };
+ };
+});
- if (last.text == text && last.caseState == caseState && last.wholeWord == wholeWord) {
- if (findSpansByIndex(currentIndex + 1).length === 0) {
- notFoundAlert();
- return;
- }
+// Included from: js/tinymce/plugins/table/classes/Plugin.js
- self.next();
- updateButtonStates();
- return;
- }
+/**
+ * Plugin.js
+ *
+ * Released under LGPL License.
+ * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
+ *
+ * License: http://www.tinymce.com/license
+ * Contributing: http://www.tinymce.com/contributing
+ */
- count = self.find(text, caseState, wholeWord);
- if (!count) {
- notFoundAlert();
- }
+/**
+ * This class contains all core logic for the table plugin.
+ *
+ * @class tinymce.tableplugin.Plugin
+ * @private
+ */
+define("tinymce/tableplugin/Plugin", [
+ "tinymce/tableplugin/TableGrid",
+ "tinymce/tableplugin/Quirks",
+ "tinymce/tableplugin/CellSelection",
+ "tinymce/tableplugin/Dialogs",
+ "tinymce/util/Tools",
+ "tinymce/dom/TreeWalker",
+ "tinymce/Env",
+ "tinymce/PluginManager"
+], function(TableGrid, Quirks, CellSelection, Dialogs, Tools, TreeWalker, Env, PluginManager) {
+ var each = Tools.each;
- win.statusbar.items().slice(1).disabled(count === 0);
- updateButtonStates();
+ function Plugin(editor) {
+ var clipboardRows, self = this, dialogs = new Dialogs(editor);
- last = {
- text: text,
- caseState: caseState,
- wholeWord: wholeWord
- };
- },
- buttons: [
- {text: "Find", subtype: 'primary', 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: selectedText},
- {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();
+ function cmd(command) {
+ return function() {
+ editor.execCommand(command);
+ };
}
- self.init = function(ed) {
- ed.addMenuItem('searchreplace', {
- text: 'Find and replace',
- shortcut: 'Meta+F',
- onclick: showDialog,
- separator: 'before',
- context: 'edit'
- });
+ function insertTable(cols, rows) {
+ var y, x, html, tableElm;
- ed.addButton('searchreplace', {
- tooltip: 'Find and replace',
- shortcut: 'Meta+F',
- onclick: showDialog
- });
+ html = '';
- ed.addCommand("SearchReplace", showDialog);
- ed.shortcuts.add('Meta+F', '', showDialog);
- };
+ for (y = 0; y < rows; y++) {
+ html += '';
- function getElmIndex(elm) {
- var value = elm.getAttribute('data-mce-index');
+ for (x = 0; x < cols; x++) {
+ html += '' + (Env.ie ? " " : ' ') + ' | ';
+ }
- if (typeof value == "number") {
- return "" + value;
+ html += '
';
}
- return value;
- }
-
- function markAllMatches(regex) {
- var node, marker;
+ html += '
';
- marker = editor.dom.create('span', {
- "data-mce-bogus": 1
- });
+ editor.undoManager.transact(function() {
+ editor.insertContent(html);
- marker.className = 'mce-match-marker'; // IE 7 adds class="mce-match-marker" and class=mce-match-marker
- node = editor.getBody();
+ tableElm = editor.dom.get('__mce');
+ editor.dom.setAttrib(tableElm, 'id', null);
- self.done(false);
+ editor.dom.setAttribs(tableElm, editor.settings.table_default_attributes || {});
+ editor.dom.setStyles(tableElm, editor.settings.table_default_styles || {});
+ });
- return findAndReplaceDOMText(regex, node, marker, false, editor.schema);
+ return tableElm;
}
- function unwrap(node) {
- var parentNode = node.parentNode;
+ function handleDisabledState(ctrl, selector) {
+ function bindStateListener() {
+ ctrl.disabled(!editor.dom.getParent(editor.selection.getStart(), selector));
- if (node.firstChild) {
- parentNode.insertBefore(node.firstChild, node);
+ editor.selection.selectorChanged(selector, function(state) {
+ ctrl.disabled(!state);
+ });
}
- node.parentNode.removeChild(node);
+ if (editor.initialized) {
+ bindStateListener();
+ } else {
+ editor.on('init', bindStateListener);
+ }
}
- 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]);
- }
- }
- }
+ function postRender() {
+ /*jshint validthis:true*/
+ handleDisabledState(this, 'table');
+ }
- return spans;
+ function postRenderCell() {
+ /*jshint validthis:true*/
+ handleDisabledState(this, 'td,th');
}
- function moveSelection(forward) {
- var testIndex = currentIndex, dom = editor.dom;
+ function generateTableGrid() {
+ var html = '';
- forward = forward !== false;
+ html = '';
- if (forward) {
- testIndex++;
- } else {
- testIndex--;
- }
+ for (var y = 0; y < 10; y++) {
+ html += '';
- dom.removeClass(findSpansByIndex(currentIndex), 'mce-match-marker-selected');
+ for (var x = 0; x < 10; x++) {
+ html += ' | ';
+ }
- var spans = findSpansByIndex(testIndex);
- if (spans.length) {
- dom.addClass(findSpansByIndex(testIndex), 'mce-match-marker-selected');
- editor.selection.scrollIntoView(spans[0]);
- return testIndex;
+ html += '
';
}
- return -1;
- }
-
- function removeNode(node) {
- var dom = editor.dom, parent = node.parentNode;
+ html += '
';
- dom.remove(node);
+ html += '1 x 1
';
- if (dom.isEmpty(parent)) {
- dom.remove(parent);
- }
+ return html;
}
- self.find = function(text, matchCase, wholeWord) {
- text = text.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
- text = wholeWord ? '\\b' + text + '\\b' : text;
+ function selectGrid(tx, ty, control) {
+ var table = control.getEl().getElementsByTagName('table')[0];
+ var x, y, focusCell, cell, active;
+ var rtl = control.isRtl() || control.parent().rel == 'tl-tr';
- var count = markAllMatches(new RegExp(text, matchCase ? 'g' : 'gi'));
+ table.nextSibling.innerHTML = (tx + 1) + ' x ' + (ty + 1);
- if (count) {
- currentIndex = -1;
- currentIndex = moveSelection(true);
+ if (rtl) {
+ tx = 9 - tx;
}
- return count;
- };
+ for (y = 0; y < 10; y++) {
+ for (x = 0; x < 10; x++) {
+ cell = table.rows[y].childNodes[x].firstChild;
+ active = (rtl ? x >= tx : x <= tx) && y <= ty;
- self.next = function() {
- var index = moveSelection(true);
+ editor.dom.toggleClass(cell, 'mce-active', active);
- if (index !== -1) {
- currentIndex = index;
+ if (active) {
+ focusCell = cell;
+ }
+ }
}
- };
- self.prev = function() {
- var index = moveSelection(false);
+ return focusCell.parentNode;
+ }
- if (index !== -1) {
- currentIndex = index;
- }
- };
+ if (editor.settings.table_grid === false) {
+ editor.addMenuItem('inserttable', {
+ text: 'Insert table',
+ icon: 'table',
+ context: 'table',
+ onclick: dialogs.table
+ });
+ } else {
+ editor.addMenuItem('inserttable', {
+ text: 'Insert table',
+ icon: 'table',
+ context: 'table',
+ ariaHideMenu: true,
+ onclick: function(e) {
+ if (e.aria) {
+ this.parent().hideAll();
+ e.stopImmediatePropagation();
+ dialogs.table();
+ }
+ },
+ onshow: function() {
+ selectGrid(0, 0, this.menu.items()[0]);
+ },
+ onhide: function() {
+ var elements = this.menu.items()[0].getEl().getElementsByTagName('a');
+ editor.dom.removeClass(elements, 'mce-active');
+ editor.dom.addClass(elements[0], 'mce-active');
+ },
+ menu: [
+ {
+ type: 'container',
+ html: generateTableGrid(),
- function isMatchSpan(node) {
- var matchIndex = getElmIndex(node);
+ onPostRender: function() {
+ this.lastX = this.lastY = 0;
+ },
- return matchIndex !== null && matchIndex.length > 0;
- }
+ onmousemove: function(e) {
+ var target = e.target, x, y;
- self.replace = function(text, forward, all) {
- var i, nodes, node, matchIndex, currentMatchIndex, nextIndex = currentIndex, hasMore;
+ if (target.tagName.toUpperCase() == 'A') {
+ x = parseInt(target.getAttribute('data-mce-x'), 10);
+ y = parseInt(target.getAttribute('data-mce-y'), 10);
- forward = forward !== false;
+ if (this.isRtl() || this.parent().rel == 'tl-tr') {
+ x = 9 - x;
+ }
- node = editor.getBody();
- nodes = tinymce.grep(tinymce.toArray(node.getElementsByTagName('span')), isMatchSpan);
- for (i = 0; i < nodes.length; i++) {
- var nodeIndex = getElmIndex(nodes[i]);
+ if (x !== this.lastX || y !== this.lastY) {
+ selectGrid(x, y, e.control);
- matchIndex = currentMatchIndex = parseInt(nodeIndex, 10);
- if (all || matchIndex === currentIndex) {
- if (text.length) {
- nodes[i].firstChild.nodeValue = text;
- unwrap(nodes[i]);
- } else {
- removeNode(nodes[i]);
- }
+ this.lastX = x;
+ this.lastY = y;
+ }
+ }
+ },
- while (nodes[++i]) {
- matchIndex = parseInt(getElmIndex(nodes[i]), 10);
+ onclick: function(e) {
+ var self = this;
- if (matchIndex === currentMatchIndex) {
- removeNode(nodes[i]);
- } else {
- i--;
- break;
+ if (e.target.tagName.toUpperCase() == 'A') {
+ e.preventDefault();
+ e.stopPropagation();
+ self.parent().cancel();
+
+ editor.undoManager.transact(function() {
+ insertTable(self.lastX + 1, self.lastY + 1);
+ });
+
+ editor.addVisual();
+ }
}
}
+ ]
+ });
+ }
- if (forward) {
- nextIndex--;
- }
- } else if (currentMatchIndex > currentIndex) {
- nodes[i].setAttribute('data-mce-index', currentMatchIndex - 1);
- }
- }
+ editor.addMenuItem('tableprops', {
+ text: 'Table properties',
+ context: 'table',
+ onPostRender: postRender,
+ onclick: dialogs.tableProps
+ });
- editor.undoManager.add();
- currentIndex = nextIndex;
+ editor.addMenuItem('deletetable', {
+ text: 'Delete table',
+ context: 'table',
+ onPostRender: postRender,
+ cmd: 'mceTableDelete'
+ });
- if (forward) {
- hasMore = findSpansByIndex(nextIndex + 1).length > 0;
- self.next();
- } else {
- hasMore = findSpansByIndex(nextIndex - 1).length > 0;
- self.prev();
- }
+ editor.addMenuItem('cell', {
+ separator: 'before',
+ text: 'Cell',
+ context: 'table',
+ menu: [
+ {text: 'Cell properties', onclick: cmd('mceTableCellProps'), onPostRender: postRenderCell},
+ {text: 'Merge cells', onclick: cmd('mceTableMergeCells'), onPostRender: postRenderCell},
+ {text: 'Split cell', onclick: cmd('mceTableSplitCells'), onPostRender: postRenderCell}
+ ]
+ });
- return !all && hasMore;
- };
+ editor.addMenuItem('row', {
+ text: 'Row',
+ context: 'table',
+ menu: [
+ {text: 'Insert row before', onclick: cmd('mceTableInsertRowBefore'), onPostRender: postRenderCell},
+ {text: 'Insert row after', onclick: cmd('mceTableInsertRowAfter'), onPostRender: postRenderCell},
+ {text: 'Delete row', onclick: cmd('mceTableDeleteRow'), onPostRender: postRenderCell},
+ {text: 'Row properties', onclick: cmd('mceTableRowProps'), onPostRender: postRenderCell},
+ {text: '-'},
+ {text: 'Cut row', onclick: cmd('mceTableCutRow'), onPostRender: postRenderCell},
+ {text: 'Copy row', onclick: cmd('mceTableCopyRow'), onPostRender: postRenderCell},
+ {text: 'Paste row before', onclick: cmd('mceTablePasteRowBefore'), onPostRender: postRenderCell},
+ {text: 'Paste row after', onclick: cmd('mceTablePasteRowAfter'), onPostRender: postRenderCell}
+ ]
+ });
- self.done = function(keepEditorSelection) {
- var i, nodes, startContainer, endContainer;
+ editor.addMenuItem('column', {
+ text: 'Column',
+ context: 'table',
+ menu: [
+ {text: 'Insert column before', onclick: cmd('mceTableInsertColBefore'), onPostRender: postRenderCell},
+ {text: 'Insert column after', onclick: cmd('mceTableInsertColAfter'), onPostRender: postRenderCell},
+ {text: 'Delete column', onclick: cmd('mceTableDeleteCol'), onPostRender: postRenderCell}
+ ]
+ });
- nodes = tinymce.toArray(editor.getBody().getElementsByTagName('span'));
- for (i = 0; i < nodes.length; i++) {
- var nodeIndex = getElmIndex(nodes[i]);
+ var menuItems = [];
+ each("inserttable tableprops deletetable | cell row column".split(' '), function(name) {
+ if (name == '|') {
+ menuItems.push({text: '-'});
+ } else {
+ menuItems.push(editor.menuItems[name]);
+ }
+ });
- if (nodeIndex !== null && nodeIndex.length) {
- if (nodeIndex === currentIndex.toString()) {
- if (!startContainer) {
- startContainer = nodes[i].firstChild;
- }
+ editor.addButton("table", {
+ type: "menubutton",
+ title: "Table",
+ menu: menuItems
+ });
- endContainer = nodes[i].firstChild;
- }
+ // Select whole table is a table border is clicked
+ if (!Env.isIE) {
+ editor.on('click', function(e) {
+ e = e.target;
- unwrap(nodes[i]);
+ if (e.nodeName === 'TABLE') {
+ editor.selection.select(e);
+ editor.nodeChanged();
}
- }
-
- 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);
- }
+ self.quirks = new Quirks(editor);
- return rng;
- }
- };
- }
+ editor.on('Init', function() {
+ self.cellSelection = new CellSelection(editor);
+ });
- tinymce.PluginManager.add('searchreplace', Plugin);
-})();
+ editor.on('PreInit', function() {
+ // Remove internal data attributes
+ editor.serializer.addAttributeFilter(
+ 'data-mce-cell-padding,data-mce-border,data-mce-border-color',
+ function(nodes, name) {
+ var i = nodes.length;
- }).apply(root, arguments);
-});
-}(this));
+ while (i--) {
+ nodes[i].attr(name, null);
+ }
+ });
+ });
-(function(root) {
-define("tinymce-spellchecker", ["tinymce"], function() {
- return (function() {
-/**
- * Compiled inline version. (Library mode)
- */
+ // Register action commands
+ each({
+ mceTableSplitCells: function(grid) {
+ grid.split();
+ },
-/*jshint smarttabs:true, undef:true, latedef:true, curly:true, bitwise:true, camelcase:true */
-/*globals $code */
+ mceTableMergeCells: function(grid) {
+ var cell;
-(function(exports, undefined) {
- "use strict";
+ cell = editor.dom.getParent(editor.selection.getStart(), 'th,td');
- var modules = {};
+ if (!editor.dom.select('td.mce-item-selected,th.mce-item-selected').length) {
+ dialogs.merge(grid, cell);
+ } else {
+ grid.merge();
+ }
+ },
- function require(ids, callback) {
- var module, defs = [];
+ mceTableInsertRowBefore: function(grid) {
+ grid.insertRow(true);
+ },
- for (var i = 0; i < ids.length; ++i) {
- module = modules[ids[i]] || resolve(ids[i]);
- if (!module) {
- throw 'module definition dependecy not found: ' + ids[i];
- }
+ mceTableInsertRowAfter: function(grid) {
+ grid.insertRow();
+ },
- defs.push(module);
- }
+ mceTableInsertColBefore: function(grid) {
+ grid.insertCol(true);
+ },
- callback.apply(null, defs);
- }
+ mceTableInsertColAfter: function(grid) {
+ grid.insertCol();
+ },
- function define(id, dependencies, definition) {
- if (typeof id !== 'string') {
- throw 'invalid module definition, module id must be defined and be a string';
- }
+ mceTableDeleteCol: function(grid) {
+ grid.deleteCols();
+ },
- if (dependencies === undefined) {
- throw 'invalid module definition, dependencies must be specified';
- }
+ mceTableDeleteRow: function(grid) {
+ grid.deleteRows();
+ },
- if (definition === undefined) {
- throw 'invalid module definition, definition function must be specified';
- }
+ mceTableCutRow: function(grid) {
+ clipboardRows = grid.cutRows();
+ },
- require(dependencies, function() {
- modules[id] = definition.apply(null, arguments);
- });
- }
+ mceTableCopyRow: function(grid) {
+ clipboardRows = grid.copyRows();
+ },
- function defined(id) {
- return !!modules[id];
- }
+ mceTablePasteRowBefore: function(grid) {
+ grid.pasteRows(clipboardRows, true);
+ },
- function resolve(id) {
- var target = exports;
- var fragments = id.split(/[.\/]/);
+ mceTablePasteRowAfter: function(grid) {
+ grid.pasteRows(clipboardRows);
+ },
- for (var fi = 0; fi < fragments.length; ++fi) {
- if (!target[fragments[fi]]) {
- return;
+ mceTableDelete: function(grid) {
+ grid.deleteTable();
}
+ }, function(func, name) {
+ editor.addCommand(name, function() {
+ var grid = new TableGrid(editor);
- target = target[fragments[fi]];
- }
+ if (grid) {
+ func(grid);
+ editor.execCommand('mceRepaint');
+ self.cellSelection.clear();
+ }
+ });
+ });
- return target;
- }
+ // Register dialog commands
+ each({
+ mceInsertTable: dialogs.table,
+ mceTableProps: function() {
+ dialogs.table(true);
+ },
+ mceTableRowProps: dialogs.row,
+ mceTableCellProps: dialogs.cell
+ }, function(func, name) {
+ editor.addCommand(name, function(ui, val) {
+ func(val);
+ });
+ });
- function expose(ids) {
- var i, target, id, fragments, privateModules;
+ // Enable tab key cell navigation
+ if (editor.settings.table_tab_navigation !== false) {
+ editor.on('keydown', function(e) {
+ var cellElm, grid, delta;
- for (i = 0; i < ids.length; i++) {
- target = exports;
- id = ids[i];
- fragments = id.split(/[.\/]/);
+ if (e.keyCode == 9) {
+ cellElm = editor.dom.getParent(editor.selection.getStart(), 'th,td');
- for (var fi = 0; fi < fragments.length - 1; ++fi) {
- if (target[fragments[fi]] === undefined) {
- target[fragments[fi]] = {};
- }
+ if (cellElm) {
+ e.preventDefault();
- target = target[fragments[fi]];
- }
+ grid = new TableGrid(editor);
+ delta = e.shiftKey ? -1 : 1;
- target[fragments[fragments.length - 1]] = modules[id];
+ editor.undoManager.transact(function() {
+ if (!grid.moveRelIdx(cellElm, delta) && delta > 0) {
+ grid.insertRow();
+ grid.refresh();
+ grid.moveRelIdx(cellElm, delta);
+ }
+ });
+ }
+ }
+ });
}
-
- // Expose private modules for unit tests
- if (exports.AMDLC_TESTS) {
- privateModules = exports.privateModules || {};
-
- for (id in modules) {
- privateModules[id] = modules[id];
- }
-
- for (i = 0; i < ids.length; i++) {
- delete privateModules[ids[i]];
- }
- exports.privateModules = privateModules;
- }
+ self.insertTable = insertTable;
}
-// Included from: js/tinymce/plugins/spellchecker/classes/DomTextMatcher.js
+ PluginManager.add('table', Plugin);
+});
+})(this);
+
+ }).apply(root, arguments);
+});
+}(this));
+(function(root) {
+define("tinymce-template", ["tinymce"], function() {
+ return (function() {
/**
- * DomTextMatcher.js
+ * plugin.js
*
* Released under LGPL License.
* Copyright (c) 1999-2015 Ephox Corp. All rights reserved
@@ -57424,471 +73684,559 @@ define("tinymce-spellchecker", ["tinymce"], function() {
* Contributing: http://www.tinymce.com/contributing
*/
-/*eslint no-labels:0, no-constant-condition: 0 */
+/*global tinymce:true */
-/**
- * This class logic for filtering text and matching words.
- *
- * @class tinymce.spellcheckerplugin.TextFilter
- * @private
- */
-define("tinymce/spellcheckerplugin/DomTextMatcher", [], 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
- return function(node, editor) {
- var m, matches = [], text, dom = editor.dom;
- var blockElementsMap, hiddenTextElementsMap, shortEndedElementsMap;
+tinymce.PluginManager.add('template', function(editor) {
+ var each = tinymce.each;
- blockElementsMap = editor.schema.getBlockElements(); // H1-H6, P, TD etc
- hiddenTextElementsMap = editor.schema.getWhiteSpaceElements(); // TEXTAREA, PRE, STYLE, SCRIPT
- shortEndedElementsMap = editor.schema.getShortEndedElements(); // BR, IMG, INPUT
+ function createTemplateList(callback) {
+ return function() {
+ var templateList = editor.settings.templates;
- function createMatch(m, data) {
- if (!m[0]) {
- throw 'findAndReplaceDOMText cannot handle zero-length matches';
+ if (typeof templateList == "string") {
+ tinymce.util.XHR.send({
+ url: templateList,
+ success: function(text) {
+ callback(tinymce.util.JSON.parse(text));
+ }
+ });
+ } else {
+ callback(templateList);
}
+ };
+ }
- return {
- start: m.index,
- end: m.index + m[0].length,
- text: m[0],
- data: data
- };
+ function showDialog(templateList) {
+ var win, values = [], templateHtml;
+
+ if (!templateList || templateList.length === 0) {
+ editor.windowManager.alert('No templates defined');
+ return;
}
- function getText(node) {
- var txt;
+ tinymce.each(templateList, function(template) {
+ values.push({
+ selected: !values.length,
+ text: template.title,
+ value: {
+ url: template.url,
+ content: template.content,
+ description: template.description
+ }
+ });
+ });
- if (node.nodeType === 3) {
- return node.data;
- }
+ function onSelectTemplate(e) {
+ var value = e.control.value();
- if (hiddenTextElementsMap[node.nodeName] && !blockElementsMap[node.nodeName]) {
- return '';
- }
+ function insertIframeHtml(html) {
+ if (html.indexOf('') == -1) {
+ var contentCssLinks = '';
- txt = '';
+ tinymce.each(editor.contentCSS, function(url) {
+ contentCssLinks += '';
+ });
- if (blockElementsMap[node.nodeName] || shortEndedElementsMap[node.nodeName]) {
- txt += '\n';
+ html = (
+ '' +
+ '' +
+ '' +
+ contentCssLinks +
+ '' +
+ '' +
+ html +
+ '' +
+ ''
+ );
+ }
+
+ html = replaceTemplateValues(html, 'template_preview_replace_values');
+
+ var doc = win.find('iframe')[0].getEl().contentWindow.document;
+ doc.open();
+ doc.write(html);
+ doc.close();
}
- if ((node = node.firstChild)) {
- do {
- txt += getText(node);
- } while ((node = node.nextSibling));
+ if (value.url) {
+ tinymce.util.XHR.send({
+ url: value.url,
+ success: function(html) {
+ templateHtml = html;
+ insertIframeHtml(templateHtml);
+ }
+ });
+ } else {
+ templateHtml = value.content;
+ insertIframeHtml(templateHtml);
}
- return txt;
+ win.find('#description')[0].text(e.control.value().description);
}
- function stepThroughMatches(node, matches, replaceFn) {
- var startNode, endNode, startNodeIndex,
- endNodeIndex, innerNodes = [], atIndex = 0, curNode = node,
- matchLocation, matchIndex = 0;
+ win = editor.windowManager.open({
+ title: 'Insert template',
+ layout: 'flex',
+ direction: 'column',
+ align: 'stretch',
+ padding: 15,
+ spacing: 10,
- matches = matches.slice(0);
- matches.sort(function(a, b) {
- return a.start - b.start;
- });
+ items: [
+ {type: 'form', flex: 0, padding: 0, items: [
+ {type: 'container', label: 'Templates', items: {
+ type: 'listbox', label: 'Templates', name: 'template', values: values, onselect: onSelectTemplate
+ }}
+ ]},
+ {type: 'label', name: 'description', label: 'Description', text: '\u00a0'},
+ {type: 'iframe', flex: 1, border: 1}
+ ],
- matchLocation = matches.shift();
+ onsubmit: function() {
+ insertTemplate(false, templateHtml);
+ },
- out: while (true) {
- if (blockElementsMap[curNode.nodeName] || shortEndedElementsMap[curNode.nodeName]) {
- atIndex++;
- }
+ width: editor.getParam('template_popup_width', 600),
+ height: editor.getParam('template_popup_height', 500)
+ });
- if (curNode.nodeType === 3) {
- if (!endNode && curNode.length + atIndex >= matchLocation.end) {
- // We've found the ending
- endNode = curNode;
- endNodeIndex = matchLocation.end - atIndex;
- } else if (startNode) {
- // Intersecting node
- innerNodes.push(curNode);
- }
+ win.find('listbox')[0].fire('select');
+ }
- if (!startNode && curNode.length + atIndex > matchLocation.start) {
- // We've found the match start
- startNode = curNode;
- startNodeIndex = matchLocation.start - atIndex;
- }
+ function getDateTime(fmt, date) {
+ var daysShort = "Sun Mon Tue Wed Thu Fri Sat Sun".split(' ');
+ var daysLong = "Sunday Monday Tuesday Wednesday Thursday Friday Saturday Sunday".split(' ');
+ var monthsShort = "Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec".split(' ');
+ var monthsLong = "January February March April May June July August September October November December".split(' ');
- atIndex += curNode.length;
+ function addZeros(value, len) {
+ value = "" + value;
+
+ if (value.length < len) {
+ for (var i = 0; i < (len - value.length); i++) {
+ value = "0" + value;
}
+ }
- if (startNode && endNode) {
- curNode = replaceFn({
- startNode: startNode,
- startNodeIndex: startNodeIndex,
- endNode: endNode,
- endNodeIndex: endNodeIndex,
- innerNodes: innerNodes,
- match: matchLocation.text,
- matchIndex: matchIndex
- });
+ return value;
+ }
- // 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++;
+ date = date || new Date();
- 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;
- }
+ fmt = fmt.replace("%D", "%m/%d/%Y");
+ fmt = fmt.replace("%r", "%I:%M:%S %p");
+ fmt = fmt.replace("%Y", "" + date.getFullYear());
+ fmt = fmt.replace("%y", "" + date.getYear());
+ fmt = fmt.replace("%m", addZeros(date.getMonth() + 1, 2));
+ fmt = fmt.replace("%d", addZeros(date.getDate(), 2));
+ fmt = fmt.replace("%H", "" + addZeros(date.getHours(), 2));
+ fmt = fmt.replace("%M", "" + addZeros(date.getMinutes(), 2));
+ fmt = fmt.replace("%S", "" + addZeros(date.getSeconds(), 2));
+ fmt = fmt.replace("%I", "" + ((date.getHours() + 11) % 12 + 1));
+ fmt = fmt.replace("%p", "" + (date.getHours() < 12 ? "AM" : "PM"));
+ fmt = fmt.replace("%B", "" + editor.translate(monthsLong[date.getMonth()]));
+ fmt = fmt.replace("%b", "" + editor.translate(monthsShort[date.getMonth()]));
+ fmt = fmt.replace("%A", "" + editor.translate(daysLong[date.getDay()]));
+ fmt = fmt.replace("%a", "" + editor.translate(daysShort[date.getDay()]));
+ fmt = fmt.replace("%%", "%");
- // Move forward or up:
- while (true) {
- if (curNode.nextSibling) {
- curNode = curNode.nextSibling;
- break;
- } else if (curNode.parentNode !== node) {
- curNode = curNode.parentNode;
- } else {
- break out;
+ return fmt;
+ }
+
+ function replaceVals(e) {
+ var dom = editor.dom, vl = editor.getParam('template_replace_values');
+
+ each(dom.select('*', e), function(e) {
+ each(vl, function(v, k) {
+ if (dom.hasClass(e, k)) {
+ if (typeof vl[k] == 'function') {
+ vl[k](e);
}
}
+ });
+ });
+ }
+
+ function replaceTemplateValues(html, templateValuesOptionName) {
+ each(editor.getParam(templateValuesOptionName), function(v, k) {
+ if (typeof v != 'function') {
+ html = html.replace(new RegExp('\\{\\$' + k + '\\}', 'g'), v);
}
- }
+ });
- /**
- * Generates the actual replaceFn which splits up text nodes
- * and inserts the replacement element.
- */
- function genReplacer(callback) {
- function makeReplacementNode(fill, matchIndex) {
- var match = matches[matchIndex];
+ return html;
+ }
- if (!match.stencil) {
- match.stencil = callback(match);
- }
+ function insertTemplate(ui, html) {
+ var el, n, dom = editor.dom, sel = editor.selection.getContent();
- var clone = match.stencil.cloneNode(false);
- clone.setAttribute('data-mce-index', matchIndex);
+ html = replaceTemplateValues(html, 'template_replace_values');
+ el = dom.create('div', null, html);
- if (fill) {
- clone.appendChild(dom.doc.createTextNode(fill));
- }
+ // Find template element within div
+ n = dom.select('.mceTmpl', el);
+ if (n && n.length > 0) {
+ el = dom.create('div', null);
+ el.appendChild(n[0].cloneNode(true));
+ }
- return clone;
+ function hasClass(n, c) {
+ return new RegExp('\\b' + c + '\\b', 'g').test(n.className);
+ }
+
+ each(dom.select('*', el), function(n) {
+ // Replace cdate
+ if (hasClass(n, editor.getParam('template_cdate_classes', 'cdate').replace(/\s+/g, '|'))) {
+ n.innerHTML = getDateTime(editor.getParam("template_cdate_format", editor.getLang("template.cdate_format")));
}
- return function(range) {
- var before, after, parentNode, startNode = range.startNode,
- endNode = range.endNode, matchIndex = range.matchIndex,
- doc = dom.doc;
+ // Replace mdate
+ if (hasClass(n, editor.getParam('template_mdate_classes', 'mdate').replace(/\s+/g, '|'))) {
+ n.innerHTML = getDateTime(editor.getParam("template_mdate_format", editor.getLang("template.mdate_format")));
+ }
- if (startNode === endNode) {
- var node = startNode;
+ // Replace selection
+ if (hasClass(n, editor.getParam('template_selected_content_classes', 'selcontent').replace(/\s+/g, '|'))) {
+ n.innerHTML = sel;
+ }
+ });
- 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);
- }
+ replaceVals(el);
- // Create the replacement node:
- var el = makeReplacementNode(range.match, 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);
- }
+ editor.execCommand('mceInsertContent', false, el.innerHTML);
+ editor.addVisual();
+ }
- node.parentNode.removeChild(node);
+ editor.addCommand('mceInsertTemplate', insertTemplate);
- return el;
- }
+ editor.addButton('template', {
+ title: 'Insert template',
+ onclick: createTemplateList(showDialog)
+ });
- // 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 = [];
+ editor.addMenuItem('template', {
+ text: 'Insert template',
+ onclick: createTemplateList(showDialog),
+ context: 'insert'
+ });
- 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);
- }
+ editor.on('PreProcess', function(o) {
+ var dom = editor.dom;
- var elB = makeReplacementNode(endNode.data.substring(0, range.endNodeIndex), matchIndex);
+ each(dom.select('div', o.node), function(e) {
+ if (dom.hasClass(e, 'mceTmpl')) {
+ each(dom.select('*', e), function(e) {
+ if (dom.hasClass(e, editor.getParam('template_mdate_classes', 'mdate').replace(/\s+/g, '|'))) {
+ e.innerHTML = getDateTime(editor.getParam("template_mdate_format", editor.getLang("template.mdate_format")));
+ }
+ });
- parentNode = startNode.parentNode;
- parentNode.insertBefore(before, startNode);
- parentNode.insertBefore(elA, startNode);
- parentNode.removeChild(startNode);
+ replaceVals(e);
+ }
+ });
+ });
+});
- parentNode = endNode.parentNode;
- parentNode.insertBefore(elB, endNode);
- parentNode.insertBefore(after, endNode);
- parentNode.removeChild(endNode);
+ }).apply(root, arguments);
+});
+}(this));
- return elB;
- };
- }
+(function(root) {
+define("tinymce-textcolor", ["tinymce"], function() {
+ return (function() {
+/**
+ * plugin.js
+ *
+ * Released under LGPL License.
+ * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
+ *
+ * License: http://www.tinymce.com/license
+ * Contributing: http://www.tinymce.com/contributing
+ */
- function unwrapElement(element) {
- var parentNode = element.parentNode;
- parentNode.insertBefore(element.firstChild, element);
- element.parentNode.removeChild(element);
- }
+/*global tinymce:true */
+/*eslint consistent-this:0 */
- function getWrappersByIndex(index) {
- var elements = node.getElementsByTagName('*'), wrappers = [];
+tinymce.PluginManager.add('textcolor', function(editor) {
+ var cols, rows;
- index = typeof index == "number" ? "" + index : null;
+ rows = editor.settings.textcolor_rows || 5;
+ cols = editor.settings.textcolor_cols || 8;
- for (var i = 0; i < elements.length; i++) {
- var element = elements[i], dataIndex = element.getAttribute('data-mce-index');
+ function getCurrentColor(format) {
+ var color;
- if (dataIndex !== null && dataIndex.length) {
- if (dataIndex === index || index === null) {
- wrappers.push(element);
- }
- }
+ editor.dom.getParents(editor.selection.getStart(), function(elm) {
+ var value;
+
+ if ((value = elm.style[format == 'forecolor' ? 'color' : 'background-color'])) {
+ color = value;
}
+ });
- return wrappers;
- }
+ return color;
+ }
- /**
- * Returns the index of a specific match object or -1 if it isn't found.
- *
- * @param {Match} match Text match object.
- * @return {Number} Index of match or -1 if it isn't found.
- */
- function indexOf(match) {
- var i = matches.length;
- while (i--) {
- if (matches[i] === match) {
- return i;
- }
- }
+ function mapColors() {
+ var i, colors = [], colorMap;
- return -1;
+ colorMap = editor.settings.textcolor_map || [
+ "000000", "Black",
+ "993300", "Burnt orange",
+ "333300", "Dark olive",
+ "003300", "Dark green",
+ "003366", "Dark azure",
+ "000080", "Navy Blue",
+ "333399", "Indigo",
+ "333333", "Very dark gray",
+ "800000", "Maroon",
+ "FF6600", "Orange",
+ "808000", "Olive",
+ "008000", "Green",
+ "008080", "Teal",
+ "0000FF", "Blue",
+ "666699", "Grayish blue",
+ "808080", "Gray",
+ "FF0000", "Red",
+ "FF9900", "Amber",
+ "99CC00", "Yellow green",
+ "339966", "Sea green",
+ "33CCCC", "Turquoise",
+ "3366FF", "Royal blue",
+ "800080", "Purple",
+ "999999", "Medium gray",
+ "FF00FF", "Magenta",
+ "FFCC00", "Gold",
+ "FFFF00", "Yellow",
+ "00FF00", "Lime",
+ "00FFFF", "Aqua",
+ "00CCFF", "Sky blue",
+ "993366", "Red violet",
+ "FFFFFF", "White",
+ "FF99CC", "Pink",
+ "FFCC99", "Peach",
+ "FFFF99", "Light yellow",
+ "CCFFCC", "Pale green",
+ "CCFFFF", "Pale cyan",
+ "99CCFF", "Light sky blue",
+ "CC99FF", "Plum"
+ ];
+
+ for (i = 0; i < colorMap.length; i += 2) {
+ colors.push({
+ text: colorMap[i + 1],
+ color: '#' + colorMap[i]
+ });
}
- /**
- * Filters the matches. If the callback returns true it stays if not it gets removed.
- *
- * @param {Function} callback Callback to execute for each match.
- * @return {DomTextMatcher} Current DomTextMatcher instance.
- */
- function filter(callback) {
- var filteredMatches = [];
+ return colors;
+ }
- each(function(match, i) {
- if (callback(match, i)) {
- filteredMatches.push(match);
- }
- });
+ function renderColorPicker() {
+ var ctrl = this, colors, color, html, last, x, y, i, id = ctrl._id, count = 0;
- matches = filteredMatches;
+ function getColorCellHtml(color, title) {
+ var isNoColor = color == 'transparent';
- /*jshint validthis:true*/
- return this;
+ return (
+ '' +
+ ' ' +
+ (isNoColor ? '×' : '') +
+ ' ' +
+ ' | '
+ );
}
- /**
- * Executes the specified callback for each match.
- *
- * @param {Function} callback Callback to execute for each match.
- * @return {DomTextMatcher} Current DomTextMatcher instance.
- */
- function each(callback) {
- for (var i = 0, l = matches.length; i < l; i++) {
- if (callback(matches[i], i) === false) {
- break;
+ colors = mapColors();
+ colors.push({
+ text: tinymce.translate("No color"),
+ color: "transparent"
+ });
+
+ html = '';
- i = elements.length;
- while (i--) {
- unwrapElement(elements[i]);
- }
+ return html;
+ }
- return this;
+ function applyFormat(format, value) {
+ editor.undoManager.transact(function() {
+ editor.focus();
+ editor.formatter.apply(format, {value: value});
+ editor.nodeChanged();
+ });
+ }
+
+ function removeFormat(format) {
+ editor.undoManager.transact(function() {
+ editor.focus();
+ editor.formatter.remove(format, {value: null}, null, true);
+ editor.nodeChanged();
+ });
+ }
+
+ function onPanelClick(e) {
+ var buttonCtrl = this.parent(), value;
+
+ function selectColor(value) {
+ buttonCtrl.hidePanel();
+ buttonCtrl.color(value);
+ applyFormat(buttonCtrl.settings.format, value);
}
- /**
- * Returns a match object by the specified DOM element.
- *
- * @param {DOMElement} element Element to return match object for.
- * @return {Object} Match object for the specified element.
- */
- function matchFromElement(element) {
- return matches[element.getAttribute('data-mce-index')];
+ function resetColor() {
+ buttonCtrl.hidePanel();
+ buttonCtrl.resetColor();
+ removeFormat(buttonCtrl.settings.format);
}
- /**
- * Returns a DOM element from the specified match element. This will be the first element if it's split
- * on multiple nodes.
- *
- * @param {Object} match Match element to get first element of.
- * @return {DOMElement} DOM element for the specified match object.
- */
- function elementFromMatch(match) {
- return getWrappersByIndex(indexOf(match))[0];
+ function setDivColor(div, value) {
+ div.style.background = value;
+ div.setAttribute('data-mce-color', value);
}
- /**
- * Adds match the specified range for example a grammar line.
- *
- * @param {Number} start Start offset.
- * @param {Number} length Length of the text.
- * @param {Object} data Custom data object for match.
- * @return {DomTextMatcher} Current DomTextMatcher instance.
- */
- function add(start, length, data) {
- matches.push({
- start: start,
- end: start + length,
- text: text.substr(start, length),
- data: data
- });
+ if (tinymce.DOM.getParent(e.target, '.mce-custom-color-btn')) {
+ buttonCtrl.hidePanel();
- return this;
- }
+ editor.settings.color_picker_callback.call(editor, function(value) {
+ var tableElm = buttonCtrl.panel.getEl().getElementsByTagName('table')[0];
+ var customColorCells, div, i;
- /**
- * Returns a DOM range for the specified match.
- *
- * @param {Object} match Match object to get range for.
- * @return {DOMRange} DOM Range for the specified match.
- */
- function rangeFromMatch(match) {
- var wrappers = getWrappersByIndex(indexOf(match));
+ customColorCells = tinymce.map(tableElm.rows[tableElm.rows.length - 1].childNodes, function(elm) {
+ return elm.firstChild;
+ });
- var rng = editor.dom.createRng();
- rng.setStartBefore(wrappers[0]);
- rng.setEndAfter(wrappers[wrappers.length - 1]);
+ for (i = 0; i < customColorCells.length; i++) {
+ div = customColorCells[i];
+ if (!div.getAttribute('data-mce-color')) {
+ break;
+ }
+ }
- return rng;
+ // Shift colors to the right
+ // TODO: Might need to be the left on RTL
+ if (i == cols) {
+ for (i = 0; i < cols - 1; i++) {
+ setDivColor(customColorCells[i], customColorCells[i + 1].getAttribute('data-mce-color'));
+ }
+ }
+
+ setDivColor(div, value);
+ selectColor(value);
+ }, getCurrentColor(buttonCtrl.settings.format));
}
- /**
- * Replaces the specified match with the specified text.
- *
- * @param {Object} match Match object to replace.
- * @param {String} text Text to replace the match with.
- * @return {DOMRange} DOM range produced after the replace.
- */
- function replace(match, text) {
- var rng = rangeFromMatch(match);
+ value = e.target.getAttribute('data-mce-color');
+ if (value) {
+ if (this.lastId) {
+ document.getElementById(this.lastId).setAttribute('aria-selected', false);
+ }
- rng.deleteContents();
+ e.target.setAttribute('aria-selected', true);
+ this.lastId = e.target.id;
- if (text.length > 0) {
- rng.insertNode(editor.dom.doc.createTextNode(text));
+ if (value == 'transparent') {
+ resetColor();
+ } else {
+ selectColor(value);
}
-
- return rng;
+ } else if (value !== null) {
+ buttonCtrl.hidePanel();
}
+ }
- /**
- * Resets the DomTextMatcher instance. This will remove any wrapped nodes and remove any matches.
- *
- * @return {[type]} [description]
- */
- function reset() {
- matches.splice(0, matches.length);
- unwrap();
+ function onButtonClick() {
+ var self = this;
- return this;
+ if (self._color) {
+ applyFormat(self.settings.format, self._color);
+ } else {
+ removeFormat(self.settings.format);
}
+ }
- text = getText(node);
+ editor.addButton('forecolor', {
+ type: 'colorbutton',
+ tooltip: 'Text color',
+ format: 'forecolor',
+ panel: {
+ role: 'application',
+ ariaRemember: true,
+ html: renderColorPicker,
+ onclick: onPanelClick
+ },
+ onclick: onButtonClick
+ });
- return {
- text: text,
- matches: matches,
- each: each,
- filter: filter,
- reset: reset,
- matchFromElement: matchFromElement,
- elementFromMatch: elementFromMatch,
- find: find,
- add: add,
- wrap: wrap,
- unwrap: unwrap,
- replace: replace,
- rangeFromMatch: rangeFromMatch,
- indexOf: indexOf
- };
- };
+ editor.addButton('backcolor', {
+ type: 'colorbutton',
+ tooltip: 'Background color',
+ format: 'hilitecolor',
+ panel: {
+ role: 'application',
+ ariaRemember: true,
+ html: renderColorPicker,
+ onclick: onPanelClick
+ },
+ onclick: onButtonClick
+ });
});
-// Included from: js/tinymce/plugins/spellchecker/classes/Plugin.js
+ }).apply(root, arguments);
+});
+}(this));
+
+(function(root) {
+define("tinymce-textpattern", ["tinymce"], function() {
+ return (function() {
/**
- * Plugin.js
+ * plugin.js
*
* Released under LGPL License.
* Copyright (c) 1999-2015 Ephox Corp. All rights reserved
@@ -57897,441 +74245,366 @@ define("tinymce/spellcheckerplugin/DomTextMatcher", [], function() {
* Contributing: http://www.tinymce.com/contributing
*/
-/*jshint camelcase:false */
+/*global tinymce:true */
-/**
- * This class contains all core logic for the spellchecker plugin.
- *
- * @class tinymce.spellcheckerplugin.Plugin
- * @private
- */
-define("tinymce/spellcheckerplugin/Plugin", [
- "tinymce/spellcheckerplugin/DomTextMatcher",
- "tinymce/PluginManager",
- "tinymce/util/Tools",
- "tinymce/ui/Menu",
- "tinymce/dom/DOMUtils",
- "tinymce/util/XHR",
- "tinymce/util/URI",
- "tinymce/util/JSON"
-], function(DomTextMatcher, PluginManager, Tools, Menu, DOMUtils, XHR, URI, JSON) {
- PluginManager.add('spellchecker', function(editor, url) {
- var languageMenuItems, self = this, lastSuggestions, started, suggestionsMenu, settings = editor.settings;
- var hasDictionarySupport;
+tinymce.PluginManager.add('textpattern', function(editor) {
+ var isPatternsDirty = true, patterns;
- function getTextMatcher() {
- if (!self.textMatcher) {
- self.textMatcher = new DomTextMatcher(editor.getBody(), editor);
- }
+ patterns = editor.settings.textpattern_patterns || [
+ {start: '*', end: '*', format: 'italic'},
+ {start: '**', end: '**', format: 'bold'},
+ {start: '#', format: 'h1'},
+ {start: '##', format: 'h2'},
+ {start: '###', format: 'h3'},
+ {start: '####', format: 'h4'},
+ {start: '#####', format: 'h5'},
+ {start: '######', format: 'h6'},
+ {start: '1. ', cmd: 'InsertOrderedList'},
+ {start: '* ', cmd: 'InsertUnorderedList'},
+ {start: '- ', cmd: 'InsertUnorderedList'}
+ ];
- return self.textMatcher;
- }
+ // Returns a sorted patterns list, ordered descending by start length
+ function getPatterns() {
+ if (isPatternsDirty) {
+ patterns.sort(function(a, b) {
+ if (a.start.length > b.start.length) {
+ return -1;
+ }
- function buildMenuItems(listName, languageValues) {
- var items = [];
+ if (a.start.length < b.start.length) {
+ return 1;
+ }
- Tools.each(languageValues, function(languageValue) {
- items.push({
- selectable: true,
- text: languageValue.name,
- data: languageValue.value
- });
+ return 0;
});
- return items;
- }
-
- var languagesString = settings.spellchecker_languages ||
- 'English=en,Danish=da,Dutch=nl,Finnish=fi,French=fr_FR,' +
- 'German=de,Italian=it,Polish=pl,Portuguese=pt_BR,' +
- 'Spanish=es,Swedish=sv';
-
- languageMenuItems = buildMenuItems('Language',
- Tools.map(languagesString.split(','), function(langPair) {
- langPair = langPair.split('=');
-
- return {
- name: langPair[0],
- value: langPair[1]
- };
- })
- );
-
- function isEmpty(obj) {
- /*jshint unused:false*/
- /*eslint no-unused-vars:0 */
- for (var name in obj) {
- return false;
- }
-
- return true;
+ isPatternsDirty = false;
}
- function showSuggestions(word, spans) {
- var items = [], suggestions = lastSuggestions[word];
-
- Tools.each(suggestions, function(suggestion) {
- items.push({
- text: suggestion,
- onclick: function() {
- editor.insertContent(editor.dom.encode(suggestion));
- editor.dom.remove(spans);
- checkIfFinished();
- }
- });
- });
+ return patterns;
+ }
- items.push({text: '-'});
+ // Finds a matching pattern to the specified text
+ function findPattern(text) {
+ var patterns = getPatterns();
- if (hasDictionarySupport) {
- items.push({text: 'Add to Dictionary', onclick: function() {
- addToDictionary(word, spans);
- }});
+ for (var i = 0; i < patterns.length; i++) {
+ if (text.indexOf(patterns[i].start) !== 0) {
+ continue;
}
- items.push.apply(items, [
- {text: 'Ignore', onclick: function() {
- ignoreWord(word, spans);
- }},
-
- {text: 'Ignore all', onclick: function() {
- ignoreWord(word, spans, true);
- }}
- ]);
-
- // Render menu
- suggestionsMenu = new Menu({
- items: items,
- context: 'contextmenu',
- onautohide: function(e) {
- if (e.target.className.indexOf('spellchecker') != -1) {
- e.preventDefault();
- }
- },
- onhide: function() {
- suggestionsMenu.remove();
- suggestionsMenu = null;
- }
- });
-
- suggestionsMenu.renderTo(document.body);
-
- // Position menu
- var pos = DOMUtils.DOM.getPos(editor.getContentAreaContainer());
- var targetPos = editor.dom.getPos(spans[0]);
- var root = editor.dom.getRoot();
-
- // Adjust targetPos for scrolling in the editor
- if (root.nodeName == 'BODY') {
- targetPos.x -= root.ownerDocument.documentElement.scrollLeft || root.scrollLeft;
- targetPos.y -= root.ownerDocument.documentElement.scrollTop || root.scrollTop;
- } else {
- targetPos.x -= root.scrollLeft;
- targetPos.y -= root.scrollTop;
+ if (patterns[i].end && text.lastIndexOf(patterns[i].end) != text.length - patterns[i].end.length) {
+ continue;
}
- pos.x += targetPos.x;
- pos.y += targetPos.y;
-
- suggestionsMenu.moveTo(pos.x, pos.y + spans[0].offsetHeight);
- }
-
- function getWordCharPattern() {
- // Regexp for finding word specific characters this will split words by
- // spaces, quotes, copy right characters etc. It's escaped with unicode characters
- // to make it easier to output scripts on servers using different encodings
- // so if you add any characters outside the 128 byte range make sure to escape it
- return editor.getParam('spellchecker_wordchar_pattern') || new RegExp("[^" +
- "\\s!\"#$%&()*+,-./:;<=>?@[\\]^_{|}`" +
- "\u00a7\u00a9\u00ab\u00ae\u00b1\u00b6\u00b7\u00b8\u00bb" +
- "\u00bc\u00bd\u00be\u00bf\u00d7\u00f7\u00a4\u201d\u201c\u201e\u00a0\u2002\u2003\u2009" +
- "]+", "g");
+ return patterns[i];
}
+ }
- function defaultSpellcheckCallback(method, text, doneCallback, errorCallback) {
- var data = {method: method}, postData = '';
-
- if (method == "spellcheck") {
- data.text = text;
- data.lang = settings.spellchecker_language;
- }
+ // Finds the best matching end pattern
+ function findEndPattern(text, offset, delta) {
+ var patterns, pattern, i;
- if (method == "addToDictionary") {
- data.word = text;
+ // Find best matching end
+ patterns = getPatterns();
+ for (i = 0; i < patterns.length; i++) {
+ pattern = patterns[i];
+ if (pattern.end && text.substr(offset - pattern.end.length - delta, pattern.end.length) == pattern.end) {
+ return pattern;
}
+ }
+ }
- Tools.each(data, function(value, key) {
- if (postData) {
- postData += '&';
- }
+ // Handles inline formats like *abc* and **abc**
+ function applyInlineFormat(space) {
+ var selection, dom, rng, container, offset, startOffset, text, patternRng, pattern, delta, format;
- postData += key + '=' + encodeURIComponent(value);
- });
+ function splitContainer() {
+ // Split text node and remove start/end from text node
+ container = container.splitText(startOffset);
+ container.splitText(offset - startOffset - delta);
+ container.deleteData(0, pattern.start.length);
+ container.deleteData(container.data.length - pattern.end.length, pattern.end.length);
+ }
- XHR.send({
- url: new URI(url).toAbsolute(settings.spellchecker_rpc_url),
- type: "post",
- content_type: 'application/x-www-form-urlencoded',
- data: postData,
- success: function(result) {
- result = JSON.parse(result);
+ selection = editor.selection;
+ dom = editor.dom;
- if (!result) {
- errorCallback("Sever response wasn't proper JSON.");
- } else if (result.error) {
- errorCallback(result.error);
- } else {
- doneCallback(result);
- }
- },
- error: function(type, xhr) {
- errorCallback("Spellchecker request error: " + xhr.status);
- }
- });
+ if (!selection.isCollapsed()) {
+ return;
}
- function sendRpcCall(name, data, successCallback, errorCallback) {
- var spellCheckCallback = settings.spellchecker_callback || defaultSpellcheckCallback;
- spellCheckCallback.call(self, name, data, successCallback, errorCallback);
+ rng = selection.getRng(true);
+ container = rng.startContainer;
+ offset = rng.startOffset;
+ text = container.data;
+ delta = space ? 1 : 0;
+
+ if (container.nodeType != 3) {
+ return;
}
- function spellcheck() {
- finish();
-
- if (started) {
- return;
- }
+ // Find best matching end
+ pattern = findEndPattern(text, offset, delta);
+ if (!pattern) {
+ return;
+ }
- function errorCallback(message) {
- editor.windowManager.alert(message);
- editor.setProgressState(false);
- finish();
- }
+ // Find start of matched pattern
+ // TODO: Might need to improve this if there is nested formats
+ startOffset = Math.max(0, offset - delta);
+ startOffset = text.lastIndexOf(pattern.start, startOffset - pattern.end.length - 1);
- editor.setProgressState(true);
- sendRpcCall("spellcheck", getTextMatcher().text, markErrors, errorCallback);
- editor.focus();
+ if (startOffset === -1) {
+ return;
}
- function checkIfFinished() {
- if (!editor.dom.select('span.mce-spellchecker-word').length) {
- finish();
- }
+ // Setup a range for the matching word
+ patternRng = dom.createRng();
+ patternRng.setStart(container, startOffset);
+ patternRng.setEnd(container, offset - delta);
+ pattern = findPattern(patternRng.toString());
+
+ if (!pattern || !pattern.end) {
+ return;
}
- function addToDictionary(word, spans) {
- editor.setProgressState(true);
+ // If container match doesn't have anything between start/end then do nothing
+ if (container.data.length <= pattern.start.length + pattern.end.length) {
+ return;
+ }
- sendRpcCall("addToDictionary", word, function() {
- editor.setProgressState(false);
- editor.dom.remove(spans, true);
- checkIfFinished();
- }, function(message) {
- editor.windowManager.alert(message);
- editor.setProgressState(false);
- });
+ format = editor.formatter.get(pattern.format);
+ if (format && format[0].inline) {
+ splitContainer();
+ editor.formatter.apply(pattern.format, {}, container);
+ return container;
}
+ }
- function ignoreWord(word, spans, all) {
- editor.selection.collapse();
+ // Handles block formats like ##abc or 1. abc
+ function applyBlockFormat() {
+ var selection, dom, container, firstTextNode, node, format, textBlockElm, pattern, walker, rng, offset;
- if (all) {
- Tools.each(editor.dom.select('span.mce-spellchecker-word'), function(span) {
- if (span.getAttribute('data-mce-word') == word) {
- editor.dom.remove(span, true);
- }
- });
- } else {
- editor.dom.remove(spans, true);
- }
+ selection = editor.selection;
+ dom = editor.dom;
- checkIfFinished();
+ if (!selection.isCollapsed()) {
+ return;
}
- function finish() {
- getTextMatcher().reset();
- self.textMatcher = null;
-
- if (started) {
- started = false;
- editor.fire('SpellcheckEnd');
+ textBlockElm = dom.getParent(selection.getStart(), 'p');
+ if (textBlockElm) {
+ walker = new tinymce.dom.TreeWalker(textBlockElm, textBlockElm);
+ while ((node = walker.next())) {
+ if (node.nodeType == 3) {
+ firstTextNode = node;
+ break;
+ }
}
- }
- function getElmIndex(elm) {
- var value = elm.getAttribute('data-mce-index');
+ if (firstTextNode) {
+ pattern = findPattern(firstTextNode.data);
+ if (!pattern) {
+ return;
+ }
- if (typeof value == "number") {
- return "" + value;
- }
+ rng = selection.getRng(true);
+ container = rng.startContainer;
+ offset = rng.startOffset;
- return value;
- }
+ if (firstTextNode == container) {
+ offset = Math.max(0, offset - pattern.start.length);
+ }
- function findSpansByIndex(index) {
- var nodes, spans = [];
+ if (tinymce.trim(firstTextNode.data).length == pattern.start.length) {
+ return;
+ }
- nodes = Tools.toArray(editor.getBody().getElementsByTagName('span'));
- if (nodes.length) {
- for (var i = 0; i < nodes.length; i++) {
- var nodeIndex = getElmIndex(nodes[i]);
+ if (pattern.format) {
+ format = editor.formatter.get(pattern.format);
+ if (format && format[0].block) {
+ firstTextNode.deleteData(0, pattern.start.length);
+ editor.formatter.apply(pattern.format, {}, firstTextNode);
- if (nodeIndex === null || !nodeIndex.length) {
- continue;
+ rng.setStart(container, offset);
+ rng.collapse(true);
+ selection.setRng(rng);
}
+ }
- if (nodeIndex === index.toString()) {
- spans.push(nodes[i]);
- }
+ if (pattern.cmd) {
+ editor.undoManager.transact(function() {
+ firstTextNode.deleteData(0, pattern.start.length);
+ editor.execCommand(pattern.cmd);
+ });
}
}
+ }
+ }
- return spans;
+ function handleEnter() {
+ var rng, wrappedTextNode;
+
+ wrappedTextNode = applyInlineFormat();
+ if (wrappedTextNode) {
+ rng = editor.dom.createRng();
+ rng.setStart(wrappedTextNode, wrappedTextNode.data.length);
+ rng.setEnd(wrappedTextNode, wrappedTextNode.data.length);
+ editor.selection.setRng(rng);
}
- editor.on('click', function(e) {
- var target = e.target;
+ applyBlockFormat();
+ }
- if (target.className == "mce-spellchecker-word") {
- e.preventDefault();
+ function handleSpace() {
+ var wrappedTextNode, lastChar, lastCharNode, rng, dom;
- var spans = findSpansByIndex(getElmIndex(target));
+ wrappedTextNode = applyInlineFormat(true);
+ if (wrappedTextNode) {
+ dom = editor.dom;
+ lastChar = wrappedTextNode.data.slice(-1);
- if (spans.length > 0) {
- var rng = editor.dom.createRng();
- rng.setStartBefore(spans[0]);
- rng.setEndAfter(spans[spans.length - 1]);
- editor.selection.setRng(rng);
- showSuggestions(target.getAttribute('data-mce-word'), spans);
+ // Move space after the newly formatted node
+ if (/[\u00a0 ]/.test(lastChar)) {
+ wrappedTextNode.deleteData(wrappedTextNode.data.length - 1, 1);
+ lastCharNode = dom.doc.createTextNode(lastChar);
+
+ if (wrappedTextNode.nextSibling) {
+ dom.insertAfter(lastCharNode, wrappedTextNode.nextSibling);
+ } else {
+ wrappedTextNode.parentNode.appendChild(lastCharNode);
}
+
+ rng = dom.createRng();
+ rng.setStart(lastCharNode, 1);
+ rng.setEnd(lastCharNode, 1);
+ editor.selection.setRng(rng);
}
- });
+ }
+ }
- editor.addMenuItem('spellchecker', {
- text: 'Spellcheck',
- context: 'tools',
- onclick: spellcheck,
- selectable: true,
- onPostRender: function() {
- var self = this;
+ editor.on('keydown', function(e) {
+ if (e.keyCode == 13 && !tinymce.util.VK.modifierPressed(e)) {
+ handleEnter();
+ }
+ }, true);
- self.active(started);
+ editor.on('keyup', function(e) {
+ if (e.keyCode == 32 && !tinymce.util.VK.modifierPressed(e)) {
+ handleSpace();
+ }
+ });
- editor.on('SpellcheckStart SpellcheckEnd', function() {
- self.active(started);
- });
- }
- });
+ this.getPatterns = getPatterns;
+ this.setPatterns = function(newPatterns) {
+ patterns = newPatterns;
+ isPatternsDirty = true;
+ };
+});
- function updateSelection(e) {
- var selectedLanguage = settings.spellchecker_language;
+ }).apply(root, arguments);
+});
+}(this));
- e.control.items().each(function(ctrl) {
- ctrl.active(ctrl.settings.data === selectedLanguage);
- });
- }
+(function(root) {
+define("tinymce-visualblocks", ["tinymce"], function() {
+ return (function() {
+/**
+ * plugin.js
+ *
+ * Released under LGPL License.
+ * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
+ *
+ * License: http://www.tinymce.com/license
+ * Contributing: http://www.tinymce.com/contributing
+ */
- /**
- * Find the specified words and marks them. It will also show suggestions for those words.
- *
- * @example
- * editor.plugins.spellchecker.markErrors({
- * dictionary: true,
- * words: {
- * "word1": ["suggestion 1", "Suggestion 2"]
- * }
- * });
- * @param {Object} data Data object containing the words with suggestions.
- */
- function markErrors(data) {
- var suggestions;
+/*global tinymce:true */
- if (data.words) {
- hasDictionarySupport = !!data.dictionary;
- suggestions = data.words;
- } else {
- // Fallback to old format
- suggestions = data;
- }
+tinymce.PluginManager.add('visualblocks', function(editor, url) {
+ var cssId, visualBlocksMenuItem, enabled;
- editor.setProgressState(false);
+ // We don't support older browsers like IE6/7 and they don't provide prototypes for DOM objects
+ if (!window.NodeList) {
+ return;
+ }
- if (isEmpty(suggestions)) {
- editor.windowManager.alert('No misspellings found');
- started = false;
- return;
- }
+ function toggleActiveState() {
+ var self = this;
- lastSuggestions = suggestions;
+ self.active(enabled);
- getTextMatcher().find(getWordCharPattern()).filter(function(match) {
- return !!suggestions[match.text];
- }).wrap(function(match) {
- return editor.dom.create('span', {
- "class": 'mce-spellchecker-word',
- "data-mce-bogus": 1,
- "data-mce-word": match.text
- });
+ editor.on('VisualBlocks', function() {
+ self.active(editor.dom.hasClass(editor.getBody(), 'mce-visualblocks'));
+ });
+ }
+
+ editor.addCommand('mceVisualBlocks', function() {
+ var dom = editor.dom, linkElm;
+
+ if (!cssId) {
+ cssId = dom.uniqueId();
+ linkElm = dom.create('link', {
+ id: cssId,
+ rel: 'stylesheet',
+ href: url + '/css/visualblocks.css'
});
- started = true;
- editor.fire('SpellcheckStart');
+ editor.getDoc().getElementsByTagName('head')[0].appendChild(linkElm);
}
- var buttonArgs = {
- tooltip: 'Spellcheck',
- onclick: spellcheck,
- onPostRender: function() {
- var self = this;
-
- editor.on('SpellcheckStart SpellcheckEnd', function() {
- self.active(started);
- });
+ // Toggle on/off visual blocks while computing previews
+ editor.on("PreviewFormats AfterPreviewFormats", function(e) {
+ if (enabled) {
+ dom.toggleClass(editor.getBody(), 'mce-visualblocks', e.type == "afterpreviewformats");
}
- };
+ });
- if (languageMenuItems.length > 1) {
- buttonArgs.type = 'splitbutton';
- buttonArgs.menu = languageMenuItems;
- buttonArgs.onshow = updateSelection;
- buttonArgs.onselect = function(e) {
- settings.spellchecker_language = e.control.settings.data;
- };
+ dom.toggleClass(editor.getBody(), 'mce-visualblocks');
+ enabled = editor.dom.hasClass(editor.getBody(), 'mce-visualblocks');
+
+ if (visualBlocksMenuItem) {
+ visualBlocksMenuItem.active(dom.hasClass(editor.getBody(), 'mce-visualblocks'));
}
- editor.addButton('spellchecker', buttonArgs);
- editor.addCommand('mceSpellCheck', spellcheck);
+ editor.fire('VisualBlocks');
+ });
- editor.on('remove', function() {
- if (suggestionsMenu) {
- suggestionsMenu.remove();
- suggestionsMenu = null;
- }
- });
+ editor.addButton('visualblocks', {
+ title: 'Show blocks',
+ cmd: 'mceVisualBlocks',
+ onPostRender: toggleActiveState
+ });
- editor.on('change', checkIfFinished);
+ editor.addMenuItem('visualblocks', {
+ text: 'Show blocks',
+ cmd: 'mceVisualBlocks',
+ onPostRender: toggleActiveState,
+ selectable: true,
+ context: 'view',
+ prependToContext: true
+ });
- this.getTextMatcher = getTextMatcher;
- this.getWordCharPattern = getWordCharPattern;
- this.markErrors = markErrors;
- this.getLanguage = function() {
- return settings.spellchecker_language;
- };
+ editor.on('init', function() {
+ if (editor.settings.visualblocks_default_state) {
+ editor.execCommand('mceVisualBlocks', false, null, {skip_focus: true});
+ }
+ });
- // Set default spellchecker language if it's not specified
- settings.spellchecker_language = settings.spellchecker_language || settings.language || 'en';
+ editor.on('remove', function() {
+ editor.dom.removeClass(editor.getBody(), 'mce-visualblocks');
});
});
-expose(["tinymce/spellcheckerplugin/DomTextMatcher"]);
-})(this);
}).apply(root, arguments);
});
}(this));
(function(root) {
-define("tinymce-tabfocus", ["tinymce"], function() {
+define("tinymce-visualchars", ["tinymce"], function() {
return (function() {
/**
* plugin.js
@@ -58345,111 +74618,114 @@ define("tinymce-tabfocus", ["tinymce"], function() {
/*global tinymce:true */
-tinymce.PluginManager.add('tabfocus', function(editor) {
- var DOM = tinymce.DOM, each = tinymce.each, explode = tinymce.explode;
+tinymce.PluginManager.add('visualchars', function(editor) {
+ var self = this, state;
- function tabCancel(e) {
- if (e.keyCode === 9 && !e.ctrlKey && !e.altKey && !e.metaKey) {
- e.preventDefault();
- }
- }
+ function toggleVisualChars(addBookmark) {
+ var node, nodeList, i, body = editor.getBody(), nodeValue, selection = editor.selection, div, bookmark;
+ var charMap, visualCharsRegExp;
- function tabHandler(e) {
- var x, el, v, i;
+ charMap = {
+ '\u00a0': 'nbsp',
+ '\u00ad': 'shy'
+ };
- if (e.keyCode !== 9 || e.ctrlKey || e.altKey || e.metaKey || e.isDefaultPrevented()) {
- return;
+ function wrapCharWithSpan(value) {
+ return '' + value + '';
}
- function find(direction) {
- el = DOM.select(':input:enabled,*[tabindex]:not(iframe)');
+ function compileCharMapToRegExp() {
+ var key, regExp = '';
- function canSelectRecursive(e) {
- return e.nodeName === "BODY" || (e.type != 'hidden' &&
- e.style.display != "none" &&
- e.style.visibility != "hidden" && canSelectRecursive(e.parentNode));
+ for (key in charMap) {
+ regExp += key;
}
- function canSelect(el) {
- return /INPUT|TEXTAREA|BUTTON/.test(el.tagName) && tinymce.get(e.id) && el.tabIndex != -1 && canSelectRecursive(el);
- }
+ return new RegExp('[' + regExp + ']', 'g');
+ }
- each(el, function(e, i) {
- if (e.id == editor.id) {
- x = i;
- return false;
- }
- });
- if (direction > 0) {
- for (i = x + 1; i < el.length; i++) {
- if (canSelect(el[i])) {
- return el[i];
- }
- }
- } else {
- for (i = x - 1; i >= 0; i--) {
- if (canSelect(el[i])) {
- return el[i];
- }
+ function compileCharMapToCssSelector() {
+ var key, selector = '';
+
+ for (key in charMap) {
+ if (selector) {
+ selector += ',';
}
+
+ selector += 'span.mce-' + charMap[key];
}
- return null;
+ return selector;
}
- v = explode(editor.getParam('tab_focus', editor.getParam('tabfocus_elements', ':prev,:next')));
+ state = !state;
+ self.state = state;
+ editor.fire('VisualChars', {state: state});
+ visualCharsRegExp = compileCharMapToRegExp();
- if (v.length == 1) {
- v[1] = v[0];
- v[0] = ':prev';
+ if (addBookmark) {
+ bookmark = selection.getBookmark();
}
- // Find element to focus
- if (e.shiftKey) {
- if (v[0] == ':prev') {
- el = find(-1);
- } else {
- el = DOM.get(v[0]);
+ if (state) {
+ nodeList = [];
+ tinymce.walk(body, function(n) {
+ if (n.nodeType == 3 && n.nodeValue && visualCharsRegExp.test(n.nodeValue)) {
+ nodeList.push(n);
+ }
+ }, 'childNodes');
+
+ for (i = 0; i < nodeList.length; i++) {
+ nodeValue = nodeList[i].nodeValue;
+ nodeValue = nodeValue.replace(visualCharsRegExp, wrapCharWithSpan);
+
+ div = editor.dom.create('div', null, nodeValue);
+ while ((node = div.lastChild)) {
+ editor.dom.insertAfter(node, nodeList[i]);
+ }
+
+ editor.dom.remove(nodeList[i]);
}
} else {
- if (v[1] == ':next') {
- el = find(1);
- } else {
- el = DOM.get(v[1]);
+ nodeList = editor.dom.select(compileCharMapToCssSelector(), body);
+
+ for (i = nodeList.length - 1; i >= 0; i--) {
+ editor.dom.remove(nodeList[i], 1);
}
}
- if (el) {
- var focusEditor = tinymce.get(el.id || el.name);
-
- if (el.id && focusEditor) {
- focusEditor.focus();
- } else {
- window.setTimeout(function() {
- if (!tinymce.Env.webkit) {
- window.focus();
- }
+ selection.moveToBookmark(bookmark);
+ }
- el.focus();
- }, 10);
- }
+ function toggleActiveState() {
+ var self = this;
- e.preventDefault();
- }
+ editor.on('VisualChars', function(e) {
+ self.active(e.state);
+ });
}
- editor.on('init', function() {
- if (editor.inline) {
- // Remove default tabIndex in inline mode
- tinymce.DOM.setAttrib(editor.getBody(), 'tabIndex', null);
- }
+ editor.addCommand('mceVisualChars', toggleVisualChars);
- editor.on('keyup', tabCancel);
+ editor.addButton('visualchars', {
+ title: 'Show invisible characters',
+ cmd: 'mceVisualChars',
+ onPostRender: toggleActiveState
+ });
+
+ editor.addMenuItem('visualchars', {
+ text: 'Show invisible characters',
+ cmd: 'mceVisualChars',
+ onPostRender: toggleActiveState,
+ selectable: true,
+ context: 'view',
+ prependToContext: true
+ });
- if (tinymce.Env.gecko) {
- editor.on('keypress keydown', tabHandler);
- } else {
- editor.on('keydown', tabHandler);
+ editor.on('beforegetcontent', function(e) {
+ if (state && e.format != 'raw' && !e.draft) {
+ state = true;
+ toggleVisualChars(false);
}
});
});
@@ -58460,111 +74736,87 @@ tinymce.PluginManager.add('tabfocus', function(editor) {
}(this));
(function(root) {
-define("tinymce-table", ["tinymce"], function() {
+define("tinymce-wordcount", ["tinymce"], function() {
return (function() {
/**
- * Compiled inline version. (Library mode)
+ * plugin.js
+ *
+ * Released under LGPL License.
+ * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
+ *
+ * License: http://www.tinymce.com/license
+ * Contributing: http://www.tinymce.com/contributing
*/
-/*jshint smarttabs:true, undef:true, latedef:true, curly:true, bitwise:true, camelcase:true */
-/*globals $code */
-
-(function(exports, undefined) {
- "use strict";
-
- var modules = {};
-
- function require(ids, callback) {
- var module, defs = [];
-
- for (var i = 0; i < ids.length; ++i) {
- module = modules[ids[i]] || resolve(ids[i]);
- if (!module) {
- throw 'module definition dependecy not found: ' + ids[i];
- }
-
- defs.push(module);
- }
-
- callback.apply(null, defs);
- }
-
- function define(id, dependencies, definition) {
- if (typeof id !== 'string') {
- throw 'invalid module definition, module id must be defined and be a string';
- }
+/*global tinymce:true */
- if (dependencies === undefined) {
- throw 'invalid module definition, dependencies must be specified';
- }
+tinymce.PluginManager.add('wordcount', function(editor) {
+ var self = this, countre, cleanre;
- if (definition === undefined) {
- throw 'invalid module definition, definition function must be specified';
- }
+ // Included most unicode blocks see: http://en.wikipedia.org/wiki/Unicode_block
+ // Latin-1_Supplement letters, a-z, u2019 == ’
+ countre = editor.getParam('wordcount_countregex', /[\w\u2019\x27\-\u00C0-\u1FFF]+/g);
+ cleanre = editor.getParam('wordcount_cleanregex', /[0-9.(),;:!?%#$?\x27\x22_+=\\\/\-]*/g);
- require(dependencies, function() {
- modules[id] = definition.apply(null, arguments);
- });
+ function update() {
+ editor.theme.panel.find('#wordcount').text(['Words: {0}', self.getCount()]);
}
- function defined(id) {
- return !!modules[id];
- }
+ editor.on('init', function() {
+ var statusbar = editor.theme.panel && editor.theme.panel.find('#statusbar')[0];
- function resolve(id) {
- var target = exports;
- var fragments = id.split(/[.\/]/);
+ if (statusbar) {
+ window.setTimeout(function() {
+ statusbar.insert({
+ type: 'label',
+ name: 'wordcount',
+ text: ['Words: {0}', self.getCount()],
+ classes: 'wordcount',
+ disabled: editor.settings.readonly
+ }, 0);
- for (var fi = 0; fi < fragments.length; ++fi) {
- if (!target[fragments[fi]]) {
- return;
- }
+ editor.on('setcontent beforeaddundo', update);
- target = target[fragments[fi]];
+ editor.on('keyup', function(e) {
+ if (e.keyCode == 32) {
+ update();
+ }
+ });
+ }, 0);
}
+ });
- return target;
- }
-
- function expose(ids) {
- var i, target, id, fragments, privateModules;
+ self.getCount = function() {
+ var tx = editor.getContent({format: 'raw'});
+ var tc = 0;
- for (i = 0; i < ids.length; i++) {
- target = exports;
- id = ids[i];
- fragments = id.split(/[.\/]/);
+ if (tx) {
+ tx = tx.replace(/\.\.\./g, ' '); // convert ellipses to spaces
+ tx = tx.replace(/<.[^<>]*?>/g, ' ').replace(/ | /gi, ' '); // remove html tags and space chars
- for (var fi = 0; fi < fragments.length - 1; ++fi) {
- if (target[fragments[fi]] === undefined) {
- target[fragments[fi]] = {};
- }
+ // deal with html entities
+ tx = tx.replace(/(\w+)(?[a-z0-9]+;)+(\w+)/i, "$1$3").replace(/&.+?;/g, ' ');
+ tx = tx.replace(cleanre, ''); // remove numbers and punctuation
- target = target[fragments[fi]];
+ var wordArray = tx.match(countre);
+ if (wordArray) {
+ tc = wordArray.length;
}
-
- target[fragments[fragments.length - 1]] = modules[id];
}
-
- // Expose private modules for unit tests
- if (exports.AMDLC_TESTS) {
- privateModules = exports.privateModules || {};
-
- for (id in modules) {
- privateModules[id] = modules[id];
- }
-
- for (i = 0; i < ids.length; i++) {
- delete privateModules[ids[i]];
- }
- exports.privateModules = privateModules;
- }
- }
+ return tc;
+ };
+});
-// Included from: js/tinymce/plugins/table/classes/Utils.js
+ }).apply(root, arguments);
+});
+}(this));
+(function(root) {
+define("tinymce-compat3x", ["tinymce"], function() {
+ return (function() {
/**
- * Utils.js
+ * plugin.js
*
* Released under LGPL License.
* Copyright (c) 1999-2015 Ephox Corp. All rights reserved
@@ -58573,4765 +74825,4728 @@ define("tinymce-table", ["tinymce"], function() {
* Contributing: http://www.tinymce.com/contributing
*/
+/*global tinymce:true, console:true */
+/*eslint no-console:0, new-cap:0 */
+
/**
- * Various utility functions.
+ * This plugin adds missing events form the 4.x API back. Not every event is
+ * properly supported but most things should work.
*
- * @class tinymce.tableplugin.Utils
- * @private
+ * Unsupported things:
+ * - No editor.onEvent
+ * - Can't cancel execCommands with beforeExecCommand
*/
-define("tinymce/tableplugin/Utils", [
- "tinymce/Env"
-], function(Env) {
- function getSpanVal(td, name) {
- return parseInt(td.getAttribute(name) || 1, 10);
+(function(tinymce) {
+ var reported;
+
+ function noop() {
}
- function paddCell(cell) {
- if (!Env.ie || Env.ie > 10) {
- cell.innerHTML = '
';
+ function log(apiCall) {
+ if (!reported && window && window.console) {
+ reported = true;
+ console.log("Deprecated TinyMCE API call: " + apiCall);
}
}
- return {
- getSpanVal: getSpanVal,
- paddCell: paddCell
- };
-});
-
-// Included from: js/tinymce/plugins/table/classes/TableGrid.js
+ function Dispatcher(target, newEventName, argsMap, defaultScope) {
+ target = target || this;
-/**
- * TableGrid.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
+ if (!newEventName) {
+ this.add = this.addToTop = this.remove = this.dispatch = noop;
+ return;
+ }
-/**
- * This class creates a grid out of a table element. This
- * makes it a whole lot easier to handle complex tables with
- * col/row spans.
- *
- * @class tinymce.tableplugin.TableGrid
- * @private
- */
-define("tinymce/tableplugin/TableGrid", [
- "tinymce/util/Tools",
- "tinymce/Env",
- "tinymce/tableplugin/Utils"
-], function(Tools, Env, Utils) {
- var each = Tools.each, getSpanVal = Utils.getSpanVal;
+ this.add = function(callback, scope, prepend) {
+ log('.on' + newEventName + ".add(..)");
- return function(editor, table) {
- var grid, gridWidth, startPos, endPos, selectedCell, selection = editor.selection, dom = selection.dom;
+ // Convert callback({arg1:x, arg2:x}) -> callback(arg1, arg2)
+ function patchedEventCallback(e) {
+ var callbackArgs = [];
- function buildGrid() {
- var startY = 0;
+ if (typeof argsMap == "string") {
+ argsMap = argsMap.split(" ");
+ }
- grid = [];
- gridWidth = 0;
+ if (argsMap && typeof argsMap != "function") {
+ for (var i = 0; i < argsMap.length; i++) {
+ callbackArgs.push(e[argsMap[i]]);
+ }
+ }
- each(['thead', 'tbody', 'tfoot'], function(part) {
- var rows = dom.select('> ' + part + ' tr', table);
+ if (typeof argsMap == "function") {
+ callbackArgs = argsMap(newEventName, e, target);
+ if (!callbackArgs) {
+ return;
+ }
+ }
- each(rows, function(tr, y) {
- y += startY;
+ if (!argsMap) {
+ callbackArgs = [e];
+ }
- each(dom.select('> td, > th', tr), function(td, x) {
- var x2, y2, rowspan, colspan;
+ callbackArgs.unshift(defaultScope || target);
- // Skip over existing cells produced by rowspan
- if (grid[y]) {
- while (grid[y][x]) {
- x++;
- }
- }
+ if (callback.apply(scope || defaultScope || target, callbackArgs) === false) {
+ e.stopImmediatePropagation();
+ }
+ }
- // Get col/rowspan from cell
- rowspan = getSpanVal(td, 'rowspan');
- colspan = getSpanVal(td, 'colspan');
+ target.on(newEventName, patchedEventCallback, prepend);
- // Fill out rowspan/colspan right and down
- for (y2 = y; y2 < y + rowspan; y2++) {
- if (!grid[y2]) {
- grid[y2] = [];
- }
+ return patchedEventCallback;
+ };
- for (x2 = x; x2 < x + colspan; x2++) {
- grid[y2][x2] = {
- part: part,
- real: y2 == y && x2 == x,
- elm: td,
- rowspan: rowspan,
- colspan: colspan
- };
- }
- }
+ this.addToTop = function(callback, scope) {
+ this.add(callback, scope, true);
+ };
- gridWidth = Math.max(gridWidth, x + 1);
- });
- });
+ this.remove = function(callback) {
+ return target.off(newEventName, callback);
+ };
- startY += rows.length;
- });
- }
+ this.dispatch = function() {
+ target.fire(newEventName);
- function cloneNode(node, children) {
- node = node.cloneNode(children);
- node.removeAttribute('id');
+ return true;
+ };
+ }
- return node;
- }
+ tinymce.util.Dispatcher = Dispatcher;
+ tinymce.onBeforeUnload = new Dispatcher(tinymce, "BeforeUnload");
+ tinymce.onAddEditor = new Dispatcher(tinymce, "AddEditor", "editor");
+ tinymce.onRemoveEditor = new Dispatcher(tinymce, "RemoveEditor", "editor");
- function getCell(x, y) {
- var row;
+ tinymce.util.Cookie = {
+ get: noop, getHash: noop, remove: noop, set: noop, setHash: noop
+ };
- row = grid[y];
- if (row) {
- return row[x];
- }
+ function patchEditor(editor) {
+ function patchEditorEvents(oldEventNames, argsMap) {
+ tinymce.each(oldEventNames.split(" "), function(oldName) {
+ editor["on" + oldName] = new Dispatcher(editor, oldName, argsMap);
+ });
}
- function setSpanVal(td, name, val) {
- if (td) {
- val = parseInt(val, 10);
-
- if (val === 1) {
- td.removeAttribute(name, 1);
- } else {
- td.setAttribute(name, val, 1);
- }
- }
+ function convertUndoEventArgs(type, event, target) {
+ return [
+ event.level,
+ target
+ ];
}
- function isCellSelected(cell) {
- return cell && (dom.hasClass(cell.elm, 'mce-item-selected') || cell == selectedCell);
+ function filterSelectionEvents(needsSelection) {
+ return function(type, e) {
+ if ((!e.selection && !needsSelection) || e.selection == needsSelection) {
+ return [e];
+ }
+ };
}
- function getSelectedRows() {
- var rows = [];
-
- each(table.rows, function(row) {
- each(row.cells, function(cell) {
- if (dom.hasClass(cell, 'mce-item-selected') || (selectedCell && cell == selectedCell.elm)) {
- rows.push(row);
- return false;
- }
- });
- });
-
- return rows;
+ if (editor.controlManager) {
+ return;
}
- function deleteTable() {
- var rng = dom.createRng();
-
- rng.setStartAfter(table);
- rng.setEndAfter(table);
-
- selection.setRng(rng);
-
- dom.remove(table);
- }
+ function cmNoop() {
+ var obj = {}, methods = 'add addMenu addSeparator collapse createMenu destroy displayColor expand focus ' +
+ 'getLength hasMenus hideMenu isActive isCollapsed isDisabled isRendered isSelected mark ' +
+ 'postRender remove removeAll renderHTML renderMenu renderNode renderTo select selectByIndex ' +
+ 'setActive setAriaProperty setColor setDisabled setSelected setState showMenu update';
- function cloneCell(cell) {
- var formatNode, cloneFormats = {};
+ log('editor.controlManager.*');
- if (editor.settings.table_clone_elements !== false) {
- cloneFormats = Tools.makeMap(
- (editor.settings.table_clone_elements || 'strong em b i span font h1 h2 h3 h4 h5 h6 p div').toUpperCase(),
- /[ ,]/
- );
+ function _noop() {
+ return cmNoop();
}
- // Clone formats
- Tools.walk(cell, function(node) {
- var curNode;
+ tinymce.each(methods.split(' '), function(method) {
+ obj[method] = _noop;
+ });
- if (node.nodeType == 3) {
- each(dom.getParents(node.parentNode, null, cell).reverse(), function(node) {
- if (!cloneFormats[node.nodeName]) {
- return;
- }
+ return obj;
+ }
- node = cloneNode(node, false);
+ editor.controlManager = {
+ buttons: {},
- if (!formatNode) {
- formatNode = curNode = node;
- } else if (curNode) {
- curNode.appendChild(node);
- }
+ setDisabled: function(name, state) {
+ log("controlManager.setDisabled(..)");
- curNode = node;
- });
+ if (this.buttons[name]) {
+ this.buttons[name].disabled(state);
+ }
+ },
- // Add something to the inner node
- if (curNode) {
- curNode.innerHTML = Env.ie ? ' ' : '
';
- }
+ setActive: function(name, state) {
+ log("controlManager.setActive(..)");
- return false;
+ if (this.buttons[name]) {
+ this.buttons[name].active(state);
}
- }, 'childNodes');
+ },
- cell = cloneNode(cell, false);
- setSpanVal(cell, 'rowSpan', 1);
- setSpanVal(cell, 'colSpan', 1);
+ onAdd: new Dispatcher(),
+ onPostRender: new Dispatcher(),
- if (formatNode) {
- cell.appendChild(formatNode);
- } else {
- Utils.paddCell(cell);
- }
+ add: function(obj) {
+ return obj;
+ },
+ createButton: cmNoop,
+ createColorSplitButton: cmNoop,
+ createControl: cmNoop,
+ createDropMenu: cmNoop,
+ createListBox: cmNoop,
+ createMenuButton: cmNoop,
+ createSeparator: cmNoop,
+ createSplitButton: cmNoop,
+ createToolbar: cmNoop,
+ createToolbarGroup: cmNoop,
+ destroy: noop,
+ get: noop,
+ setControlType: cmNoop
+ };
- return cell;
- }
+ patchEditorEvents("PreInit BeforeRenderUI PostRender Load Init Remove Activate Deactivate", "editor");
+ patchEditorEvents("Click MouseUp MouseDown DblClick KeyDown KeyUp KeyPress ContextMenu Paste Submit Reset");
+ patchEditorEvents("BeforeExecCommand ExecCommand", "command ui value args"); // args.terminate not supported
+ patchEditorEvents("PreProcess PostProcess LoadContent SaveContent Change");
+ patchEditorEvents("BeforeSetContent BeforeGetContent SetContent GetContent", filterSelectionEvents(false));
+ patchEditorEvents("SetProgressState", "state time");
+ patchEditorEvents("VisualAid", "element hasVisual");
+ patchEditorEvents("Undo Redo", convertUndoEventArgs);
- function cleanup() {
- var rng = dom.createRng(), row;
+ patchEditorEvents("NodeChange", function(type, e) {
+ return [
+ editor.controlManager,
+ e.element,
+ editor.selection.isCollapsed(),
+ e
+ ];
+ });
- // Empty rows
- each(dom.select('tr', table), function(tr) {
- if (tr.cells.length === 0) {
- dom.remove(tr);
- }
- });
+ var originalAddButton = editor.addButton;
+ editor.addButton = function(name, settings) {
+ var originalOnPostRender;
- // Empty table
- if (dom.select('tr', table).length === 0) {
- rng.setStartBefore(table);
- rng.setEndBefore(table);
- selection.setRng(rng);
- dom.remove(table);
- return;
- }
+ function patchedPostRender() {
+ editor.controlManager.buttons[name] = this;
- // Empty header/body/footer
- each(dom.select('thead,tbody,tfoot', table), function(part) {
- if (part.rows.length === 0) {
- dom.remove(part);
+ if (originalOnPostRender) {
+ return originalOnPostRender.call(this);
}
- });
-
- // Restore selection to start position if it still exists
- buildGrid();
+ }
- // If we have a valid startPos object
- if (startPos) {
- // Restore the selection to the closest table position
- row = grid[Math.min(grid.length - 1, startPos.y)];
- if (row) {
- selection.select(row[Math.min(row.length - 1, startPos.x)].elm, true);
- selection.collapse(true);
+ for (var key in settings) {
+ if (key.toLowerCase() === "onpostrender") {
+ originalOnPostRender = settings[key];
+ settings.onPostRender = patchedPostRender;
}
}
- }
- function fillLeftDown(x, y, rows, cols) {
- var tr, x2, r, c, cell;
+ if (!originalOnPostRender) {
+ settings.onPostRender = patchedPostRender;
+ }
- tr = grid[y][x].elm.parentNode;
- for (r = 1; r <= rows; r++) {
- tr = dom.getNext(tr, 'tr');
+ if (settings.title) {
+ settings.title = tinymce.i18n.translate((editor.settings.language || "en") + "." + settings.title);
+ }
- if (tr) {
- // Loop left to find real cell
- for (x2 = x; x2 >= 0; x2--) {
- cell = grid[y + r][x2].elm;
+ return originalAddButton.call(this, name, settings);
+ };
- if (cell.parentNode == tr) {
- // Append clones after
- for (c = 1; c <= cols; c++) {
- dom.insertAfter(cloneCell(cell), cell);
- }
+ editor.on('init', function() {
+ var undoManager = editor.undoManager, selection = editor.selection;
- break;
- }
- }
+ undoManager.onUndo = new Dispatcher(editor, "Undo", convertUndoEventArgs, null, undoManager);
+ undoManager.onRedo = new Dispatcher(editor, "Redo", convertUndoEventArgs, null, undoManager);
+ undoManager.onBeforeAdd = new Dispatcher(editor, "BeforeAddUndo", null, undoManager);
+ undoManager.onAdd = new Dispatcher(editor, "AddUndo", null, undoManager);
- if (x2 == -1) {
- // Insert nodes before first cell
- for (c = 1; c <= cols; c++) {
- tr.insertBefore(cloneCell(tr.cells[0]), tr.cells[0]);
- }
- }
- }
- }
- }
+ selection.onBeforeGetContent = new Dispatcher(editor, "BeforeGetContent", filterSelectionEvents(true), selection);
+ selection.onGetContent = new Dispatcher(editor, "GetContent", filterSelectionEvents(true), selection);
+ selection.onBeforeSetContent = new Dispatcher(editor, "BeforeSetContent", filterSelectionEvents(true), selection);
+ selection.onSetContent = new Dispatcher(editor, "SetContent", filterSelectionEvents(true), selection);
+ });
- function split() {
- each(grid, function(row, y) {
- each(row, function(cell, x) {
- var colSpan, rowSpan, i;
+ editor.on('BeforeRenderUI', function() {
+ var windowManager = editor.windowManager;
- if (isCellSelected(cell)) {
- cell = cell.elm;
- colSpan = getSpanVal(cell, 'colspan');
- rowSpan = getSpanVal(cell, 'rowspan');
+ windowManager.onOpen = new Dispatcher();
+ windowManager.onClose = new Dispatcher();
+ windowManager.createInstance = function(className, a, b, c, d, e) {
+ log("windowManager.createInstance(..)");
- if (colSpan > 1 || rowSpan > 1) {
- setSpanVal(cell, 'rowSpan', 1);
- setSpanVal(cell, 'colSpan', 1);
+ var constr = tinymce.resolve(className);
+ return new constr(a, b, c, d, e);
+ };
+ });
+ }
- // Insert cells right
- for (i = 0; i < colSpan - 1; i++) {
- dom.insertAfter(cloneCell(cell), cell);
- }
+ tinymce.on('SetupEditor', patchEditor);
+ tinymce.PluginManager.add("compat3x", patchEditor);
- fillLeftDown(x, y, rowSpan - 1, colSpan);
+ tinymce.addI18n = function(prefix, o) {
+ var I18n = tinymce.util.I18n, each = tinymce.each;
+
+ if (typeof prefix == "string" && prefix.indexOf('.') === -1) {
+ I18n.add(prefix, o);
+ return;
+ }
+
+ if (!tinymce.is(prefix, 'string')) {
+ each(prefix, function(o, lc) {
+ each(o, function(o, g) {
+ each(o, function(o, k) {
+ if (g === 'common') {
+ I18n.data[lc + '.' + k] = o;
+ } else {
+ I18n.data[lc + '.' + g + '.' + k] = o;
}
- }
+ });
});
});
+ } else {
+ each(o, function(o, k) {
+ I18n.data[prefix + '.' + k] = o;
+ });
}
+ };
+})(tinymce);
- function merge(cell, cols, rows) {
- var pos, startX, startY, endX, endY, x, y, startCell, endCell, children, count;
- // Use specified cell and cols/rows
- if (cell) {
- pos = getPos(cell);
- startX = pos.x;
- startY = pos.y;
- endX = startX + (cols - 1);
- endY = startY + (rows - 1);
- } else {
- startPos = endPos = null;
+ }).apply(root, arguments);
+});
+}(this));
- // Calculate start/end pos by checking for selected cells in grid works better with context menu
- each(grid, function(row, y) {
- each(row, function(cell, x) {
- if (isCellSelected(cell)) {
- if (!startPos) {
- startPos = {x: x, y: y};
- }
+/* TinyMCE pattern.
+ *
+ * Options:
+ * relatedItems(object): Related items pattern options. ({ attributes: ["UID", "Title", "Description", "getURL", "portal_type", "path", "ModificationDate"], batchSize: 20, basePath: "/", vocabularyUrl: null, width: 500, maximumSelectionSize: 1, placeholder: "Search for item on site..." })
+ * upload(object): Upload pattern options. ({ attributes: look at upload pattern for getting the options list })
+ * text(object): Translation strings ({ insertBtn: "Insert", cancelBtn: "Cancel", insertHeading: "Insert link", title: "Title", internal: "Internal", external: "External", email: "Email", anchor: "Anchor", subject: "Subject" image: "Image", imageAlign: "Align", scale: "Size", alt: "Alternative Text", externalImage: "External Image URI"})
+ * scales(string): TODO: is this even used ('Listing (16x16):listing,Icon (32x32):icon,Tile (64x64):tile,Thumb (128x128):thumb,Mini (200x200):mini,Preview (400x400):preview,Large (768x768):large')
+ * targetList(array): TODO ([ {text: "Open in this window / frame", value: ""}, {text: "Open in new window", value: "_blank"}, {text: "Open in parent window / frame", value: "_parent"}, {text: "Open in top frame (replaces all frames)", value: "_top"}])
+ * imageTypes(string): TODO ('Image')
+ * folderTypes(string): TODO ('Folder,Plone Site')
+ * linkableTypes(string): TODO ('Document,Event,File,Folder,Image,News Item,Topic')
+ * tiny(object): TODO ({ plugins: [ "advlist autolink lists charmap print preview anchor", "usearchreplace visualblocks code fullscreen autoresize", "insertdatetime media table contextmenu paste plonelink ploneimage" ], menubar: "edit table format tools view insert",
+ toolbar: "undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | unlink plonelink ploneimage", autoresize_max_height: 1500 })
+ * prependToUrl(string): Text to prepend to generated internal urls. ('')
+ * appendToUrl(string): Text to append to generated internal urls. ('')
+ * prependToScalePart(string): Text to prepend to generated image scale url part. ('/imagescale/')
+ * appendToScalePart(string): Text to append to generated image scale url part. ('')
+ * linkAttribute(string): Ajax response data attribute to use for url. ('path')
+ * defaultScale(string): Scale name to default to. ('Original')
+ * inline(boolean): Show tinyMCE editor inline instead in an iframe. Use this on textarea inputs. If you want to use this pattern directly on a contenteditable, pass "inline: true" to the "tiny" options object. (false)
+ *
+ * Documentation:
+ * # Default
+ *
+ * {{ example-1 }}
+ *
+ * # With dropzone
+ *
+ * {{ example-2 }}
+ *
+ * # Inline editing
+ *
+ * {{ example-3 }}
+ *
+ * Example: example-1
+ *
+ *
+ * Example: example-2
+ *
+ *
+ * Example: example-3
+ *
+ *
+ */
- endPos = {x: x, y: y};
- }
- });
- });
+define('mockup-patterns-tinymce',[
+ 'jquery',
+ 'underscore',
+ 'pat-base',
+ 'mockup-patterns-relateditems',
+ 'mockup-patterns-modal',
+ 'tinymce',
+ 'mockup-patterns-autotoc',
+ 'text!mockup-patterns-tinymce-url/templates/result.xml',
+ 'text!mockup-patterns-tinymce-url/templates/selection.xml',
+ 'mockup-utils',
+ 'mockup-patterns-tinymce-url/js/links',
+ 'mockup-i18n',
+ 'translate',
+ 'tinymce-modern-theme',
+ 'tinymce-advlist',
+ 'tinymce-anchor',
+ 'tinymce-autolink',
+ 'tinymce-autoresize',
+ 'tinymce-autosave',
+ 'tinymce-bbcode',
+ 'tinymce-charmap',
+ 'tinymce-code',
+ 'tinymce-colorpicker',
+ 'tinymce-contextmenu',
+ 'tinymce-directionality',
+ 'tinymce-emoticons',
+ 'tinymce-fullpage',
+ 'tinymce-fullscreen',
+ 'tinymce-hr',
+ 'tinymce-image',
+ 'tinymce-importcss',
+ 'tinymce-insertdatetime',
+ 'tinymce-layer',
+ 'tinymce-legacyoutput',
+ 'tinymce-link',
+ 'tinymce-lists',
+ 'tinymce-media',
+ 'tinymce-nonbreaking',
+ 'tinymce-noneditable',
+ 'tinymce-pagebreak',
+ 'tinymce-paste',
+ 'tinymce-preview',
+ 'tinymce-print',
+ 'tinymce-save',
+ 'tinymce-searchreplace',
+ 'tinymce-spellchecker',
+ 'tinymce-tabfocus',
+ 'tinymce-table',
+ 'tinymce-template',
+ 'tinymce-textcolor',
+ 'tinymce-textpattern',
+ 'tinymce-visualblocks',
+ 'tinymce-visualchars',
+ 'tinymce-wordcount',
+ 'tinymce-compat3x'
+], function($, _,
+ Base, RelatedItems, Modal, tinymce,
+ AutoTOC, ResultTemplate, SelectionTemplate,
+ utils, LinkModal, I18n, _t) {
+ 'use strict';
- // Use selection, but make sure startPos is valid before accessing
- if (startPos) {
- startX = startPos.x;
- startY = startPos.y;
- endX = endPos.x;
- endY = endPos.y;
- }
- }
+ var TinyMCE = Base.extend({
+ name: 'tinymce',
+ trigger: '.pat-tinymce',
+ parser: 'mockup',
+ defaults: {
+ upload: {
+ uploadMultiple: false,
+ maxFiles: 1,
+ showTitle: false
+ },
+ relatedItems: {
+ // UID attribute is required here since we're working with related items
+ attributes: ['UID', 'Title', 'portal_type', 'path','getURL', 'getIcon','is_folderish','review_state'],
+ batchSize: 20,
+ basePath: '/',
+ vocabularyUrl: null,
+ width: 500,
+ maximumSelectionSize: 1,
+ placeholder: _t('Search for item on site...')
+ },
+ text: {
+ insertBtn: _t('Insert'), // so this can be configurable for different languages
+ cancelBtn: _t('Cancel'),
+ insertHeading: _t('Insert link'),
+ title: _t('Title'),
+ internal: _t('Internal'),
+ external: _t('External URL (can be relative within this site or absolute if it starts with http:// or https://)'),
+ email: _t('Email Address'),
+ anchor: _t('Anchor'),
+ subject: _t('Email Subject (optional)'),
+ image: _t('Image'),
+ imageAlign: _t('Align'),
+ scale: _t('Size'),
+ alt: _t('Alternative Text'),
+ externalImage: _t('External Image URL (can be relative within this site or absolute if it starts with http:// or https://)')
+ },
+ // URL generation options
+ loadingBaseUrl: '../../../bower_components/tinymce-builded/js/tinymce/',
+ prependToUrl: '',
+ appendToUrl: '',
+ linkAttribute: 'path', // attribute to get link value from data
+ prependToScalePart: '/imagescale/', // some value here is required to be able to parse scales back
+ appendToScalePart: '',
+ appendToOriginalScalePart: '',
+ defaultScale: 'large',
+ scales: _t('Listing (16x16):listing,Icon (32x32):icon,Tile (64x64):tile,' +
+ 'Thumb (128x128):thumb,Mini (200x200):mini,Preview (400x400):preview,' +
+ 'Large (768x768):large'),
+ targetList: [
+ {text: _t('Open in this window / frame'), value: ''},
+ {text: _t('Open in new window'), value: '_blank'},
+ {text: _t('Open in parent window / frame'), value: '_parent'},
+ {text: _t('Open in top frame (replaces all frames)'), value: '_top'}
+ ],
+ imageTypes: ['Image'],
+ folderTypes: ['Folder', 'Plone Site'],
+ tiny: {
+ 'content_css': '../../../bower_components/tinymce-builded/js/tinymce/skins/lightgray/content.min.css',
+ theme: '-modern',
+ plugins: ['advlist', 'autolink', 'lists', 'charmap', 'print', 'preview', 'anchor', 'searchreplace',
+ 'visualblocks', 'code', 'fullscreen', 'insertdatetime', 'media', 'table', 'contextmenu',
+ 'paste', 'plonelink', 'ploneimage'],
+ menubar: 'edit table format tools view insert',
+ toolbar: 'undo redo | styleselect | bold italic | ' +
+ 'alignleft aligncenter alignright alignjustify | ' +
+ 'bullist numlist outdent indent | ' +
+ 'unlink plonelink ploneimage',
+ //'autoresize_max_height': 900,
+ 'height': 400
+ },
+ inline: false
+ },
+ addLinkClicked: function() {
+ var self = this;
+ if (self.linkModal === null) {
+ var $el = $('').insertAfter(self.$el);
+ var linkTypes = ['internal', 'upload', 'external', 'email', 'anchor'];
+ if(!self.options.upload){
+ linkTypes.splice(1, 1);
+ }
+ self.linkModal = new LinkModal($el,
+ $.extend(true, {}, self.options, {
+ tinypattern: self,
+ linkTypes: linkTypes
+ })
+ );
+ self.linkModal.show();
+ } else {
+ self.linkModal.reinitialize();
+ self.linkModal.show();
+ }
+ },
+ addImageClicked: function() {
+ var self = this;
+ if (self.imageModal === null) {
+ var linkTypes = ['image', 'uploadImage', 'externalImage'];
+ if(!self.options.upload){
+ linkTypes.splice(1, 1);
+ }
+ var options = $.extend(true, {}, self.options, {
+ tinypattern: self,
+ linkTypes: linkTypes,
+ initialLinkType: 'image',
+ text: {
+ insertHeading: _t('Insert Image')
+ },
+ relatedItems: {
+ baseCriteria: [{
+ i: 'portal_type',
+ o: 'plone.app.querystring.operation.list.contains',
+ v: self.options.imageTypes.concat(self.options.folderTypes)
+ }],
+ selectableTypes: self.options.imageTypes,
+ resultTemplate: ResultTemplate,
+ selectionTemplate: SelectionTemplate
+ }
+ });
+ var $el = $('').insertAfter(self.$el);
+ self.imageModal = new LinkModal($el, options);
+ self.imageModal.show();
+ } else {
+ self.imageModal.reinitialize();
+ self.imageModal.show();
+ }
+ },
+ generateUrl: function(data) {
+ var self = this;
+ var part = data[self.options.linkAttribute];
+ return self.options.prependToUrl + part + self.options.appendToUrl;
+ },
+ generateImageUrl: function(data, scale_name) {
+ var self = this;
+ var url = self.generateUrl(data);
+ if (scale_name !== ''){
+ var part = scale_name;
+ for(var i=0; i 1){
+ lang = lang.split('_')[0];
+ } else if(lang.split('-') > 1){
+ lang = lang.split('-')[0];
+ }else {
+ lang = lang + '_' + lang.toUpperCase();
+ }
+ $.ajax({
+ url: tinymce.baseURL + '/langs/' + lang + '.js',
+ method: 'GET',
+ cache: 'true',
+ success: function() {
+ self.options.tiny.language = lang;
+ call_back();
+ },
+ error: function() {
+ call_back();
+ }
+ });
+ }
+ });
+ } else {
+ call_back();
+ }
+ },
+ init: function() {
+ var self = this;
+ self.linkModal = self.imageModal = self.uploadModal = self.pasteModal = null;
+ // tiny needs an id in order to initialize. Creat it if not set.
+ var id = utils.setId(self.$el);
+ var tinyOptions = self.options.tiny;
+ if (self.options.inline === true) {
+ self.options.tiny.inline = true;
+ }
+ self.tinyId = self.options.inline ? id + '-editable' : id; // when displaying TinyMCE inline, a separate div is created.
+ tinyOptions.selector = '#' + self.tinyId;
+ tinyOptions.addLinkClicked = function() {
+ self.addLinkClicked.apply(self, []);
+ };
+ tinyOptions.addImageClicked = function(file) {
+ self.addImageClicked.apply(self, [file] );
+ };
+ // XXX: disabled skin means it wont load css files which we already
+ // include in widgets.min.css
+ tinyOptions.skin = false;
+ self.options.relatedItems.generateImageUrl = function(data, scale) {
+ // this is so, in our result and selection template, we can
+ // access getting actual urls from related items
+ return self.generateImageUrl.apply(self, [data, scale]);
+ };
+
+ tinyOptions.init_instance_callback = function(editor) {
+ if (self.tiny === undefined || self.tiny === null) {
+ self.tiny = editor;
+ }
+ };
+
+ self.initLanguage(function() {
+ if(typeof(self.options.scales) === 'string'){
+ self.options.scales = _.map(self.options.scales.split(','), function(scale){
+ var scale = scale.split(':');
+ return {
+ part: scale[1],
+ name: scale[1],
+ label: scale[0]
+ };
+ });
+ }
+ if(typeof(self.options.folderTypes) === 'string'){
+ self.options.folderTypes = self.options.folderTypes.split(',');
+ }
+ if(typeof(self.options.imageTypes) === 'string'){
+ self.options.imageTypes = self.options.imageTypes.split(',');
+ }
- // Find start/end cells
- startCell = getCell(startX, startY);
- endCell = getCell(endX, endY);
+ if (self.options.inline === true) {
+ // create a div, which will be made content-editable by TinyMCE and
+ // copy contents from textarea to it. Then hide textarea.
+ self.$el.after('' + self.$el.val() + '
');
+ self.$el.hide();
+ }
- // Check if the cells exists and if they are of the same part for example tbody = tbody
- if (startCell && endCell && startCell.part == endCell.part) {
- // Split and rebuild grid
- split();
- buildGrid();
+ tinymce.init(tinyOptions);
+ self.tiny = tinymce.get(self.tinyId);
- // Set row/col span to start cell
- startCell = getCell(startX, startY).elm;
- setSpanVal(startCell, 'colSpan', (endX - startX) + 1);
- setSpanVal(startCell, 'rowSpan', (endY - startY) + 1);
+ /* tiny really should be doing this by default
+ * but this fixes overlays not saving data */
+ var $form = self.$el.parents('form');
+ $form.on('submit', function() {
+ if (self.options.inline === true) {
+ // save back from contenteditable to textarea
+ self.$el.val(self.tiny.getContent());
+ } else {
+ // normal case
+ self.tiny.save();
+ }
+ });
+ });
+ },
+ destroy: function() {
+ if (this.tiny) {
+ if (this.options.inline === true) {
+ // destroy also inline editable
+ this.$el.val(this.tiny.getContent());
+ $('#' + this.tinyId).remove();
+ this.$el.show();
+ }
+ this.tiny.destroy();
+ this.tiny = undefined;
+ }
+ }
+ });
- // Remove other cells and add it's contents to the start cell
- for (y = startY; y <= endY; y++) {
- for (x = startX; x <= endX; x++) {
- if (!grid[y] || !grid[y][x]) {
- continue;
- }
+ return TinyMCE;
- cell = grid[y][x].elm;
+});
- /*jshint loopfunc:true */
- /*eslint no-loop-func:0 */
- if (cell != startCell) {
- // Move children to startCell
- children = Tools.grep(cell.childNodes);
- each(children, function(node) {
- startCell.appendChild(node);
- });
+/* TextareaMimetypeSelector pattern.
+ *
+ *
+ * Options:
+ * textareaName(string): Value of name attribute of the textarea ('')
+ * widgets(object): MimeType/PatternConfig pairs ({'text/html': {pattern: 'tinymce', patternOptions: {}}})
+ *
+ *
+ * Documentation:
+ * # General
+ *
+ * This pattern displays a mimetype selection widget for textareas. It
+ * switches the widget according to the selected mimetype.
+ *
+ * ## widgets option Structure
+ *
+ * Complex Object/JSON structure with MimeType/PatternConfig pairs. The
+ * MimeType is a string like "text/html". The PatternConfig is a object with
+ * a "pattern" and an optional "patternOptions" attribute. The "pattern"
+ * attribute's value is a string with the patterns name and the
+ * "patternOptions" attribute is a object with whatever options the pattern
+ * needs. For example, to use the TinyMCE pattern for the HTML mimetype, use
+ * "text/html": {"pattern": "tinymce"}
+ *
+ * # Mimetype selection on textarea including text/html mimetype with TinyMCE editor.
+ *
+ * {{ example-1 }}
+ *
+ * # Mimetype selection on textarea with inline TinyMCE editor.
+ *
+ * {{ example-2 }}
+ *
+ * Example: example-1
+ *
+ *
+ *
+ * Example: example-2
+ *
+ *
+ *
+ */
- // Remove bogus nodes if there is children in the target cell
- if (children.length) {
- children = Tools.grep(startCell.childNodes);
- count = 0;
- each(children, function(node) {
- if (node.nodeName == 'BR' && dom.getAttrib(node, 'data-mce-bogus') && count++ < children.length - 1) {
- startCell.removeChild(node);
- }
- });
- }
+define('mockup-patterns-textareamimetypeselector',[
+ 'jquery',
+ 'pat-base',
+ 'pat-registry',
+ 'mockup-patterns-tinymce'
+], function ($, Base, registry, tinymce) {
+ 'use strict';
- dom.remove(cell);
- }
- }
- }
+ var TextareaMimetypeSelector = Base.extend({
+ name: 'textareamimetypeselector',
+ trigger: '.pat-textareamimetypeselector',
+ parser: 'mockup',
+ textarea: undefined,
+ currentWidget: undefined,
+ defaults: {
+ textareaName: '',
+ widgets: {'text/html': {pattern: 'tinymce', patternOptions: {}}}
+ },
+ init: function () {
+ var self = this,
+ $el = self.$el,
+ current;
+ self.textarea = $('[name="' + self.options.textareaName + '"]');
+ $el.change(function (e) {
+ self.initTextarea(e.target.value);
+ });
+ self.initTextarea($el.val());
- // Remove empty rows etc and restore caret location
- cleanup();
- }
- }
+ },
+ initTextarea: function (mimetype) {
+ var self = this,
+ patternConfig = self.options.widgets[mimetype],
+ pattern;
+ // First, destroy current
+ if (self.currentWidget) {
+ // The pattern must implement the destroy method.
+ self.currentWidget.destroy();
+ }
+ // Then, setup new
+ if (patternConfig) {
+ pattern = new registry.patterns[patternConfig.pattern](
+ self.textarea,
+ patternConfig.patternOptions || {}
+ );
+ self.currentWidget = pattern;
+ }
+ }
- function insertRow(before) {
- var posY, cell, lastCell, x, rowElm, newRow, newCell, otherCell, rowSpan;
+ });
- // Find first/last row
- each(grid, function(row, y) {
- each(row, function(cell) {
- if (isCellSelected(cell)) {
- cell = cell.elm;
- rowElm = cell.parentNode;
- newRow = cloneNode(rowElm, false);
- posY = y;
+ return TextareaMimetypeSelector;
+});
- if (before) {
- return false;
- }
- }
- });
+/*!
+ * pickadate.js v3.5.6, 2015/04/20
+ * By Amsul, http://amsul.ca
+ * Hosted on http://amsul.github.io/pickadate.js
+ * Licensed under MIT
+ */
- if (before) {
- return !posY;
- }
- });
+(function ( factory ) {
- // If posY is undefined there is nothing for us to do here...just return to avoid crashing below
- if (posY === undefined) {
- return;
- }
+ // AMD.
+ if ( typeof define == 'function' && define.amd )
+ define( 'picker', ['jquery'], factory )
- for (x = 0; x < grid[0].length; x++) {
- // Cell not found could be because of an invalid table structure
- if (!grid[posY][x]) {
- continue;
- }
+ // Node.js/browserify.
+ else if ( typeof exports == 'object' )
+ module.exports = factory( require('jquery') )
- cell = grid[posY][x].elm;
+ // Browser globals.
+ else this.Picker = factory( jQuery )
- if (cell != lastCell) {
- if (!before) {
- rowSpan = getSpanVal(cell, 'rowspan');
- if (rowSpan > 1) {
- setSpanVal(cell, 'rowSpan', rowSpan + 1);
- continue;
- }
- } else {
- // Check if cell above can be expanded
- if (posY > 0 && grid[posY - 1][x]) {
- otherCell = grid[posY - 1][x].elm;
- rowSpan = getSpanVal(otherCell, 'rowSpan');
- if (rowSpan > 1) {
- setSpanVal(otherCell, 'rowSpan', rowSpan + 1);
- continue;
- }
- }
- }
+}(function( $ ) {
- // Insert new cell into new row
- newCell = cloneCell(cell);
- setSpanVal(newCell, 'colSpan', cell.colSpan);
+var $window = $( window )
+var $document = $( document )
+var $html = $( document.documentElement )
+var supportsTransitions = document.documentElement.style.transition != null
- newRow.appendChild(newCell);
- lastCell = cell;
- }
- }
+/**
+ * The picker constructor that creates a blank picker.
+ */
+function PickerConstructor( ELEMENT, NAME, COMPONENT, OPTIONS ) {
- if (newRow.hasChildNodes()) {
- if (!before) {
- dom.insertAfter(newRow, rowElm);
- } else {
- rowElm.parentNode.insertBefore(newRow, rowElm);
- }
- }
- }
+ // If there’s no element, return the picker constructor.
+ if ( !ELEMENT ) return PickerConstructor
- function insertCol(before) {
- var posX, lastCell;
- // Find first/last column
- each(grid, function(row) {
- each(row, function(cell, x) {
- if (isCellSelected(cell)) {
- posX = x;
+ var
+ IS_DEFAULT_THEME = false,
- if (before) {
- return false;
- }
- }
- });
- if (before) {
- return !posX;
- }
- });
+ // The state of the picker.
+ STATE = {
+ id: ELEMENT.id || 'P' + Math.abs( ~~(Math.random() * new Date()) )
+ },
- each(grid, function(row, y) {
- var cell, rowSpan, colSpan;
- if (!row[posX]) {
- return;
- }
+ // Merge the defaults and options passed.
+ SETTINGS = COMPONENT ? $.extend( true, {}, COMPONENT.defaults, OPTIONS ) : OPTIONS || {},
- cell = row[posX].elm;
- if (cell != lastCell) {
- colSpan = getSpanVal(cell, 'colspan');
- rowSpan = getSpanVal(cell, 'rowspan');
- if (colSpan == 1) {
- if (!before) {
- dom.insertAfter(cloneCell(cell), cell);
- fillLeftDown(posX, y, rowSpan - 1, colSpan);
- } else {
- cell.parentNode.insertBefore(cloneCell(cell), cell);
- fillLeftDown(posX, y, rowSpan - 1, colSpan);
- }
- } else {
- setSpanVal(cell, 'colSpan', cell.colSpan + 1);
- }
+ // Merge the default classes with the settings classes.
+ CLASSES = $.extend( {}, PickerConstructor.klasses(), SETTINGS.klass ),
- lastCell = cell;
- }
- });
- }
- function deleteCols() {
- var cols = [];
+ // The element node wrapper into a jQuery object.
+ $ELEMENT = $( ELEMENT ),
- // Get selected column indexes
- each(grid, function(row) {
- each(row, function(cell, x) {
- if (isCellSelected(cell) && Tools.inArray(cols, x) === -1) {
- each(grid, function(row) {
- var cell = row[x].elm, colSpan;
- colSpan = getSpanVal(cell, 'colSpan');
+ // Pseudo picker constructor.
+ PickerInstance = function() {
+ return this.start()
+ },
- if (colSpan > 1) {
- setSpanVal(cell, 'colSpan', colSpan - 1);
- } else {
- dom.remove(cell);
- }
- });
- cols.push(x);
- }
- });
- });
+ // The picker prototype.
+ P = PickerInstance.prototype = {
- cleanup();
- }
+ constructor: PickerInstance,
- function deleteRows() {
- var rows;
+ $node: $ELEMENT,
- function deleteRow(tr) {
- var pos, lastCell;
- // Move down row spanned cells
- each(tr.cells, function(cell) {
- var rowSpan = getSpanVal(cell, 'rowSpan');
+ /**
+ * Initialize everything
+ */
+ start: function() {
- if (rowSpan > 1) {
- setSpanVal(cell, 'rowSpan', rowSpan - 1);
- pos = getPos(cell);
- fillLeftDown(pos.x, pos.y, 1, 1);
- }
- });
+ // If it’s already started, do nothing.
+ if ( STATE && STATE.start ) return P
- // Delete cells
- pos = getPos(tr.cells[0]);
- each(grid[pos.y], function(cell) {
- var rowSpan;
- cell = cell.elm;
+ // Update the picker states.
+ STATE.methods = {}
+ STATE.start = true
+ STATE.open = false
+ STATE.type = ELEMENT.type
- if (cell != lastCell) {
- rowSpan = getSpanVal(cell, 'rowSpan');
- if (rowSpan <= 1) {
- dom.remove(cell);
- } else {
- setSpanVal(cell, 'rowSpan', rowSpan - 1);
- }
+ // Confirm focus state, convert into text input to remove UA stylings,
+ // and set as readonly to prevent keyboard popup.
+ ELEMENT.autofocus = ELEMENT == getActiveElement()
+ ELEMENT.readOnly = !SETTINGS.editable
+ ELEMENT.id = ELEMENT.id || STATE.id
+ if ( ELEMENT.type != 'text' ) {
+ ELEMENT.type = 'text'
+ }
- lastCell = cell;
- }
- });
- }
- // Get selected rows and move selection out of scope
- rows = getSelectedRows();
+ // Create a new picker component with the settings.
+ P.component = new COMPONENT(P, SETTINGS)
- // Delete all selected rows
- each(rows.reverse(), function(tr) {
- deleteRow(tr);
- });
- cleanup();
- }
+ // Create the picker root and then prepare it.
+ P.$root = $( '' )
+ prepareElementRoot()
- function cutRows() {
- var rows = getSelectedRows();
- dom.remove(rows);
- cleanup();
+ // Create the picker holder and then prepare it.
+ P.$holder = $( createWrappedComponent() ).appendTo( P.$root )
+ prepareElementHolder()
- return rows;
- }
- function copyRows() {
- var rows = getSelectedRows();
+ // If there’s a format for the hidden input element, create the element.
+ if ( SETTINGS.formatSubmit ) {
+ prepareElementHidden()
+ }
- each(rows, function(row, i) {
- rows[i] = cloneNode(row, true);
- });
- return rows;
- }
+ // Prepare the input element.
+ prepareElement()
- function pasteRows(rows, before) {
- var selectedRows = getSelectedRows(),
- targetRow = selectedRows[before ? 0 : selectedRows.length - 1],
- targetCellCount = targetRow.cells.length;
- // Nothing to paste
- if (!rows) {
- return;
- }
+ // Insert the hidden input as specified in the settings.
+ if ( SETTINGS.containerHidden ) $( SETTINGS.containerHidden ).append( P._hidden )
+ else $ELEMENT.after( P._hidden )
- // Calc target cell count
- each(grid, function(row) {
- var match;
- targetCellCount = 0;
- each(row, function(cell) {
- if (cell.real) {
- targetCellCount += cell.colspan;
- }
+ // Insert the root as specified in the settings.
+ if ( SETTINGS.container ) $( SETTINGS.container ).append( P.$root )
+ else $ELEMENT.after( P.$root )
- if (cell.elm.parentNode == targetRow) {
- match = 1;
- }
- });
- if (match) {
- return false;
- }
- });
+ // Bind the default component and settings events.
+ P.on({
+ start: P.component.onStart,
+ render: P.component.onRender,
+ stop: P.component.onStop,
+ open: P.component.onOpen,
+ close: P.component.onClose,
+ set: P.component.onSet
+ }).on({
+ start: SETTINGS.onStart,
+ render: SETTINGS.onRender,
+ stop: SETTINGS.onStop,
+ open: SETTINGS.onOpen,
+ close: SETTINGS.onClose,
+ set: SETTINGS.onSet
+ })
- if (!before) {
- rows.reverse();
- }
- each(rows, function(row) {
- var i, cellCount = row.cells.length, cell;
+ // Once we’re all set, check the theme in use.
+ IS_DEFAULT_THEME = isUsingDefaultTheme( P.$holder[0] )
- // Remove col/rowspans
- for (i = 0; i < cellCount; i++) {
- cell = row.cells[i];
- setSpanVal(cell, 'colSpan', 1);
- setSpanVal(cell, 'rowSpan', 1);
- }
- // Needs more cells
- for (i = cellCount; i < targetCellCount; i++) {
- row.appendChild(cloneCell(row.cells[cellCount - 1]));
- }
+ // If the element has autofocus, open the picker.
+ if ( ELEMENT.autofocus ) {
+ P.open()
+ }
- // Needs less cells
- for (i = targetCellCount; i < cellCount; i++) {
- dom.remove(row.cells[i]);
- }
- // Add before/after
- if (before) {
- targetRow.parentNode.insertBefore(row, targetRow);
- } else {
- dom.insertAfter(row, targetRow);
- }
- });
+ // Trigger queued the “start” and “render” events.
+ return P.trigger( 'start' ).trigger( 'render' )
+ }, //start
- // Remove current selection
- dom.removeClass(dom.select('td.mce-item-selected,th.mce-item-selected'), 'mce-item-selected');
- }
- function getPos(target) {
- var pos;
+ /**
+ * Render a new picker
+ */
+ render: function( entireComponent ) {
- each(grid, function(row, y) {
- each(row, function(cell, x) {
- if (cell.elm == target) {
- pos = {x: x, y: y};
- return false;
- }
- });
+ // Insert a new component holder in the root or box.
+ if ( entireComponent ) {
+ P.$holder = $( createWrappedComponent() )
+ prepareElementHolder()
+ P.$root.html( P.$holder )
+ }
+ else P.$root.find( '.' + CLASSES.box ).html( P.component.nodes( STATE.open ) )
- return !pos;
- });
+ // Trigger the queued “render” events.
+ return P.trigger( 'render' )
+ }, //render
- return pos;
- }
- function setStartCell(cell) {
- startPos = getPos(cell);
- }
+ /**
+ * Destroy everything
+ */
+ stop: function() {
- function findEndPos() {
- var maxX, maxY;
+ // If it’s already stopped, do nothing.
+ if ( !STATE.start ) return P
- maxX = maxY = 0;
+ // Then close the picker.
+ P.close()
- each(grid, function(row, y) {
- each(row, function(cell, x) {
- var colSpan, rowSpan;
+ // Remove the hidden field.
+ if ( P._hidden ) {
+ P._hidden.parentNode.removeChild( P._hidden )
+ }
- if (isCellSelected(cell)) {
- cell = grid[y][x];
+ // Remove the root.
+ P.$root.remove()
- if (x > maxX) {
- maxX = x;
- }
+ // Remove the input class, remove the stored data, and unbind
+ // the events (after a tick for IE - see `P.close`).
+ $ELEMENT.removeClass( CLASSES.input ).removeData( NAME )
+ setTimeout( function() {
+ $ELEMENT.off( '.' + STATE.id )
+ }, 0)
- if (y > maxY) {
- maxY = y;
- }
+ // Restore the element state
+ ELEMENT.type = STATE.type
+ ELEMENT.readOnly = false
- if (cell.real) {
- colSpan = cell.colspan - 1;
- rowSpan = cell.rowspan - 1;
+ // Trigger the queued “stop” events.
+ P.trigger( 'stop' )
- if (colSpan) {
- if (x + colSpan > maxX) {
- maxX = x + colSpan;
- }
- }
+ // Reset the picker states.
+ STATE.methods = {}
+ STATE.start = false
- if (rowSpan) {
- if (y + rowSpan > maxY) {
- maxY = y + rowSpan;
- }
- }
- }
- }
- });
- });
+ return P
+ }, //stop
- return {x: maxX, y: maxY};
- }
- function setEndCell(cell) {
- var startX, startY, endX, endY, maxX, maxY, colSpan, rowSpan, x, y;
+ /**
+ * Open up the picker
+ */
+ open: function( dontGiveFocus ) {
- endPos = getPos(cell);
+ // If it’s already open, do nothing.
+ if ( STATE.open ) return P
- if (startPos && endPos) {
- // Get start/end positions
- startX = Math.min(startPos.x, endPos.x);
- startY = Math.min(startPos.y, endPos.y);
- endX = Math.max(startPos.x, endPos.x);
- endY = Math.max(startPos.y, endPos.y);
+ // Add the “active” class.
+ $ELEMENT.addClass( CLASSES.active )
+ aria( ELEMENT, 'expanded', true )
- // Expand end positon to include spans
- maxX = endX;
- maxY = endY;
+ // * A Firefox bug, when `html` has `overflow:hidden`, results in
+ // killing transitions :(. So add the “opened” state on the next tick.
+ // Bug: https://bugzilla.mozilla.org/show_bug.cgi?id=625289
+ setTimeout( function() {
- // Expand startX
- for (y = startY; y <= maxY; y++) {
- cell = grid[y][startX];
+ // Add the “opened” class to the picker root.
+ P.$root.addClass( CLASSES.opened )
+ aria( P.$root[0], 'hidden', false )
- if (!cell.real) {
- if (startX - (cell.colspan - 1) < startX) {
- startX -= cell.colspan - 1;
- }
- }
- }
+ }, 0 )
- // Expand startY
- for (x = startX; x <= maxX; x++) {
- cell = grid[startY][x];
+ // If we have to give focus, bind the element and doc events.
+ if ( dontGiveFocus !== false ) {
- if (!cell.real) {
- if (startY - (cell.rowspan - 1) < startY) {
- startY -= cell.rowspan - 1;
- }
- }
- }
+ // Set it as open.
+ STATE.open = true
- // Find max X, Y
- for (y = startY; y <= endY; y++) {
- for (x = startX; x <= endX; x++) {
- cell = grid[y][x];
+ // Prevent the page from scrolling.
+ if ( IS_DEFAULT_THEME ) {
+ $html.
+ css( 'overflow', 'hidden' ).
+ css( 'padding-right', '+=' + getScrollbarWidth() )
+ }
- if (cell.real) {
- colSpan = cell.colspan - 1;
- rowSpan = cell.rowspan - 1;
+ // Pass focus to the root element’s jQuery object.
+ focusPickerOnceOpened()
- if (colSpan) {
- if (x + colSpan > maxX) {
- maxX = x + colSpan;
- }
- }
+ // Bind the document events.
+ $document.on( 'click.' + STATE.id + ' focusin.' + STATE.id, function( event ) {
- if (rowSpan) {
- if (y + rowSpan > maxY) {
- maxY = y + rowSpan;
- }
- }
- }
- }
- }
+ var target = event.target
- // Remove current selection
- dom.removeClass(dom.select('td.mce-item-selected,th.mce-item-selected'), 'mce-item-selected');
+ // If the target of the event is not the element, close the picker picker.
+ // * Don’t worry about clicks or focusins on the root because those don’t bubble up.
+ // Also, for Firefox, a click on an `option` element bubbles up directly
+ // to the doc. So make sure the target wasn't the doc.
+ // * In Firefox stopPropagation() doesn’t prevent right-click events from bubbling,
+ // which causes the picker to unexpectedly close when right-clicking it. So make
+ // sure the event wasn’t a right-click.
+ if ( target != ELEMENT && target != document && event.which != 3 ) {
- // Add new selection
- for (y = startY; y <= maxY; y++) {
- for (x = startX; x <= maxX; x++) {
- if (grid[y][x]) {
- dom.addClass(grid[y][x].elm, 'mce-item-selected');
- }
- }
- }
- }
- }
+ // If the target was the holder that covers the screen,
+ // keep the element focused to maintain tabindex.
+ P.close( target === P.$holder[0] )
+ }
- function moveRelIdx(cellElm, delta) {
- var pos, index, cell;
+ }).on( 'keydown.' + STATE.id, function( event ) {
- pos = getPos(cellElm);
- index = pos.y * gridWidth + pos.x;
+ var
+ // Get the keycode.
+ keycode = event.keyCode,
- do {
- index += delta;
- cell = getCell(index % gridWidth, Math.floor(index / gridWidth));
+ // Translate that to a selection change.
+ keycodeToMove = P.component.key[ keycode ],
- if (!cell) {
- break;
- }
+ // Grab the target.
+ target = event.target
- if (cell.elm != cellElm) {
- selection.select(cell.elm, true);
- if (dom.isEmpty(cell.elm)) {
- selection.collapse(true);
- }
+ // On escape, close the picker and give focus.
+ if ( keycode == 27 ) {
+ P.close( true )
+ }
- return true;
- }
- } while (cell.elm == cellElm);
- return false;
- }
+ // Check if there is a key movement or “enter” keypress on the element.
+ else if ( target == P.$holder[0] && ( keycodeToMove || keycode == 13 ) ) {
- table = table || dom.getParent(selection.getStart(), 'table');
+ // Prevent the default action to stop page movement.
+ event.preventDefault()
- buildGrid();
+ // Trigger the key movement action.
+ if ( keycodeToMove ) {
+ PickerConstructor._.trigger( P.component.key.go, P, [ PickerConstructor._.trigger( keycodeToMove ) ] )
+ }
- selectedCell = dom.getParent(selection.getStart(), 'th,td');
- if (selectedCell) {
- startPos = getPos(selectedCell);
- endPos = findEndPos();
- selectedCell = getCell(startPos.x, startPos.y);
- }
+ // On “enter”, if the highlighted item isn’t disabled, set the value and close.
+ else if ( !P.$root.find( '.' + CLASSES.highlighted ).hasClass( CLASSES.disabled ) ) {
+ P.set( 'select', P.component.item.highlight )
+ if ( SETTINGS.closeOnSelect ) {
+ P.close( true )
+ }
+ }
+ }
- Tools.extend(this, {
- deleteTable: deleteTable,
- split: split,
- merge: merge,
- insertRow: insertRow,
- insertCol: insertCol,
- deleteCols: deleteCols,
- deleteRows: deleteRows,
- cutRows: cutRows,
- copyRows: copyRows,
- pasteRows: pasteRows,
- getPos: getPos,
- setStartCell: setStartCell,
- setEndCell: setEndCell,
- moveRelIdx: moveRelIdx,
- refresh: buildGrid
- });
- };
-});
-// Included from: js/tinymce/plugins/table/classes/Quirks.js
+ // If the target is within the root and “enter” is pressed,
+ // prevent the default action and trigger a click on the target instead.
+ else if ( $.contains( P.$root[0], target ) && keycode == 13 ) {
+ event.preventDefault()
+ target.click()
+ }
+ })
+ }
-/**
- * Quirks.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
+ // Trigger the queued “open” events.
+ return P.trigger( 'open' )
+ }, //open
-/**
- * This class includes fixes for various browser quirks.
- *
- * @class tinymce.tableplugin.Quirks
- * @private
- */
-define("tinymce/tableplugin/Quirks", [
- "tinymce/util/VK",
- "tinymce/Env",
- "tinymce/util/Tools",
- "tinymce/tableplugin/Utils"
-], function(VK, Env, Tools, Utils) {
- var each = Tools.each, getSpanVal = Utils.getSpanVal;
- return function(editor) {
- /**
- * Fixed caret movement around tables on WebKit.
- */
- function moveWebKitSelection() {
- function eventHandler(e) {
- var key = e.keyCode;
+ /**
+ * Close the picker
+ */
+ close: function( giveFocus ) {
- function handle(upBool, sourceNode) {
- var siblingDirection = upBool ? 'previousSibling' : 'nextSibling';
- var currentRow = editor.dom.getParent(sourceNode, 'tr');
- var siblingRow = currentRow[siblingDirection];
+ // If we need to give focus, do it before changing states.
+ if ( giveFocus ) {
+ if ( SETTINGS.editable ) {
+ ELEMENT.focus()
+ }
+ else {
+ // ....ah yes! It would’ve been incomplete without a crazy workaround for IE :|
+ // The focus is triggered *after* the close has completed - causing it
+ // to open again. So unbind and rebind the event at the next tick.
+ P.$holder.off( 'focus.toOpen' ).focus()
+ setTimeout( function() {
+ P.$holder.on( 'focus.toOpen', handleFocusToOpenEvent )
+ }, 0 )
+ }
+ }
- if (siblingRow) {
- moveCursorToRow(editor, sourceNode, siblingRow, upBool);
- e.preventDefault();
- return true;
- }
+ // Remove the “active” class.
+ $ELEMENT.removeClass( CLASSES.active )
+ aria( ELEMENT, 'expanded', false )
- var tableNode = editor.dom.getParent(currentRow, 'table');
- var middleNode = currentRow.parentNode;
- var parentNodeName = middleNode.nodeName.toLowerCase();
- if (parentNodeName === 'tbody' || parentNodeName === (upBool ? 'tfoot' : 'thead')) {
- var targetParent = getTargetParent(upBool, tableNode, middleNode, 'tbody');
- if (targetParent !== null) {
- return moveToRowInTarget(upBool, targetParent, sourceNode);
- }
- }
+ // * A Firefox bug, when `html` has `overflow:hidden`, results in
+ // killing transitions :(. So remove the “opened” state on the next tick.
+ // Bug: https://bugzilla.mozilla.org/show_bug.cgi?id=625289
+ setTimeout( function() {
- return escapeTable(upBool, currentRow, siblingDirection, tableNode);
- }
+ // Remove the “opened” and “focused” class from the picker root.
+ P.$root.removeClass( CLASSES.opened + ' ' + CLASSES.focused )
+ aria( P.$root[0], 'hidden', true )
- function getTargetParent(upBool, topNode, secondNode, nodeName) {
- var tbodies = editor.dom.select('>' + nodeName, topNode);
- var position = tbodies.indexOf(secondNode);
- if (upBool && position === 0 || !upBool && position === tbodies.length - 1) {
- return getFirstHeadOrFoot(upBool, topNode);
- } else if (position === -1) {
- var topOrBottom = secondNode.tagName.toLowerCase() === 'thead' ? 0 : tbodies.length - 1;
- return tbodies[topOrBottom];
- }
+ }, 0 )
- return tbodies[position + (upBool ? -1 : 1)];
- }
+ // If it’s already closed, do nothing more.
+ if ( !STATE.open ) return P
- function getFirstHeadOrFoot(upBool, parent) {
- var tagName = upBool ? 'thead' : 'tfoot';
- var headOrFoot = editor.dom.select('>' + tagName, parent);
- return headOrFoot.length !== 0 ? headOrFoot[0] : null;
- }
+ // Set it as closed.
+ STATE.open = false
- function moveToRowInTarget(upBool, targetParent, sourceNode) {
- var targetRow = getChildForDirection(targetParent, upBool);
+ // Allow the page to scroll.
+ if ( IS_DEFAULT_THEME ) {
+ $html.
+ css( 'overflow', '' ).
+ css( 'padding-right', '-=' + getScrollbarWidth() )
+ }
- if (targetRow) {
- moveCursorToRow(editor, sourceNode, targetRow, upBool);
- }
+ // Unbind the document events.
+ $document.off( '.' + STATE.id )
- e.preventDefault();
- return true;
- }
+ // Trigger the queued “close” events.
+ return P.trigger( 'close' )
+ }, //close
- function escapeTable(upBool, currentRow, siblingDirection, table) {
- var tableSibling = table[siblingDirection];
- if (tableSibling) {
- moveCursorToStartOfElement(tableSibling);
- return true;
- }
+ /**
+ * Clear the values
+ */
+ clear: function( options ) {
+ return P.set( 'clear', null, options )
+ }, //clear
- var parentCell = editor.dom.getParent(table, 'td,th');
- if (parentCell) {
- return handle(upBool, parentCell, e);
- }
- var backUpSibling = getChildForDirection(currentRow, !upBool);
- moveCursorToStartOfElement(backUpSibling);
- e.preventDefault();
- return false;
- }
+ /**
+ * Set something
+ */
+ set: function( thing, value, options ) {
- function getChildForDirection(parent, up) {
- var child = parent && parent[up ? 'lastChild' : 'firstChild'];
- // BR is not a valid table child to return in this case we return the table cell
- return child && child.nodeName === 'BR' ? editor.dom.getParent(child, 'td,th') : child;
- }
+ var thingItem, thingValue,
+ thingIsObject = $.isPlainObject( thing ),
+ thingObject = thingIsObject ? thing : {}
- function moveCursorToStartOfElement(n) {
- editor.selection.setCursorLocation(n, 0);
- }
+ // Make sure we have usable options.
+ options = thingIsObject && $.isPlainObject( value ) ? value : options || {}
- function isVerticalMovement() {
- return key == VK.UP || key == VK.DOWN;
- }
+ if ( thing ) {
- function isInTable(editor) {
- var node = editor.selection.getNode();
- var currentRow = editor.dom.getParent(node, 'tr');
- return currentRow !== null;
- }
+ // If the thing isn’t an object, make it one.
+ if ( !thingIsObject ) {
+ thingObject[ thing ] = value
+ }
- function columnIndex(column) {
- var colIndex = 0;
- var c = column;
- while (c.previousSibling) {
- c = c.previousSibling;
- colIndex = colIndex + getSpanVal(c, "colspan");
- }
- return colIndex;
- }
+ // Go through the things of items to set.
+ for ( thingItem in thingObject ) {
- function findColumn(rowElement, columnIndex) {
- var c = 0, r = 0;
+ // Grab the value of the thing.
+ thingValue = thingObject[ thingItem ]
- each(rowElement.children, function(cell, i) {
- c = c + getSpanVal(cell, "colspan");
- r = i;
- if (c > columnIndex) {
- return false;
- }
- });
- return r;
- }
+ // First, if the item exists and there’s a value, set it.
+ if ( thingItem in P.component.item ) {
+ if ( thingValue === undefined ) thingValue = null
+ P.component.set( thingItem, thingValue, options )
+ }
- function moveCursorToRow(ed, node, row, upBool) {
- var srcColumnIndex = columnIndex(editor.dom.getParent(node, 'td,th'));
- var tgtColumnIndex = findColumn(row, srcColumnIndex);
- var tgtNode = row.childNodes[tgtColumnIndex];
- var rowCellTarget = getChildForDirection(tgtNode, upBool);
- moveCursorToStartOfElement(rowCellTarget || tgtNode);
- }
+ // Then, check to update the element value and broadcast a change.
+ if ( thingItem == 'select' || thingItem == 'clear' ) {
+ $ELEMENT.
+ val( thingItem == 'clear' ? '' : P.get( thingItem, SETTINGS.format ) ).
+ trigger( 'change' )
+ }
+ }
- function shouldFixCaret(preBrowserNode) {
- var newNode = editor.selection.getNode();
- var newParent = editor.dom.getParent(newNode, 'td,th');
- var oldParent = editor.dom.getParent(preBrowserNode, 'td,th');
+ // Render a new picker.
+ P.render()
+ }
- return newParent && newParent !== oldParent && checkSameParentTable(newParent, oldParent);
- }
+ // When the method isn’t muted, trigger queued “set” events and pass the `thingObject`.
+ return options.muted ? P : P.trigger( 'set', thingObject )
+ }, //set
- function checkSameParentTable(nodeOne, NodeTwo) {
- return editor.dom.getParent(nodeOne, 'TABLE') === editor.dom.getParent(NodeTwo, 'TABLE');
- }
- if (isVerticalMovement() && isInTable(editor)) {
- var preBrowserNode = editor.selection.getNode();
- setTimeout(function() {
- if (shouldFixCaret(preBrowserNode)) {
- handle(!e.shiftKey && key === VK.UP, preBrowserNode, e);
- }
- }, 0);
- }
- }
+ /**
+ * Get something
+ */
+ get: function( thing, format ) {
- editor.on('KeyDown', function(e) {
- eventHandler(e);
- });
- }
+ // Make sure there’s something to get.
+ thing = thing || 'value'
- function fixBeforeTableCaretBug() {
- // Checks if the selection/caret is at the start of the specified block element
- function isAtStart(rng, par) {
- var doc = par.ownerDocument, rng2 = doc.createRange(), elm;
+ // If a picker state exists, return that.
+ if ( STATE[ thing ] != null ) {
+ return STATE[ thing ]
+ }
- rng2.setStartBefore(par);
- rng2.setEnd(rng.endContainer, rng.endOffset);
+ // Return the submission value, if that.
+ if ( thing == 'valueSubmit' ) {
+ if ( P._hidden ) {
+ return P._hidden.value
+ }
+ thing = 'value'
+ }
- elm = doc.createElement('body');
- elm.appendChild(rng2.cloneContents());
+ // Return the value, if that.
+ if ( thing == 'value' ) {
+ return ELEMENT.value
+ }
- // Check for text characters of other elements that should be treated as content
- return elm.innerHTML.replace(/<(br|img|object|embed|input|textarea)[^>]*>/gi, '-').replace(/<[^>]+>/g, '').length === 0;
- }
+ // Check if a component item exists, return that.
+ if ( thing in P.component.item ) {
+ if ( typeof format == 'string' ) {
+ var thingValue = P.component.get( thing )
+ return thingValue ?
+ PickerConstructor._.trigger(
+ P.component.formats.toString,
+ P.component,
+ [ format, thingValue ]
+ ) : ''
+ }
+ return P.component.get( thing )
+ }
+ }, //get
- // Fixes an bug where it's impossible to place the caret before a table in Gecko
- // this fix solves it by detecting when the caret is at the beginning of such a table
- // and then manually moves the caret infront of the table
- editor.on('KeyDown', function(e) {
- var rng, table, dom = editor.dom;
- // On gecko it's not possible to place the caret before a table
- if (e.keyCode == 37 || e.keyCode == 38) {
- rng = editor.selection.getRng();
- table = dom.getParent(rng.startContainer, 'table');
- if (table && editor.getBody().firstChild == table) {
- if (isAtStart(rng, table)) {
- rng = dom.createRng();
+ /**
+ * Bind events on the things.
+ */
+ on: function( thing, method, internal ) {
- rng.setStartBefore(table);
- rng.setEndBefore(table);
+ var thingName, thingMethod,
+ thingIsObject = $.isPlainObject( thing ),
+ thingObject = thingIsObject ? thing : {}
- editor.selection.setRng(rng);
+ if ( thing ) {
- e.preventDefault();
- }
- }
- }
- });
- }
+ // If the thing isn’t an object, make it one.
+ if ( !thingIsObject ) {
+ thingObject[ thing ] = method
+ }
- // Fixes an issue on Gecko where it's impossible to place the caret behind a table
- // This fix will force a paragraph element after the table but only when the forced_root_block setting is enabled
- function fixTableCaretPos() {
- editor.on('KeyDown SetContent VisualAid', function() {
- var last;
+ // Go through the things to bind to.
+ for ( thingName in thingObject ) {
- // Skip empty text nodes from the end
- for (last = editor.getBody().lastChild; last; last = last.previousSibling) {
- if (last.nodeType == 3) {
- if (last.nodeValue.length > 0) {
- break;
- }
- } else if (last.nodeType == 1 && (last.tagName == 'BR' || !last.getAttribute('data-mce-bogus'))) {
- break;
- }
- }
+ // Grab the method of the thing.
+ thingMethod = thingObject[ thingName ]
- if (last && last.nodeName == 'TABLE') {
- if (editor.settings.forced_root_block) {
- editor.dom.add(
- editor.getBody(),
- editor.settings.forced_root_block,
- editor.settings.forced_root_block_attrs,
- Env.ie && Env.ie < 11 ? ' ' : '
'
- );
- } else {
- editor.dom.add(editor.getBody(), 'br', {'data-mce-bogus': '1'});
- }
- }
- });
+ // If it was an internal binding, prefix it.
+ if ( internal ) {
+ thingName = '_' + thingName
+ }
- editor.on('PreProcess', function(o) {
- var last = o.node.lastChild;
+ // Make sure the thing methods collection exists.
+ STATE.methods[ thingName ] = STATE.methods[ thingName ] || []
- if (last && (last.nodeName == "BR" || (last.childNodes.length == 1 &&
- (last.firstChild.nodeName == 'BR' || last.firstChild.nodeValue == '\u00a0'))) &&
- last.previousSibling && last.previousSibling.nodeName == "TABLE") {
- editor.dom.remove(last);
- }
- });
- }
+ // Add the method to the relative method collection.
+ STATE.methods[ thingName ].push( thingMethod )
+ }
+ }
- // this nasty hack is here to work around some WebKit selection bugs.
- function fixTableCellSelection() {
- function tableCellSelected(ed, rng, n, currentCell) {
- // The decision of when a table cell is selected is somewhat involved. The fact that this code is
- // required is actually a pointer to the root cause of this bug. A cell is selected when the start
- // and end offsets are 0, the start container is a text, and the selection node is either a TR (most cases)
- // or the parent of the table (in the case of the selection containing the last cell of a table).
- var TEXT_NODE = 3, table = ed.dom.getParent(rng.startContainer, 'TABLE');
- var tableParent, allOfCellSelected, tableCellSelection;
+ return P
+ }, //on
- if (table) {
- tableParent = table.parentNode;
- }
- allOfCellSelected = rng.startContainer.nodeType == TEXT_NODE &&
- rng.startOffset === 0 &&
- rng.endOffset === 0 &&
- currentCell &&
- (n.nodeName == "TR" || n == tableParent);
- tableCellSelection = (n.nodeName == "TD" || n.nodeName == "TH") && !currentCell;
+ /**
+ * Unbind events on the things.
+ */
+ off: function() {
+ var i, thingName,
+ names = arguments;
+ for ( i = 0, namesCount = names.length; i < namesCount; i += 1 ) {
+ thingName = names[i]
+ if ( thingName in STATE.methods ) {
+ delete STATE.methods[thingName]
+ }
+ }
+ return P
+ },
- return allOfCellSelected || tableCellSelection;
- }
- function fixSelection() {
- var rng = editor.selection.getRng();
- var n = editor.selection.getNode();
- var currentCell = editor.dom.getParent(rng.startContainer, 'TD,TH');
+ /**
+ * Fire off method events.
+ */
+ trigger: function( name, data ) {
+ var _trigger = function( name ) {
+ var methodList = STATE.methods[ name ]
+ if ( methodList ) {
+ methodList.map( function( method ) {
+ PickerConstructor._.trigger( method, P, [ data ] )
+ })
+ }
+ }
+ _trigger( '_' + name )
+ _trigger( name )
+ return P
+ } //trigger
+ } //PickerInstance.prototype
- if (!tableCellSelected(editor, rng, n, currentCell)) {
- return;
- }
- if (!currentCell) {
- currentCell = n;
- }
+ /**
+ * Wrap the picker holder components together.
+ */
+ function createWrappedComponent() {
- // Get the very last node inside the table cell
- var end = currentCell.lastChild;
- while (end.lastChild) {
- end = end.lastChild;
- }
+ // Create a picker wrapper holder
+ return PickerConstructor._.node( 'div',
- // Select the entire table cell. Nothing outside of the table cell should be selected.
- if (end.nodeType == 3) {
- rng.setEnd(end, end.data.length);
- editor.selection.setRng(rng);
- }
- }
+ // Create a picker wrapper node
+ PickerConstructor._.node( 'div',
- editor.on('KeyDown', function() {
- fixSelection();
- });
+ // Create a picker frame
+ PickerConstructor._.node( 'div',
- editor.on('MouseDown', function(e) {
- if (e.button != 2) {
- fixSelection();
- }
- });
- }
+ // Create a picker box node
+ PickerConstructor._.node( 'div',
- /**
- * Delete table if all cells are selected.
- */
- function deleteTable() {
- function placeCaretInCell(cell) {
- editor.selection.select(cell, true);
- editor.selection.collapse(true);
- }
+ // Create the components nodes.
+ P.component.nodes( STATE.open ),
- function clearCell(cell) {
- editor.$(cell).empty();
- Utils.paddCell(cell);
- }
+ // The picker box class
+ CLASSES.box
+ ),
- editor.on('keydown', function(e) {
- if ((e.keyCode == VK.DELETE || e.keyCode == VK.BACKSPACE) && !e.isDefaultPrevented()) {
- var table, tableCells, selectedTableCells, cell;
+ // Picker wrap class
+ CLASSES.wrap
+ ),
- table = editor.dom.getParent(editor.selection.getStart(), 'table');
- if (table) {
- tableCells = editor.dom.select('td,th', table);
- selectedTableCells = Tools.grep(tableCells, function(cell) {
- return editor.dom.hasClass(cell, 'mce-item-selected');
- });
+ // Picker frame class
+ CLASSES.frame
+ ),
- if (selectedTableCells.length === 0) {
- // If caret is within an empty table cell then empty it for real
- cell = editor.dom.getParent(editor.selection.getStart(), 'td,th');
- if (editor.selection.isCollapsed() && cell && editor.dom.isEmpty(cell)) {
- e.preventDefault();
- clearCell(cell);
- placeCaretInCell(cell);
- }
+ // Picker holder class
+ CLASSES.holder,
- return;
- }
+ 'tabindex="-1"'
+ ) //endreturn
+ } //createWrappedComponent
- e.preventDefault();
- if (tableCells.length == selectedTableCells.length) {
- editor.execCommand('mceTableDelete');
- } else {
- Tools.each(selectedTableCells, clearCell);
- placeCaretInCell(selectedTableCells[0]);
- }
- }
- }
- });
- }
- deleteTable();
+ /**
+ * Prepare the input element with all bindings.
+ */
+ function prepareElement() {
- if (Env.webkit) {
- moveWebKitSelection();
- fixTableCellSelection();
- }
+ $ELEMENT.
- if (Env.gecko) {
- fixBeforeTableCaretBug();
- fixTableCaretPos();
- }
+ // Store the picker data by component name.
+ data(NAME, P).
- if (Env.ie > 10) {
- fixBeforeTableCaretBug();
- fixTableCaretPos();
- }
- };
-});
+ // Add the “input” class name.
+ addClass(CLASSES.input).
-// Included from: js/tinymce/plugins/table/classes/CellSelection.js
+ // If there’s a `data-value`, update the value of the element.
+ val( $ELEMENT.data('value') ?
+ P.get('select', SETTINGS.format) :
+ ELEMENT.value
+ )
-/**
- * CellSelection.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
-/**
- * This class handles table cell selection by faking it using a css class that gets applied
- * to cells when dragging the mouse from one cell to another.
- *
- * @class tinymce.tableplugin.CellSelection
- * @private
- */
-define("tinymce/tableplugin/CellSelection", [
- "tinymce/tableplugin/TableGrid",
- "tinymce/dom/TreeWalker",
- "tinymce/util/Tools"
-], function(TableGrid, TreeWalker, Tools) {
- return function(editor) {
- var dom = editor.dom, tableGrid, startCell, startTable, hasCellSelection = true, resizing;
+ // Only bind keydown events if the element isn’t editable.
+ if ( !SETTINGS.editable ) {
+
+ $ELEMENT.
+
+ // On focus/click, open the picker.
+ on( 'focus.' + STATE.id + ' click.' + STATE.id, function(event) {
+ event.preventDefault()
+ P.open()
+ }).
+
+ // Handle keyboard event based on the picker being opened or not.
+ on( 'keydown.' + STATE.id, handleKeydownEvent )
+ }
+
+
+ // Update the aria attributes.
+ aria(ELEMENT, {
+ haspopup: true,
+ expanded: false,
+ readonly: false,
+ owns: ELEMENT.id + '_root'
+ })
+ }
+
+
+ /**
+ * Prepare the root picker element with all bindings.
+ */
+ function prepareElementRoot() {
+ aria( P.$root[0], 'hidden', true )
+ }
- function clear(force) {
- // Restore selection possibilities
- editor.getBody().style.webkitUserSelect = '';
- if (force || hasCellSelection) {
- editor.dom.removeClass(
- editor.dom.select('td.mce-item-selected,th.mce-item-selected'),
- 'mce-item-selected'
- );
+ /**
+ * Prepare the holder picker element with all bindings.
+ */
+ function prepareElementHolder() {
- hasCellSelection = false;
- }
- }
+ P.$holder.
- function cellSelectionHandler(e) {
- var sel, table, target = e.target;
+ on({
- if (resizing) {
- return;
- }
+ // For iOS8.
+ keydown: handleKeydownEvent,
- if (startCell && (tableGrid || target != startCell) && (target.nodeName == 'TD' || target.nodeName == 'TH')) {
- table = dom.getParent(target, 'table');
- if (table == startTable) {
- if (!tableGrid) {
- tableGrid = new TableGrid(editor, table);
- tableGrid.setStartCell(startCell);
+ 'focus.toOpen': handleFocusToOpenEvent,
- editor.getBody().style.webkitUserSelect = 'none';
- }
+ blur: function() {
+ // Remove the “target” class.
+ $ELEMENT.removeClass( CLASSES.target )
+ },
- tableGrid.setEndCell(target);
- hasCellSelection = true;
- }
+ // When something within the holder is focused, stop from bubbling
+ // to the doc and remove the “focused” state from the root.
+ focusin: function( event ) {
+ P.$root.removeClass( CLASSES.focused )
+ event.stopPropagation()
+ },
- // Remove current selection
- sel = editor.selection.getSel();
+ // When something within the holder is clicked, stop it
+ // from bubbling to the doc.
+ 'mousedown click': function( event ) {
- try {
- if (sel.removeAllRanges) {
- sel.removeAllRanges();
- } else {
- sel.empty();
- }
- } catch (ex) {
- // IE9 might throw errors here
- }
+ var target = event.target
- e.preventDefault();
- }
- }
+ // Make sure the target isn’t the root holder so it can bubble up.
+ if ( target != P.$holder[0] ) {
- // Add cell selection logic
- editor.on('MouseDown', function(e) {
- if (e.button != 2 && !resizing) {
- clear();
+ event.stopPropagation()
- startCell = dom.getParent(e.target, 'td,th');
- startTable = dom.getParent(startCell, 'table');
- }
- });
+ // * For mousedown events, cancel the default action in order to
+ // prevent cases where focus is shifted onto external elements
+ // when using things like jQuery mobile or MagnificPopup (ref: #249 & #120).
+ // Also, for Firefox, don’t prevent action on the `option` element.
+ if ( event.type == 'mousedown' && !$( target ).is( 'input, select, textarea, button, option' )) {
- editor.on('mouseover', cellSelectionHandler);
+ event.preventDefault()
- editor.on('remove', function() {
- dom.unbind(editor.getDoc(), 'mouseover', cellSelectionHandler);
- });
+ // Re-focus onto the holder so that users can click away
+ // from elements focused within the picker.
+ P.$holder[0].focus()
+ }
+ }
+ }
- editor.on('MouseUp', function() {
- var rng, sel = editor.selection, selectedCells, walker, node, lastNode;
+ }).
- function setPoint(node, start) {
- var walker = new TreeWalker(node, node);
+ // If there’s a click on an actionable element, carry out the actions.
+ on( 'click', '[data-pick], [data-nav], [data-clear], [data-close]', function() {
- do {
- // Text node
- if (node.nodeType == 3 && Tools.trim(node.nodeValue).length !== 0) {
- if (start) {
- rng.setStart(node, 0);
- } else {
- rng.setEnd(node, node.nodeValue.length);
- }
+ var $target = $( this ),
+ targetData = $target.data(),
+ targetDisabled = $target.hasClass( CLASSES.navDisabled ) || $target.hasClass( CLASSES.disabled ),
- return;
- }
+ // * For IE, non-focusable elements can be active elements as well
+ // (http://stackoverflow.com/a/2684561).
+ activeElement = getActiveElement()
+ activeElement = activeElement && ( activeElement.type || activeElement.href )
- // BR element
- if (node.nodeName == 'BR') {
- if (start) {
- rng.setStartBefore(node);
- } else {
- rng.setEndBefore(node);
- }
+ // If it’s disabled or nothing inside is actively focused, re-focus the element.
+ if ( targetDisabled || activeElement && !$.contains( P.$root[0], activeElement ) ) {
+ P.$holder[0].focus()
+ }
- return;
- }
- } while ((node = (start ? walker.next() : walker.prev())));
- }
+ // If something is superficially changed, update the `highlight` based on the `nav`.
+ if ( !targetDisabled && targetData.nav ) {
+ P.set( 'highlight', P.component.item.highlight, { nav: targetData.nav } )
+ }
- // Move selection to startCell
- if (startCell) {
- if (tableGrid) {
- editor.getBody().style.webkitUserSelect = '';
- }
+ // If something is picked, set `select` then close with focus.
+ else if ( !targetDisabled && 'pick' in targetData ) {
+ P.set( 'select', targetData.pick )
+ if ( SETTINGS.closeOnSelect ) {
+ P.close( true )
+ }
+ }
- // Try to expand text selection as much as we can only Gecko supports cell selection
- selectedCells = dom.select('td.mce-item-selected,th.mce-item-selected');
- if (selectedCells.length > 0) {
- rng = dom.createRng();
- node = selectedCells[0];
- rng.setStartBefore(node);
- rng.setEndAfter(node);
+ // If a “clear” button is pressed, empty the values and close with focus.
+ else if ( targetData.clear ) {
+ P.clear()
+ if ( SETTINGS.closeOnClear ) {
+ P.close( true )
+ }
+ }
- setPoint(node, 1);
- walker = new TreeWalker(node, dom.getParent(selectedCells[0], 'table'));
+ else if ( targetData.close ) {
+ P.close( true )
+ }
- do {
- if (node.nodeName == 'TD' || node.nodeName == 'TH') {
- if (!dom.hasClass(node, 'mce-item-selected')) {
- break;
- }
+ }) //P.$holder
- lastNode = node;
- }
- } while ((node = walker.next()));
+ }
- setPoint(lastNode);
- sel.setRng(rng);
- }
+ /**
+ * Prepare the hidden input element along with all bindings.
+ */
+ function prepareElementHidden() {
- editor.nodeChanged();
- startCell = tableGrid = startTable = null;
- }
- });
+ var name
- editor.on('KeyUp Drop SetContent', function(e) {
- clear(e.type == 'setcontent');
- startCell = tableGrid = startTable = null;
- resizing = false;
- });
+ if ( SETTINGS.hiddenName === true ) {
+ name = ELEMENT.name
+ ELEMENT.name = ''
+ }
+ else {
+ name = [
+ typeof SETTINGS.hiddenPrefix == 'string' ? SETTINGS.hiddenPrefix : '',
+ typeof SETTINGS.hiddenSuffix == 'string' ? SETTINGS.hiddenSuffix : '_submit'
+ ]
+ name = name[0] + ELEMENT.name + name[1]
+ }
- editor.on('ObjectResizeStart ObjectResized', function(e) {
- resizing = e.type != 'objectresized';
- });
+ P._hidden = $(
+ ''
+ )[0]
+
+ $ELEMENT.
+
+ // If the value changes, update the hidden input with the correct format.
+ on('change.' + STATE.id, function() {
+ P._hidden.value = ELEMENT.value ?
+ P.get('select', SETTINGS.formatSubmit) :
+ ''
+ })
+ }
- return {
- clear: clear
- };
- };
-});
-// Included from: js/tinymce/plugins/table/classes/Dialogs.js
+ // Wait for transitions to end before focusing the holder. Otherwise, while
+ // using the `container` option, the view jumps to the container.
+ function focusPickerOnceOpened() {
-/**
- * Dialogs.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
+ if (IS_DEFAULT_THEME && supportsTransitions) {
+ P.$holder.find('.' + CLASSES.frame).one('transitionend', function() {
+ P.$holder[0].focus()
+ })
+ }
+ else {
+ P.$holder[0].focus()
+ }
+ }
-/*eslint dot-notation:0*/
-/**
- * ...
- *
- * @class tinymce.tableplugin.Dialogs
- * @private
- */
-define("tinymce/tableplugin/Dialogs", [
- "tinymce/util/Tools",
- "tinymce/Env"
-], function(Tools, Env) {
- var each = Tools.each;
+ function handleFocusToOpenEvent(event) {
- return function(editor) {
- var self = this;
+ // Stop the event from propagating to the doc.
+ event.stopPropagation()
- function createColorPickAction() {
- var colorPickerCallback = editor.settings.color_picker_callback;
+ // Add the “target” class.
+ $ELEMENT.addClass( CLASSES.target )
- if (colorPickerCallback) {
- return function() {
- var self = this;
+ // Add the “focused” class to the root.
+ P.$root.addClass( CLASSES.focused )
- colorPickerCallback.call(
- editor,
- function(value) {
- self.value(value).fire('change');
- },
- self.value()
- );
- };
- }
- }
+ // And then finally open the picker.
+ P.open()
+ }
- function createStyleForm(dom) {
- return {
- title: 'Advanced',
- type: 'form',
- defaults: {
- onchange: function() {
- updateStyle(dom, this.parents().reverse()[0], this.name() == "style");
- }
- },
- items: [
- {
- label: 'Style',
- name: 'style',
- type: 'textbox'
- },
- {
- type: 'form',
- padding: 0,
- formItemDefaults: {
- layout: 'grid',
- alignH: ['start', 'right']
- },
- defaults: {
- size: 7
- },
- items: [
- {
- label: 'Border color',
- type: 'colorbox',
- name: 'borderColor',
- onaction: createColorPickAction()
- },
+ // For iOS8.
+ function handleKeydownEvent( event ) {
- {
- label: 'Background color',
- type: 'colorbox',
- name: 'backgroundColor',
- onaction: createColorPickAction()
- }
- ]
- }
- ]
- };
- }
+ var keycode = event.keyCode,
- function removePxSuffix(size) {
- return size ? size.replace(/px$/, '') : "";
- }
+ // Check if one of the delete keys was pressed.
+ isKeycodeDelete = /^(8|46)$/.test(keycode)
- function addSizeSuffix(size) {
- if (/^[0-9]+$/.test(size)) {
- size += "px";
- }
+ // For some reason IE clears the input value on “escape”.
+ if ( keycode == 27 ) {
+ P.close( true )
+ return false
+ }
- return size;
- }
+ // Check if `space` or `delete` was pressed or the picker is closed with a key movement.
+ if ( keycode == 32 || isKeycodeDelete || !STATE.open && P.component.key[keycode] ) {
- function unApplyAlign(elm) {
- each('left center right'.split(' '), function(name) {
- editor.formatter.remove('align' + name, {}, elm);
- });
- }
+ // Prevent it from moving the page and bubbling to doc.
+ event.preventDefault()
+ event.stopPropagation()
- function unApplyVAlign(elm) {
- each('top middle bottom'.split(' '), function(name) {
- editor.formatter.remove('valign' + name, {}, elm);
- });
- }
+ // If `delete` was pressed, clear the values and close the picker.
+ // Otherwise open the picker.
+ if ( isKeycodeDelete ) { P.clear().close() }
+ else { P.open() }
+ }
+ }
- function buildListItems(inputList, itemCallback, startItems) {
- function appendItems(values, output) {
- output = output || [];
- Tools.each(values, function(item) {
- var menuItem = {text: item.text || item.title};
+ // Return a new picker instance.
+ return new PickerInstance()
+} //PickerConstructor
- if (item.menu) {
- menuItem.menu = appendItems(item.menu);
- } else {
- menuItem.value = item.value;
- if (itemCallback) {
- itemCallback(menuItem);
- }
- }
- output.push(menuItem);
- });
+/**
+ * The default classes and prefix to use for the HTML classes.
+ */
+PickerConstructor.klasses = function( prefix ) {
+ prefix = prefix || 'picker'
+ return {
- return output;
- }
+ picker: prefix,
+ opened: prefix + '--opened',
+ focused: prefix + '--focused',
- return appendItems(inputList, startItems || []);
- }
+ input: prefix + '__input',
+ active: prefix + '__input--active',
+ target: prefix + '__input--target',
- function updateStyle(dom, win, isStyleCtrl) {
- var data = win.toJSON();
- var css = dom.parseStyle(data.style);
+ holder: prefix + '__holder',
- if (isStyleCtrl) {
- win.find('#borderColor').value(css["border-color"] || '')[0].fire('change');
- win.find('#backgroundColor').value(css["background-color"] || '')[0].fire('change');
- } else {
- css["border-color"] = data.borderColor;
- css["background-color"] = data.backgroundColor;
- }
+ frame: prefix + '__frame',
+ wrap: prefix + '__wrap',
- win.find('#style').value(dom.serializeStyle(dom.parseStyle(dom.serializeStyle(css))));
- }
+ box: prefix + '__box'
+ }
+} //PickerConstructor.klasses
- function appendStylesToData(dom, data, elm) {
- var css = dom.parseStyle(dom.getAttrib(elm, 'style'));
- if (css["border-color"]) {
- data.borderColor = css["border-color"];
- }
- if (css["background-color"]) {
- data.backgroundColor = css["background-color"];
- }
+/**
+ * Check if the default theme is being used.
+ */
+function isUsingDefaultTheme( element ) {
- data.style = dom.serializeStyle(css);
- }
+ var theme,
+ prop = 'position'
- function mergeStyles(dom, elm, styles) {
- var css = dom.parseStyle(dom.getAttrib(elm, 'style'));
+ // For IE.
+ if ( element.currentStyle ) {
+ theme = element.currentStyle[prop]
+ }
- each(styles, function(style) {
- css[style.name] = style.value;
- });
+ // For normal browsers.
+ else if ( window.getComputedStyle ) {
+ theme = getComputedStyle( element )[prop]
+ }
- dom.setAttrib(elm, 'style', dom.serializeStyle(dom.parseStyle(dom.serializeStyle(css))));
- }
+ return theme == 'fixed'
+}
- self.tableProps = function() {
- self.table(true);
- };
- self.table = function(isProps) {
- var dom = editor.dom, tableElm, colsCtrl, rowsCtrl, classListCtrl, data = {}, generalTableForm, stylesToMerge;
- function onSubmitTableForm() {
+/**
+ * Get the width of the browser’s scrollbar.
+ * Taken from: https://github.com/VodkaBears/Remodal/blob/master/src/jquery.remodal.js
+ */
+function getScrollbarWidth() {
- //Explore the layers of the table till we find the first layer of tds or ths
- function styleTDTH(elm, name, value) {
- if (elm.tagName === "TD" || elm.tagName === "TH") {
- dom.setStyle(elm, name, value);
- } else {
- if (elm.children) {
- for (var i = 0; i < elm.children.length; i++) {
- styleTDTH(elm.children[i], name, value);
- }
- }
- }
- }
+ if ( $html.height() <= $window.height() ) {
+ return 0
+ }
- var captionElm;
+ var $outer = $( '' ).
+ appendTo( 'body' )
- updateStyle(dom, this);
- data = Tools.extend(data, this.toJSON());
+ // Get the width without scrollbars.
+ var widthWithoutScroll = $outer[0].offsetWidth
- if (data["class"] === false) {
- delete data["class"];
- }
+ // Force adding scrollbars.
+ $outer.css( 'overflow', 'scroll' )
- editor.undoManager.transact(function() {
- if (!tableElm) {
- tableElm = editor.plugins.table.insertTable(data.cols || 1, data.rows || 1);
- }
+ // Add the inner div.
+ var $inner = $( '' ).appendTo( $outer )
- editor.dom.setAttribs(tableElm, {
- style: data.style,
- 'class': data['class']
- });
+ // Get the width with scrollbars.
+ var widthWithScroll = $inner[0].offsetWidth
- if (editor.settings.table_style_by_css) {
- stylesToMerge = [];
- stylesToMerge.push({name: 'border', value: data.border});
- stylesToMerge.push({name: 'border-spacing', value: addSizeSuffix(data.cellspacing)});
- mergeStyles(dom, tableElm, stylesToMerge);
- dom.setAttribs(tableElm, {
- 'data-mce-border-color': data.borderColor,
- 'data-mce-cell-padding': data.cellpadding,
- 'data-mce-border': data.border
- });
- if (tableElm.children) {
- for (var i = 0; i < tableElm.children.length; i++) {
- styleTDTH(tableElm.children[i], 'border', data.border);
- styleTDTH(tableElm.children[i], 'padding', addSizeSuffix(data.cellpadding));
- }
- }
- } else {
- editor.dom.setAttribs(tableElm, {
- border: data.border,
- cellpadding: data.cellpadding,
- cellspacing: data.cellspacing
- });
- }
+ // Remove the divs.
+ $outer.remove()
- if (dom.getAttrib(tableElm, 'width') && !editor.settings.table_style_by_css) {
- dom.setAttrib(tableElm, 'width', removePxSuffix(data.width));
- } else {
- dom.setStyle(tableElm, 'width', addSizeSuffix(data.width));
- }
+ // Return the difference between the widths.
+ return widthWithoutScroll - widthWithScroll
+}
- dom.setStyle(tableElm, 'height', addSizeSuffix(data.height));
- // Toggle caption on/off
- captionElm = dom.select('caption', tableElm)[0];
- if (captionElm && !data.caption) {
- dom.remove(captionElm);
- }
+/**
+ * PickerConstructor helper methods.
+ */
+PickerConstructor._ = {
- if (!captionElm && data.caption) {
- captionElm = dom.create('caption');
- captionElm.innerHTML = !Env.ie ? '
' : '\u00a0';
- tableElm.insertBefore(captionElm, tableElm.firstChild);
- }
- unApplyAlign(tableElm);
- if (data.align) {
- editor.formatter.apply('align' + data.align, {}, tableElm);
- }
+ /**
+ * Create a group of nodes. Expects:
+ * `
+ {
+ min: {Integer},
+ max: {Integer},
+ i: {Integer},
+ node: {String},
+ item: {Function}
+ }
+ * `
+ */
+ group: function( groupObject ) {
- editor.focus();
- editor.addVisual();
- });
- }
+ var
+ // Scope for the looped object
+ loopObjectScope,
- function getTDTHOverallStyle(elm, name) {
- var cells = editor.dom.select("td,th", elm), firstChildStyle;
+ // Create the nodes list
+ nodesList = '',
- function checkChildren(firstChildStyle, elms) {
+ // The counter starts from the `min`
+ counter = PickerConstructor._.trigger( groupObject.min, groupObject )
- for (var i = 0; i < elms.length; i++) {
- var currentStyle = dom.getStyle(elms[i], name);
- if (typeof firstChildStyle === "undefined") {
- firstChildStyle = currentStyle;
- }
- if (firstChildStyle != currentStyle) {
- return "";
- }
- }
- return firstChildStyle;
+ // Loop from the `min` to `max`, incrementing by `i`
+ for ( ; counter <= PickerConstructor._.trigger( groupObject.max, groupObject, [ counter ] ); counter += groupObject.i ) {
- }
+ // Trigger the `item` function within scope of the object
+ loopObjectScope = PickerConstructor._.trigger( groupObject.item, groupObject, [ counter ] )
- firstChildStyle = checkChildren(firstChildStyle, cells);
+ // Splice the subgroup and create nodes out of the sub nodes
+ nodesList += PickerConstructor._.node(
+ groupObject.node,
+ loopObjectScope[ 0 ], // the node
+ loopObjectScope[ 1 ], // the classes
+ loopObjectScope[ 2 ] // the attributes
+ )
+ }
- return firstChildStyle;
- }
+ // Return the list of nodes
+ return nodesList
+ }, //group
- if (isProps === true) {
- tableElm = dom.getParent(editor.selection.getStart(), 'table');
- if (tableElm) {
- data = {
- width: removePxSuffix(dom.getStyle(tableElm, 'width') || dom.getAttrib(tableElm, 'width')),
- height: removePxSuffix(dom.getStyle(tableElm, 'height') || dom.getAttrib(tableElm, 'height')),
- cellspacing: removePxSuffix(dom.getStyle(tableElm, 'border-spacing') ||
- dom.getAttrib(tableElm, 'cellspacing')),
- cellpadding: dom.getAttrib(tableElm, 'data-mce-cell-padding') || dom.getAttrib(tableElm, 'cellpadding') ||
- getTDTHOverallStyle(tableElm, 'padding'),
- border: dom.getAttrib(tableElm, 'data-mce-border') || dom.getAttrib(tableElm, 'border') ||
- getTDTHOverallStyle(tableElm, 'border'),
- borderColor: dom.getAttrib(tableElm, 'data-mce-border-color'),
- caption: !!dom.select('caption', tableElm)[0],
- 'class': dom.getAttrib(tableElm, 'class')
- };
+ /**
+ * Create a dom node string
+ */
+ node: function( wrapper, item, klass, attribute ) {
- each('left center right'.split(' '), function(name) {
- if (editor.formatter.matchNode(tableElm, 'align' + name)) {
- data.align = name;
- }
- });
- }
- } else {
- colsCtrl = {label: 'Cols', name: 'cols'};
- rowsCtrl = {label: 'Rows', name: 'rows'};
- }
+ // If the item is false-y, just return an empty string
+ if ( !item ) return ''
- if (editor.settings.table_class_list) {
- if (data["class"]) {
- data["class"] = data["class"].replace(/\s*mce\-item\-table\s*/g, '');
- }
+ // If the item is an array, do a join
+ item = $.isArray( item ) ? item.join( '' ) : item
- classListCtrl = {
- name: 'class',
- type: 'listbox',
- label: 'Class',
- values: buildListItems(
- editor.settings.table_class_list,
- function(item) {
- if (item.value) {
- item.textStyle = function() {
- return editor.formatter.getCssText({block: 'table', classes: [item.value]});
- };
- }
- }
- )
- };
- }
+ // Check for the class
+ klass = klass ? ' class="' + klass + '"' : ''
- generalTableForm = {
- type: 'form',
- layout: 'flex',
- direction: 'column',
- labelGapCalc: 'children',
- padding: 0,
- items: [
- {
- type: 'form',
- labelGapCalc: false,
- padding: 0,
- layout: 'grid',
- columns: 2,
- defaults: {
- type: 'textbox',
- maxWidth: 50
- },
- items: (editor.settings.table_appearance_options !== false) ? [
- colsCtrl,
- rowsCtrl,
- {label: 'Width', name: 'width'},
- {label: 'Height', name: 'height'},
- {label: 'Cell spacing', name: 'cellspacing'},
- {label: 'Cell padding', name: 'cellpadding'},
- {label: 'Border', name: 'border'},
- {label: 'Caption', name: 'caption', type: 'checkbox'}
- ] : [
- colsCtrl,
- rowsCtrl,
- {label: 'Width', name: 'width'},
- {label: 'Height', name: 'height'}
- ]
- },
+ // Check for any attributes
+ attribute = attribute ? ' ' + attribute : ''
- {
- label: 'Alignment',
- name: 'align',
- type: 'listbox',
- text: 'None',
- values: [
- {text: 'None', value: ''},
- {text: 'Left', value: 'left'},
- {text: 'Center', value: 'center'},
- {text: 'Right', value: 'right'}
- ]
- },
+ // Return the wrapped item
+ return '<' + wrapper + klass + attribute + '>' + item + '' + wrapper + '>'
+ }, //node
+
+
+ /**
+ * Lead numbers below 10 with a zero.
+ */
+ lead: function( number ) {
+ return ( number < 10 ? '0': '' ) + number
+ },
+
+
+ /**
+ * Trigger a function otherwise return the value.
+ */
+ trigger: function( callback, scope, args ) {
+ return typeof callback == 'function' ? callback.apply( scope, args || [] ) : callback
+ },
- classListCtrl
- ]
- };
- if (editor.settings.table_advtab !== false) {
- appendStylesToData(dom, data, tableElm);
+ /**
+ * If the second character is a digit, length is 2 otherwise 1.
+ */
+ digits: function( string ) {
+ return ( /\d/ ).test( string[ 1 ] ) ? 2 : 1
+ },
- editor.windowManager.open({
- title: "Table properties",
- data: data,
- bodyType: 'tabpanel',
- body: [
- {
- title: 'General',
- type: 'form',
- items: generalTableForm
- },
- createStyleForm(dom)
- ],
- onsubmit: onSubmitTableForm
- });
- } else {
- editor.windowManager.open({
- title: "Table properties",
- data: data,
- body: generalTableForm,
- onsubmit: onSubmitTableForm
- });
- }
- };
+ /**
+ * Tell if something is a date object.
+ */
+ isDate: function( value ) {
+ return {}.toString.call( value ).indexOf( 'Date' ) > -1 && this.isInteger( value.getDate() )
+ },
- self.merge = function(grid, cell) {
- editor.windowManager.open({
- title: "Merge cells",
- body: [
- {label: 'Cols', name: 'cols', type: 'textbox', value: '1', size: 10},
- {label: 'Rows', name: 'rows', type: 'textbox', value: '1', size: 10}
- ],
- onsubmit: function() {
- var data = this.toJSON();
- editor.undoManager.transact(function() {
- grid.merge(cell, data.cols, data.rows);
- });
- }
- });
- };
+ /**
+ * Tell if something is an integer.
+ */
+ isInteger: function( value ) {
+ return {}.toString.call( value ).indexOf( 'Number' ) > -1 && value % 1 === 0
+ },
- self.cell = function() {
- var dom = editor.dom, cellElm, data, classListCtrl, cells = [];
- function onSubmitCellForm() {
- updateStyle(dom, this);
- data = Tools.extend(data, this.toJSON());
+ /**
+ * Create ARIA attribute strings.
+ */
+ ariaAttr: ariaAttr
+} //PickerConstructor._
- editor.undoManager.transact(function() {
- each(cells, function(cellElm) {
- editor.dom.setAttribs(cellElm, {
- scope: data.scope,
- style: data.style,
- 'class': data['class']
- });
- editor.dom.setStyles(cellElm, {
- width: addSizeSuffix(data.width),
- height: addSizeSuffix(data.height)
- });
- // Switch cell type
- if (data.type && cellElm.nodeName.toLowerCase() != data.type) {
- cellElm = dom.rename(cellElm, data.type);
- }
+/**
+ * Extend the picker with a component and defaults.
+ */
+PickerConstructor.extend = function( name, Component ) {
- // Apply/remove alignment
- unApplyAlign(cellElm);
- if (data.align) {
- editor.formatter.apply('align' + data.align, {}, cellElm);
- }
+ // Extend jQuery.
+ $.fn[ name ] = function( options, action ) {
- // Apply/remove vertical alignment
- unApplyVAlign(cellElm);
- if (data.valign) {
- editor.formatter.apply('valign' + data.valign, {}, cellElm);
- }
- });
+ // Grab the component data.
+ var componentData = this.data( name )
- editor.focus();
- });
- }
+ // If the picker is requested, return the data object.
+ if ( options == 'picker' ) {
+ return componentData
+ }
- // Get selected cells or the current cell
- cells = editor.dom.select('td.mce-item-selected,th.mce-item-selected');
- cellElm = editor.dom.getParent(editor.selection.getStart(), 'td,th');
- if (!cells.length && cellElm) {
- cells.push(cellElm);
- }
+ // If the component data exists and `options` is a string, carry out the action.
+ if ( componentData && typeof options == 'string' ) {
+ return PickerConstructor._.trigger( componentData[ options ], componentData, [ action ] )
+ }
- cellElm = cellElm || cells[0];
+ // Otherwise go through each matched element and if the component
+ // doesn’t exist, create a new picker using `this` element
+ // and merging the defaults and options with a deep copy.
+ return this.each( function() {
+ var $this = $( this )
+ if ( !$this.data( name ) ) {
+ new PickerConstructor( this, name, Component, options )
+ }
+ })
+ }
- if (!cellElm) {
- // If this element is null, return now to avoid crashing.
- return;
- }
+ // Set the defaults.
+ $.fn[ name ].defaults = Component.defaults
+} //PickerConstructor.extend
- data = {
- width: removePxSuffix(dom.getStyle(cellElm, 'width') || dom.getAttrib(cellElm, 'width')),
- height: removePxSuffix(dom.getStyle(cellElm, 'height') || dom.getAttrib(cellElm, 'height')),
- scope: dom.getAttrib(cellElm, 'scope'),
- 'class': dom.getAttrib(cellElm, 'class')
- };
- data.type = cellElm.nodeName.toLowerCase();
- each('left center right'.split(' '), function(name) {
- if (editor.formatter.matchNode(cellElm, 'align' + name)) {
- data.align = name;
- }
- });
+function aria(element, attribute, value) {
+ if ( $.isPlainObject(attribute) ) {
+ for ( var key in attribute ) {
+ ariaSet(element, key, attribute[key])
+ }
+ }
+ else {
+ ariaSet(element, attribute, value)
+ }
+}
+function ariaSet(element, attribute, value) {
+ element.setAttribute(
+ (attribute == 'role' ? '' : 'aria-') + attribute,
+ value
+ )
+}
+function ariaAttr(attribute, data) {
+ if ( !$.isPlainObject(attribute) ) {
+ attribute = { attribute: data }
+ }
+ data = ''
+ for ( var key in attribute ) {
+ var attr = (key == 'role' ? '' : 'aria-') + key,
+ attrVal = attribute[key]
+ data += attrVal == null ? '' : attr + '="' + attribute[key] + '"'
+ }
+ return data
+}
- each('top middle bottom'.split(' '), function(name) {
- if (editor.formatter.matchNode(cellElm, 'valign' + name)) {
- data.valign = name;
- }
- });
+// IE8 bug throws an error for activeElements within iframes.
+function getActiveElement() {
+ try {
+ return document.activeElement
+ } catch ( err ) { }
+}
- if (editor.settings.table_cell_class_list) {
- classListCtrl = {
- name: 'class',
- type: 'listbox',
- label: 'Class',
- values: buildListItems(
- editor.settings.table_cell_class_list,
- function(item) {
- if (item.value) {
- item.textStyle = function() {
- return editor.formatter.getCssText({block: 'td', classes: [item.value]});
- };
- }
- }
- )
- };
- }
- var generalCellForm = {
- type: 'form',
- layout: 'flex',
- direction: 'column',
- labelGapCalc: 'children',
- padding: 0,
- items: [
- {
- type: 'form',
- layout: 'grid',
- columns: 2,
- labelGapCalc: false,
- padding: 0,
- defaults: {
- type: 'textbox',
- maxWidth: 50
- },
- items: [
- {label: 'Width', name: 'width'},
- {label: 'Height', name: 'height'},
- {
- label: 'Cell type',
- name: 'type',
- type: 'listbox',
- text: 'None',
- minWidth: 90,
- maxWidth: null,
- values: [
- {text: 'Cell', value: 'td'},
- {text: 'Header cell', value: 'th'}
- ]
- },
- {
- label: 'Scope',
- name: 'scope',
- type: 'listbox',
- text: 'None',
- minWidth: 90,
- maxWidth: null,
- values: [
- {text: 'None', value: ''},
- {text: 'Row', value: 'row'},
- {text: 'Column', value: 'col'},
- {text: 'Row group', value: 'rowgroup'},
- {text: 'Column group', value: 'colgroup'}
- ]
- },
- {
- label: 'H Align',
- name: 'align',
- type: 'listbox',
- text: 'None',
- minWidth: 90,
- maxWidth: null,
- values: [
- {text: 'None', value: ''},
- {text: 'Left', value: 'left'},
- {text: 'Center', value: 'center'},
- {text: 'Right', value: 'right'}
- ]
- },
- {
- label: 'V Align',
- name: 'valign',
- type: 'listbox',
- text: 'None',
- minWidth: 90,
- maxWidth: null,
- values: [
- {text: 'None', value: ''},
- {text: 'Top', value: 'top'},
- {text: 'Middle', value: 'middle'},
- {text: 'Bottom', value: 'bottom'}
- ]
- }
- ]
- },
- classListCtrl
- ]
- };
+// Expose the picker constructor.
+return PickerConstructor
- if (editor.settings.table_cell_advtab !== false) {
- appendStylesToData(dom, data, cellElm);
- editor.windowManager.open({
- title: "Cell properties",
- bodyType: 'tabpanel',
- data: data,
- body: [
- {
- title: 'General',
- type: 'form',
- items: generalCellForm
- },
+}));
- createStyleForm(dom)
- ],
- onsubmit: onSubmitCellForm
- });
- } else {
- editor.windowManager.open({
- title: "Cell properties",
- data: data,
- body: generalCellForm,
- onsubmit: onSubmitCellForm
- });
- }
- };
- self.row = function() {
- var dom = editor.dom, tableElm, cellElm, rowElm, classListCtrl, data, rows = [], generalRowForm;
- function onSubmitRowForm() {
- var tableElm, oldParentElm, parentElm;
+/*!
+ * Date picker for pickadate.js v3.5.6
+ * http://amsul.github.io/pickadate.js/date.htm
+ */
- updateStyle(dom, this);
- data = Tools.extend(data, this.toJSON());
+(function ( factory ) {
- editor.undoManager.transact(function() {
- var toType = data.type;
+ // AMD.
+ if ( typeof define == 'function' && define.amd )
+ define( 'picker.date',['picker', 'jquery'], factory )
- each(rows, function(rowElm) {
- editor.dom.setAttribs(rowElm, {
- scope: data.scope,
- style: data.style,
- 'class': data['class']
- });
+ // Node.js/browserify.
+ else if ( typeof exports == 'object' )
+ module.exports = factory( require('./picker.js'), require('jquery') )
- editor.dom.setStyles(rowElm, {
- height: addSizeSuffix(data.height)
- });
+ // Browser globals.
+ else factory( Picker, jQuery )
- if (toType != rowElm.parentNode.nodeName.toLowerCase()) {
- tableElm = dom.getParent(rowElm, 'table');
+}(function( Picker, $ ) {
- oldParentElm = rowElm.parentNode;
- parentElm = dom.select(toType, tableElm)[0];
- if (!parentElm) {
- parentElm = dom.create(toType);
- if (tableElm.firstChild) {
- tableElm.insertBefore(parentElm, tableElm.firstChild);
- } else {
- tableElm.appendChild(parentElm);
- }
- }
- parentElm.appendChild(rowElm);
+/**
+ * Globals and constants
+ */
+var DAYS_IN_WEEK = 7,
+ WEEKS_IN_CALENDAR = 6,
+ _ = Picker._
- if (!oldParentElm.hasChildNodes()) {
- dom.remove(oldParentElm);
- }
- }
- // Apply/remove alignment
- unApplyAlign(rowElm);
- if (data.align) {
- editor.formatter.apply('align' + data.align, {}, rowElm);
- }
- });
- editor.focus();
- });
- }
+/**
+ * The date picker constructor
+ */
+function DatePicker( picker, settings ) {
- tableElm = editor.dom.getParent(editor.selection.getStart(), 'table');
- cellElm = editor.dom.getParent(editor.selection.getStart(), 'td,th');
+ var calendar = this,
+ element = picker.$node[ 0 ],
+ elementValue = element.value,
+ elementDataValue = picker.$node.data( 'value' ),
+ valueString = elementDataValue || elementValue,
+ formatString = elementDataValue ? settings.formatSubmit : settings.format,
+ isRTL = function() {
- each(tableElm.rows, function(row) {
- each(row.cells, function(cell) {
- if (dom.hasClass(cell, 'mce-item-selected') || cell == cellElm) {
- rows.push(row);
- return false;
- }
- });
- });
+ return element.currentStyle ?
- rowElm = rows[0];
- if (!rowElm) {
- // If this element is null, return now to avoid crashing.
- return;
- }
+ // For IE.
+ element.currentStyle.direction == 'rtl' :
- data = {
- height: removePxSuffix(dom.getStyle(rowElm, 'height') || dom.getAttrib(rowElm, 'height')),
- scope: dom.getAttrib(rowElm, 'scope'),
- 'class': dom.getAttrib(rowElm, 'class')
- };
+ // For normal browsers.
+ getComputedStyle( picker.$root[0] ).direction == 'rtl'
+ }
- data.type = rowElm.parentNode.nodeName.toLowerCase();
+ calendar.settings = settings
+ calendar.$node = picker.$node
+
+ // The queue of methods that will be used to build item objects.
+ calendar.queue = {
+ min: 'measure create',
+ max: 'measure create',
+ now: 'now create',
+ select: 'parse create validate',
+ highlight: 'parse navigate create validate',
+ view: 'parse create validate viewset',
+ disable: 'deactivate',
+ enable: 'activate'
+ }
- each('left center right'.split(' '), function(name) {
- if (editor.formatter.matchNode(rowElm, 'align' + name)) {
- data.align = name;
- }
- });
+ // The component's item object.
+ calendar.item = {}
+
+ calendar.item.clear = null
+ calendar.item.disable = ( settings.disable || [] ).slice( 0 )
+ calendar.item.enable = -(function( collectionDisabled ) {
+ return collectionDisabled[ 0 ] === true ? collectionDisabled.shift() : -1
+ })( calendar.item.disable )
+
+ calendar.
+ set( 'min', settings.min ).
+ set( 'max', settings.max ).
+ set( 'now' )
+
+ // When there’s a value, set the `select`, which in turn
+ // also sets the `highlight` and `view`.
+ if ( valueString ) {
+ calendar.set( 'select', valueString, {
+ format: formatString,
+ defaultValue: true
+ })
+ }
- if (editor.settings.table_row_class_list) {
- classListCtrl = {
- name: 'class',
- type: 'listbox',
- label: 'Class',
- values: buildListItems(
- editor.settings.table_row_class_list,
- function(item) {
- if (item.value) {
- item.textStyle = function() {
- return editor.formatter.getCssText({block: 'tr', classes: [item.value]});
- };
- }
- }
- )
- };
- }
+ // If there’s no value, default to highlighting “today”.
+ else {
+ calendar.
+ set( 'select', null ).
+ set( 'highlight', calendar.item.now )
+ }
- generalRowForm = {
- type: 'form',
- columns: 2,
- padding: 0,
- defaults: {
- type: 'textbox'
- },
- items: [
- {
- type: 'listbox',
- name: 'type',
- label: 'Row type',
- text: 'None',
- maxWidth: null,
- values: [
- {text: 'Header', value: 'thead'},
- {text: 'Body', value: 'tbody'},
- {text: 'Footer', value: 'tfoot'}
- ]
- },
- {
- type: 'listbox',
- name: 'align',
- label: 'Alignment',
- text: 'None',
- maxWidth: null,
- values: [
- {text: 'None', value: ''},
- {text: 'Left', value: 'left'},
- {text: 'Center', value: 'center'},
- {text: 'Right', value: 'right'}
- ]
- },
- {label: 'Height', name: 'height'},
- classListCtrl
- ]
- };
- if (editor.settings.table_row_advtab !== false) {
- appendStylesToData(dom, data, rowElm);
+ // The keycode to movement mapping.
+ calendar.key = {
+ 40: 7, // Down
+ 38: -7, // Up
+ 39: function() { return isRTL() ? -1 : 1 }, // Right
+ 37: function() { return isRTL() ? 1 : -1 }, // Left
+ go: function( timeChange ) {
+ var highlightedObject = calendar.item.highlight,
+ targetDate = new Date( highlightedObject.year, highlightedObject.month, highlightedObject.date + timeChange )
+ calendar.set(
+ 'highlight',
+ targetDate,
+ { interval: timeChange }
+ )
+ this.render()
+ }
+ }
- editor.windowManager.open({
- title: "Row properties",
- data: data,
- bodyType: 'tabpanel',
- body: [
- {
- title: 'General',
- type: 'form',
- items: generalRowForm
- },
- createStyleForm(dom)
- ],
- onsubmit: onSubmitRowForm
- });
- } else {
- editor.windowManager.open({
- title: "Row properties",
- data: data,
- body: generalRowForm,
- onsubmit: onSubmitRowForm
- });
- }
- };
- };
-});
+ // Bind some picker events.
+ picker.
+ on( 'render', function() {
+ picker.$root.find( '.' + settings.klass.selectMonth ).on( 'change', function() {
+ var value = this.value
+ if ( value ) {
+ picker.set( 'highlight', [ picker.get( 'view' ).year, value, picker.get( 'highlight' ).date ] )
+ picker.$root.find( '.' + settings.klass.selectMonth ).trigger( 'focus' )
+ }
+ })
+ picker.$root.find( '.' + settings.klass.selectYear ).on( 'change', function() {
+ var value = this.value
+ if ( value ) {
+ picker.set( 'highlight', [ value, picker.get( 'view' ).month, picker.get( 'highlight' ).date ] )
+ picker.$root.find( '.' + settings.klass.selectYear ).trigger( 'focus' )
+ }
+ })
+ }, 1 ).
+ on( 'open', function() {
+ var includeToday = ''
+ if ( calendar.disabled( calendar.get('now') ) ) {
+ includeToday = ':not(.' + settings.klass.buttonToday + ')'
+ }
+ picker.$root.find( 'button' + includeToday + ', select' ).attr( 'disabled', false )
+ }, 1 ).
+ on( 'close', function() {
+ picker.$root.find( 'button, select' ).attr( 'disabled', true )
+ }, 1 )
-// Included from: js/tinymce/plugins/table/classes/Plugin.js
+} //DatePicker
-/**
- * Plugin.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
/**
- * This class contains all core logic for the table plugin.
- *
- * @class tinymce.tableplugin.Plugin
- * @private
+ * Set a datepicker item object.
*/
-define("tinymce/tableplugin/Plugin", [
- "tinymce/tableplugin/TableGrid",
- "tinymce/tableplugin/Quirks",
- "tinymce/tableplugin/CellSelection",
- "tinymce/tableplugin/Dialogs",
- "tinymce/util/Tools",
- "tinymce/dom/TreeWalker",
- "tinymce/Env",
- "tinymce/PluginManager"
-], function(TableGrid, Quirks, CellSelection, Dialogs, Tools, TreeWalker, Env, PluginManager) {
- var each = Tools.each;
+DatePicker.prototype.set = function( type, value, options ) {
- function Plugin(editor) {
- var clipboardRows, self = this, dialogs = new Dialogs(editor);
+ var calendar = this,
+ calendarItem = calendar.item
- function cmd(command) {
- return function() {
- editor.execCommand(command);
- };
- }
+ // If the value is `null` just set it immediately.
+ if ( value === null ) {
+ if ( type == 'clear' ) type = 'select'
+ calendarItem[ type ] = value
+ return calendar
+ }
- function insertTable(cols, rows) {
- var y, x, html, tableElm;
+ // Otherwise go through the queue of methods, and invoke the functions.
+ // Update this as the time unit, and set the final value as this item.
+ // * In the case of `enable`, keep the queue but set `disable` instead.
+ // And in the case of `flip`, keep the queue but set `enable` instead.
+ calendarItem[ ( type == 'enable' ? 'disable' : type == 'flip' ? 'enable' : type ) ] = calendar.queue[ type ].split( ' ' ).map( function( method ) {
+ value = calendar[ method ]( type, value, options )
+ return value
+ }).pop()
+
+ // Check if we need to cascade through more updates.
+ if ( type == 'select' ) {
+ calendar.set( 'highlight', calendarItem.select, options )
+ }
+ else if ( type == 'highlight' ) {
+ calendar.set( 'view', calendarItem.highlight, options )
+ }
+ else if ( type.match( /^(flip|min|max|disable|enable)$/ ) ) {
+ if ( calendarItem.select && calendar.disabled( calendarItem.select ) ) {
+ calendar.set( 'select', calendarItem.select, options )
+ }
+ if ( calendarItem.highlight && calendar.disabled( calendarItem.highlight ) ) {
+ calendar.set( 'highlight', calendarItem.highlight, options )
+ }
+ }
- html = '';
+ return calendar
+} //DatePicker.prototype.set
- for (y = 0; y < rows; y++) {
- html += '';
- for (x = 0; x < cols; x++) {
- html += '' + (Env.ie ? " " : ' ') + ' | ';
- }
+/**
+ * Get a datepicker item object.
+ */
+DatePicker.prototype.get = function( type ) {
+ return this.item[ type ]
+} //DatePicker.prototype.get
- html += '
';
- }
- html += '
';
+/**
+ * Create a picker date object.
+ */
+DatePicker.prototype.create = function( type, value, options ) {
- editor.undoManager.transact(function() {
- editor.insertContent(html);
+ var isInfiniteValue,
+ calendar = this
- tableElm = editor.dom.get('__mce');
- editor.dom.setAttrib(tableElm, 'id', null);
+ // If there’s no value, use the type as the value.
+ value = value === undefined ? type : value
- editor.dom.setAttribs(tableElm, editor.settings.table_default_attributes || {});
- editor.dom.setStyles(tableElm, editor.settings.table_default_styles || {});
- });
- return tableElm;
- }
+ // If it’s infinity, update the value.
+ if ( value == -Infinity || value == Infinity ) {
+ isInfiniteValue = value
+ }
- function handleDisabledState(ctrl, selector) {
- function bindStateListener() {
- ctrl.disabled(!editor.dom.getParent(editor.selection.getStart(), selector));
+ // If it’s an object, use the native date object.
+ else if ( $.isPlainObject( value ) && _.isInteger( value.pick ) ) {
+ value = value.obj
+ }
- editor.selection.selectorChanged(selector, function(state) {
- ctrl.disabled(!state);
- });
- }
+ // If it’s an array, convert it into a date and make sure
+ // that it’s a valid date – otherwise default to today.
+ else if ( $.isArray( value ) ) {
+ value = new Date( value[ 0 ], value[ 1 ], value[ 2 ] )
+ value = _.isDate( value ) ? value : calendar.create().obj
+ }
- if (editor.initialized) {
- bindStateListener();
- } else {
- editor.on('init', bindStateListener);
- }
- }
+ // If it’s a number or date object, make a normalized date.
+ else if ( _.isInteger( value ) || _.isDate( value ) ) {
+ value = calendar.normalize( new Date( value ), options )
+ }
- function postRender() {
- /*jshint validthis:true*/
- handleDisabledState(this, 'table');
- }
+ // If it’s a literal true or any other case, set it to now.
+ else /*if ( value === true )*/ {
+ value = calendar.now( type, value, options )
+ }
+
+ // Return the compiled object.
+ return {
+ year: isInfiniteValue || value.getFullYear(),
+ month: isInfiniteValue || value.getMonth(),
+ date: isInfiniteValue || value.getDate(),
+ day: isInfiniteValue || value.getDay(),
+ obj: isInfiniteValue || value,
+ pick: isInfiniteValue || value.getTime()
+ }
+} //DatePicker.prototype.create
+
+
+/**
+ * Create a range limit object using an array, date object,
+ * literal “true”, or integer relative to another time.
+ */
+DatePicker.prototype.createRange = function( from, to ) {
- function postRenderCell() {
- /*jshint validthis:true*/
- handleDisabledState(this, 'td,th');
- }
+ var calendar = this,
+ createDate = function( date ) {
+ if ( date === true || $.isArray( date ) || _.isDate( date ) ) {
+ return calendar.create( date )
+ }
+ return date
+ }
- function generateTableGrid() {
- var html = '';
+ // Create objects if possible.
+ if ( !_.isInteger( from ) ) {
+ from = createDate( from )
+ }
+ if ( !_.isInteger( to ) ) {
+ to = createDate( to )
+ }
- html = '';
+ // Create relative dates.
+ if ( _.isInteger( from ) && $.isPlainObject( to ) ) {
+ from = [ to.year, to.month, to.date + from ];
+ }
+ else if ( _.isInteger( to ) && $.isPlainObject( from ) ) {
+ to = [ from.year, from.month, from.date + to ];
+ }
- for (var y = 0; y < 10; y++) {
- html += '';
+ return {
+ from: createDate( from ),
+ to: createDate( to )
+ }
+} //DatePicker.prototype.createRange
- for (var x = 0; x < 10; x++) {
- html += ' | ';
- }
- html += '
';
- }
+/**
+ * Check if a date unit falls within a date range object.
+ */
+DatePicker.prototype.withinRange = function( range, dateUnit ) {
+ range = this.createRange(range.from, range.to)
+ return dateUnit.pick >= range.from.pick && dateUnit.pick <= range.to.pick
+}
- html += '
';
- html += '1 x 1
';
+/**
+ * Check if two date range objects overlap.
+ */
+DatePicker.prototype.overlapRanges = function( one, two ) {
- return html;
- }
+ var calendar = this
- function selectGrid(tx, ty, control) {
- var table = control.getEl().getElementsByTagName('table')[0];
- var x, y, focusCell, cell, active;
- var rtl = control.isRtl() || control.parent().rel == 'tl-tr';
+ // Convert the ranges into comparable dates.
+ one = calendar.createRange( one.from, one.to )
+ two = calendar.createRange( two.from, two.to )
- table.nextSibling.innerHTML = (tx + 1) + ' x ' + (ty + 1);
+ return calendar.withinRange( one, two.from ) || calendar.withinRange( one, two.to ) ||
+ calendar.withinRange( two, one.from ) || calendar.withinRange( two, one.to )
+}
- if (rtl) {
- tx = 9 - tx;
- }
- for (y = 0; y < 10; y++) {
- for (x = 0; x < 10; x++) {
- cell = table.rows[y].childNodes[x].firstChild;
- active = (rtl ? x >= tx : x <= tx) && y <= ty;
+/**
+ * Get the date today.
+ */
+DatePicker.prototype.now = function( type, value, options ) {
+ value = new Date()
+ if ( options && options.rel ) {
+ value.setDate( value.getDate() + options.rel )
+ }
+ return this.normalize( value, options )
+}
- editor.dom.toggleClass(cell, 'mce-active', active);
- if (active) {
- focusCell = cell;
- }
- }
- }
+/**
+ * Navigate to next/prev month.
+ */
+DatePicker.prototype.navigate = function( type, value, options ) {
- return focusCell.parentNode;
- }
+ var targetDateObject,
+ targetYear,
+ targetMonth,
+ targetDate,
+ isTargetArray = $.isArray( value ),
+ isTargetObject = $.isPlainObject( value ),
+ viewsetObject = this.item.view/*,
+ safety = 100*/
- if (editor.settings.table_grid === false) {
- editor.addMenuItem('inserttable', {
- text: 'Insert table',
- icon: 'table',
- context: 'table',
- onclick: dialogs.table
- });
- } else {
- editor.addMenuItem('inserttable', {
- text: 'Insert table',
- icon: 'table',
- context: 'table',
- ariaHideMenu: true,
- onclick: function(e) {
- if (e.aria) {
- this.parent().hideAll();
- e.stopImmediatePropagation();
- dialogs.table();
- }
- },
- onshow: function() {
- selectGrid(0, 0, this.menu.items()[0]);
- },
- onhide: function() {
- var elements = this.menu.items()[0].getEl().getElementsByTagName('a');
- editor.dom.removeClass(elements, 'mce-active');
- editor.dom.addClass(elements[0], 'mce-active');
- },
- menu: [
- {
- type: 'container',
- html: generateTableGrid(),
- onPostRender: function() {
- this.lastX = this.lastY = 0;
- },
+ if ( isTargetArray || isTargetObject ) {
- onmousemove: function(e) {
- var target = e.target, x, y;
+ if ( isTargetObject ) {
+ targetYear = value.year
+ targetMonth = value.month
+ targetDate = value.date
+ }
+ else {
+ targetYear = +value[0]
+ targetMonth = +value[1]
+ targetDate = +value[2]
+ }
- if (target.tagName.toUpperCase() == 'A') {
- x = parseInt(target.getAttribute('data-mce-x'), 10);
- y = parseInt(target.getAttribute('data-mce-y'), 10);
+ // If we’re navigating months but the view is in a different
+ // month, navigate to the view’s year and month.
+ if ( options && options.nav && viewsetObject && viewsetObject.month !== targetMonth ) {
+ targetYear = viewsetObject.year
+ targetMonth = viewsetObject.month
+ }
- if (this.isRtl() || this.parent().rel == 'tl-tr') {
- x = 9 - x;
- }
+ // Figure out the expected target year and month.
+ targetDateObject = new Date( targetYear, targetMonth + ( options && options.nav ? options.nav : 0 ), 1 )
+ targetYear = targetDateObject.getFullYear()
+ targetMonth = targetDateObject.getMonth()
+
+ // If the month we’re going to doesn’t have enough days,
+ // keep decreasing the date until we reach the month’s last date.
+ while ( /*safety &&*/ new Date( targetYear, targetMonth, targetDate ).getMonth() !== targetMonth ) {
+ targetDate -= 1
+ /*safety -= 1
+ if ( !safety ) {
+ throw 'Fell into an infinite loop while navigating to ' + new Date( targetYear, targetMonth, targetDate ) + '.'
+ }*/
+ }
- if (x !== this.lastX || y !== this.lastY) {
- selectGrid(x, y, e.control);
+ value = [ targetYear, targetMonth, targetDate ]
+ }
- this.lastX = x;
- this.lastY = y;
- }
- }
- },
+ return value
+} //DatePicker.prototype.navigate
- onclick: function(e) {
- var self = this;
- if (e.target.tagName.toUpperCase() == 'A') {
- e.preventDefault();
- e.stopPropagation();
- self.parent().cancel();
+/**
+ * Normalize a date by setting the hours to midnight.
+ */
+DatePicker.prototype.normalize = function( value/*, options*/ ) {
+ value.setHours( 0, 0, 0, 0 )
+ return value
+}
- editor.undoManager.transact(function() {
- insertTable(self.lastX + 1, self.lastY + 1);
- });
- editor.addVisual();
- }
- }
- }
- ]
- });
- }
+/**
+ * Measure the range of dates.
+ */
+DatePicker.prototype.measure = function( type, value/*, options*/ ) {
- editor.addMenuItem('tableprops', {
- text: 'Table properties',
- context: 'table',
- onPostRender: postRender,
- onclick: dialogs.tableProps
- });
+ var calendar = this
- editor.addMenuItem('deletetable', {
- text: 'Delete table',
- context: 'table',
- onPostRender: postRender,
- cmd: 'mceTableDelete'
- });
+ // If it’s anything false-y, remove the limits.
+ if ( !value ) {
+ value = type == 'min' ? -Infinity : Infinity
+ }
- editor.addMenuItem('cell', {
- separator: 'before',
- text: 'Cell',
- context: 'table',
- menu: [
- {text: 'Cell properties', onclick: cmd('mceTableCellProps'), onPostRender: postRenderCell},
- {text: 'Merge cells', onclick: cmd('mceTableMergeCells'), onPostRender: postRenderCell},
- {text: 'Split cell', onclick: cmd('mceTableSplitCells'), onPostRender: postRenderCell}
- ]
- });
+ // If it’s a string, parse it.
+ else if ( typeof value == 'string' ) {
+ value = calendar.parse( type, value )
+ }
- editor.addMenuItem('row', {
- text: 'Row',
- context: 'table',
- menu: [
- {text: 'Insert row before', onclick: cmd('mceTableInsertRowBefore'), onPostRender: postRenderCell},
- {text: 'Insert row after', onclick: cmd('mceTableInsertRowAfter'), onPostRender: postRenderCell},
- {text: 'Delete row', onclick: cmd('mceTableDeleteRow'), onPostRender: postRenderCell},
- {text: 'Row properties', onclick: cmd('mceTableRowProps'), onPostRender: postRenderCell},
- {text: '-'},
- {text: 'Cut row', onclick: cmd('mceTableCutRow'), onPostRender: postRenderCell},
- {text: 'Copy row', onclick: cmd('mceTableCopyRow'), onPostRender: postRenderCell},
- {text: 'Paste row before', onclick: cmd('mceTablePasteRowBefore'), onPostRender: postRenderCell},
- {text: 'Paste row after', onclick: cmd('mceTablePasteRowAfter'), onPostRender: postRenderCell}
- ]
- });
+ // If it's an integer, get a date relative to today.
+ else if ( _.isInteger( value ) ) {
+ value = calendar.now( type, value, { rel: value } )
+ }
- editor.addMenuItem('column', {
- text: 'Column',
- context: 'table',
- menu: [
- {text: 'Insert column before', onclick: cmd('mceTableInsertColBefore'), onPostRender: postRenderCell},
- {text: 'Insert column after', onclick: cmd('mceTableInsertColAfter'), onPostRender: postRenderCell},
- {text: 'Delete column', onclick: cmd('mceTableDeleteCol'), onPostRender: postRenderCell}
- ]
- });
+ return value
+} ///DatePicker.prototype.measure
- var menuItems = [];
- each("inserttable tableprops deletetable | cell row column".split(' '), function(name) {
- if (name == '|') {
- menuItems.push({text: '-'});
- } else {
- menuItems.push(editor.menuItems[name]);
- }
- });
- editor.addButton("table", {
- type: "menubutton",
- title: "Table",
- menu: menuItems
- });
+/**
+ * Create a viewset object based on navigation.
+ */
+DatePicker.prototype.viewset = function( type, dateObject/*, options*/ ) {
+ return this.create([ dateObject.year, dateObject.month, 1 ])
+}
- // Select whole table is a table border is clicked
- if (!Env.isIE) {
- editor.on('click', function(e) {
- e = e.target;
- if (e.nodeName === 'TABLE') {
- editor.selection.select(e);
- editor.nodeChanged();
- }
- });
- }
+/**
+ * Validate a date as enabled and shift if needed.
+ */
+DatePicker.prototype.validate = function( type, dateObject, options ) {
- self.quirks = new Quirks(editor);
+ var calendar = this,
- editor.on('Init', function() {
- self.cellSelection = new CellSelection(editor);
- });
+ // Keep a reference to the original date.
+ originalDateObject = dateObject,
- editor.on('PreInit', function() {
- // Remove internal data attributes
- editor.serializer.addAttributeFilter(
- 'data-mce-cell-padding,data-mce-border,data-mce-border-color',
- function(nodes, name) {
+ // Make sure we have an interval.
+ interval = options && options.interval ? options.interval : 1,
- var i = nodes.length;
+ // Check if the calendar enabled dates are inverted.
+ isFlippedBase = calendar.item.enable === -1,
- while (i--) {
- nodes[i].attr(name, null);
- }
- });
- });
+ // Check if we have any enabled dates after/before now.
+ hasEnabledBeforeTarget, hasEnabledAfterTarget,
- // Register action commands
- each({
- mceTableSplitCells: function(grid) {
- grid.split();
- },
+ // The min & max limits.
+ minLimitObject = calendar.item.min,
+ maxLimitObject = calendar.item.max,
- mceTableMergeCells: function(grid) {
- var cell;
+ // Check if we’ve reached the limit during shifting.
+ reachedMin, reachedMax,
- cell = editor.dom.getParent(editor.selection.getStart(), 'th,td');
+ // Check if the calendar is inverted and at least one weekday is enabled.
+ hasEnabledWeekdays = isFlippedBase && calendar.item.disable.filter( function( value ) {
- if (!editor.dom.select('td.mce-item-selected,th.mce-item-selected').length) {
- dialogs.merge(grid, cell);
- } else {
- grid.merge();
- }
- },
+ // If there’s a date, check where it is relative to the target.
+ if ( $.isArray( value ) ) {
+ var dateTime = calendar.create( value ).pick
+ if ( dateTime < dateObject.pick ) hasEnabledBeforeTarget = true
+ else if ( dateTime > dateObject.pick ) hasEnabledAfterTarget = true
+ }
- mceTableInsertRowBefore: function(grid) {
- grid.insertRow(true);
- },
+ // Return only integers for enabled weekdays.
+ return _.isInteger( value )
+ }).length/*,
- mceTableInsertRowAfter: function(grid) {
- grid.insertRow();
- },
+ safety = 100*/
- mceTableInsertColBefore: function(grid) {
- grid.insertCol(true);
- },
- mceTableInsertColAfter: function(grid) {
- grid.insertCol();
- },
- mceTableDeleteCol: function(grid) {
- grid.deleteCols();
- },
+ // Cases to validate for:
+ // [1] Not inverted and date disabled.
+ // [2] Inverted and some dates enabled.
+ // [3] Not inverted and out of range.
+ //
+ // Cases to **not** validate for:
+ // • Navigating months.
+ // • Not inverted and date enabled.
+ // • Inverted and all dates disabled.
+ // • ..and anything else.
+ if ( !options || (!options.nav && !options.defaultValue) ) if (
+ /* 1 */ ( !isFlippedBase && calendar.disabled( dateObject ) ) ||
+ /* 2 */ ( isFlippedBase && calendar.disabled( dateObject ) && ( hasEnabledWeekdays || hasEnabledBeforeTarget || hasEnabledAfterTarget ) ) ||
+ /* 3 */ ( !isFlippedBase && (dateObject.pick <= minLimitObject.pick || dateObject.pick >= maxLimitObject.pick) )
+ ) {
- mceTableDeleteRow: function(grid) {
- grid.deleteRows();
- },
- mceTableCutRow: function(grid) {
- clipboardRows = grid.cutRows();
- },
+ // When inverted, flip the direction if there aren’t any enabled weekdays
+ // and there are no enabled dates in the direction of the interval.
+ if ( isFlippedBase && !hasEnabledWeekdays && ( ( !hasEnabledAfterTarget && interval > 0 ) || ( !hasEnabledBeforeTarget && interval < 0 ) ) ) {
+ interval *= -1
+ }
- mceTableCopyRow: function(grid) {
- clipboardRows = grid.copyRows();
- },
- mceTablePasteRowBefore: function(grid) {
- grid.pasteRows(clipboardRows, true);
- },
+ // Keep looping until we reach an enabled date.
+ while ( /*safety &&*/ calendar.disabled( dateObject ) ) {
- mceTablePasteRowAfter: function(grid) {
- grid.pasteRows(clipboardRows);
- },
+ /*safety -= 1
+ if ( !safety ) {
+ throw 'Fell into an infinite loop while validating ' + dateObject.obj + '.'
+ }*/
- mceTableDelete: function(grid) {
- grid.deleteTable();
- }
- }, function(func, name) {
- editor.addCommand(name, function() {
- var grid = new TableGrid(editor);
- if (grid) {
- func(grid);
- editor.execCommand('mceRepaint');
- self.cellSelection.clear();
- }
- });
- });
+ // If we’ve looped into the next/prev month with a large interval, return to the original date and flatten the interval.
+ if ( Math.abs( interval ) > 1 && ( dateObject.month < originalDateObject.month || dateObject.month > originalDateObject.month ) ) {
+ dateObject = originalDateObject
+ interval = interval > 0 ? 1 : -1
+ }
- // Register dialog commands
- each({
- mceInsertTable: dialogs.table,
- mceTableProps: function() {
- dialogs.table(true);
- },
- mceTableRowProps: dialogs.row,
- mceTableCellProps: dialogs.cell
- }, function(func, name) {
- editor.addCommand(name, function(ui, val) {
- func(val);
- });
- });
- // Enable tab key cell navigation
- if (editor.settings.table_tab_navigation !== false) {
- editor.on('keydown', function(e) {
- var cellElm, grid, delta;
+ // If we’ve reached the min/max limit, reverse the direction, flatten the interval and set it to the limit.
+ if ( dateObject.pick <= minLimitObject.pick ) {
+ reachedMin = true
+ interval = 1
+ dateObject = calendar.create([
+ minLimitObject.year,
+ minLimitObject.month,
+ minLimitObject.date + (dateObject.pick === minLimitObject.pick ? 0 : -1)
+ ])
+ }
+ else if ( dateObject.pick >= maxLimitObject.pick ) {
+ reachedMax = true
+ interval = -1
+ dateObject = calendar.create([
+ maxLimitObject.year,
+ maxLimitObject.month,
+ maxLimitObject.date + (dateObject.pick === maxLimitObject.pick ? 0 : 1)
+ ])
+ }
- if (e.keyCode == 9) {
- cellElm = editor.dom.getParent(editor.selection.getStart(), 'th,td');
- if (cellElm) {
- e.preventDefault();
+ // If we’ve reached both limits, just break out of the loop.
+ if ( reachedMin && reachedMax ) {
+ break
+ }
- grid = new TableGrid(editor);
- delta = e.shiftKey ? -1 : 1;
- editor.undoManager.transact(function() {
- if (!grid.moveRelIdx(cellElm, delta) && delta > 0) {
- grid.insertRow();
- grid.refresh();
- grid.moveRelIdx(cellElm, delta);
- }
- });
- }
- }
- });
- }
+ // Finally, create the shifted date using the interval and keep looping.
+ dateObject = calendar.create([ dateObject.year, dateObject.month, dateObject.date + interval ])
+ }
- self.insertTable = insertTable;
- }
+ } //endif
- PluginManager.add('table', Plugin);
-});
-})(this);
- }).apply(root, arguments);
-});
-}(this));
+ // Return the date object settled on.
+ return dateObject
+} //DatePicker.prototype.validate
+
-(function(root) {
-define("tinymce-template", ["tinymce"], function() {
- return (function() {
/**
- * plugin.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
+ * Check if a date is disabled.
*/
+DatePicker.prototype.disabled = function( dateToVerify ) {
-/*global tinymce:true */
+ var
+ calendar = this,
-tinymce.PluginManager.add('template', function(editor) {
- var each = tinymce.each;
+ // Filter through the disabled dates to check if this is one.
+ isDisabledMatch = calendar.item.disable.filter( function( dateToDisable ) {
- function createTemplateList(callback) {
- return function() {
- var templateList = editor.settings.templates;
+ // If the date is a number, match the weekday with 0index and `firstDay` check.
+ if ( _.isInteger( dateToDisable ) ) {
+ return dateToVerify.day === ( calendar.settings.firstDay ? dateToDisable : dateToDisable - 1 ) % 7
+ }
- if (typeof templateList == "string") {
- tinymce.util.XHR.send({
- url: templateList,
- success: function(text) {
- callback(tinymce.util.JSON.parse(text));
- }
- });
- } else {
- callback(templateList);
- }
- };
- }
+ // If it’s an array or a native JS date, create and match the exact date.
+ if ( $.isArray( dateToDisable ) || _.isDate( dateToDisable ) ) {
+ return dateToVerify.pick === calendar.create( dateToDisable ).pick
+ }
- function showDialog(templateList) {
- var win, values = [], templateHtml;
+ // If it’s an object, match a date within the “from” and “to” range.
+ if ( $.isPlainObject( dateToDisable ) ) {
+ return calendar.withinRange( dateToDisable, dateToVerify )
+ }
+ })
- if (!templateList || templateList.length === 0) {
- editor.windowManager.alert('No templates defined');
- return;
- }
+ // If this date matches a disabled date, confirm it’s not inverted.
+ isDisabledMatch = isDisabledMatch.length && !isDisabledMatch.filter(function( dateToDisable ) {
+ return $.isArray( dateToDisable ) && dateToDisable[3] == 'inverted' ||
+ $.isPlainObject( dateToDisable ) && dateToDisable.inverted
+ }).length
- tinymce.each(templateList, function(template) {
- values.push({
- selected: !values.length,
- text: template.title,
- value: {
- url: template.url,
- content: template.content,
- description: template.description
- }
- });
- });
+ // Check the calendar “enabled” flag and respectively flip the
+ // disabled state. Then also check if it’s beyond the min/max limits.
+ return calendar.item.enable === -1 ? !isDisabledMatch : isDisabledMatch ||
+ dateToVerify.pick < calendar.item.min.pick ||
+ dateToVerify.pick > calendar.item.max.pick
- function onSelectTemplate(e) {
- var value = e.control.value();
+} //DatePicker.prototype.disabled
- function insertIframeHtml(html) {
- if (html.indexOf('') == -1) {
- var contentCssLinks = '';
- tinymce.each(editor.contentCSS, function(url) {
- contentCssLinks += '';
- });
+/**
+ * Parse a string into a usable type.
+ */
+DatePicker.prototype.parse = function( type, value, options ) {
- html = (
- '' +
- '' +
- '' +
- contentCssLinks +
- '' +
- '' +
- html +
- '' +
- ''
- );
- }
+ var calendar = this,
+ parsingObject = {}
- html = replaceTemplateValues(html, 'template_preview_replace_values');
+ // If it’s already parsed, we’re good.
+ if ( !value || typeof value != 'string' ) {
+ return value
+ }
- var doc = win.find('iframe')[0].getEl().contentWindow.document;
- doc.open();
- doc.write(html);
- doc.close();
- }
+ // We need a `.format` to parse the value with.
+ if ( !( options && options.format ) ) {
+ options = options || {}
+ options.format = calendar.settings.format
+ }
- if (value.url) {
- tinymce.util.XHR.send({
- url: value.url,
- success: function(html) {
- templateHtml = html;
- insertIframeHtml(templateHtml);
- }
- });
- } else {
- templateHtml = value.content;
- insertIframeHtml(templateHtml);
- }
+ // Convert the format into an array and then map through it.
+ calendar.formats.toArray( options.format ).map( function( label ) {
- win.find('#description')[0].text(e.control.value().description);
- }
+ var
+ // Grab the formatting label.
+ formattingLabel = calendar.formats[ label ],
- win = editor.windowManager.open({
- title: 'Insert template',
- layout: 'flex',
- direction: 'column',
- align: 'stretch',
- padding: 15,
- spacing: 10,
+ // The format length is from the formatting label function or the
+ // label length without the escaping exclamation (!) mark.
+ formatLength = formattingLabel ? _.trigger( formattingLabel, calendar, [ value, parsingObject ] ) : label.replace( /^!/, '' ).length
- items: [
- {type: 'form', flex: 0, padding: 0, items: [
- {type: 'container', label: 'Templates', items: {
- type: 'listbox', label: 'Templates', name: 'template', values: values, onselect: onSelectTemplate
- }}
- ]},
- {type: 'label', name: 'description', label: 'Description', text: '\u00a0'},
- {type: 'iframe', flex: 1, border: 1}
- ],
+ // If there's a format label, split the value up to the format length.
+ // Then add it to the parsing object with appropriate label.
+ if ( formattingLabel ) {
+ parsingObject[ label ] = value.substr( 0, formatLength )
+ }
- onsubmit: function() {
- insertTemplate(false, templateHtml);
- },
+ // Update the value as the substring from format length to end.
+ value = value.substr( formatLength )
+ })
- width: editor.getParam('template_popup_width', 600),
- height: editor.getParam('template_popup_height', 500)
- });
+ // Compensate for month 0index.
+ return [
+ parsingObject.yyyy || parsingObject.yy,
+ +( parsingObject.mm || parsingObject.m ) - 1,
+ parsingObject.dd || parsingObject.d
+ ]
+} //DatePicker.prototype.parse
- win.find('listbox')[0].fire('select');
- }
- function getDateTime(fmt, date) {
- var daysShort = "Sun Mon Tue Wed Thu Fri Sat Sun".split(' ');
- var daysLong = "Sunday Monday Tuesday Wednesday Thursday Friday Saturday Sunday".split(' ');
- var monthsShort = "Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec".split(' ');
- var monthsLong = "January February March April May June July August September October November December".split(' ');
+/**
+ * Various formats to display the object in.
+ */
+DatePicker.prototype.formats = (function() {
- function addZeros(value, len) {
- value = "" + value;
+ // Return the length of the first word in a collection.
+ function getWordLengthFromCollection( string, collection, dateObject ) {
- if (value.length < len) {
- for (var i = 0; i < (len - value.length); i++) {
- value = "0" + value;
- }
- }
+ // Grab the first word from the string.
+ // Regex pattern from http://stackoverflow.com/q/150033
+ var word = string.match( /[^\x00-\x7F]+|\w+/ )[ 0 ]
- return value;
- }
+ // If there's no month index, add it to the date object
+ if ( !dateObject.mm && !dateObject.m ) {
+ dateObject.m = collection.indexOf( word ) + 1
+ }
- date = date || new Date();
+ // Return the length of the word.
+ return word.length
+ }
- fmt = fmt.replace("%D", "%m/%d/%Y");
- fmt = fmt.replace("%r", "%I:%M:%S %p");
- fmt = fmt.replace("%Y", "" + date.getFullYear());
- fmt = fmt.replace("%y", "" + date.getYear());
- fmt = fmt.replace("%m", addZeros(date.getMonth() + 1, 2));
- fmt = fmt.replace("%d", addZeros(date.getDate(), 2));
- fmt = fmt.replace("%H", "" + addZeros(date.getHours(), 2));
- fmt = fmt.replace("%M", "" + addZeros(date.getMinutes(), 2));
- fmt = fmt.replace("%S", "" + addZeros(date.getSeconds(), 2));
- fmt = fmt.replace("%I", "" + ((date.getHours() + 11) % 12 + 1));
- fmt = fmt.replace("%p", "" + (date.getHours() < 12 ? "AM" : "PM"));
- fmt = fmt.replace("%B", "" + editor.translate(monthsLong[date.getMonth()]));
- fmt = fmt.replace("%b", "" + editor.translate(monthsShort[date.getMonth()]));
- fmt = fmt.replace("%A", "" + editor.translate(daysLong[date.getDay()]));
- fmt = fmt.replace("%a", "" + editor.translate(daysShort[date.getDay()]));
- fmt = fmt.replace("%%", "%");
+ // Get the length of the first word in a string.
+ function getFirstWordLength( string ) {
+ return string.match( /\w+/ )[ 0 ].length
+ }
- return fmt;
- }
+ return {
- function replaceVals(e) {
- var dom = editor.dom, vl = editor.getParam('template_replace_values');
+ d: function( string, dateObject ) {
- each(dom.select('*', e), function(e) {
- each(vl, function(v, k) {
- if (dom.hasClass(e, k)) {
- if (typeof vl[k] == 'function') {
- vl[k](e);
- }
- }
- });
- });
- }
+ // If there's string, then get the digits length.
+ // Otherwise return the selected date.
+ return string ? _.digits( string ) : dateObject.date
+ },
+ dd: function( string, dateObject ) {
+
+ // If there's a string, then the length is always 2.
+ // Otherwise return the selected date with a leading zero.
+ return string ? 2 : _.lead( dateObject.date )
+ },
+ ddd: function( string, dateObject ) {
+
+ // If there's a string, then get the length of the first word.
+ // Otherwise return the short selected weekday.
+ return string ? getFirstWordLength( string ) : this.settings.weekdaysShort[ dateObject.day ]
+ },
+ dddd: function( string, dateObject ) {
+
+ // If there's a string, then get the length of the first word.
+ // Otherwise return the full selected weekday.
+ return string ? getFirstWordLength( string ) : this.settings.weekdaysFull[ dateObject.day ]
+ },
+ m: function( string, dateObject ) {
- function replaceTemplateValues(html, templateValuesOptionName) {
- each(editor.getParam(templateValuesOptionName), function(v, k) {
- if (typeof v != 'function') {
- html = html.replace(new RegExp('\\{\\$' + k + '\\}', 'g'), v);
- }
- });
+ // If there's a string, then get the length of the digits
+ // Otherwise return the selected month with 0index compensation.
+ return string ? _.digits( string ) : dateObject.month + 1
+ },
+ mm: function( string, dateObject ) {
- return html;
- }
+ // If there's a string, then the length is always 2.
+ // Otherwise return the selected month with 0index and leading zero.
+ return string ? 2 : _.lead( dateObject.month + 1 )
+ },
+ mmm: function( string, dateObject ) {
- function insertTemplate(ui, html) {
- var el, n, dom = editor.dom, sel = editor.selection.getContent();
+ var collection = this.settings.monthsShort
- html = replaceTemplateValues(html, 'template_replace_values');
- el = dom.create('div', null, html);
+ // If there's a string, get length of the relevant month from the short
+ // months collection. Otherwise return the selected month from that collection.
+ return string ? getWordLengthFromCollection( string, collection, dateObject ) : collection[ dateObject.month ]
+ },
+ mmmm: function( string, dateObject ) {
- // Find template element within div
- n = dom.select('.mceTmpl', el);
- if (n && n.length > 0) {
- el = dom.create('div', null);
- el.appendChild(n[0].cloneNode(true));
- }
+ var collection = this.settings.monthsFull
- function hasClass(n, c) {
- return new RegExp('\\b' + c + '\\b', 'g').test(n.className);
- }
+ // If there's a string, get length of the relevant month from the full
+ // months collection. Otherwise return the selected month from that collection.
+ return string ? getWordLengthFromCollection( string, collection, dateObject ) : collection[ dateObject.month ]
+ },
+ yy: function( string, dateObject ) {
- each(dom.select('*', el), function(n) {
- // Replace cdate
- if (hasClass(n, editor.getParam('template_cdate_classes', 'cdate').replace(/\s+/g, '|'))) {
- n.innerHTML = getDateTime(editor.getParam("template_cdate_format", editor.getLang("template.cdate_format")));
- }
+ // If there's a string, then the length is always 2.
+ // Otherwise return the selected year by slicing out the first 2 digits.
+ return string ? 2 : ( '' + dateObject.year ).slice( 2 )
+ },
+ yyyy: function( string, dateObject ) {
- // Replace mdate
- if (hasClass(n, editor.getParam('template_mdate_classes', 'mdate').replace(/\s+/g, '|'))) {
- n.innerHTML = getDateTime(editor.getParam("template_mdate_format", editor.getLang("template.mdate_format")));
- }
+ // If there's a string, then the length is always 4.
+ // Otherwise return the selected year.
+ return string ? 4 : dateObject.year
+ },
- // Replace selection
- if (hasClass(n, editor.getParam('template_selected_content_classes', 'selcontent').replace(/\s+/g, '|'))) {
- n.innerHTML = sel;
- }
- });
+ // Create an array by splitting the formatting string passed.
+ toArray: function( formatString ) { return formatString.split( /(d{1,4}|m{1,4}|y{4}|yy|!.)/g ) },
- replaceVals(el);
+ // Format an object into a string using the formatting options.
+ toString: function ( formatString, itemObject ) {
+ var calendar = this
+ return calendar.formats.toArray( formatString ).map( function( label ) {
+ return _.trigger( calendar.formats[ label ], calendar, [ 0, itemObject ] ) || label.replace( /^!/, '' )
+ }).join( '' )
+ }
+ }
+})() //DatePicker.prototype.formats
- editor.execCommand('mceInsertContent', false, el.innerHTML);
- editor.addVisual();
- }
- editor.addCommand('mceInsertTemplate', insertTemplate);
- editor.addButton('template', {
- title: 'Insert template',
- onclick: createTemplateList(showDialog)
- });
- editor.addMenuItem('template', {
- text: 'Insert template',
- onclick: createTemplateList(showDialog),
- context: 'insert'
- });
+/**
+ * Check if two date units are the exact.
+ */
+DatePicker.prototype.isDateExact = function( one, two ) {
- editor.on('PreProcess', function(o) {
- var dom = editor.dom;
+ var calendar = this
- each(dom.select('div', o.node), function(e) {
- if (dom.hasClass(e, 'mceTmpl')) {
- each(dom.select('*', e), function(e) {
- if (dom.hasClass(e, editor.getParam('template_mdate_classes', 'mdate').replace(/\s+/g, '|'))) {
- e.innerHTML = getDateTime(editor.getParam("template_mdate_format", editor.getLang("template.mdate_format")));
- }
- });
+ // When we’re working with weekdays, do a direct comparison.
+ if (
+ ( _.isInteger( one ) && _.isInteger( two ) ) ||
+ ( typeof one == 'boolean' && typeof two == 'boolean' )
+ ) {
+ return one === two
+ }
- replaceVals(e);
- }
- });
- });
-});
+ // When we’re working with date representations, compare the “pick” value.
+ if (
+ ( _.isDate( one ) || $.isArray( one ) ) &&
+ ( _.isDate( two ) || $.isArray( two ) )
+ ) {
+ return calendar.create( one ).pick === calendar.create( two ).pick
+ }
+
+ // When we’re working with range objects, compare the “from” and “to”.
+ if ( $.isPlainObject( one ) && $.isPlainObject( two ) ) {
+ return calendar.isDateExact( one.from, two.from ) && calendar.isDateExact( one.to, two.to )
+ }
+
+ return false
+}
- }).apply(root, arguments);
-});
-}(this));
-(function(root) {
-define("tinymce-textcolor", ["tinymce"], function() {
- return (function() {
/**
- * plugin.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
+ * Check if two date units overlap.
*/
+DatePicker.prototype.isDateOverlap = function( one, two ) {
-/*global tinymce:true */
-/*eslint consistent-this:0 */
+ var calendar = this,
+ firstDay = calendar.settings.firstDay ? 1 : 0
-tinymce.PluginManager.add('textcolor', function(editor) {
- var cols, rows;
+ // When we’re working with a weekday index, compare the days.
+ if ( _.isInteger( one ) && ( _.isDate( two ) || $.isArray( two ) ) ) {
+ one = one % 7 + firstDay
+ return one === calendar.create( two ).day + 1
+ }
+ if ( _.isInteger( two ) && ( _.isDate( one ) || $.isArray( one ) ) ) {
+ two = two % 7 + firstDay
+ return two === calendar.create( one ).day + 1
+ }
- rows = editor.settings.textcolor_rows || 5;
- cols = editor.settings.textcolor_cols || 8;
+ // When we’re working with range objects, check if the ranges overlap.
+ if ( $.isPlainObject( one ) && $.isPlainObject( two ) ) {
+ return calendar.overlapRanges( one, two )
+ }
- function getCurrentColor(format) {
- var color;
+ return false
+}
- editor.dom.getParents(editor.selection.getStart(), function(elm) {
- var value;
- if ((value = elm.style[format == 'forecolor' ? 'color' : 'background-color'])) {
- color = value;
- }
- });
+/**
+ * Flip the “enabled” state.
+ */
+DatePicker.prototype.flipEnable = function(val) {
+ var itemObject = this.item
+ itemObject.enable = val || (itemObject.enable == -1 ? 1 : -1)
+}
- return color;
- }
- function mapColors() {
- var i, colors = [], colorMap;
+/**
+ * Mark a collection of dates as “disabled”.
+ */
+DatePicker.prototype.deactivate = function( type, datesToDisable ) {
- colorMap = editor.settings.textcolor_map || [
- "000000", "Black",
- "993300", "Burnt orange",
- "333300", "Dark olive",
- "003300", "Dark green",
- "003366", "Dark azure",
- "000080", "Navy Blue",
- "333399", "Indigo",
- "333333", "Very dark gray",
- "800000", "Maroon",
- "FF6600", "Orange",
- "808000", "Olive",
- "008000", "Green",
- "008080", "Teal",
- "0000FF", "Blue",
- "666699", "Grayish blue",
- "808080", "Gray",
- "FF0000", "Red",
- "FF9900", "Amber",
- "99CC00", "Yellow green",
- "339966", "Sea green",
- "33CCCC", "Turquoise",
- "3366FF", "Royal blue",
- "800080", "Purple",
- "999999", "Medium gray",
- "FF00FF", "Magenta",
- "FFCC00", "Gold",
- "FFFF00", "Yellow",
- "00FF00", "Lime",
- "00FFFF", "Aqua",
- "00CCFF", "Sky blue",
- "993366", "Red violet",
- "FFFFFF", "White",
- "FF99CC", "Pink",
- "FFCC99", "Peach",
- "FFFF99", "Light yellow",
- "CCFFCC", "Pale green",
- "CCFFFF", "Pale cyan",
- "99CCFF", "Light sky blue",
- "CC99FF", "Plum"
- ];
+ var calendar = this,
+ disabledItems = calendar.item.disable.slice(0)
- for (i = 0; i < colorMap.length; i += 2) {
- colors.push({
- text: colorMap[i + 1],
- color: '#' + colorMap[i]
- });
- }
- return colors;
- }
+ // If we’re flipping, that’s all we need to do.
+ if ( datesToDisable == 'flip' ) {
+ calendar.flipEnable()
+ }
- function renderColorPicker() {
- var ctrl = this, colors, color, html, last, x, y, i, id = ctrl._id, count = 0;
+ else if ( datesToDisable === false ) {
+ calendar.flipEnable(1)
+ disabledItems = []
+ }
- function getColorCellHtml(color, title) {
- var isNoColor = color == 'transparent';
+ else if ( datesToDisable === true ) {
+ calendar.flipEnable(-1)
+ disabledItems = []
+ }
- return (
- '' +
- ' ' +
- (isNoColor ? '×' : '') +
- ' ' +
- ' | '
- );
- }
+ // Otherwise go through the dates to disable.
+ else {
- colors = mapColors();
- colors.push({
- text: tinymce.translate("No color"),
- color: "transparent"
- });
+ datesToDisable.map(function( unitToDisable ) {
- html = '';
+ else if ( datesToEnable === false ) {
+ calendar.flipEnable(-1)
+ disabledItems = []
+ }
- return html;
- }
+ // Otherwise go through the disabled dates.
+ else {
- function applyFormat(format, value) {
- editor.undoManager.transact(function() {
- editor.focus();
- editor.formatter.apply(format, {value: value});
- editor.nodeChanged();
- });
- }
+ datesToEnable.map(function( unitToEnable ) {
- function removeFormat(format) {
- editor.undoManager.transact(function() {
- editor.focus();
- editor.formatter.remove(format, {value: null}, null, true);
- editor.nodeChanged();
- });
- }
+ var matchFound,
+ disabledUnit,
+ index,
+ isExactRange
- function onPanelClick(e) {
- var buttonCtrl = this.parent(), value;
+ // Go through the disabled items and try to find a match.
+ for ( index = 0; index < disabledItemsCount; index += 1 ) {
- function selectColor(value) {
- buttonCtrl.hidePanel();
- buttonCtrl.color(value);
- applyFormat(buttonCtrl.settings.format, value);
- }
+ disabledUnit = disabledItems[index]
- function resetColor() {
- buttonCtrl.hidePanel();
- buttonCtrl.resetColor();
- removeFormat(buttonCtrl.settings.format);
- }
+ // When an exact match is found, remove it from the collection.
+ if ( calendar.isDateExact( disabledUnit, unitToEnable ) ) {
+ matchFound = disabledItems[index] = null
+ isExactRange = true
+ break
+ }
- function setDivColor(div, value) {
- div.style.background = value;
- div.setAttribute('data-mce-color', value);
- }
+ // When an overlapped match is found, add the “inverted” state to it.
+ else if ( calendar.isDateOverlap( disabledUnit, unitToEnable ) ) {
+ if ( $.isPlainObject( unitToEnable ) ) {
+ unitToEnable.inverted = true
+ matchFound = unitToEnable
+ }
+ else if ( $.isArray( unitToEnable ) ) {
+ matchFound = unitToEnable
+ if ( !matchFound[3] ) matchFound.push( 'inverted' )
+ }
+ else if ( _.isDate( unitToEnable ) ) {
+ matchFound = [ unitToEnable.getFullYear(), unitToEnable.getMonth(), unitToEnable.getDate(), 'inverted' ]
+ }
+ break
+ }
+ }
- if (tinymce.DOM.getParent(e.target, '.mce-custom-color-btn')) {
- buttonCtrl.hidePanel();
+ // If a match was found, remove a previous duplicate entry.
+ if ( matchFound ) for ( index = 0; index < disabledItemsCount; index += 1 ) {
+ if ( calendar.isDateExact( disabledItems[index], unitToEnable ) ) {
+ disabledItems[index] = null
+ break
+ }
+ }
- editor.settings.color_picker_callback.call(editor, function(value) {
- var tableElm = buttonCtrl.panel.getEl().getElementsByTagName('table')[0];
- var customColorCells, div, i;
+ // In the event that we’re dealing with an exact range of dates,
+ // make sure there are no “inverted” dates because of it.
+ if ( isExactRange ) for ( index = 0; index < disabledItemsCount; index += 1 ) {
+ if ( calendar.isDateOverlap( disabledItems[index], unitToEnable ) ) {
+ disabledItems[index] = null
+ break
+ }
+ }
- customColorCells = tinymce.map(tableElm.rows[tableElm.rows.length - 1].childNodes, function(elm) {
- return elm.firstChild;
- });
+ // If something is still matched, add it into the collection.
+ if ( matchFound ) {
+ disabledItems.push( matchFound )
+ }
+ })
+ }
- for (i = 0; i < customColorCells.length; i++) {
- div = customColorCells[i];
- if (!div.getAttribute('data-mce-color')) {
- break;
- }
- }
+ // Return the updated collection.
+ return disabledItems.filter(function( val ) { return val != null })
+} //DatePicker.prototype.activate
- // Shift colors to the right
- // TODO: Might need to be the left on RTL
- if (i == cols) {
- for (i = 0; i < cols - 1; i++) {
- setDivColor(customColorCells[i], customColorCells[i + 1].getAttribute('data-mce-color'));
- }
- }
- setDivColor(div, value);
- selectColor(value);
- }, getCurrentColor(buttonCtrl.settings.format));
- }
+/**
+ * Create a string for the nodes in the picker.
+ */
+DatePicker.prototype.nodes = function( isOpen ) {
+
+ var
+ calendar = this,
+ settings = calendar.settings,
+ calendarItem = calendar.item,
+ nowObject = calendarItem.now,
+ selectedObject = calendarItem.select,
+ highlightedObject = calendarItem.highlight,
+ viewsetObject = calendarItem.view,
+ disabledCollection = calendarItem.disable,
+ minLimitObject = calendarItem.min,
+ maxLimitObject = calendarItem.max,
+
+
+ // Create the calendar table head using a copy of weekday labels collection.
+ // * We do a copy so we don't mutate the original array.
+ tableHead = (function( collection, fullCollection ) {
+
+ // If the first day should be Monday, move Sunday to the end.
+ if ( settings.firstDay ) {
+ collection.push( collection.shift() )
+ fullCollection.push( fullCollection.shift() )
+ }
- value = e.target.getAttribute('data-mce-color');
- if (value) {
- if (this.lastId) {
- document.getElementById(this.lastId).setAttribute('aria-selected', false);
- }
+ // Create and return the table head group.
+ return _.node(
+ 'thead',
+ _.node(
+ 'tr',
+ _.group({
+ min: 0,
+ max: DAYS_IN_WEEK - 1,
+ i: 1,
+ node: 'th',
+ item: function( counter ) {
+ return [
+ collection[ counter ],
+ settings.klass.weekdays,
+ 'scope=col title="' + fullCollection[ counter ] + '"'
+ ]
+ }
+ })
+ )
+ ) //endreturn
+ })( ( settings.showWeekdaysFull ? settings.weekdaysFull : settings.weekdaysShort ).slice( 0 ), settings.weekdaysFull.slice( 0 ) ), //tableHead
+
+
+ // Create the nav for next/prev month.
+ createMonthNav = function( next ) {
+
+ // Otherwise, return the created month tag.
+ return _.node(
+ 'div',
+ ' ',
+ settings.klass[ 'nav' + ( next ? 'Next' : 'Prev' ) ] + (
+
+ // If the focused month is outside the range, disabled the button.
+ ( next && viewsetObject.year >= maxLimitObject.year && viewsetObject.month >= maxLimitObject.month ) ||
+ ( !next && viewsetObject.year <= minLimitObject.year && viewsetObject.month <= minLimitObject.month ) ?
+ ' ' + settings.klass.navDisabled : ''
+ ),
+ 'data-nav=' + ( next || -1 ) + ' ' +
+ _.ariaAttr({
+ role: 'button',
+ controls: calendar.$node[0].id + '_table'
+ }) + ' ' +
+ 'title="' + (next ? settings.labelMonthNext : settings.labelMonthPrev ) + '"'
+ ) //endreturn
+ }, //createMonthNav
+
+
+ // Create the month label.
+ createMonthLabel = function() {
+
+ var monthsCollection = settings.showMonthsShort ? settings.monthsShort : settings.monthsFull
+
+ // If there are months to select, add a dropdown menu.
+ if ( settings.selectMonths ) {
+
+ return _.node( 'select',
+ _.group({
+ min: 0,
+ max: 11,
+ i: 1,
+ node: 'option',
+ item: function( loopedMonth ) {
+
+ return [
+
+ // The looped month and no classes.
+ monthsCollection[ loopedMonth ], 0,
+
+ // Set the value and selected index.
+ 'value=' + loopedMonth +
+ ( viewsetObject.month == loopedMonth ? ' selected' : '' ) +
+ (
+ (
+ ( viewsetObject.year == minLimitObject.year && loopedMonth < minLimitObject.month ) ||
+ ( viewsetObject.year == maxLimitObject.year && loopedMonth > maxLimitObject.month )
+ ) ?
+ ' disabled' : ''
+ )
+ ]
+ }
+ }),
+ settings.klass.selectMonth,
+ ( isOpen ? '' : 'disabled' ) + ' ' +
+ _.ariaAttr({ controls: calendar.$node[0].id + '_table' }) + ' ' +
+ 'title="' + settings.labelMonthSelect + '"'
+ )
+ }
- e.target.setAttribute('aria-selected', true);
- this.lastId = e.target.id;
+ // If there's a need for a month selector
+ return _.node( 'div', monthsCollection[ viewsetObject.month ], settings.klass.month )
+ }, //createMonthLabel
- if (value == 'transparent') {
- resetColor();
- } else {
- selectColor(value);
- }
- } else if (value !== null) {
- buttonCtrl.hidePanel();
- }
- }
- function onButtonClick() {
- var self = this;
+ // Create the year label.
+ createYearLabel = function() {
- if (self._color) {
- applyFormat(self.settings.format, self._color);
- } else {
- removeFormat(self.settings.format);
- }
- }
+ var focusedYear = viewsetObject.year,
- editor.addButton('forecolor', {
- type: 'colorbutton',
- tooltip: 'Text color',
- format: 'forecolor',
- panel: {
- role: 'application',
- ariaRemember: true,
- html: renderColorPicker,
- onclick: onPanelClick
- },
- onclick: onButtonClick
- });
+ // If years selector is set to a literal "true", set it to 5. Otherwise
+ // divide in half to get half before and half after focused year.
+ numberYears = settings.selectYears === true ? 5 : ~~( settings.selectYears / 2 )
- editor.addButton('backcolor', {
- type: 'colorbutton',
- tooltip: 'Background color',
- format: 'hilitecolor',
- panel: {
- role: 'application',
- ariaRemember: true,
- html: renderColorPicker,
- onclick: onPanelClick
- },
- onclick: onButtonClick
- });
-});
+ // If there are years to select, add a dropdown menu.
+ if ( numberYears ) {
+ var
+ minYear = minLimitObject.year,
+ maxYear = maxLimitObject.year,
+ lowestYear = focusedYear - numberYears,
+ highestYear = focusedYear + numberYears
- }).apply(root, arguments);
-});
-}(this));
+ // If the min year is greater than the lowest year, increase the highest year
+ // by the difference and set the lowest year to the min year.
+ if ( minYear > lowestYear ) {
+ highestYear += minYear - lowestYear
+ lowestYear = minYear
+ }
-(function(root) {
-define("tinymce-textpattern", ["tinymce"], function() {
- return (function() {
-/**
- * plugin.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
+ // If the max year is less than the highest year, decrease the lowest year
+ // by the lower of the two: available and needed years. Then set the
+ // highest year to the max year.
+ if ( maxYear < highestYear ) {
-/*global tinymce:true */
+ var availableYears = lowestYear - minYear,
+ neededYears = highestYear - maxYear
-tinymce.PluginManager.add('textpattern', function(editor) {
- var isPatternsDirty = true, patterns;
+ lowestYear -= availableYears > neededYears ? neededYears : availableYears
+ highestYear = maxYear
+ }
- patterns = editor.settings.textpattern_patterns || [
- {start: '*', end: '*', format: 'italic'},
- {start: '**', end: '**', format: 'bold'},
- {start: '#', format: 'h1'},
- {start: '##', format: 'h2'},
- {start: '###', format: 'h3'},
- {start: '####', format: 'h4'},
- {start: '#####', format: 'h5'},
- {start: '######', format: 'h6'},
- {start: '1. ', cmd: 'InsertOrderedList'},
- {start: '* ', cmd: 'InsertUnorderedList'},
- {start: '- ', cmd: 'InsertUnorderedList'}
- ];
+ return _.node( 'select',
+ _.group({
+ min: lowestYear,
+ max: highestYear,
+ i: 1,
+ node: 'option',
+ item: function( loopedYear ) {
+ return [
+
+ // The looped year and no classes.
+ loopedYear, 0,
+
+ // Set the value and selected index.
+ 'value=' + loopedYear + ( focusedYear == loopedYear ? ' selected' : '' )
+ ]
+ }
+ }),
+ settings.klass.selectYear,
+ ( isOpen ? '' : 'disabled' ) + ' ' + _.ariaAttr({ controls: calendar.$node[0].id + '_table' }) + ' ' +
+ 'title="' + settings.labelYearSelect + '"'
+ )
+ }
- // Returns a sorted patterns list, ordered descending by start length
- function getPatterns() {
- if (isPatternsDirty) {
- patterns.sort(function(a, b) {
- if (a.start.length > b.start.length) {
- return -1;
- }
+ // Otherwise just return the year focused
+ return _.node( 'div', focusedYear, settings.klass.year )
+ } //createYearLabel
+
+
+ // Create and return the entire calendar.
+ return _.node(
+ 'div',
+ ( settings.selectYears ? createYearLabel() + createMonthLabel() : createMonthLabel() + createYearLabel() ) +
+ createMonthNav() + createMonthNav( 1 ),
+ settings.klass.header
+ ) + _.node(
+ 'table',
+ tableHead +
+ _.node(
+ 'tbody',
+ _.group({
+ min: 0,
+ max: WEEKS_IN_CALENDAR - 1,
+ i: 1,
+ node: 'tr',
+ item: function( rowCounter ) {
+
+ // If Monday is the first day and the month starts on Sunday, shift the date back a week.
+ var shiftDateBy = settings.firstDay && calendar.create([ viewsetObject.year, viewsetObject.month, 1 ]).day === 0 ? -7 : 0
+
+ return [
+ _.group({
+ min: DAYS_IN_WEEK * rowCounter - viewsetObject.day + shiftDateBy + 1, // Add 1 for weekday 0index
+ max: function() {
+ return this.min + DAYS_IN_WEEK - 1
+ },
+ i: 1,
+ node: 'td',
+ item: function( targetDate ) {
+
+ // Convert the time date from a relative date to a target date.
+ targetDate = calendar.create([ viewsetObject.year, viewsetObject.month, targetDate + ( settings.firstDay ? 1 : 0 ) ])
+
+ var isSelected = selectedObject && selectedObject.pick == targetDate.pick,
+ isHighlighted = highlightedObject && highlightedObject.pick == targetDate.pick,
+ isDisabled = disabledCollection && calendar.disabled( targetDate ) || targetDate.pick < minLimitObject.pick || targetDate.pick > maxLimitObject.pick,
+ formattedDate = _.trigger( calendar.formats.toString, calendar, [ settings.format, targetDate ] )
+
+ return [
+ _.node(
+ 'div',
+ targetDate.date,
+ (function( klasses ) {
+
+ // Add the `infocus` or `outfocus` classes based on month in view.
+ klasses.push( viewsetObject.month == targetDate.month ? settings.klass.infocus : settings.klass.outfocus )
+
+ // Add the `today` class if needed.
+ if ( nowObject.pick == targetDate.pick ) {
+ klasses.push( settings.klass.now )
+ }
+
+ // Add the `selected` class if something's selected and the time matches.
+ if ( isSelected ) {
+ klasses.push( settings.klass.selected )
+ }
+
+ // Add the `highlighted` class if something's highlighted and the time matches.
+ if ( isHighlighted ) {
+ klasses.push( settings.klass.highlighted )
+ }
+
+ // Add the `disabled` class if something's disabled and the object matches.
+ if ( isDisabled ) {
+ klasses.push( settings.klass.disabled )
+ }
+
+ return klasses.join( ' ' )
+ })([ settings.klass.day ]),
+ 'data-pick=' + targetDate.pick + ' ' + _.ariaAttr({
+ role: 'gridcell',
+ label: formattedDate,
+ selected: isSelected && calendar.$node.val() === formattedDate ? true : null,
+ activedescendant: isHighlighted ? true : null,
+ disabled: isDisabled ? true : null
+ })
+ ),
+ '',
+ _.ariaAttr({ role: 'presentation' })
+ ] //endreturn
+ }
+ })
+ ] //endreturn
+ }
+ })
+ ),
+ settings.klass.table,
+ 'id="' + calendar.$node[0].id + '_table' + '" ' + _.ariaAttr({
+ role: 'grid',
+ controls: calendar.$node[0].id,
+ readonly: true
+ })
+ ) +
+
+ // * For Firefox forms to submit, make sure to set the buttons’ `type` attributes as “button”.
+ _.node(
+ 'div',
+ _.node( 'button', settings.today, settings.klass.buttonToday,
+ 'type=button data-pick=' + nowObject.pick +
+ ( isOpen && !calendar.disabled(nowObject) ? '' : ' disabled' ) + ' ' +
+ _.ariaAttr({ controls: calendar.$node[0].id }) ) +
+ _.node( 'button', settings.clear, settings.klass.buttonClear,
+ 'type=button data-clear=1' +
+ ( isOpen ? '' : ' disabled' ) + ' ' +
+ _.ariaAttr({ controls: calendar.$node[0].id }) ) +
+ _.node('button', settings.close, settings.klass.buttonClose,
+ 'type=button data-close=true ' +
+ ( isOpen ? '' : ' disabled' ) + ' ' +
+ _.ariaAttr({ controls: calendar.$node[0].id }) ),
+ settings.klass.footer
+ ) //endreturn
+} //DatePicker.prototype.nodes
- if (a.start.length < b.start.length) {
- return 1;
- }
- return 0;
- });
- isPatternsDirty = false;
- }
- return patterns;
- }
+/**
+ * The date picker defaults.
+ */
+DatePicker.defaults = (function( prefix ) {
- // Finds a matching pattern to the specified text
- function findPattern(text) {
- var patterns = getPatterns();
+ return {
- for (var i = 0; i < patterns.length; i++) {
- if (text.indexOf(patterns[i].start) !== 0) {
- continue;
- }
+ // The title label to use for the month nav buttons
+ labelMonthNext: 'Next month',
+ labelMonthPrev: 'Previous month',
- if (patterns[i].end && text.lastIndexOf(patterns[i].end) != text.length - patterns[i].end.length) {
- continue;
- }
+ // The title label to use for the dropdown selectors
+ labelMonthSelect: 'Select a month',
+ labelYearSelect: 'Select a year',
- return patterns[i];
- }
- }
+ // Months and weekdays
+ monthsFull: [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ],
+ monthsShort: [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ],
+ weekdaysFull: [ 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday' ],
+ weekdaysShort: [ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' ],
- // Finds the best matching end pattern
- function findEndPattern(text, offset, delta) {
- var patterns, pattern, i;
+ // Today and clear
+ today: 'Today',
+ clear: 'Clear',
+ close: 'Close',
- // Find best matching end
- patterns = getPatterns();
- for (i = 0; i < patterns.length; i++) {
- pattern = patterns[i];
- if (pattern.end && text.substr(offset - pattern.end.length - delta, pattern.end.length) == pattern.end) {
- return pattern;
- }
- }
- }
+ // Picker close behavior
+ closeOnSelect: true,
+ closeOnClear: true,
- // Handles inline formats like *abc* and **abc**
- function applyInlineFormat(space) {
- var selection, dom, rng, container, offset, startOffset, text, patternRng, pattern, delta, format;
+ // The format to show on the `input` element
+ format: 'd mmmm, yyyy',
- function splitContainer() {
- // Split text node and remove start/end from text node
- container = container.splitText(startOffset);
- container.splitText(offset - startOffset - delta);
- container.deleteData(0, pattern.start.length);
- container.deleteData(container.data.length - pattern.end.length, pattern.end.length);
- }
+ // Classes
+ klass: {
- selection = editor.selection;
- dom = editor.dom;
+ table: prefix + 'table',
- if (!selection.isCollapsed()) {
- return;
- }
+ header: prefix + 'header',
- rng = selection.getRng(true);
- container = rng.startContainer;
- offset = rng.startOffset;
- text = container.data;
- delta = space ? 1 : 0;
+ navPrev: prefix + 'nav--prev',
+ navNext: prefix + 'nav--next',
+ navDisabled: prefix + 'nav--disabled',
- if (container.nodeType != 3) {
- return;
- }
+ month: prefix + 'month',
+ year: prefix + 'year',
- // Find best matching end
- pattern = findEndPattern(text, offset, delta);
- if (!pattern) {
- return;
- }
+ selectMonth: prefix + 'select--month',
+ selectYear: prefix + 'select--year',
- // Find start of matched pattern
- // TODO: Might need to improve this if there is nested formats
- startOffset = Math.max(0, offset - delta);
- startOffset = text.lastIndexOf(pattern.start, startOffset - pattern.end.length - 1);
+ weekdays: prefix + 'weekday',
- if (startOffset === -1) {
- return;
- }
+ day: prefix + 'day',
+ disabled: prefix + 'day--disabled',
+ selected: prefix + 'day--selected',
+ highlighted: prefix + 'day--highlighted',
+ now: prefix + 'day--today',
+ infocus: prefix + 'day--infocus',
+ outfocus: prefix + 'day--outfocus',
- // Setup a range for the matching word
- patternRng = dom.createRng();
- patternRng.setStart(container, startOffset);
- patternRng.setEnd(container, offset - delta);
- pattern = findPattern(patternRng.toString());
+ footer: prefix + 'footer',
- if (!pattern || !pattern.end) {
- return;
- }
+ buttonClear: prefix + 'button--clear',
+ buttonToday: prefix + 'button--today',
+ buttonClose: prefix + 'button--close'
+ }
+ }
+})( Picker.klasses().picker + '__' )
- // If container match doesn't have anything between start/end then do nothing
- if (container.data.length <= pattern.start.length + pattern.end.length) {
- return;
- }
- format = editor.formatter.get(pattern.format);
- if (format && format[0].inline) {
- splitContainer();
- editor.formatter.apply(pattern.format, {}, container);
- return container;
- }
- }
- // Handles block formats like ##abc or 1. abc
- function applyBlockFormat() {
- var selection, dom, container, firstTextNode, node, format, textBlockElm, pattern, walker, rng, offset;
- selection = editor.selection;
- dom = editor.dom;
- if (!selection.isCollapsed()) {
- return;
- }
+/**
+ * Extend the picker to add the date picker.
+ */
+Picker.extend( 'pickadate', DatePicker )
+
+
+}));
- textBlockElm = dom.getParent(selection.getStart(), 'p');
- if (textBlockElm) {
- walker = new tinymce.dom.TreeWalker(textBlockElm, textBlockElm);
- while ((node = walker.next())) {
- if (node.nodeType == 3) {
- firstTextNode = node;
- break;
- }
- }
- if (firstTextNode) {
- pattern = findPattern(firstTextNode.data);
- if (!pattern) {
- return;
- }
- rng = selection.getRng(true);
- container = rng.startContainer;
- offset = rng.startOffset;
- if (firstTextNode == container) {
- offset = Math.max(0, offset - pattern.start.length);
- }
+/*!
+ * Time picker for pickadate.js v3.5.6
+ * http://amsul.github.io/pickadate.js/time.htm
+ */
- if (tinymce.trim(firstTextNode.data).length == pattern.start.length) {
- return;
- }
+(function ( factory ) {
- if (pattern.format) {
- format = editor.formatter.get(pattern.format);
- if (format && format[0].block) {
- firstTextNode.deleteData(0, pattern.start.length);
- editor.formatter.apply(pattern.format, {}, firstTextNode);
+ // AMD.
+ if ( typeof define == 'function' && define.amd )
+ define( 'picker.time',['picker', 'jquery'], factory )
- rng.setStart(container, offset);
- rng.collapse(true);
- selection.setRng(rng);
- }
- }
+ // Node.js/browserify.
+ else if ( typeof exports == 'object' )
+ module.exports = factory( require('./picker.js'), require('jquery') )
- if (pattern.cmd) {
- editor.undoManager.transact(function() {
- firstTextNode.deleteData(0, pattern.start.length);
- editor.execCommand(pattern.cmd);
- });
- }
- }
- }
- }
+ // Browser globals.
+ else factory( Picker, jQuery )
- function handleEnter() {
- var rng, wrappedTextNode;
+}(function( Picker, $ ) {
- wrappedTextNode = applyInlineFormat();
- if (wrappedTextNode) {
- rng = editor.dom.createRng();
- rng.setStart(wrappedTextNode, wrappedTextNode.data.length);
- rng.setEnd(wrappedTextNode, wrappedTextNode.data.length);
- editor.selection.setRng(rng);
- }
- applyBlockFormat();
- }
+/**
+ * Globals and constants
+ */
+var HOURS_IN_DAY = 24,
+ MINUTES_IN_HOUR = 60,
+ HOURS_TO_NOON = 12,
+ MINUTES_IN_DAY = HOURS_IN_DAY * MINUTES_IN_HOUR,
+ _ = Picker._
- function handleSpace() {
- var wrappedTextNode, lastChar, lastCharNode, rng, dom;
- wrappedTextNode = applyInlineFormat(true);
- if (wrappedTextNode) {
- dom = editor.dom;
- lastChar = wrappedTextNode.data.slice(-1);
- // Move space after the newly formatted node
- if (/[\u00a0 ]/.test(lastChar)) {
- wrappedTextNode.deleteData(wrappedTextNode.data.length - 1, 1);
- lastCharNode = dom.doc.createTextNode(lastChar);
+/**
+ * The time picker constructor
+ */
+function TimePicker( picker, settings ) {
+
+ var clock = this,
+ elementValue = picker.$node[ 0 ].value,
+ elementDataValue = picker.$node.data( 'value' ),
+ valueString = elementDataValue || elementValue,
+ formatString = elementDataValue ? settings.formatSubmit : settings.format
+
+ clock.settings = settings
+ clock.$node = picker.$node
+
+ // The queue of methods that will be used to build item objects.
+ clock.queue = {
+ interval: 'i',
+ min: 'measure create',
+ max: 'measure create',
+ now: 'now create',
+ select: 'parse create validate',
+ highlight: 'parse create validate',
+ view: 'parse create validate',
+ disable: 'deactivate',
+ enable: 'activate'
+ }
- if (wrappedTextNode.nextSibling) {
- dom.insertAfter(lastCharNode, wrappedTextNode.nextSibling);
- } else {
- wrappedTextNode.parentNode.appendChild(lastCharNode);
- }
+ // The component's item object.
+ clock.item = {}
+
+ clock.item.clear = null
+ clock.item.interval = settings.interval || 30
+ clock.item.disable = ( settings.disable || [] ).slice( 0 )
+ clock.item.enable = -(function( collectionDisabled ) {
+ return collectionDisabled[ 0 ] === true ? collectionDisabled.shift() : -1
+ })( clock.item.disable )
+
+ clock.
+ set( 'min', settings.min ).
+ set( 'max', settings.max ).
+ set( 'now' )
+
+ // When there’s a value, set the `select`, which in turn
+ // also sets the `highlight` and `view`.
+ if ( valueString ) {
+ clock.set( 'select', valueString, {
+ format: formatString
+ })
+ }
- rng = dom.createRng();
- rng.setStart(lastCharNode, 1);
- rng.setEnd(lastCharNode, 1);
- editor.selection.setRng(rng);
- }
- }
- }
+ // If there’s no value, default to highlighting “today”.
+ else {
+ clock.
+ set( 'select', null ).
+ set( 'highlight', clock.item.now )
+ }
- editor.on('keydown', function(e) {
- if (e.keyCode == 13 && !tinymce.util.VK.modifierPressed(e)) {
- handleEnter();
- }
- }, true);
+ // The keycode to movement mapping.
+ clock.key = {
+ 40: 1, // Down
+ 38: -1, // Up
+ 39: 1, // Right
+ 37: -1, // Left
+ go: function( timeChange ) {
+ clock.set(
+ 'highlight',
+ clock.item.highlight.pick + timeChange * clock.item.interval,
+ { interval: timeChange * clock.item.interval }
+ )
+ this.render()
+ }
+ }
- editor.on('keyup', function(e) {
- if (e.keyCode == 32 && !tinymce.util.VK.modifierPressed(e)) {
- handleSpace();
- }
- });
- this.getPatterns = getPatterns;
- this.setPatterns = function(newPatterns) {
- patterns = newPatterns;
- isPatternsDirty = true;
- };
-});
+ // Bind some picker events.
+ picker.
+ on( 'render', function() {
+ var $pickerHolder = picker.$root.children(),
+ $viewset = $pickerHolder.find( '.' + settings.klass.viewset ),
+ vendors = function( prop ) {
+ return ['webkit', 'moz', 'ms', 'o', ''].map(function( vendor ) {
+ return ( vendor ? '-' + vendor + '-' : '' ) + prop
+ })
+ },
+ animations = function( $el, state ) {
+ vendors( 'transform' ).map(function( prop ) {
+ $el.css( prop, state )
+ })
+ vendors( 'transition' ).map(function( prop ) {
+ $el.css( prop, state )
+ })
+ }
+ if ( $viewset.length ) {
+ animations( $pickerHolder, 'none' )
+ $pickerHolder[ 0 ].scrollTop = ~~$viewset.position().top - ( $viewset[ 0 ].clientHeight * 2 )
+ animations( $pickerHolder, '' )
+ }
+ }, 1 ).
+ on( 'open', function() {
+ picker.$root.find( 'button' ).attr( 'disabled', false )
+ }, 1 ).
+ on( 'close', function() {
+ picker.$root.find( 'button' ).attr( 'disabled', true )
+ }, 1 )
+
+} //TimePicker
- }).apply(root, arguments);
-});
-}(this));
-(function(root) {
-define("tinymce-visualblocks", ["tinymce"], function() {
- return (function() {
/**
- * plugin.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
+ * Set a timepicker item object.
*/
+TimePicker.prototype.set = function( type, value, options ) {
-/*global tinymce:true */
+ var clock = this,
+ clockItem = clock.item
-tinymce.PluginManager.add('visualblocks', function(editor, url) {
- var cssId, visualBlocksMenuItem, enabled;
+ // If the value is `null` just set it immediately.
+ if ( value === null ) {
+ if ( type == 'clear' ) type = 'select'
+ clockItem[ type ] = value
+ return clock
+ }
- // We don't support older browsers like IE6/7 and they don't provide prototypes for DOM objects
- if (!window.NodeList) {
- return;
- }
+ // Otherwise go through the queue of methods, and invoke the functions.
+ // Update this as the time unit, and set the final value as this item.
+ // * In the case of `enable`, keep the queue but set `disable` instead.
+ // And in the case of `flip`, keep the queue but set `enable` instead.
+ clockItem[ ( type == 'enable' ? 'disable' : type == 'flip' ? 'enable' : type ) ] = clock.queue[ type ].split( ' ' ).map( function( method ) {
+ value = clock[ method ]( type, value, options )
+ return value
+ }).pop()
+
+ // Check if we need to cascade through more updates.
+ if ( type == 'select' ) {
+ clock.set( 'highlight', clockItem.select, options )
+ }
+ else if ( type == 'highlight' ) {
+ clock.set( 'view', clockItem.highlight, options )
+ }
+ else if ( type == 'interval' ) {
+ clock.
+ set( 'min', clockItem.min, options ).
+ set( 'max', clockItem.max, options )
+ }
+ else if ( type.match( /^(flip|min|max|disable|enable)$/ ) ) {
+ if ( clockItem.select && clock.disabled( clockItem.select ) ) {
+ clock.set( 'select', value, options )
+ }
+ if ( clockItem.highlight && clock.disabled( clockItem.highlight ) ) {
+ clock.set( 'highlight', value, options )
+ }
+ if ( type == 'min' ) {
+ clock.set( 'max', clockItem.max, options )
+ }
+ }
- function toggleActiveState() {
- var self = this;
+ return clock
+} //TimePicker.prototype.set
- self.active(enabled);
- editor.on('VisualBlocks', function() {
- self.active(editor.dom.hasClass(editor.getBody(), 'mce-visualblocks'));
- });
- }
+/**
+ * Get a timepicker item object.
+ */
+TimePicker.prototype.get = function( type ) {
+ return this.item[ type ]
+} //TimePicker.prototype.get
- editor.addCommand('mceVisualBlocks', function() {
- var dom = editor.dom, linkElm;
- if (!cssId) {
- cssId = dom.uniqueId();
- linkElm = dom.create('link', {
- id: cssId,
- rel: 'stylesheet',
- href: url + '/css/visualblocks.css'
- });
+/**
+ * Create a picker time object.
+ */
+TimePicker.prototype.create = function( type, value, options ) {
- editor.getDoc().getElementsByTagName('head')[0].appendChild(linkElm);
- }
+ var clock = this
- // Toggle on/off visual blocks while computing previews
- editor.on("PreviewFormats AfterPreviewFormats", function(e) {
- if (enabled) {
- dom.toggleClass(editor.getBody(), 'mce-visualblocks', e.type == "afterpreviewformats");
- }
- });
+ // If there’s no value, use the type as the value.
+ value = value === undefined ? type : value
- dom.toggleClass(editor.getBody(), 'mce-visualblocks');
- enabled = editor.dom.hasClass(editor.getBody(), 'mce-visualblocks');
+ // If it’s a date object, convert it into an array.
+ if ( _.isDate( value ) ) {
+ value = [ value.getHours(), value.getMinutes() ]
+ }
- if (visualBlocksMenuItem) {
- visualBlocksMenuItem.active(dom.hasClass(editor.getBody(), 'mce-visualblocks'));
- }
+ // If it’s an object, use the “pick” value.
+ if ( $.isPlainObject( value ) && _.isInteger( value.pick ) ) {
+ value = value.pick
+ }
- editor.fire('VisualBlocks');
- });
+ // If it’s an array, convert it into minutes.
+ else if ( $.isArray( value ) ) {
+ value = +value[ 0 ] * MINUTES_IN_HOUR + (+value[ 1 ])
+ }
- editor.addButton('visualblocks', {
- title: 'Show blocks',
- cmd: 'mceVisualBlocks',
- onPostRender: toggleActiveState
- });
+ // If no valid value is passed, set it to “now”.
+ else if ( !_.isInteger( value ) ) {
+ value = clock.now( type, value, options )
+ }
- editor.addMenuItem('visualblocks', {
- text: 'Show blocks',
- cmd: 'mceVisualBlocks',
- onPostRender: toggleActiveState,
- selectable: true,
- context: 'view',
- prependToContext: true
- });
+ // If we’re setting the max, make sure it’s greater than the min.
+ if ( type == 'max' && value < clock.item.min.pick ) {
+ value += MINUTES_IN_DAY
+ }
- editor.on('init', function() {
- if (editor.settings.visualblocks_default_state) {
- editor.execCommand('mceVisualBlocks', false, null, {skip_focus: true});
- }
- });
+ // If the value doesn’t fall directly on the interval,
+ // add one interval to indicate it as “passed”.
+ if ( type != 'min' && type != 'max' && (value - clock.item.min.pick) % clock.item.interval !== 0 ) {
+ value += clock.item.interval
+ }
- editor.on('remove', function() {
- editor.dom.removeClass(editor.getBody(), 'mce-visualblocks');
- });
-});
+ // Normalize it into a “reachable” interval.
+ value = clock.normalize( type, value, options )
+ // Return the compiled object.
+ return {
+
+ // Divide to get hours from minutes.
+ hour: ~~( HOURS_IN_DAY + value / MINUTES_IN_HOUR ) % HOURS_IN_DAY,
+
+ // The remainder is the minutes.
+ mins: ( MINUTES_IN_HOUR + value % MINUTES_IN_HOUR ) % MINUTES_IN_HOUR,
+
+ // The time in total minutes.
+ time: ( MINUTES_IN_DAY + value ) % MINUTES_IN_DAY,
+
+ // Reference to the “relative” value to pick.
+ pick: value % MINUTES_IN_DAY
+ }
+} //TimePicker.prototype.create
- }).apply(root, arguments);
-});
-}(this));
-(function(root) {
-define("tinymce-visualchars", ["tinymce"], function() {
- return (function() {
/**
- * plugin.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
+ * Create a range limit object using an array, date object,
+ * literal “true”, or integer relative to another time.
*/
+TimePicker.prototype.createRange = function( from, to ) {
-/*global tinymce:true */
+ var clock = this,
+ createTime = function( time ) {
+ if ( time === true || $.isArray( time ) || _.isDate( time ) ) {
+ return clock.create( time )
+ }
+ return time
+ }
-tinymce.PluginManager.add('visualchars', function(editor) {
- var self = this, state;
+ // Create objects if possible.
+ if ( !_.isInteger( from ) ) {
+ from = createTime( from )
+ }
+ if ( !_.isInteger( to ) ) {
+ to = createTime( to )
+ }
- function toggleVisualChars(addBookmark) {
- var node, nodeList, i, body = editor.getBody(), nodeValue, selection = editor.selection, div, bookmark;
- var charMap, visualCharsRegExp;
+ // Create relative times.
+ if ( _.isInteger( from ) && $.isPlainObject( to ) ) {
+ from = [ to.hour, to.mins + ( from * clock.settings.interval ) ];
+ }
+ else if ( _.isInteger( to ) && $.isPlainObject( from ) ) {
+ to = [ from.hour, from.mins + ( to * clock.settings.interval ) ];
+ }
- charMap = {
- '\u00a0': 'nbsp',
- '\u00ad': 'shy'
- };
+ return {
+ from: createTime( from ),
+ to: createTime( to )
+ }
+} //TimePicker.prototype.createRange
- function wrapCharWithSpan(value) {
- return '' + value + '';
- }
- function compileCharMapToRegExp() {
- var key, regExp = '';
+/**
+ * Check if a time unit falls within a time range object.
+ */
+TimePicker.prototype.withinRange = function( range, timeUnit ) {
+ range = this.createRange(range.from, range.to)
+ return timeUnit.pick >= range.from.pick && timeUnit.pick <= range.to.pick
+}
- for (key in charMap) {
- regExp += key;
- }
- return new RegExp('[' + regExp + ']', 'g');
- }
+/**
+ * Check if two time range objects overlap.
+ */
+TimePicker.prototype.overlapRanges = function( one, two ) {
- function compileCharMapToCssSelector() {
- var key, selector = '';
+ var clock = this
- for (key in charMap) {
- if (selector) {
- selector += ',';
- }
+ // Convert the ranges into comparable times.
+ one = clock.createRange( one.from, one.to )
+ two = clock.createRange( two.from, two.to )
- selector += 'span.mce-' + charMap[key];
- }
+ return clock.withinRange( one, two.from ) || clock.withinRange( one, two.to ) ||
+ clock.withinRange( two, one.from ) || clock.withinRange( two, one.to )
+}
- return selector;
- }
- state = !state;
- self.state = state;
- editor.fire('VisualChars', {state: state});
- visualCharsRegExp = compileCharMapToRegExp();
+/**
+ * Get the time relative to now.
+ */
+TimePicker.prototype.now = function( type, value/*, options*/ ) {
+
+ var interval = this.item.interval,
+ date = new Date(),
+ nowMinutes = date.getHours() * MINUTES_IN_HOUR + date.getMinutes(),
+ isValueInteger = _.isInteger( value ),
+ isBelowInterval
+
+ // Make sure “now” falls within the interval range.
+ nowMinutes -= nowMinutes % interval
+
+ // Check if the difference is less than the interval itself.
+ isBelowInterval = value < 0 && interval * value + nowMinutes <= -interval
+
+ // Add an interval because the time has “passed”.
+ nowMinutes += type == 'min' && isBelowInterval ? 0 : interval
+
+ // If the value is a number, adjust by that many intervals.
+ if ( isValueInteger ) {
+ nowMinutes += interval * (
+ isBelowInterval && type != 'max' ?
+ value + 1 :
+ value
+ )
+ }
- if (addBookmark) {
- bookmark = selection.getBookmark();
- }
+ // Return the final calculation.
+ return nowMinutes
+} //TimePicker.prototype.now
- if (state) {
- nodeList = [];
- tinymce.walk(body, function(n) {
- if (n.nodeType == 3 && n.nodeValue && visualCharsRegExp.test(n.nodeValue)) {
- nodeList.push(n);
- }
- }, 'childNodes');
- for (i = 0; i < nodeList.length; i++) {
- nodeValue = nodeList[i].nodeValue;
- nodeValue = nodeValue.replace(visualCharsRegExp, wrapCharWithSpan);
+/**
+ * Normalize minutes to be “reachable” based on the min and interval.
+ */
+TimePicker.prototype.normalize = function( type, value/*, options*/ ) {
- div = editor.dom.create('div', null, nodeValue);
- while ((node = div.lastChild)) {
- editor.dom.insertAfter(node, nodeList[i]);
- }
+ var interval = this.item.interval,
+ minTime = this.item.min && this.item.min.pick || 0
- editor.dom.remove(nodeList[i]);
- }
- } else {
- nodeList = editor.dom.select(compileCharMapToCssSelector(), body);
+ // If setting min time, don’t shift anything.
+ // Otherwise get the value and min difference and then
+ // normalize the difference with the interval.
+ value -= type == 'min' ? 0 : ( value - minTime ) % interval
- for (i = nodeList.length - 1; i >= 0; i--) {
- editor.dom.remove(nodeList[i], 1);
- }
- }
+ // Return the adjusted value.
+ return value
+} //TimePicker.prototype.normalize
- selection.moveToBookmark(bookmark);
- }
- function toggleActiveState() {
- var self = this;
+/**
+ * Measure the range of minutes.
+ */
+TimePicker.prototype.measure = function( type, value, options ) {
- editor.on('VisualChars', function(e) {
- self.active(e.state);
- });
- }
+ var clock = this
- editor.addCommand('mceVisualChars', toggleVisualChars);
+ // If it’s anything false-y, set it to the default.
+ if ( !value ) {
+ value = type == 'min' ? [ 0, 0 ] : [ HOURS_IN_DAY - 1, MINUTES_IN_HOUR - 1 ]
+ }
- editor.addButton('visualchars', {
- title: 'Show invisible characters',
- cmd: 'mceVisualChars',
- onPostRender: toggleActiveState
- });
+ // If it’s a string, parse it.
+ if ( typeof value == 'string' ) {
+ value = clock.parse( type, value )
+ }
- editor.addMenuItem('visualchars', {
- text: 'Show invisible characters',
- cmd: 'mceVisualChars',
- onPostRender: toggleActiveState,
- selectable: true,
- context: 'view',
- prependToContext: true
- });
+ // If it’s a literal true, or an integer, make it relative to now.
+ else if ( value === true || _.isInteger( value ) ) {
+ value = clock.now( type, value, options )
+ }
- editor.on('beforegetcontent', function(e) {
- if (state && e.format != 'raw' && !e.draft) {
- state = true;
- toggleVisualChars(false);
- }
- });
-});
+ // If it’s an object already, just normalize it.
+ else if ( $.isPlainObject( value ) && _.isInteger( value.pick ) ) {
+ value = clock.normalize( type, value.pick, options )
+ }
+ return value
+} ///TimePicker.prototype.measure
- }).apply(root, arguments);
-});
-}(this));
-(function(root) {
-define("tinymce-wordcount", ["tinymce"], function() {
- return (function() {
/**
- * plugin.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
+ * Validate an object as enabled.
*/
+TimePicker.prototype.validate = function( type, timeObject, options ) {
-/*global tinymce:true */
+ var clock = this,
+ interval = options && options.interval ? options.interval : clock.item.interval
-tinymce.PluginManager.add('wordcount', function(editor) {
- var self = this, countre, cleanre;
+ // Check if the object is disabled.
+ if ( clock.disabled( timeObject ) ) {
- // Included most unicode blocks see: http://en.wikipedia.org/wiki/Unicode_block
- // Latin-1_Supplement letters, a-z, u2019 == ’
- countre = editor.getParam('wordcount_countregex', /[\w\u2019\x27\-\u00C0-\u1FFF]+/g);
- cleanre = editor.getParam('wordcount_cleanregex', /[0-9.(),;:!?%#$?\x27\x22_+=\\\/\-]*/g);
+ // Shift with the interval until we reach an enabled time.
+ timeObject = clock.shift( timeObject, interval )
+ }
- function update() {
- editor.theme.panel.find('#wordcount').text(['Words: {0}', self.getCount()]);
- }
+ // Scope the object into range.
+ timeObject = clock.scope( timeObject )
- editor.on('init', function() {
- var statusbar = editor.theme.panel && editor.theme.panel.find('#statusbar')[0];
+ // Do a second check to see if we landed on a disabled min/max.
+ // In that case, shift using the opposite interval as before.
+ if ( clock.disabled( timeObject ) ) {
+ timeObject = clock.shift( timeObject, interval * -1 )
+ }
- if (statusbar) {
- window.setTimeout(function() {
- statusbar.insert({
- type: 'label',
- name: 'wordcount',
- text: ['Words: {0}', self.getCount()],
- classes: 'wordcount',
- disabled: editor.settings.readonly
- }, 0);
+ // Return the final object.
+ return timeObject
+} //TimePicker.prototype.validate
- editor.on('setcontent beforeaddundo', update);
- editor.on('keyup', function(e) {
- if (e.keyCode == 32) {
- update();
- }
- });
- }, 0);
- }
- });
+/**
+ * Check if an object is disabled.
+ */
+TimePicker.prototype.disabled = function( timeToVerify ) {
- self.getCount = function() {
- var tx = editor.getContent({format: 'raw'});
- var tc = 0;
+ var clock = this,
- if (tx) {
- tx = tx.replace(/\.\.\./g, ' '); // convert ellipses to spaces
- tx = tx.replace(/<.[^<>]*?>/g, ' ').replace(/ | /gi, ' '); // remove html tags and space chars
+ // Filter through the disabled times to check if this is one.
+ isDisabledMatch = clock.item.disable.filter( function( timeToDisable ) {
- // deal with html entities
- tx = tx.replace(/(\w+)(?[a-z0-9]+;)+(\w+)/i, "$1$3").replace(/&.+?;/g, ' ');
- tx = tx.replace(cleanre, ''); // remove numbers and punctuation
+ // If the time is a number, match the hours.
+ if ( _.isInteger( timeToDisable ) ) {
+ return timeToVerify.hour == timeToDisable
+ }
- var wordArray = tx.match(countre);
- if (wordArray) {
- tc = wordArray.length;
- }
- }
+ // If it’s an array, create the object and match the times.
+ if ( $.isArray( timeToDisable ) || _.isDate( timeToDisable ) ) {
+ return timeToVerify.pick == clock.create( timeToDisable ).pick
+ }
- return tc;
- };
-});
+ // If it’s an object, match a time within the “from” and “to” range.
+ if ( $.isPlainObject( timeToDisable ) ) {
+ return clock.withinRange( timeToDisable, timeToVerify )
+ }
+ })
- }).apply(root, arguments);
-});
-}(this));
+ // If this time matches a disabled time, confirm it’s not inverted.
+ isDisabledMatch = isDisabledMatch.length && !isDisabledMatch.filter(function( timeToDisable ) {
+ return $.isArray( timeToDisable ) && timeToDisable[2] == 'inverted' ||
+ $.isPlainObject( timeToDisable ) && timeToDisable.inverted
+ }).length
-(function(root) {
-define("tinymce-compat3x", ["tinymce"], function() {
- return (function() {
-/**
- * plugin.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
+ // If the clock is "enabled" flag is flipped, flip the condition.
+ return clock.item.enable === -1 ? !isDisabledMatch : isDisabledMatch ||
+ timeToVerify.pick < clock.item.min.pick ||
+ timeToVerify.pick > clock.item.max.pick
+} //TimePicker.prototype.disabled
-/*global tinymce:true, console:true */
-/*eslint no-console:0, new-cap:0 */
/**
- * This plugin adds missing events form the 4.x API back. Not every event is
- * properly supported but most things should work.
- *
- * Unsupported things:
- * - No editor.onEvent
- * - Can't cancel execCommands with beforeExecCommand
+ * Shift an object by an interval until we reach an enabled object.
*/
-(function(tinymce) {
- var reported;
+TimePicker.prototype.shift = function( timeObject, interval ) {
- function noop() {
- }
+ var clock = this,
+ minLimit = clock.item.min.pick,
+ maxLimit = clock.item.max.pick/*,
+ safety = 1000*/
- function log(apiCall) {
- if (!reported && window && window.console) {
- reported = true;
- console.log("Deprecated TinyMCE API call: " + apiCall);
- }
- }
+ interval = interval || clock.item.interval
- function Dispatcher(target, newEventName, argsMap, defaultScope) {
- target = target || this;
+ // Keep looping as long as the time is disabled.
+ while ( /*safety &&*/ clock.disabled( timeObject ) ) {
- if (!newEventName) {
- this.add = this.addToTop = this.remove = this.dispatch = noop;
- return;
- }
+ /*safety -= 1
+ if ( !safety ) {
+ throw 'Fell into an infinite loop while shifting to ' + timeObject.hour + ':' + timeObject.mins + '.'
+ }*/
- this.add = function(callback, scope, prepend) {
- log('.on' + newEventName + ".add(..)");
+ // Increase/decrease the time by the interval and keep looping.
+ timeObject = clock.create( timeObject.pick += interval )
- // Convert callback({arg1:x, arg2:x}) -> callback(arg1, arg2)
- function patchedEventCallback(e) {
- var callbackArgs = [];
+ // If we've looped beyond the limits, break out of the loop.
+ if ( timeObject.pick <= minLimit || timeObject.pick >= maxLimit ) {
+ break
+ }
+ }
- if (typeof argsMap == "string") {
- argsMap = argsMap.split(" ");
- }
+ // Return the final object.
+ return timeObject
+} //TimePicker.prototype.shift
- if (argsMap && typeof argsMap != "function") {
- for (var i = 0; i < argsMap.length; i++) {
- callbackArgs.push(e[argsMap[i]]);
- }
- }
- if (typeof argsMap == "function") {
- callbackArgs = argsMap(newEventName, e, target);
- if (!callbackArgs) {
- return;
- }
- }
+/**
+ * Scope an object to be within range of min and max.
+ */
+TimePicker.prototype.scope = function( timeObject ) {
+ var minLimit = this.item.min.pick,
+ maxLimit = this.item.max.pick
+ return this.create( timeObject.pick > maxLimit ? maxLimit : timeObject.pick < minLimit ? minLimit : timeObject )
+} //TimePicker.prototype.scope
- if (!argsMap) {
- callbackArgs = [e];
- }
- callbackArgs.unshift(defaultScope || target);
+/**
+ * Parse a string into a usable type.
+ */
+TimePicker.prototype.parse = function( type, value, options ) {
- if (callback.apply(scope || defaultScope || target, callbackArgs) === false) {
- e.stopImmediatePropagation();
- }
- }
+ var hour, minutes, isPM, item, parseValue,
+ clock = this,
+ parsingObject = {}
- target.on(newEventName, patchedEventCallback, prepend);
+ // If it’s already parsed, we’re good.
+ if ( !value || typeof value != 'string' ) {
+ return value
+ }
- return patchedEventCallback;
- };
+ // We need a `.format` to parse the value with.
+ if ( !( options && options.format ) ) {
+ options = options || {}
+ options.format = clock.settings.format
+ }
- this.addToTop = function(callback, scope) {
- this.add(callback, scope, true);
- };
+ // Convert the format into an array and then map through it.
+ clock.formats.toArray( options.format ).map( function( label ) {
- this.remove = function(callback) {
- return target.off(newEventName, callback);
- };
+ var
+ substring,
- this.dispatch = function() {
- target.fire(newEventName);
+ // Grab the formatting label.
+ formattingLabel = clock.formats[ label ],
- return true;
- };
- }
+ // The format length is from the formatting label function or the
+ // label length without the escaping exclamation (!) mark.
+ formatLength = formattingLabel ?
+ _.trigger( formattingLabel, clock, [ value, parsingObject ] ) :
+ label.replace( /^!/, '' ).length
- tinymce.util.Dispatcher = Dispatcher;
- tinymce.onBeforeUnload = new Dispatcher(tinymce, "BeforeUnload");
- tinymce.onAddEditor = new Dispatcher(tinymce, "AddEditor", "editor");
- tinymce.onRemoveEditor = new Dispatcher(tinymce, "RemoveEditor", "editor");
+ // If there's a format label, split the value up to the format length.
+ // Then add it to the parsing object with appropriate label.
+ if ( formattingLabel ) {
+ substring = value.substr( 0, formatLength )
+ parsingObject[ label ] = substring.match(/^\d+$/) ? +substring : substring
+ }
- tinymce.util.Cookie = {
- get: noop, getHash: noop, remove: noop, set: noop, setHash: noop
- };
+ // Update the time value as the substring from format length to end.
+ value = value.substr( formatLength )
+ })
- function patchEditor(editor) {
- function patchEditorEvents(oldEventNames, argsMap) {
- tinymce.each(oldEventNames.split(" "), function(oldName) {
- editor["on" + oldName] = new Dispatcher(editor, oldName, argsMap);
- });
- }
+ // Grab the hour and minutes from the parsing object.
+ for ( item in parsingObject ) {
+ parseValue = parsingObject[item]
+ if ( _.isInteger(parseValue) ) {
+ if ( item.match(/^(h|hh)$/i) ) {
+ hour = parseValue
+ if ( item == 'h' || item == 'hh' ) {
+ hour %= 12
+ }
+ }
+ else if ( item == 'i' ) {
+ minutes = parseValue
+ }
+ }
+ else if ( item.match(/^a$/i) && parseValue.match(/^p/i) && ('h' in parsingObject || 'hh' in parsingObject) ) {
+ isPM = true
+ }
+ }
+
+ // Calculate it in minutes and return.
+ return (isPM ? hour + 12 : hour) * MINUTES_IN_HOUR + minutes
+} //TimePicker.prototype.parse
+
+
+/**
+ * Various formats to display the object in.
+ */
+TimePicker.prototype.formats = {
+
+ h: function( string, timeObject ) {
+
+ // If there's string, then get the digits length.
+ // Otherwise return the selected hour in "standard" format.
+ return string ? _.digits( string ) : timeObject.hour % HOURS_TO_NOON || HOURS_TO_NOON
+ },
+ hh: function( string, timeObject ) {
- function convertUndoEventArgs(type, event, target) {
- return [
- event.level,
- target
- ];
- }
+ // If there's a string, then the length is always 2.
+ // Otherwise return the selected hour in "standard" format with a leading zero.
+ return string ? 2 : _.lead( timeObject.hour % HOURS_TO_NOON || HOURS_TO_NOON )
+ },
+ H: function( string, timeObject ) {
- function filterSelectionEvents(needsSelection) {
- return function(type, e) {
- if ((!e.selection && !needsSelection) || e.selection == needsSelection) {
- return [e];
- }
- };
- }
+ // If there's string, then get the digits length.
+ // Otherwise return the selected hour in "military" format as a string.
+ return string ? _.digits( string ) : '' + ( timeObject.hour % 24 )
+ },
+ HH: function( string, timeObject ) {
- if (editor.controlManager) {
- return;
- }
+ // If there's string, then get the digits length.
+ // Otherwise return the selected hour in "military" format with a leading zero.
+ return string ? _.digits( string ) : _.lead( timeObject.hour % 24 )
+ },
+ i: function( string, timeObject ) {
- function cmNoop() {
- var obj = {}, methods = 'add addMenu addSeparator collapse createMenu destroy displayColor expand focus ' +
- 'getLength hasMenus hideMenu isActive isCollapsed isDisabled isRendered isSelected mark ' +
- 'postRender remove removeAll renderHTML renderMenu renderNode renderTo select selectByIndex ' +
- 'setActive setAriaProperty setColor setDisabled setSelected setState showMenu update';
+ // If there's a string, then the length is always 2.
+ // Otherwise return the selected minutes.
+ return string ? 2 : _.lead( timeObject.mins )
+ },
+ a: function( string, timeObject ) {
- log('editor.controlManager.*');
+ // If there's a string, then the length is always 4.
+ // Otherwise check if it's more than "noon" and return either am/pm.
+ return string ? 4 : MINUTES_IN_DAY / 2 > timeObject.time % MINUTES_IN_DAY ? 'a.m.' : 'p.m.'
+ },
+ A: function( string, timeObject ) {
- function _noop() {
- return cmNoop();
- }
+ // If there's a string, then the length is always 2.
+ // Otherwise check if it's more than "noon" and return either am/pm.
+ return string ? 2 : MINUTES_IN_DAY / 2 > timeObject.time % MINUTES_IN_DAY ? 'AM' : 'PM'
+ },
- tinymce.each(methods.split(' '), function(method) {
- obj[method] = _noop;
- });
+ // Create an array by splitting the formatting string passed.
+ toArray: function( formatString ) { return formatString.split( /(h{1,2}|H{1,2}|i|a|A|!.)/g ) },
- return obj;
- }
+ // Format an object into a string using the formatting options.
+ toString: function ( formatString, itemObject ) {
+ var clock = this
+ return clock.formats.toArray( formatString ).map( function( label ) {
+ return _.trigger( clock.formats[ label ], clock, [ 0, itemObject ] ) || label.replace( /^!/, '' )
+ }).join( '' )
+ }
+} //TimePicker.prototype.formats
- editor.controlManager = {
- buttons: {},
- setDisabled: function(name, state) {
- log("controlManager.setDisabled(..)");
- if (this.buttons[name]) {
- this.buttons[name].disabled(state);
- }
- },
- setActive: function(name, state) {
- log("controlManager.setActive(..)");
+/**
+ * Check if two time units are the exact.
+ */
+TimePicker.prototype.isTimeExact = function( one, two ) {
- if (this.buttons[name]) {
- this.buttons[name].active(state);
- }
- },
+ var clock = this
- onAdd: new Dispatcher(),
- onPostRender: new Dispatcher(),
+ // When we’re working with minutes, do a direct comparison.
+ if (
+ ( _.isInteger( one ) && _.isInteger( two ) ) ||
+ ( typeof one == 'boolean' && typeof two == 'boolean' )
+ ) {
+ return one === two
+ }
- add: function(obj) {
- return obj;
- },
- createButton: cmNoop,
- createColorSplitButton: cmNoop,
- createControl: cmNoop,
- createDropMenu: cmNoop,
- createListBox: cmNoop,
- createMenuButton: cmNoop,
- createSeparator: cmNoop,
- createSplitButton: cmNoop,
- createToolbar: cmNoop,
- createToolbarGroup: cmNoop,
- destroy: noop,
- get: noop,
- setControlType: cmNoop
- };
+ // When we’re working with time representations, compare the “pick” value.
+ if (
+ ( _.isDate( one ) || $.isArray( one ) ) &&
+ ( _.isDate( two ) || $.isArray( two ) )
+ ) {
+ return clock.create( one ).pick === clock.create( two ).pick
+ }
- patchEditorEvents("PreInit BeforeRenderUI PostRender Load Init Remove Activate Deactivate", "editor");
- patchEditorEvents("Click MouseUp MouseDown DblClick KeyDown KeyUp KeyPress ContextMenu Paste Submit Reset");
- patchEditorEvents("BeforeExecCommand ExecCommand", "command ui value args"); // args.terminate not supported
- patchEditorEvents("PreProcess PostProcess LoadContent SaveContent Change");
- patchEditorEvents("BeforeSetContent BeforeGetContent SetContent GetContent", filterSelectionEvents(false));
- patchEditorEvents("SetProgressState", "state time");
- patchEditorEvents("VisualAid", "element hasVisual");
- patchEditorEvents("Undo Redo", convertUndoEventArgs);
+ // When we’re working with range objects, compare the “from” and “to”.
+ if ( $.isPlainObject( one ) && $.isPlainObject( two ) ) {
+ return clock.isTimeExact( one.from, two.from ) && clock.isTimeExact( one.to, two.to )
+ }
- patchEditorEvents("NodeChange", function(type, e) {
- return [
- editor.controlManager,
- e.element,
- editor.selection.isCollapsed(),
- e
- ];
- });
+ return false
+}
- var originalAddButton = editor.addButton;
- editor.addButton = function(name, settings) {
- var originalOnPostRender;
- function patchedPostRender() {
- editor.controlManager.buttons[name] = this;
+/**
+ * Check if two time units overlap.
+ */
+TimePicker.prototype.isTimeOverlap = function( one, two ) {
- if (originalOnPostRender) {
- return originalOnPostRender.call(this);
- }
- }
+ var clock = this
- for (var key in settings) {
- if (key.toLowerCase() === "onpostrender") {
- originalOnPostRender = settings[key];
- settings.onPostRender = patchedPostRender;
- }
- }
+ // When we’re working with an integer, compare the hours.
+ if ( _.isInteger( one ) && ( _.isDate( two ) || $.isArray( two ) ) ) {
+ return one === clock.create( two ).hour
+ }
+ if ( _.isInteger( two ) && ( _.isDate( one ) || $.isArray( one ) ) ) {
+ return two === clock.create( one ).hour
+ }
- if (!originalOnPostRender) {
- settings.onPostRender = patchedPostRender;
- }
+ // When we’re working with range objects, check if the ranges overlap.
+ if ( $.isPlainObject( one ) && $.isPlainObject( two ) ) {
+ return clock.overlapRanges( one, two )
+ }
- if (settings.title) {
- settings.title = tinymce.i18n.translate((editor.settings.language || "en") + "." + settings.title);
- }
+ return false
+}
- return originalAddButton.call(this, name, settings);
- };
- editor.on('init', function() {
- var undoManager = editor.undoManager, selection = editor.selection;
+/**
+ * Flip the “enabled” state.
+ */
+TimePicker.prototype.flipEnable = function(val) {
+ var itemObject = this.item
+ itemObject.enable = val || (itemObject.enable == -1 ? 1 : -1)
+}
- undoManager.onUndo = new Dispatcher(editor, "Undo", convertUndoEventArgs, null, undoManager);
- undoManager.onRedo = new Dispatcher(editor, "Redo", convertUndoEventArgs, null, undoManager);
- undoManager.onBeforeAdd = new Dispatcher(editor, "BeforeAddUndo", null, undoManager);
- undoManager.onAdd = new Dispatcher(editor, "AddUndo", null, undoManager);
- selection.onBeforeGetContent = new Dispatcher(editor, "BeforeGetContent", filterSelectionEvents(true), selection);
- selection.onGetContent = new Dispatcher(editor, "GetContent", filterSelectionEvents(true), selection);
- selection.onBeforeSetContent = new Dispatcher(editor, "BeforeSetContent", filterSelectionEvents(true), selection);
- selection.onSetContent = new Dispatcher(editor, "SetContent", filterSelectionEvents(true), selection);
- });
+/**
+ * Mark a collection of times as “disabled”.
+ */
+TimePicker.prototype.deactivate = function( type, timesToDisable ) {
- editor.on('BeforeRenderUI', function() {
- var windowManager = editor.windowManager;
+ var clock = this,
+ disabledItems = clock.item.disable.slice(0)
- windowManager.onOpen = new Dispatcher();
- windowManager.onClose = new Dispatcher();
- windowManager.createInstance = function(className, a, b, c, d, e) {
- log("windowManager.createInstance(..)");
- var constr = tinymce.resolve(className);
- return new constr(a, b, c, d, e);
- };
- });
- }
+ // If we’re flipping, that’s all we need to do.
+ if ( timesToDisable == 'flip' ) {
+ clock.flipEnable()
+ }
- tinymce.on('SetupEditor', patchEditor);
- tinymce.PluginManager.add("compat3x", patchEditor);
+ else if ( timesToDisable === false ) {
+ clock.flipEnable(1)
+ disabledItems = []
+ }
- tinymce.addI18n = function(prefix, o) {
- var I18n = tinymce.util.I18n, each = tinymce.each;
+ else if ( timesToDisable === true ) {
+ clock.flipEnable(-1)
+ disabledItems = []
+ }
- if (typeof prefix == "string" && prefix.indexOf('.') === -1) {
- I18n.add(prefix, o);
- return;
- }
+ // Otherwise go through the times to disable.
+ else {
- if (!tinymce.is(prefix, 'string')) {
- each(prefix, function(o, lc) {
- each(o, function(o, g) {
- each(o, function(o, k) {
- if (g === 'common') {
- I18n.data[lc + '.' + k] = o;
- } else {
- I18n.data[lc + '.' + g + '.' + k] = o;
- }
- });
- });
- });
- } else {
- each(o, function(o, k) {
- I18n.data[prefix + '.' + k] = o;
- });
- }
- };
-})(tinymce);
+ timesToDisable.map(function( unitToDisable ) {
+ var matchFound
- }).apply(root, arguments);
-});
-}(this));
+ // When we have disabled items, check for matches.
+ // If something is matched, immediately break out.
+ for ( var index = 0; index < disabledItems.length; index += 1 ) {
+ if ( clock.isTimeExact( unitToDisable, disabledItems[index] ) ) {
+ matchFound = true
+ break
+ }
+ }
-/* TinyMCE pattern.
- *
- * Options:
- * relatedItems(object): Related items pattern options. ({ attributes: ["UID", "Title", "Description", "getURL", "portal_type", "path", "ModificationDate"], batchSize: 20, basePath: "/", vocabularyUrl: null, width: 500, maximumSelectionSize: 1, placeholder: "Search for item on site..." })
- * upload(object): Upload pattern options. ({ attributes: look at upload pattern for getting the options list })
- * text(object): Translation strings ({ insertBtn: "Insert", cancelBtn: "Cancel", insertHeading: "Insert link", title: "Title", internal: "Internal", external: "External", email: "Email", anchor: "Anchor", subject: "Subject" image: "Image", imageAlign: "Align", scale: "Size", alt: "Alternative Text", externalImage: "External Image URI"})
- * scales(string): TODO: is this even used ('Listing (16x16):listing,Icon (32x32):icon,Tile (64x64):tile,Thumb (128x128):thumb,Mini (200x200):mini,Preview (400x400):preview,Large (768x768):large')
- * targetList(array): TODO ([ {text: "Open in this window / frame", value: ""}, {text: "Open in new window", value: "_blank"}, {text: "Open in parent window / frame", value: "_parent"}, {text: "Open in top frame (replaces all frames)", value: "_top"}])
- * imageTypes(string): TODO ('Image')
- * folderTypes(string): TODO ('Folder,Plone Site')
- * linkableTypes(string): TODO ('Document,Event,File,Folder,Image,News Item,Topic')
- * tiny(object): TODO ({ plugins: [ "advlist autolink lists charmap print preview anchor", "usearchreplace visualblocks code fullscreen autoresize", "insertdatetime media table contextmenu paste plonelink ploneimage" ], menubar: "edit table format tools view insert",
- toolbar: "undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | unlink plonelink ploneimage", autoresize_max_height: 1500 })
- * prependToUrl(string): Text to prepend to generated internal urls. ('')
- * appendToUrl(string): Text to append to generated internal urls. ('')
- * prependToScalePart(string): Text to prepend to generated image scale url part. ('/imagescale/')
- * appendToScalePart(string): Text to append to generated image scale url part. ('')
- * linkAttribute(string): Ajax response data attribute to use for url. ('path')
- * defaultScale(string): Scale name to default to. ('Original')
- * inline(boolean): Show tinyMCE editor inline instead in an iframe. Use this on textarea inputs. If you want to use this pattern directly on a contenteditable, pass "inline: true" to the "tiny" options object. (false)
- *
- * Documentation:
- * # Default
- *
- * {{ example-1 }}
- *
- * # With dropzone
- *
- * {{ example-2 }}
- *
- * # Inline editing
- *
- * {{ example-3 }}
- *
- * Example: example-1
- *
- *
- * Example: example-2
- *
- *
- * Example: example-3
- *
- *
+ // If nothing was found, add the validated unit to the collection.
+ if ( !matchFound ) {
+ if (
+ _.isInteger( unitToDisable ) ||
+ _.isDate( unitToDisable ) ||
+ $.isArray( unitToDisable ) ||
+ ( $.isPlainObject( unitToDisable ) && unitToDisable.from && unitToDisable.to )
+ ) {
+ disabledItems.push( unitToDisable )
+ }
+ }
+ })
+ }
+
+ // Return the updated collection.
+ return disabledItems
+} //TimePicker.prototype.deactivate
+
+
+/**
+ * Mark a collection of times as “enabled”.
*/
+TimePicker.prototype.activate = function( type, timesToEnable ) {
-define('mockup-patterns-tinymce',[
- 'jquery',
- 'underscore',
- 'pat-base',
- 'mockup-patterns-relateditems',
- 'mockup-patterns-modal',
- 'tinymce',
- 'mockup-patterns-autotoc',
- 'text!mockup-patterns-tinymce-url/templates/result.xml',
- 'text!mockup-patterns-tinymce-url/templates/selection.xml',
- 'mockup-utils',
- 'mockup-patterns-tinymce-url/js/links',
- 'mockup-i18n',
- 'translate',
- 'tinymce-modern-theme',
- 'tinymce-advlist',
- 'tinymce-anchor',
- 'tinymce-autolink',
- 'tinymce-autoresize',
- 'tinymce-autosave',
- 'tinymce-bbcode',
- 'tinymce-charmap',
- 'tinymce-code',
- 'tinymce-colorpicker',
- 'tinymce-contextmenu',
- 'tinymce-directionality',
- 'tinymce-emoticons',
- 'tinymce-fullpage',
- 'tinymce-fullscreen',
- 'tinymce-hr',
- 'tinymce-image',
- 'tinymce-importcss',
- 'tinymce-insertdatetime',
- 'tinymce-layer',
- 'tinymce-legacyoutput',
- 'tinymce-link',
- 'tinymce-lists',
- 'tinymce-media',
- 'tinymce-nonbreaking',
- 'tinymce-noneditable',
- 'tinymce-pagebreak',
- 'tinymce-paste',
- 'tinymce-preview',
- 'tinymce-print',
- 'tinymce-save',
- 'tinymce-searchreplace',
- 'tinymce-spellchecker',
- 'tinymce-tabfocus',
- 'tinymce-table',
- 'tinymce-template',
- 'tinymce-textcolor',
- 'tinymce-textpattern',
- 'tinymce-visualblocks',
- 'tinymce-visualchars',
- 'tinymce-wordcount',
- 'tinymce-compat3x'
-], function($, _,
- Base, RelatedItems, Modal, tinymce,
- AutoTOC, ResultTemplate, SelectionTemplate,
- utils, LinkModal, I18n, _t) {
- 'use strict';
+ var clock = this,
+ disabledItems = clock.item.disable,
+ disabledItemsCount = disabledItems.length
- var TinyMCE = Base.extend({
- name: 'tinymce',
- trigger: '.pat-tinymce',
- parser: 'mockup',
- defaults: {
- upload: {
- uploadMultiple: false,
- maxFiles: 1,
- showTitle: false
- },
- relatedItems: {
- // UID attribute is required here since we're working with related items
- attributes: ['UID', 'Title', 'Description', 'getURL', 'portal_type', 'path', 'ModificationDate', 'getIcon'],
- batchSize: 20,
- basePath: '/',
- vocabularyUrl: null,
- width: 500,
- maximumSelectionSize: 1,
- placeholder: _t('Search for item on site...')
- },
- text: {
- insertBtn: _t('Insert'), // so this can be configurable for different languages
- cancelBtn: _t('Cancel'),
- insertHeading: _t('Insert link'),
- title: _t('Title'),
- internal: _t('Internal'),
- external: _t('External URL (can be relative within this site or absolute if it starts with http:// or https://)'),
- email: _t('Email Address'),
- anchor: _t('Anchor'),
- subject: _t('Email Subject (optional)'),
- image: _t('Image'),
- imageAlign: _t('Align'),
- scale: _t('Size'),
- alt: _t('Alternative Text'),
- externalImage: _t('External Image URL (can be relative within this site or absolute if it starts with http:// or https://)')
- },
- // URL generation options
- loadingBaseUrl: '../../../bower_components/tinymce-builded/js/tinymce/',
- prependToUrl: '',
- appendToUrl: '',
- linkAttribute: 'path', // attribute to get link value from data
- prependToScalePart: '/imagescale/', // some value here is required to be able to parse scales back
- appendToScalePart: '',
- appendToOriginalScalePart: '',
- defaultScale: 'large',
- scales: _t('Listing (16x16):listing,Icon (32x32):icon,Tile (64x64):tile,' +
- 'Thumb (128x128):thumb,Mini (200x200):mini,Preview (400x400):preview,' +
- 'Large (768x768):large'),
- targetList: [
- {text: _t('Open in this window / frame'), value: ''},
- {text: _t('Open in new window'), value: '_blank'},
- {text: _t('Open in parent window / frame'), value: '_parent'},
- {text: _t('Open in top frame (replaces all frames)'), value: '_top'}
- ],
- imageTypes: ['Image'],
- folderTypes: ['Folder', 'Plone Site'],
- tiny: {
- 'content_css': '../../../bower_components/tinymce-builded/js/tinymce/skins/lightgray/content.min.css',
- theme: '-modern',
- plugins: ['advlist', 'autolink', 'lists', 'charmap', 'print', 'preview', 'anchor', 'searchreplace',
- 'visualblocks', 'code', 'fullscreen', 'insertdatetime', 'media', 'table', 'contextmenu',
- 'paste', 'plonelink', 'ploneimage'],
- menubar: 'edit table format tools view insert',
- toolbar: 'undo redo | styleselect | bold italic | ' +
- 'alignleft aligncenter alignright alignjustify | ' +
- 'bullist numlist outdent indent | ' +
- 'unlink plonelink ploneimage',
- //'autoresize_max_height': 900,
- 'height': 400
- },
- inline: false
- },
- addLinkClicked: function() {
- var self = this;
- if (self.linkModal === null) {
- var $el = $('').insertAfter(self.$el);
- var linkTypes = ['internal', 'upload', 'external', 'email', 'anchor'];
- if(!self.options.upload){
- linkTypes.splice(1, 1);
- }
- self.linkModal = new LinkModal($el,
- $.extend(true, {}, self.options, {
- tinypattern: self,
- linkTypes: linkTypes
- })
- );
- self.linkModal.show();
- } else {
- self.linkModal.reinitialize();
- self.linkModal.show();
- }
- },
- addImageClicked: function() {
- var self = this;
- if (self.imageModal === null) {
- var linkTypes = ['image', 'uploadImage', 'externalImage'];
- if(!self.options.upload){
- linkTypes.splice(1, 1);
- }
- var options = $.extend(true, {}, self.options, {
- tinypattern: self,
- linkTypes: linkTypes,
- initialLinkType: 'image',
- text: {
- insertHeading: _t('Insert Image')
- },
- relatedItems: {
- baseCriteria: [{
- i: 'portal_type',
- o: 'plone.app.querystring.operation.list.contains',
- v: self.options.imageTypes.concat(self.options.folderTypes)
- }],
- selectableTypes: self.options.imageTypes,
- resultTemplate: ResultTemplate,
- selectionTemplate: SelectionTemplate
- }
- });
- var $el = $('').insertAfter(self.$el);
- self.imageModal = new LinkModal($el, options);
- self.imageModal.show();
- } else {
- self.imageModal.reinitialize();
- self.imageModal.show();
- }
- },
- generateUrl: function(data) {
- var self = this;
- var part = data[self.options.linkAttribute];
- return self.options.prependToUrl + part + self.options.appendToUrl;
- },
- generateImageUrl: function(data, scale_name) {
- var self = this;
- var url = self.generateUrl(data);
- if (scale_name !== ''){
- var part = scale_name;
- for(var i=0; i 1){
- lang = lang.split('_')[0];
- } else if(lang.split('-') > 1){
- lang = lang.split('-')[0];
- }else {
- lang = lang + '_' + lang.toUpperCase();
+ // If we’re flipping, that’s all we need to do.
+ if ( timesToEnable == 'flip' ) {
+ clock.flipEnable()
+ }
+
+ else if ( timesToEnable === true ) {
+ clock.flipEnable(1)
+ disabledItems = []
+ }
+
+ else if ( timesToEnable === false ) {
+ clock.flipEnable(-1)
+ disabledItems = []
+ }
+
+ // Otherwise go through the disabled times.
+ else {
+
+ timesToEnable.map(function( unitToEnable ) {
+
+ var matchFound,
+ disabledUnit,
+ index,
+ isRangeMatched
+
+ // Go through the disabled items and try to find a match.
+ for ( index = 0; index < disabledItemsCount; index += 1 ) {
+
+ disabledUnit = disabledItems[index]
+
+ // When an exact match is found, remove it from the collection.
+ if ( clock.isTimeExact( disabledUnit, unitToEnable ) ) {
+ matchFound = disabledItems[index] = null
+ isRangeMatched = true
+ break
+ }
+
+ // When an overlapped match is found, add the “inverted” state to it.
+ else if ( clock.isTimeOverlap( disabledUnit, unitToEnable ) ) {
+ if ( $.isPlainObject( unitToEnable ) ) {
+ unitToEnable.inverted = true
+ matchFound = unitToEnable
+ }
+ else if ( $.isArray( unitToEnable ) ) {
+ matchFound = unitToEnable
+ if ( !matchFound[2] ) matchFound.push( 'inverted' )
+ }
+ else if ( _.isDate( unitToEnable ) ) {
+ matchFound = [ unitToEnable.getFullYear(), unitToEnable.getMonth(), unitToEnable.getDate(), 'inverted' ]
+ }
+ break
+ }
}
- $.ajax({
- url: tinymce.baseURL + '/langs/' + lang + '.js',
- method: 'GET',
- cache: 'true',
- success: function() {
- self.options.tiny.language = lang;
- call_back();
- },
- error: function() {
- call_back();
- }
- });
- }
- });
- } else {
- call_back();
- }
- },
- init: function() {
- var self = this;
- self.linkModal = self.imageModal = self.uploadModal = self.pasteModal = null;
- // tiny needs an id in order to initialize. Creat it if not set.
- var id = utils.setId(self.$el);
- var tinyOptions = self.options.tiny;
- if (self.options.inline === true) {
- self.options.tiny.inline = true;
- }
- self.tinyId = self.options.inline ? id + '-editable' : id; // when displaying TinyMCE inline, a separate div is created.
- tinyOptions.selector = '#' + self.tinyId;
- tinyOptions.addLinkClicked = function() {
- self.addLinkClicked.apply(self, []);
- };
- tinyOptions.addImageClicked = function(file) {
- self.addImageClicked.apply(self, [file] );
- };
- // XXX: disabled skin means it wont load css files which we already
- // include in widgets.min.css
- tinyOptions.skin = false;
- self.options.relatedItems.generateImageUrl = function(data, scale) {
- // this is so, in our result and selection template, we can
- // access getting actual urls from related items
- return self.generateImageUrl.apply(self, [data, scale]);
- };
- tinyOptions.init_instance_callback = function(editor) {
- if (self.tiny === undefined || self.tiny === null) {
- self.tiny = editor;
- }
- };
+ // If a match was found, remove a previous duplicate entry.
+ if ( matchFound ) for ( index = 0; index < disabledItemsCount; index += 1 ) {
+ if ( clock.isTimeExact( disabledItems[index], unitToEnable ) ) {
+ disabledItems[index] = null
+ break
+ }
+ }
- self.initLanguage(function() {
- if(typeof(self.options.scales) === 'string'){
- self.options.scales = _.map(self.options.scales.split(','), function(scale){
- var scale = scale.split(':');
- return {
- part: scale[1],
- name: scale[1],
- label: scale[0]
- };
- });
- }
- if(typeof(self.options.folderTypes) === 'string'){
- self.options.folderTypes = self.options.folderTypes.split(',');
- }
- if(typeof(self.options.imageTypes) === 'string'){
- self.options.imageTypes = self.options.imageTypes.split(',');
- }
+ // In the event that we’re dealing with an overlap of range times,
+ // make sure there are no “inverted” times because of it.
+ if ( isRangeMatched ) for ( index = 0; index < disabledItemsCount; index += 1 ) {
+ if ( clock.isTimeOverlap( disabledItems[index], unitToEnable ) ) {
+ disabledItems[index] = null
+ break
+ }
+ }
- if (self.options.inline === true) {
- // create a div, which will be made content-editable by TinyMCE and
- // copy contents from textarea to it. Then hide textarea.
- self.$el.after('' + self.$el.val() + '
');
- self.$el.hide();
- }
+ // If something is still matched, add it into the collection.
+ if ( matchFound ) {
+ disabledItems.push( matchFound )
+ }
+ })
+ }
- tinymce.init(tinyOptions);
- self.tiny = tinymce.get(self.tinyId);
+ // Return the updated collection.
+ return disabledItems.filter(function( val ) { return val != null })
+} //TimePicker.prototype.activate
- /* tiny really should be doing this by default
- * but this fixes overlays not saving data */
- var $form = self.$el.parents('form');
- $form.on('submit', function() {
- if (self.options.inline === true) {
- // save back from contenteditable to textarea
- self.$el.val(self.tiny.getContent());
- } else {
- // normal case
- self.tiny.save();
- }
- });
- });
- },
- destroy: function() {
- if (this.tiny) {
- if (this.options.inline === true) {
- // destroy also inline editable
- this.$el.val(this.tiny.getContent());
- $('#' + this.tinyId).remove();
- this.$el.show();
+
+/**
+ * The division to use for the range intervals.
+ */
+TimePicker.prototype.i = function( type, value/*, options*/ ) {
+ return _.isInteger( value ) && value > 0 ? value : this.item.interval
+}
+
+
+/**
+ * Create a string for the nodes in the picker.
+ */
+TimePicker.prototype.nodes = function( isOpen ) {
+
+ var
+ clock = this,
+ settings = clock.settings,
+ selectedObject = clock.item.select,
+ highlightedObject = clock.item.highlight,
+ viewsetObject = clock.item.view,
+ disabledCollection = clock.item.disable
+
+ return _.node(
+ 'ul',
+ _.group({
+ min: clock.item.min.pick,
+ max: clock.item.max.pick,
+ i: clock.item.interval,
+ node: 'li',
+ item: function( loopedTime ) {
+ loopedTime = clock.create( loopedTime )
+ var timeMinutes = loopedTime.pick,
+ isSelected = selectedObject && selectedObject.pick == timeMinutes,
+ isHighlighted = highlightedObject && highlightedObject.pick == timeMinutes,
+ isDisabled = disabledCollection && clock.disabled( loopedTime ),
+ formattedTime = _.trigger( clock.formats.toString, clock, [ settings.format, loopedTime ] )
+ return [
+ _.trigger( clock.formats.toString, clock, [ _.trigger( settings.formatLabel, clock, [ loopedTime ] ) || settings.format, loopedTime ] ),
+ (function( klasses ) {
+
+ if ( isSelected ) {
+ klasses.push( settings.klass.selected )
+ }
+
+ if ( isHighlighted ) {
+ klasses.push( settings.klass.highlighted )
+ }
+
+ if ( viewsetObject && viewsetObject.pick == timeMinutes ) {
+ klasses.push( settings.klass.viewset )
+ }
+
+ if ( isDisabled ) {
+ klasses.push( settings.klass.disabled )
+ }
+
+ return klasses.join( ' ' )
+ })( [ settings.klass.listItem ] ),
+ 'data-pick=' + loopedTime.pick + ' ' + _.ariaAttr({
+ role: 'option',
+ label: formattedTime,
+ selected: isSelected && clock.$node.val() === formattedTime ? true : null,
+ activedescendant: isHighlighted ? true : null,
+ disabled: isDisabled ? true : null
+ })
+ ]
+ }
+ }) +
+
+ // * For Firefox forms to submit, make sure to set the button’s `type` attribute as “button”.
+ _.node(
+ 'li',
+ _.node(
+ 'button',
+ settings.clear,
+ settings.klass.buttonClear,
+ 'type=button data-clear=1' + ( isOpen ? '' : ' disabled' ) + ' ' +
+ _.ariaAttr({ controls: clock.$node[0].id })
+ ),
+ '', _.ariaAttr({ role: 'presentation' })
+ ),
+ settings.klass.list,
+ _.ariaAttr({ role: 'listbox', controls: clock.$node[0].id })
+ )
+} //TimePicker.prototype.nodes
+
+
+
+
+
+
+
+/**
+ * Extend the picker to add the component with the defaults.
+ */
+TimePicker.defaults = (function( prefix ) {
+
+ return {
+
+ // Clear
+ clear: 'Clear',
+
+ // The format to show on the `input` element
+ format: 'h:i A',
+
+ // The interval between each time
+ interval: 30,
+
+ // Picker close behavior
+ closeOnSelect: true,
+ closeOnClear: true,
+
+ // Classes
+ klass: {
+
+ picker: prefix + ' ' + prefix + '--time',
+ holder: prefix + '__holder',
+
+ list: prefix + '__list',
+ listItem: prefix + '__list-item',
+
+ disabled: prefix + '__list-item--disabled',
+ selected: prefix + '__list-item--selected',
+ highlighted: prefix + '__list-item--highlighted',
+ viewset: prefix + '__list-item--viewset',
+ now: prefix + '__list-item--now',
+
+ buttonClear: prefix + '__button--clear'
}
- this.tiny.destroy();
- this.tiny = undefined;
- }
}
- });
+})( Picker.klasses().picker )
- return TinyMCE;
-});
-/* TextareaMimetypeSelector pattern.
- *
+
+
+/**
+ * Extend the picker to add the time picker.
+ */
+Picker.extend( 'pickatime', TimePicker )
+
+
+}));
+
+
+
+
+/* PickADate pattern.
*
* Options:
- * textareaName(string): Value of name attribute of the textarea ('')
- * widgets(object): MimeType/PatternConfig pairs ({'text/html': {pattern: 'tinymce', patternOptions: {}}})
- *
+ * date(object): Date widget options described here. If false is selected date picker wont be shown. ({{selectYears: true, selectMonths: true })
+ * time(object): Time widget options described here. If false is selected time picker wont be shown. ({})
+ * separator(string): Separator between date and time if both are enabled.
+ * (' ')
+ * classClearName(string): Class name of element that is generated by pattern. ('pattern-pickadate-clear')
+ * classDateName(string): Class applied to date input. ('pattern-pickadate-date')
+ * classDateWrapperName(string): Class applied to extra wrapper div around date input. ('pattern-pickadate-date-wrapper')
+ * classSeparatorName(string): Class applied to separator. ('pattern-pickadate-separator')
+ * classTimeName(string): Class applied to time input. ('pattern-pickadate-time')
+ * classTimeWrapperName(string): Class applied to wrapper div around time input. ('pattern-pickadate-time-wrapper')
+ * classTimezoneName(string): Class applied to timezone input. ('pattern-pickadate-timezone')
+ * classTimezoneWrapperName(string): Class applied to wrapper div around timezone input. ('pattern-pickadate-timezone-wrapper')
+ * classWrapperName(string): Class name of element that is generated by pattern. ('pattern-pickadate-wrapper')
*
* Documentation:
- * # General
+ * # Date and Time
*
- * This pattern displays a mimetype selection widget for textareas. It
- * switches the widget according to the selected mimetype.
+ * {{ example-1 }}
*
- * ## widgets option Structure
+ * # Date and Time with initial data
*
- * Complex Object/JSON structure with MimeType/PatternConfig pairs. The
- * MimeType is a string like "text/html". The PatternConfig is a object with
- * a "pattern" and an optional "patternOptions" attribute. The "pattern"
- * attribute's value is a string with the patterns name and the
- * "patternOptions" attribute is a object with whatever options the pattern
- * needs. For example, to use the TinyMCE pattern for the HTML mimetype, use
- * "text/html": {"pattern": "tinymce"}
+ * {{ example-2 }}
*
- * # Mimetype selection on textarea including text/html mimetype with TinyMCE editor.
+ * # Date
*
- * {{ example-1 }}
+ * {{ example-3 }}
*
- * # Mimetype selection on textarea with inline TinyMCE editor.
+ * # Date with initial date
*
- * {{ example-2 }}
+ * {{ example-4 }}
*
- * Example: example-1
- *
- *
+ * # Time
+ *
+ * {{ example-5 }}
+ *
+ * # Time with initial time
+ *
+ * {{ example-6 }}
+ *
+ * # Date and time with timezone
+ *
+ * {{ example-7 }}
+ *
+ * # Date and time with timezone and default value
+ *
+ * {{ example-8 }}
+ *
+ * # Date and time with one timezone
+ *
+ * {{ example-9 }}
+ *
+ * Example: example-1
+ *
*
* Example: example-2
- *
- *
+ *
+ *
+ * Example: example-3
+ *
+ *
+ * Example: example-4
+ *
+ *
+ * Example: example-5
+ *
+ *
+ * Example: example-6
+ *
+ *
+ * Example: example-7
+ *
+ *
+ * Example: example-8
+ *
+ *
+ * Example: example-9
+ *
*
*/
-define('mockup-patterns-textareamimetypeselector',[
+
+define('mockup-patterns-pickadate',[
'jquery',
'pat-base',
- 'pat-registry',
- 'mockup-patterns-tinymce'
-], function ($, Base, registry, tinymce) {
+ 'picker',
+ 'picker.date',
+ 'picker.time',
+ 'mockup-patterns-select2',
+ 'translate'
+], function($, Base, Picker, PickerDate, PickerTime, Select2, _t) {
'use strict';
- var TextareaMimetypeSelector = Base.extend({
- name: 'textareamimetypeselector',
- trigger: '.pat-textareamimetypeselector',
+ var PickADate = Base.extend({
+ name: 'pickadate',
+ trigger: '.pat-pickadate',
parser: 'mockup',
- textarea: undefined,
- currentWidget: undefined,
defaults: {
- textareaName: '',
- widgets: {'text/html': {pattern: 'tinymce', patternOptions: {}}}
+ separator: ' ',
+ date: {
+ selectYears: true,
+ selectMonths: true,
+ formatSubmit: 'yyyy-mm-dd',
+ format: 'yyyy-mm-dd',
+ clear: _t('Clear'),
+ close: _t('Close'),
+ today: _t('Today'),
+ labelMonthNext: _t('Next month'),
+ labelMonthPrev: _t('Previous month'),
+ labelMonthSelect: _t('Select a month'),
+ labelYearSelect: _t('Select a year')
+ },
+ time: {},
+ timezone: null,
+ classWrapperName: 'pattern-pickadate-wrapper',
+ classSeparatorName: 'pattern-pickadate-separator',
+ classDateName: 'pattern-pickadate-date',
+ classDateWrapperName: 'pattern-pickadate-date-wrapper',
+ classTimeName: 'pattern-pickadate-time',
+ classTimeWrapperName: 'pattern-pickadate-time-wrapper',
+ classTimezoneName: 'pattern-pickadate-timezone',
+ classTimezoneWrapperName: 'pattern-pickadate-timezone-wrapper',
+ classClearName: 'pattern-pickadate-clear',
+ placeholderDate: _t('Enter date...'),
+ placeholderTime: _t('Enter time...'),
+ placeholderTimezone: _t('Enter timezone...')
},
- init: function () {
+ isFalse: function(value) {
+ if (typeof(value) === 'string' && value === 'false') {
+ return false;
+ }
+ return value;
+ },
+ init: function() {
var self = this,
- $el = self.$el,
- current;
- self.textarea = $('[name="' + self.options.textareaName + '"]');
- $el.change(function (e) {
- self.initTextarea(e.target.value);
- });
- self.initTextarea($el.val());
+ value = self.$el.val().split(' '),
+ dateValue = value[0] || '',
+ timeValue = value[1] || '';
+
+ self.options.date = self.isFalse(self.options.date);
+ self.options.time = self.isFalse(self.options.time);
+
+ if (self.options.date === false) {
+ timeValue = value[0];
+ }
+
+ self.$el.hide();
+
+ self.$wrapper = $('')
+ .addClass(self.options.classWrapperName)
+ .insertAfter(self.$el);
+
+ if (self.options.date !== false) {
+ self.$date = $('')
+ .attr('placeholder', self.options.placeholderDate)
+ .attr('data-value', dateValue)
+ .addClass(self.options.classDateName)
+ .appendTo($('')
+ .addClass(self.options.classDateWrapperName)
+ .appendTo(self.$wrapper))
+ .pickadate($.extend(true, {}, self.options.date, {
+ onSet: function(e) {
+ if (e.select !== undefined) {
+ self.$date.attr('data-value', e.select);
+ if (self.options.time === false ||
+ self.$time.attr('data-value') !== '') {
+ self.updateValue.call(self);
+ }
+ }
+ if (e.hasOwnProperty('clear')) {
+ self.$el.val('');
+ self.$date.attr('data-value', '');
+ }
+ }
+ }));
+ }
+
+ if (self.options.date !== false && self.options.time !== false) {
+ self.$separator = $('')
+ .addClass(self.options.classSeparatorName)
+ .html(self.options.separator === ' ' ? ' '
+ : self.options.separator)
+ .appendTo(self.$wrapper);
+ }
+
+ if (self.options.time !== false) {
+ self.options.time.formatSubmit = 'HH:i';
+ self.$time = $('')
+ .attr('placeholder', self.options.placeholderTime)
+ .attr('data-value', timeValue)
+ .addClass(self.options.classTimeName)
+ .appendTo($('')
+ .addClass(self.options.classTimeWrapperName)
+ .appendTo(self.$wrapper))
+ .pickatime($.extend(true, {}, self.options.time, {
+ onSet: function(e) {
+ if (e.select !== undefined) {
+ self.$time.attr('data-value', e.select);
+ if (self.options.date === false ||
+ self.$date.attr('data-value') !== '') {
+ self.updateValue.call(self);
+ }
+ }
+ if (e.hasOwnProperty('clear')) {
+ self.$el.val('');
+ self.$time.attr('data-value', '');
+ }
+ }
+ }));
+
+ // XXX: bug in pickatime
+ // work around pickadate bug loading 00:xx as value
+ if (typeof(timeValue) === 'string' && timeValue.substring(0,2) === '00') {
+ self.$time.pickatime('picker').set('select', timeValue.split(':'));
+ self.$time.attr('data-value', timeValue);
+ }
+ }
+
+ if (self.options.date !== false && self.options.time !== false && self.options.timezone) {
+ self.$separator = $('')
+ .addClass(self.options.classSeparatorName)
+ .html(self.options.separator === ' ' ? ' '
+ : self.options.separator)
+ .appendTo(self.$wrapper);
+ }
+
+ if (self.options.timezone !== null) {
+ self.$timezone = $('')
+ .addClass(self.options.classTimezoneName)
+ .appendTo($('')
+ .addClass(self.options.classTimezoneWrapperName)
+ .appendTo(self.$wrapper))
+ .patternSelect2($.extend(true,
+ {
+ 'placeholder': self.options.placeholderTimezone,
+ 'width': '10em',
+ },
+ self.options.timezone,
+ { 'multiple': false }))
+ .on('change', function(e) {
+ if (e.val !== undefined){
+ self.$timezone.attr('data-value', e.val);
+ if ((self.options.date === false || self.$date.attr('data-value') !== '') &&
+ (self.options.time === false || self.$time.attr('data-value') !== '')) {
+ self.updateValue.call(self);
+ }
+ }
+ });
+ var defaultTimezone = self.options.timezone.default;
+ // if timezone has a default value included
+ if (defaultTimezone) {
+ var isInList;
+ // the timezone list contains the default value
+ self.options.timezone.data.some(function(obj) {
+ isInList = (obj.text === self.options.timezone.default) ? true : false;
+ return isInList;
+ });
+ if (isInList) {
+ self.$timezone.attr('data-value', defaultTimezone);
+ self.$timezone.parent().find('.select2-chosen').text(defaultTimezone);
+ }
+ }
+ // if data contains only one timezone this value will be chosen
+ // and the timezone dropdown list will be disabled and
+ if (self.options.timezone.data.length === 1) {
+ self.$timezone.attr('data-value', self.options.timezone.data[0].text);
+ self.$timezone.parent().find('.select2-chosen').text(self.options.timezone.data[0].text);
+ self.$timezone.select2('enable', false);
+ }
+ }
+
+ self.$clear = $('')
+ .addClass(self.options.classClearName)
+ .appendTo(self.$wrapper);
},
- initTextarea: function (mimetype) {
+ updateValue: function() {
var self = this,
- patternConfig = self.options.widgets[mimetype],
- pattern;
- // First, destroy current
- if (self.currentWidget) {
- // The pattern must implement the destroy method.
- self.currentWidget.destroy();
+ value = '';
+
+ if (self.options.date !== false) {
+ var date = self.$date.data('pickadate').component,
+ dateValue = self.$date.data('pickadate').get('select'),
+ formatDate = date.formats.toString;
+ if (dateValue) {
+ value += formatDate.apply(date, [self.options.date.formatSubmit, dateValue]);
+ }
}
- // Then, setup new
- if (patternConfig) {
- pattern = new registry.patterns[patternConfig.pattern](
- self.textarea,
- patternConfig.patternOptions || {}
- );
- self.currentWidget = pattern;
+
+ if (self.options.date !== false && self.options.time !== false) {
+ value += ' ';
+ }
+
+ if (self.options.time !== false) {
+ var time = self.$time.data('pickatime').component,
+ timeValue = self.$time.data('pickatime').get('select'),
+ formatTime = time.formats.toString;
+ if (timeValue) {
+ value += formatTime.apply(time, ['HH:i', timeValue]);
+ }
+ }
+
+ if (self.options.timezone !== null) {
+ var timezone = ' ' + self.$timezone.attr('data-value');
+ if (timezone) {
+ value += timezone;
+ }
}
- }
+ self.$el.val(value);
+
+ self.emit('updated');
+ }
});
- return TextareaMimetypeSelector;
+ return PickADate;
+
});
/* Querystring pattern.
@@ -65006,6 +81221,176 @@ define('mockup-patterns-structure-url/js/models/result',['backbone'], function(B
define('text!mockup-patterns-structure-url/templates/actionmenu.xml',[],function () { return '\n \n \n\n\n\n';});
+(function(root) {
+define("bootstrap-dropdown", ["jquery"], function() {
+ return (function() {
+/* ========================================================================
+ * Bootstrap: dropdown.js v3.3.4
+ * http://getbootstrap.com/javascript/#dropdowns
+ * ========================================================================
+ * Copyright 2011-2015 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+ 'use strict';
+
+ // DROPDOWN CLASS DEFINITION
+ // =========================
+
+ var backdrop = '.dropdown-backdrop'
+ var toggle = '[data-toggle="dropdown"]'
+ var Dropdown = function (element) {
+ $(element).on('click.bs.dropdown', this.toggle)
+ }
+
+ Dropdown.VERSION = '3.3.4'
+
+ Dropdown.prototype.toggle = function (e) {
+ var $this = $(this)
+
+ if ($this.is('.disabled, :disabled')) return
+
+ var $parent = getParent($this)
+ var isActive = $parent.hasClass('open')
+
+ clearMenus()
+
+ if (!isActive) {
+ if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) {
+ // if mobile we use a backdrop because click events don't delegate
+ $('').insertAfter($(this)).on('click', clearMenus)
+ }
+
+ var relatedTarget = { relatedTarget: this }
+ $parent.trigger(e = $.Event('show.bs.dropdown', relatedTarget))
+
+ if (e.isDefaultPrevented()) return
+
+ $this
+ .trigger('focus')
+ .attr('aria-expanded', 'true')
+
+ $parent
+ .toggleClass('open')
+ .trigger('shown.bs.dropdown', relatedTarget)
+ }
+
+ return false
+ }
+
+ Dropdown.prototype.keydown = function (e) {
+ if (!/(38|40|27|32)/.test(e.which) || /input|textarea/i.test(e.target.tagName)) return
+
+ var $this = $(this)
+
+ e.preventDefault()
+ e.stopPropagation()
+
+ if ($this.is('.disabled, :disabled')) return
+
+ var $parent = getParent($this)
+ var isActive = $parent.hasClass('open')
+
+ if ((!isActive && e.which != 27) || (isActive && e.which == 27)) {
+ if (e.which == 27) $parent.find(toggle).trigger('focus')
+ return $this.trigger('click')
+ }
+
+ var desc = ' li:not(.disabled):visible a'
+ var $items = $parent.find('[role="menu"]' + desc + ', [role="listbox"]' + desc)
+
+ if (!$items.length) return
+
+ var index = $items.index(e.target)
+
+ if (e.which == 38 && index > 0) index-- // up
+ if (e.which == 40 && index < $items.length - 1) index++ // down
+ if (!~index) index = 0
+
+ $items.eq(index).trigger('focus')
+ }
+
+ function clearMenus(e) {
+ if (e && e.which === 3) return
+ $(backdrop).remove()
+ $(toggle).each(function () {
+ var $this = $(this)
+ var $parent = getParent($this)
+ var relatedTarget = { relatedTarget: this }
+
+ if (!$parent.hasClass('open')) return
+
+ $parent.trigger(e = $.Event('hide.bs.dropdown', relatedTarget))
+
+ if (e.isDefaultPrevented()) return
+
+ $this.attr('aria-expanded', 'false')
+ $parent.removeClass('open').trigger('hidden.bs.dropdown', relatedTarget)
+ })
+ }
+
+ function getParent($this) {
+ var selector = $this.attr('data-target')
+
+ if (!selector) {
+ selector = $this.attr('href')
+ selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
+ }
+
+ var $parent = selector && $(selector)
+
+ return $parent && $parent.length ? $parent : $this.parent()
+ }
+
+
+ // DROPDOWN PLUGIN DEFINITION
+ // ==========================
+
+ function Plugin(option) {
+ return this.each(function () {
+ var $this = $(this)
+ var data = $this.data('bs.dropdown')
+
+ if (!data) $this.data('bs.dropdown', (data = new Dropdown(this)))
+ if (typeof option == 'string') data[option].call($this)
+ })
+ }
+
+ var old = $.fn.dropdown
+
+ $.fn.dropdown = Plugin
+ $.fn.dropdown.Constructor = Dropdown
+
+
+ // DROPDOWN NO CONFLICT
+ // ====================
+
+ $.fn.dropdown.noConflict = function () {
+ $.fn.dropdown = old
+ return this
+ }
+
+
+ // APPLY TO STANDARD DROPDOWN ELEMENTS
+ // ===================================
+
+ $(document)
+ .on('click.bs.dropdown.data-api', clearMenus)
+ .on('click.bs.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() })
+ .on('click.bs.dropdown.data-api', toggle, Dropdown.prototype.toggle)
+ .on('keydown.bs.dropdown.data-api', toggle, Dropdown.prototype.keydown)
+ .on('keydown.bs.dropdown.data-api', '[role="menu"]', Dropdown.prototype.keydown)
+ .on('keydown.bs.dropdown.data-api', '[role="listbox"]', Dropdown.prototype.keydown)
+
+}(jQuery);
+
+
+ }).apply(root, arguments);
+});
+}(this));
+
define('mockup-patterns-structure-url/js/views/actionmenu',[
'jquery',
'underscore',
@@ -65203,7 +81588,7 @@ define('mockup-patterns-structure-url/js/views/actionmenu',[
});
-define('text!mockup-patterns-structure-url/templates/tablerow.xml',[],function () { return ' checked="checked" <% } %>/> | \n\n <%- Title %>\n \n | \n<% _.each(activeColumns, function(column){ %>\n <% if(_.has(availableColumns, column)) { %>\n <%- attributes[column] %> | \n <% } %>\n<% }); %>\n\n';});
+define('text!mockup-patterns-structure-url/templates/tablerow.xml',[],function () { return ' checked="checked" <% } %>/> | \n\n\n \n <% if(attributes["getIcon"] ){ %> <% } %>\n <%- Title %>\n \n | \n<% _.each(activeColumns, function(column){ %>\n <% if(_.has(availableColumns, column)) { %> \n <%- attributes[column] %> | \n <% } %>\n<% }); %>\n\n';});
define('mockup-patterns-structure-url/js/views/tablerow',[
'jquery',
@@ -68016,6 +84401,124 @@ define('mockup-patterns-structure-url/js/collections/selected',[
});
+/*!
+ * jQuery Cookie Plugin v1.4.1
+ * https://github.com/carhartl/jquery-cookie
+ *
+ * Copyright 2013 Klaus Hartl
+ * Released under the MIT license
+ */
+(function (factory) {
+ if (typeof define === 'function' && define.amd) {
+ // AMD
+ define('jquery.cookie',['jquery'], factory);
+ } else if (typeof exports === 'object') {
+ // CommonJS
+ factory(require('jquery'));
+ } else {
+ // Browser globals
+ factory(jQuery);
+ }
+}(function ($) {
+
+ var pluses = /\+/g;
+
+ function encode(s) {
+ return config.raw ? s : encodeURIComponent(s);
+ }
+
+ function decode(s) {
+ return config.raw ? s : decodeURIComponent(s);
+ }
+
+ function stringifyCookieValue(value) {
+ return encode(config.json ? JSON.stringify(value) : String(value));
+ }
+
+ function parseCookieValue(s) {
+ if (s.indexOf('"') === 0) {
+ // This is a quoted cookie as according to RFC2068, unescape...
+ s = s.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, '\\');
+ }
+
+ try {
+ // Replace server-side written pluses with spaces.
+ // If we can't decode the cookie, ignore it, it's unusable.
+ // If we can't parse the cookie, ignore it, it's unusable.
+ s = decodeURIComponent(s.replace(pluses, ' '));
+ return config.json ? JSON.parse(s) : s;
+ } catch(e) {}
+ }
+
+ function read(s, converter) {
+ var value = config.raw ? s : parseCookieValue(s);
+ return $.isFunction(converter) ? converter(value) : value;
+ }
+
+ var config = $.cookie = function (key, value, options) {
+
+ // Write
+
+ if (value !== undefined && !$.isFunction(value)) {
+ options = $.extend({}, config.defaults, options);
+
+ if (typeof options.expires === 'number') {
+ var days = options.expires, t = options.expires = new Date();
+ t.setTime(+t + days * 864e+5);
+ }
+
+ return (document.cookie = [
+ encode(key), '=', stringifyCookieValue(value),
+ options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE
+ options.path ? '; path=' + options.path : '',
+ options.domain ? '; domain=' + options.domain : '',
+ options.secure ? '; secure' : ''
+ ].join(''));
+ }
+
+ // Read
+
+ var result = key ? undefined : {};
+
+ // To prevent the for loop in the first place assign an empty array
+ // in case there are no cookies at all. Also prevents odd result when
+ // calling $.cookie().
+ var cookies = document.cookie ? document.cookie.split('; ') : [];
+
+ for (var i = 0, l = cookies.length; i < l; i++) {
+ var parts = cookies[i].split('=');
+ var name = decode(parts.shift());
+ var cookie = parts.join('=');
+
+ if (key && key === name) {
+ // If second argument (value) is a function it's a converter...
+ result = read(cookie, value);
+ break;
+ }
+
+ // Prevent storing a cookie that we couldn't decode.
+ if (!key && (cookie = read(cookie)) !== undefined) {
+ result[name] = cookie;
+ }
+ }
+
+ return result;
+ };
+
+ config.defaults = {};
+
+ $.removeCookie = function (key, options) {
+ if ($.cookie(key) === undefined) {
+ return false;
+ }
+
+ // Must not alter options, thus extending a fresh object...
+ $.cookie(key, '', $.extend({}, options, { expires: -1 }));
+ return !$.cookie(key);
+ };
+
+}));
+
define('mockup-patterns-structure-url/js/views/app',[
'jquery',
'underscore',
@@ -68518,7 +85021,7 @@ define('mockup-patterns-structure-url/js/views/app',[
* moveUrl:/moveitem;
* indexOptionsUrl:/tests/json/queryStringCriteria.json;
* contextInfoUrl:{path}/context-info;">
- *
+ *
*/
@@ -68552,7 +85055,7 @@ define('mockup-patterns-structure',[
'UID', 'Title', 'portal_type', 'path', 'review_state',
'ModificationDate', 'EffectiveDate', 'CreationDate',
'is_folderish', 'Subject', 'getURL', 'id', 'exclude_from_nav',
- 'getObjSize', 'last_comment_date', 'total_comments'
+ 'getObjSize', 'last_comment_date', 'total_comments','getIcon'
],
activeColumns: [
'ModificationDate',
@@ -72643,6 +89146,38 @@ define('mockup-patterns-recurrence',[
});
+/* Mockup shim of the Patternslib Base Pattern
+ */
+
+define('mockup-patterns-base',[
+ 'jquery',
+ 'pat-base',
+], function($, Base) {
+ 'use strict';
+
+ var MockupBase = function MockupBaseWrapper() {
+ return Base.apply(this, arguments);
+ };
+ for (var key in Base) {
+ if (Base.hasOwnProperty(key)) {
+ MockupBase[key] = Base[key];
+ }
+ }
+ MockupBase.prototype = Base.prototype;
+ MockupBase.prototype.constructor = MockupBase;
+
+ MockupBase.extend = function() {
+ console.log(
+ "Usage of the mockup-patterns-base pattern is deprecated and it will eventually be removed."+
+ "Instead, use pat-base and explicitly set parser to 'mockup' when calling extend.");
+ var child = Base.extend.apply(this, arguments);
+ child.prototype.parser = 'mockup';
+ return child;
+ };
+
+ return MockupBase;
+});
+
define('plone-patterns-portletmanager',[
'jquery',
'mockup-patterns-base',
@@ -72867,5 +89402,5 @@ require([
'use strict';
});
-define("/Users/nathan/code/coredev5/src/Products.CMFPlone/Products/CMFPlone/static/plone-logged-in.js", function(){});
+define("/usr/local/p5dev/buildout.coredev/src/Products.CMFPlone/Products/CMFPlone/static/plone-logged-in.js", function(){});
diff --git a/Products/CMFPlone/static/plone-logged-in-compiled.min.js b/Products/CMFPlone/static/plone-logged-in-compiled.min.js
index d03017c860..70bab083df 100644
--- a/Products/CMFPlone/static/plone-logged-in-compiled.min.js
+++ b/Products/CMFPlone/static/plone-logged-in-compiled.min.js
@@ -1,40 +1,37 @@
-!function(a){define("jqtree",["jquery"],function(){return function(){(function(){var a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z=[].slice,A={}.hasOwnProperty,B=function(a,b){function c(){this.constructor=a}for(var d in b)A.call(b,d)&&(a[d]=b[d]);return c.prototype=b.prototype,a.prototype=new c,a.__super__=b.prototype,a};x="0.22.0",a=this.jQuery,r=function(){function b(b,c){this.$el=a(b),this.options=a.extend({},this.defaults,c)}return b.prototype.defaults={},b.prototype.destroy=function(){return this._deinit()},b.prototype._init=function(){return null},b.prototype._deinit=function(){return null},b.register=function(c,d){var e,f,g,h,i;return h=function(){return"simple_widget_"+d},i=function(c,d){var e;return e=a.data(c,d),e&&e instanceof b?e:null},f=function(b,d){var e,f,g,j,k,l;for(e=h(),k=0,l=b.length;l>k;k++)f=b[k],g=i(f,e),g||(j=new c(f,d),a.data(f,e)||a.data(f,e,j),j._init());return b},g=function(b){var c,d,e,f,g,j;for(c=h(),j=[],f=0,g=b.length;g>f;f++)d=b[f],e=i(d,c),e&&e.destroy(),j.push(a.removeData(d,c));return j},e=function(c,d,e){var f,g,i,j,k,l;for(g=null,k=0,l=c.length;l>k;k++)f=c[k],i=a.data(f,h()),i&&i instanceof b&&(j=i[d],j&&"function"==typeof j&&(g=j.apply(i,e)));return g},a.fn[d]=function(){var a,b,c,d,h;return c=arguments[0],b=2<=arguments.length?z.call(arguments,1):[],a=this,void 0===c||"object"==typeof c?(h=c,f(a,h)):"string"==typeof c&&"_"!==c[0]?(d=c,"destroy"===d?g(a):e(a,d,b)):void 0}},b}(),this.SimpleWidget=r,k=function(b){function c(){return c.__super__.constructor.apply(this,arguments)}return B(c,b),c.is_mouse_handled=!1,c.prototype._init=function(){return this.$el.bind("mousedown.mousewidget",a.proxy(this._mouseDown,this)),this.$el.bind("touchstart.mousewidget",a.proxy(this._touchStart,this)),this.is_mouse_started=!1,this.mouse_delay=0,this._mouse_delay_timer=null,this._is_mouse_delay_met=!0,this.mouse_down_info=null},c.prototype._deinit=function(){var b;return this.$el.unbind("mousedown.mousewidget"),this.$el.unbind("touchstart.mousewidget"),b=a(document),b.unbind("mousemove.mousewidget"),b.unbind("mouseup.mousewidget")},c.prototype._mouseDown=function(a){var b;if(1===a.which)return b=this._handleMouseDown(a,this._getPositionInfo(a)),b&&a.preventDefault(),b},c.prototype._handleMouseDown=function(a,b){return!c.is_mouse_handled&&(this.is_mouse_started&&this._handleMouseUp(b),this.mouse_down_info=b,this._mouseCapture(b))?(this._handleStartMouse(),this.is_mouse_handled=!0,!0):void 0},c.prototype._handleStartMouse=function(){var b;return b=a(document),b.bind("mousemove.mousewidget",a.proxy(this._mouseMove,this)),b.bind("touchmove.mousewidget",a.proxy(this._touchMove,this)),b.bind("mouseup.mousewidget",a.proxy(this._mouseUp,this)),b.bind("touchend.mousewidget",a.proxy(this._touchEnd,this)),this.mouse_delay?this._startMouseDelayTimer():void 0},c.prototype._startMouseDelayTimer=function(){return this._mouse_delay_timer&&clearTimeout(this._mouse_delay_timer),this._mouse_delay_timer=setTimeout(function(a){return function(){return a._is_mouse_delay_met=!0}}(this),this.mouse_delay),this._is_mouse_delay_met=!1},c.prototype._mouseMove=function(a){return this._handleMouseMove(a,this._getPositionInfo(a))},c.prototype._handleMouseMove=function(a,b){return this.is_mouse_started?(this._mouseDrag(b),a.preventDefault()):this.mouse_delay&&!this._is_mouse_delay_met?!0:(this.is_mouse_started=this._mouseStart(this.mouse_down_info)!==!1,this.is_mouse_started?this._mouseDrag(b):this._handleMouseUp(b),!this.is_mouse_started)},c.prototype._getPositionInfo=function(a){return{page_x:a.pageX,page_y:a.pageY,target:a.target,original_event:a}},c.prototype._mouseUp=function(a){return this._handleMouseUp(this._getPositionInfo(a))},c.prototype._handleMouseUp=function(b){var c;c=a(document),c.unbind("mousemove.mousewidget"),c.unbind("touchmove.mousewidget"),c.unbind("mouseup.mousewidget"),c.unbind("touchend.mousewidget"),this.is_mouse_started&&(this.is_mouse_started=!1,this._mouseStop(b))},c.prototype._mouseCapture=function(a){return!0},c.prototype._mouseStart=function(a){return null},c.prototype._mouseDrag=function(a){return null},c.prototype._mouseStop=function(a){return null},c.prototype.setMouseDelay=function(a){return this.mouse_delay=a},c.prototype._touchStart=function(a){var b;if(!(a.originalEvent.touches.length>1))return b=a.originalEvent.changedTouches[0],this._handleMouseDown(a,this._getPositionInfo(b))},c.prototype._touchMove=function(a){var b;if(!(a.originalEvent.touches.length>1))return b=a.originalEvent.changedTouches[0],this._handleMouseMove(a,this._getPositionInfo(b))},c.prototype._touchEnd=function(a){var b;if(!(a.originalEvent.touches.length>1))return b=a.originalEvent.changedTouches[0],this._handleMouseUp(this._getPositionInfo(b))},c}(r),this.Tree={},a=this.jQuery,n={getName:function(a){return n.strings[a-1]},nameToIndex:function(a){var b,c,d;for(b=c=1,d=n.strings.length;d>=1?d>=c:c>=d;b=d>=1?++c:--c)if(n.strings[b-1]===a)return b;return 0}},n.BEFORE=1,n.AFTER=2,n.INSIDE=3,n.NONE=4,n.strings=["before","after","inside","none"],this.Tree.Position=n,l=function(){function b(a,c,d){null==c&&(c=!1),null==d&&(d=b),this.setData(a),this.children=[],this.parent=null,c&&(this.id_mapping={},this.tree=this,this.node_class=d)}return b.prototype.setData=function(a){var b,c,d;if("object"!=typeof a)return this.name=a;d=[];for(b in a)c=a[b],d.push("label"===b?this.name=c:this[b]=c);return d},b.prototype.initFromData=function(a){var b,c;return c=function(a){return function(c){return a.setData(c),c.children?b(c.children):void 0}}(this),b=function(a){return function(b){var c,d,e,f;for(e=0,f=b.length;f>e;e++)c=b[e],d=new a.tree.node_class(""),d.initFromData(c),a.addChild(d);return null}}(this),c(a),null},b.prototype.loadFromData=function(a){var b,c,d,e;for(this.removeChildren(),d=0,e=a.length;e>d;d++)c=a[d],b=new this.tree.node_class(c),this.addChild(b),"object"==typeof c&&c.children&&b.loadFromData(c.children);return null},b.prototype.addChild=function(a){return this.children.push(a),a._setParent(this)},b.prototype.addChildAtPosition=function(a,b){return this.children.splice(b,0,a),a._setParent(this)},b.prototype._setParent=function(a){return this.parent=a,this.tree=a.tree,this.tree.addNodeToIndex(this)},b.prototype.removeChild=function(a){return a.removeChildren(),this._removeChild(a)},b.prototype._removeChild=function(a){return this.children.splice(this.getChildIndex(a),1),this.tree.removeNodeFromIndex(a)},b.prototype.getChildIndex=function(b){return a.inArray(b,this.children)},b.prototype.hasChildren=function(){return 0!==this.children.length},b.prototype.isFolder=function(){return this.hasChildren()||this.load_on_demand},b.prototype.iterate=function(a){var b;return b=function(c){return function(d,e){var f,g,h,i,j;if(d.children){for(j=d.children,h=0,i=j.length;i>h;h++)f=j[h],g=a(f,e),c.hasChildren()&&g&&b(f,e+1);return null}}}(this),b(this,0),null},b.prototype.moveNode=function(a,b,c){return a.isParentOf(b)?void 0:(a.parent._removeChild(a),c===n.AFTER?b.parent.addChildAtPosition(a,b.parent.getChildIndex(b)+1):c===n.BEFORE?b.parent.addChildAtPosition(a,b.parent.getChildIndex(b)):c===n.INSIDE?b.addChildAtPosition(a,0):void 0)},b.prototype.getData=function(){var a;return(a=function(b){return function(b){var c,d,e,f,g,h,i;for(c=[],h=0,i=b.length;i>h;h++){e=b[h],f={};for(d in e)g=e[d],"parent"!==d&&"children"!==d&&"element"!==d&&"tree"!==d&&Object.prototype.hasOwnProperty.call(e,d)&&(f[d]=g);e.hasChildren()&&(f.children=a(e.children)),c.push(f)}return c}}(this))(this.children)},b.prototype.getNodeByName=function(a){var b;return b=null,this.iterate(function(c){return c.name===a?(b=c,!1):!0}),b},b.prototype.addAfter=function(a){var b,c;return this.parent?(c=new this.tree.node_class(a),b=this.parent.getChildIndex(this),this.parent.addChildAtPosition(c,b+1),c):null},b.prototype.addBefore=function(a){var b,c;return this.parent?(c=new this.tree.node_class(a),b=this.parent.getChildIndex(this),this.parent.addChildAtPosition(c,b),c):null},b.prototype.addParent=function(a){var b,c,d,e,f,g;if(this.parent){for(c=new this.tree.node_class(a),c._setParent(this.tree),d=this.parent,g=d.children,e=0,f=g.length;f>e;e++)b=g[e],c.addChild(b);return d.children=[],d.addChild(c),c}return null},b.prototype.remove=function(){return this.parent?(this.parent.removeChild(this),this.parent=null):void 0},b.prototype.append=function(a){var b;return b=new this.tree.node_class(a),this.addChild(b),b},b.prototype.prepend=function(a){var b;return b=new this.tree.node_class(a),this.addChildAtPosition(b,0),b},b.prototype.isParentOf=function(a){var b;for(b=a.parent;b;){if(b===this)return!0;b=b.parent}return!1},b.prototype.getLevel=function(){var a,b;for(a=0,b=this;b.parent;)a+=1,b=b.parent;return a},b.prototype.getNodeById=function(a){return this.id_mapping[a]},b.prototype.addNodeToIndex=function(a){return null!=a.id?this.id_mapping[a.id]=a:void 0},b.prototype.removeNodeFromIndex=function(a){return null!=a.id?delete this.id_mapping[a.id]:void 0},b.prototype.removeChildren=function(){return this.iterate(function(a){return function(b){return a.tree.removeNodeFromIndex(b),!0}}(this)),this.children=[]},b.prototype.getPreviousSibling=function(){var a;return this.parent?(a=this.parent.getChildIndex(this)-1,a>=0?this.parent.children[a]:null):null},b.prototype.getNextSibling=function(){var a;return this.parent?(a=this.parent.getChildIndex(this)+1,ah;h++)e=b[h],f=this.createLi(e),g.appendChild(f),this.attachNodeData(e,f),e.hasChildren()&&this.createDomElements(f,e.children,!1,e.is_open);return null},b.prototype.attachNodeData=function(b,c){return b.element=c,a(c).data("node",b)},b.prototype.createUl=function(a){var b,c;return b=a?"jqtree-tree":"",c=document.createElement("ul"),c.className="jqtree_common "+b,c},b.prototype.createLi=function(b){var c;return c=b.isFolder()?this.createFolderLi(b):this.createNodeLi(b),this.tree_widget.options.onCreateLi&&this.tree_widget.options.onCreateLi(b,a(c)),c},b.prototype.createFolderLi=function(a){var b,c,d,e,f,g,h,i;return b=this.getButtonClasses(a),f=this.getFolderClasses(a),e=this.escapeIfNecessary(a.name),g=a.is_open?this.opened_icon_element:this.closed_icon_element,h=document.createElement("li"),h.className="jqtree_common "+f,d=document.createElement("div"),d.className="jqtree-element jqtree_common",h.appendChild(d),c=document.createElement("a"),c.className="jqtree_common "+b,c.appendChild(g.cloneNode()),d.appendChild(c),i=document.createElement("span"),i.className="jqtree_common jqtree-title jqtree-title-folder",d.appendChild(i),i.innerHTML=e,h},b.prototype.createNodeLi=function(a){var b,c,d,e,f,g;return f=["jqtree_common"],this.tree_widget.select_node_handler&&this.tree_widget.select_node_handler.isNodeSelected(a)&&f.push("jqtree-selected"),b=f.join(" "),d=this.escapeIfNecessary(a.name),e=document.createElement("li"),e.className=b,c=document.createElement("div"),c.className="jqtree-element jqtree_common",e.appendChild(c),g=document.createElement("span"),g.className="jqtree-title jqtree_common",g.innerHTML=d,c.appendChild(g),e},b.prototype.getButtonClasses=function(a){var b;return b=["jqtree-toggler"],a.is_open||b.push("jqtree-closed"),b.join(" ")},b.prototype.getFolderClasses=function(a){var b;return b=["jqtree-folder"],a.is_open||b.push("jqtree-closed"),this.tree_widget.select_node_handler&&this.tree_widget.select_node_handler.isNodeSelected(a)&&b.push("jqtree-selected"),b.join(" ")},b.prototype.escapeIfNecessary=function(a){return this.tree_widget.options.autoEscape?u(a):a},b.prototype.createButtonElement=function(b){var c;return"string"==typeof b?(c=document.createElement("div"),c.innerHTML=b,document.createTextNode(c.innerHTML)):a(b)[0]},b}(),i=function(b){function d(){return d.__super__.constructor.apply(this,arguments)}return B(d,b),d.prototype.defaults={autoOpen:!1,saveState:!1,dragAndDrop:!1,selectable:!0,useContextMenu:!0,onCanSelectNode:null,onSetStateFromStorage:null,onGetStateFromStorage:null,onCreateLi:null,onIsMoveHandle:null,onCanMove:null,onCanMoveTo:null,onLoadFailed:null,autoEscape:!0,dataUrl:null,closedIcon:"►",openedIcon:"▼",slide:!0,nodeClass:l,dataFilter:null,keyboardSupport:!0,openFolderDelay:500},d.prototype.toggle=function(a,b){return null==b&&(b=null),null===b&&(b=this.options.slide),a.is_open?this.closeNode(a,b):this.openNode(a,b)},d.prototype.getTree=function(){return this.tree},d.prototype.selectNode=function(a){return this._selectNode(a,!1)},d.prototype._selectNode=function(a,b){var c,d,e,f;if(null==b&&(b=!1),this.select_node_handler){if(c=function(b){return function(){return b.options.onCanSelectNode?b.options.selectable&&b.options.onCanSelectNode(a):b.options.selectable}}(this),e=function(b){return function(){var c;return c=a.parent,c&&c.parent&&!c.is_open?b.openNode(c,!1):void 0}}(this),f=function(a){return function(){return a.options.saveState?a.save_state_handler.saveState():void 0}}(this),!a)return this._deselectCurrentNode(),void f();if(c())return this.select_node_handler.isNodeSelected(a)?b&&(this._deselectCurrentNode(),this._triggerEvent("tree.select",{node:null,previous_node:a})):(d=this.getSelectedNode(),this._deselectCurrentNode(),this.addToSelection(a),this._triggerEvent("tree.select",{node:a,deselected_node:d}),e()),f()}},d.prototype.getSelectedNode=function(){return this.select_node_handler.getSelectedNode()},d.prototype.toJson=function(){return JSON.stringify(this.tree.getData())},d.prototype.loadData=function(a,b){return this._loadData(a,b)},d.prototype.loadDataFromUrl=function(b,c,d){return"string"!==a.type(b)&&(d=c,c=b,b=null),this._loadDataFromUrl(b,c,d)},d.prototype.reload=function(){return this.loadDataFromUrl()},d.prototype._loadDataFromUrl=function(b,c,d){var e,g,h,i,j,k;if(e=null,g=function(a){return function(){var b;return c?(b=new f(c,a),e=b.getLi()):e=a.element,e.addClass("jqtree-loading")}}(this),k=function(a){return function(){return e?e.removeClass("jqtree-loading"):void 0}}(this),j=function(c){return function(){return"string"===a.type(b)&&(b={url:b}),b.method?void 0:b.method="get"}}(this),h=function(b){return function(e){return k(),b._loadData(e,c),d&&a.isFunction(d)?d():void 0}}(this),i=function(c){return function(){return j(),a.ajax({url:b.url,data:b.data,type:b.method.toUpperCase(),cache:!1,dataType:"json",success:function(b){var d;return d=a.isArray(b)||"object"==typeof b?b:a.parseJSON(b),c.options.dataFilter&&(d=c.options.dataFilter(d)),h(d)},error:function(a){return k(),c.options.onLoadFailed?c.options.onLoadFailed(a):void 0}})}}(this),b||(b=this._getDataUrlInfo(c)),g(),b){if(!a.isArray(b))return i();h(b)}else k()},d.prototype._loadData=function(a,b){var c,d,e,f;if(a){if(this._triggerEvent("tree.load_data",{tree_data:a}),b){for(d=this.select_node_handler.getSelectedNodesUnder(b),e=0,f=d.length;f>e;e++)c=d[e],this.select_node_handler.removeFromSelection(c);b.loadFromData(a),b.load_on_demand=!1,this._refreshElements(b.parent)}else this._initTree(a);return this.isDragging()?this.dnd_handler.refresh():void 0}},d.prototype.getNodeById=function(a){return this.tree.getNodeById(a)},d.prototype.getNodeByName=function(a){return this.tree.getNodeByName(a)},d.prototype.openNode=function(a,b){return null==b&&(b=null),null===b&&(b=this.options.slide),this._openNode(a,b)},d.prototype._openNode=function(a,b,c){var d,e;if(null==b&&(b=!0),d=function(a){return function(b,c,d){var e;return e=new f(b,a),e.open(d,c)}}(this),a.isFolder()){if(a.load_on_demand)return this._loadFolderOnDemand(a,b,c);for(e=a.parent;e&&!e.is_open;)e.parent&&d(e,!1,null),e=e.parent;return d(a,b,c),this._saveState()}},d.prototype._loadFolderOnDemand=function(a,b,c){return null==b&&(b=!0),this._loadDataFromUrl(null,a,function(d){return function(){return d._openNode(a,b,c)}}(this))},d.prototype.closeNode=function(a,b){return null==b&&(b=null),null===b&&(b=this.options.slide),a.isFolder()?(new f(a,this).close(b),this._saveState()):void 0},d.prototype.isDragging=function(){return this.dnd_handler?this.dnd_handler.is_dragging:!1},d.prototype.refreshHitAreas=function(){return this.dnd_handler.refresh()},d.prototype.addNodeAfter=function(a,b){var c;return c=b.addAfter(a),this._refreshElements(b.parent),c},d.prototype.addNodeBefore=function(a,b){var c;return c=b.addBefore(a),this._refreshElements(b.parent),c},d.prototype.addParentNode=function(a,b){var c;return c=b.addParent(a),this._refreshElements(c.parent),c},d.prototype.removeNode=function(a){var b;return b=a.parent,b?(this.select_node_handler.removeFromSelection(a,!0),a.remove(),this._refreshElements(b.parent)):void 0},d.prototype.appendNode=function(a,b){var c,d;return b||(b=this.tree),c=b.isFolder(),d=b.append(a),this._refreshElements(c?b:b.parent),d},d.prototype.prependNode=function(a,b){var c;return b||(b=this.tree),c=b.prepend(a),this._refreshElements(b),c},d.prototype.updateNode=function(a,b){var c;return c=b.id&&b.id!==a.id,c&&this.tree.removeNodeFromIndex(a),a.setData(b),c&&this.tree.addNodeToIndex(a),this.renderer.renderNode(a),this._selectCurrentNode()},d.prototype.moveNode=function(a,b,c){var d;return d=n.nameToIndex(c),this.tree.moveNode(a,b,d),this._refreshElements()},d.prototype.getStateFromStorage=function(){return this.save_state_handler.getStateFromStorage()},d.prototype.addToSelection=function(a){return a?(this.select_node_handler.addToSelection(a),this._getNodeElementForNode(a).select(),this._saveState()):void 0},d.prototype.getSelectedNodes=function(){return this.select_node_handler.getSelectedNodes()},d.prototype.isNodeSelected=function(a){return this.select_node_handler.isNodeSelected(a)},d.prototype.removeFromSelection=function(a){return this.select_node_handler.removeFromSelection(a),this._getNodeElementForNode(a).deselect(),this._saveState()},d.prototype.scrollToNode=function(b){var c,d;return c=a(b.element),d=c.offset().top-this.$el.offset().top,this.scroll_handler.scrollTo(d)},d.prototype.getState=function(){return this.save_state_handler.getState()},d.prototype.setState=function(a){return this.save_state_handler.setState(a),this._refreshElements()},d.prototype.setOption=function(a,b){return this.options[a]=b},d.prototype.getVersion=function(){return x},d.prototype._init=function(){return d.__super__._init.call(this),this.element=this.$el,this.mouse_delay=300,this.is_initialized=!1,this.renderer=new e(this),"undefined"!=typeof o&&null!==o?this.save_state_handler=new o(this):this.options.saveState=!1,"undefined"!=typeof q&&null!==q&&(this.select_node_handler=new q(this)),"undefined"!=typeof c&&null!==c?this.dnd_handler=new c(this):this.options.dragAndDrop=!1,"undefined"!=typeof p&&null!==p&&(this.scroll_handler=new p(this)),"undefined"!=typeof j&&null!==j&&"undefined"!=typeof q&&null!==q&&(this.key_handler=new j(this)),this._initData(),this.element.click(a.proxy(this._click,this)),this.element.dblclick(a.proxy(this._dblclick,this)),this.options.useContextMenu?this.element.bind("contextmenu",a.proxy(this._contextmenu,this)):void 0},d.prototype._deinit=function(){return this.element.empty(),this.element.unbind(),this.key_handler.deinit(),this.tree=null,d.__super__._deinit.call(this)},d.prototype._initData=function(){return this.options.data?this._loadData(this.options.data):this._loadDataFromUrl(this._getDataUrlInfo())},d.prototype._getDataUrlInfo=function(b){var c,d;return c=this.options.dataUrl||this.element.data("url"),d=function(a){return function(){var d,e,f;return f={url:c},b&&b.id?(d={node:b.id},f.data=d):(e=a._getNodeIdToBeSelected(),e&&(d={selected_node:e},f.data=d)),f}}(this),a.isFunction(c)?c(b):"string"===a.type(c)?d():c},d.prototype._getNodeIdToBeSelected=function(){return this.options.saveState?this.save_state_handler.getNodeIdToBeSelected():null},d.prototype._initTree=function(a){return this.tree=new this.options.nodeClass(null,!0,this.options.nodeClass),this.select_node_handler&&this.select_node_handler.clear(),this.tree.loadFromData(a),this._openNodes(),this._refreshElements(),this.is_initialized?void 0:(this.is_initialized=!0,this._triggerEvent("tree.init"))},d.prototype._openNodes=function(){var a;if(!(this.options.saveState&&this.save_state_handler.restoreState()||this.options.autoOpen===!1))return a=this.options.autoOpen===!0?-1:parseInt(this.options.autoOpen),this.tree.iterate(function(b,c){return b.hasChildren()&&(b.is_open=!0),c!==a})},d.prototype._refreshElements=function(a){return null==a&&(a=null),this.renderer.render(a),this._triggerEvent("tree.refresh")},d.prototype._click=function(a){var b,c,d;if(b=this._getClickTarget(a.target)){if("button"===b.type)return this.toggle(b.node,this.options.slide),a.preventDefault(),a.stopPropagation();if("label"===b.type&&(d=b.node,c=this._triggerEvent("tree.click",{node:d,click_event:a}),!c.isDefaultPrevented()))return this._selectNode(d,!0)}},d.prototype._dblclick=function(a){var b;return b=this._getClickTarget(a.target),b&&"label"===b.type?this._triggerEvent("tree.dblclick",{node:b.node,click_event:a}):void 0},d.prototype._getClickTarget=function(b){var c,d,e,f;if(e=a(b),c=e.closest(".jqtree-toggler"),c.length){if(f=this._getNode(c))return{type:"button",node:f}}else if(d=e.closest(".jqtree-element"),d.length&&(f=this._getNode(d)))return{type:"label",node:f};return null},d.prototype._getNode=function(a){var b;return b=a.closest("li.jqtree_common"),0===b.length?null:b.data("node")},d.prototype._getNodeElementForNode=function(a){return a.isFolder()?new f(a,this):new m(a,this)},d.prototype._getNodeElement=function(a){var b;return b=this._getNode(a),b?this._getNodeElementForNode(b):null},d.prototype._contextmenu=function(b){var c,d;return c=a(b.target).closest("ul.jqtree-tree .jqtree-element"),c.length&&(d=this._getNode(c))?(b.preventDefault(),b.stopPropagation(),this._triggerEvent("tree.contextmenu",{node:d,click_event:b}),!1):void 0},d.prototype._saveState=function(){return this.options.saveState?this.save_state_handler.saveState():void 0},d.prototype._mouseCapture=function(a){return this.options.dragAndDrop?this.dnd_handler.mouseCapture(a):!1},d.prototype._mouseStart=function(a){return this.options.dragAndDrop?this.dnd_handler.mouseStart(a):!1},d.prototype._mouseDrag=function(a){var b;return this.options.dragAndDrop?(b=this.dnd_handler.mouseDrag(a),this.scroll_handler&&this.scroll_handler.checkScrolling(),b):!1},d.prototype._mouseStop=function(a){return this.options.dragAndDrop?this.dnd_handler.mouseStop(a):!1},d.prototype._triggerEvent=function(b,c){var d;return d=a.Event(b),a.extend(d,c),this.element.trigger(d),d},d.prototype.testGenerateHitAreas=function(a){return this.dnd_handler.current_item=this._getNodeElementForNode(a),this.dnd_handler.generateHitAreas(),this.dnd_handler.hit_areas},d.prototype._selectCurrentNode=function(){var a,b;return a=this.getSelectedNode(),a&&(b=this._getNodeElementForNode(a))?b.select():void 0},d.prototype._deselectCurrentNode=function(){var a;return a=this.getSelectedNode(),a?this.removeFromSelection(a):void 0},d}(k),r.register(i,"tree"),m=function(){function c(a,b){this.init(a,b)}return c.prototype.init=function(b,c){return this.node=b,this.tree_widget=c,b.element||(b.element=this.tree_widget.element),this.$element=a(b.element)},c.prototype.getUl=function(){return this.$element.children("ul:first")},c.prototype.getSpan=function(){return this.$element.children(".jqtree-element").find("span.jqtree-title")},c.prototype.getLi=function(){return this.$element},c.prototype.addDropHint=function(a){return a===n.INSIDE?new b(this.$element):new g(this.node,this.$element,a)},c.prototype.select=function(){return this.getLi().addClass("jqtree-selected")},c.prototype.deselect=function(){return this.getLi().removeClass("jqtree-selected")},c}(),f=function(a){function c(){return c.__super__.constructor.apply(this,arguments)}return B(c,a),c.prototype.open=function(a,b){var c,d;return null==b&&(b=!0),this.node.is_open?void 0:(this.node.is_open=!0,c=this.getButton(),c.removeClass("jqtree-closed"),c.html(""),c.append(this.tree_widget.renderer.opened_icon_element.cloneNode()),d=function(b){return function(){return b.getLi().removeClass("jqtree-closed"),a&&a(),b.tree_widget._triggerEvent("tree.open",{node:b.node})}}(this),b?this.getUl().slideDown("fast",d):(this.getUl().show(),d()))},c.prototype.close=function(a){var b,c;return null==a&&(a=!0),this.node.is_open?(this.node.is_open=!1,b=this.getButton(),b.addClass("jqtree-closed"),b.html(""),b.append(this.tree_widget.renderer.closed_icon_element.cloneNode()),c=function(a){return function(){return a.getLi().addClass("jqtree-closed"),a.tree_widget._triggerEvent("tree.close",{node:a.node})}}(this),a?this.getUl().slideUp("fast",c):(this.getUl().hide(),c())):void 0},c.prototype.getButton=function(){return this.$element.children(".jqtree-element").find("a.jqtree-toggler")},c.prototype.addDropHint=function(a){return this.node.is_open||a!==n.INSIDE?new g(this.node,this.$element,a):new b(this.$element)},c}(m),u=function(a){return(""+a).replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'").replace(/\//g,"/")},y=function(a,b){var c,d,e,f;for(c=e=0,f=a.length;f>e;c=++e)if(d=a[c],d===b)return c;return-1},v=function(a,b){return a.indexOf?a.indexOf(b):y(a,b)},this.Tree.indexOf=v,this.Tree._indexOf=y,w=function(a){return"number"==typeof a&&a%1===0},t=function(){var a,b,c,d,e;return a=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,b={"\b":"\\b"," ":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"},c=function(c){return a.lastIndex=0,a.test(c)?'"'+c.replace(a,function(a){var c;return c=b[a],"string"==typeof c?c:"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+c+'"'},d=function(a,b){var e,f,g,h,i,j,k;switch(i=b[a],typeof i){case"string":return c(i);case"number":return isFinite(i)?String(i):"null";case"boolean":case"null":return String(i);case"object":if(!i)return"null";if(g=[],"[object Array]"===Object.prototype.toString.apply(i)){for(e=j=0,k=i.length;k>j;e=++j)h=i[e],g[e]=d(e,i)||"null";return 0===g.length?"[]":"["+g.join(",")+"]"}for(f in i)Object.prototype.hasOwnProperty.call(i,f)&&(h=d(f,i),h&&g.push(c(f)+":"+h));return 0===g.length?"{}":"{"+g.join(",")+"}"}},e=function(a){return d("",{"":a})}},this.Tree.get_json_stringify_function=t,(null==this.JSON||null==this.JSON.stringify||"function"!=typeof this.JSON.stringify)&&(null==this.JSON&&(this.JSON={}),this.JSON.stringify=t()),o=function(){function b(a){this.tree_widget=a}return b.prototype.saveState=function(){var b;return b=JSON.stringify(this.getState()),this.tree_widget.options.onSetStateFromStorage?this.tree_widget.options.onSetStateFromStorage(b):this.supportsLocalStorage()?localStorage.setItem(this.getCookieName(),b):a.cookie?(a.cookie.raw=!0,a.cookie(this.getCookieName(),b,{path:"/"})):void 0},b.prototype.restoreState=function(){var a;return a=this.getStateFromStorage(),a?(this.setState(a),!0):!1},b.prototype.getStateFromStorage=function(){var a;return a=this._loadFromStorage(),a?this._parseState(a):null},b.prototype._parseState=function(b){var c;return c=a.parseJSON(b),c&&c.selected_node&&w(c.selected_node)&&(c.selected_node=[c.selected_node]),c},b.prototype._loadFromStorage=function(){return this.tree_widget.options.onGetStateFromStorage?this.tree_widget.options.onGetStateFromStorage():this.supportsLocalStorage()?localStorage.getItem(this.getCookieName()):a.cookie?(a.cookie.raw=!0,a.cookie(this.getCookieName())):null},b.prototype.getState=function(){var a,b;return a=function(a){return function(){var b;return b=[],a.tree_widget.tree.iterate(function(a){return a.is_open&&a.id&&a.hasChildren()&&b.push(a.id),!0}),b}}(this),b=function(a){return function(){var b;return function(){var a,c,d,e;for(d=this.tree_widget.getSelectedNodes(),e=[],a=0,c=d.length;c>a;a++)b=d[a],e.push(b.id);return e}.call(a)}}(this),{open_nodes:a(),selected_node:b()}},b.prototype.setState=function(a){var b,c,d,e,f,g,h;if(a&&(c=a.open_nodes,e=a.selected_node,this.tree_widget.tree.iterate(function(a){return function(a){return a.is_open=a.id&&a.hasChildren()&&v(c,a.id)>=0,!0}}(this)),e&&this.tree_widget.select_node_handler)){for(this.tree_widget.select_node_handler.clear(),h=[],f=0,g=e.length;g>f;f++)b=e[f],d=this.tree_widget.getNodeById(b),h.push(d?this.tree_widget.select_node_handler.addToSelection(d):void 0);return h}},b.prototype.getCookieName=function(){return"string"==typeof this.tree_widget.options.saveState?this.tree_widget.options.saveState:"tree"},b.prototype.supportsLocalStorage=function(){var a;return a=function(){var a,b;if("undefined"==typeof localStorage||null===localStorage)return!1;try{b="_storage_test",sessionStorage.setItem(b,!0),sessionStorage.removeItem(b)}catch(c){return a=c,!1}return!0},null==this._supportsLocalStorage&&(this._supportsLocalStorage=a()),this._supportsLocalStorage},b.prototype.getNodeIdToBeSelected=function(){var a;return a=this.getStateFromStorage(),a&&a.selected_node?a.selected_node[0]:null},b}(),q=function(){function a(a){this.tree_widget=a,this.clear()}return a.prototype.getSelectedNode=function(){var a;return a=this.getSelectedNodes(),a.length?a[0]:!1},a.prototype.getSelectedNodes=function(){var a,b,c;if(this.selected_single_node)return[this.selected_single_node];c=[];for(a in this.selected_nodes)b=this.tree_widget.getNodeById(a),b&&c.push(b);return c},a.prototype.getSelectedNodesUnder=function(a){var b,c,d;if(this.selected_single_node)return a.isParentOf(this.selected_single_node)?[this.selected_single_node]:[];d=[];for(b in this.selected_nodes)c=this.tree_widget.getNodeById(b),c&&a.isParentOf(c)&&d.push(c);return d},a.prototype.isNodeSelected=function(a){return a.id?this.selected_nodes[a.id]:this.selected_single_node?this.selected_single_node.element===a.element:!1},a.prototype.clear=function(){return this.selected_nodes={},this.selected_single_node=null},a.prototype.removeFromSelection=function(a,b){if(null==b&&(b=!1),a.id){if(delete this.selected_nodes[a.id],b)return a.iterate(function(b){return function(c){return delete b.selected_nodes[a.id],!0}}(this))}else if(this.selected_single_node&&a.element===this.selected_single_node.element)return this.selected_single_node=null},a.prototype.addToSelection=function(a){return a.id?this.selected_nodes[a.id]=!0:this.selected_single_node=a},a}(),c=function(){function b(a){this.tree_widget=a,this.hovered_area=null,this.$ghost=null,this.hit_areas=[],this.is_dragging=!1,this.current_item=null}return b.prototype.mouseCapture=function(b){var c,d;return c=a(b.target),this.mustCaptureElement(c)?this.tree_widget.options.onIsMoveHandle&&!this.tree_widget.options.onIsMoveHandle(c)?null:(d=this.tree_widget._getNodeElement(c),d&&this.tree_widget.options.onCanMove&&(this.tree_widget.options.onCanMove(d.node)||(d=null)),this.current_item=d,null!==this.current_item):null;
-
-},b.prototype.mouseStart=function(b){var c;return this.refresh(),c=a(b.target).offset(),this.drag_element=new d(this.current_item.node,b.page_x-c.left,b.page_y-c.top,this.tree_widget.element),this.is_dragging=!0,this.current_item.$element.addClass("jqtree-moving"),!0},b.prototype.mouseDrag=function(a){var b,c;return this.drag_element.move(a.page_x,a.page_y),b=this.findHoveredArea(a.page_x,a.page_y),c=this.canMoveToArea(b),c&&b?(b.node.isFolder()||this.stopOpenFolderTimer(),this.hovered_area!==b&&(this.hovered_area=b,this.mustOpenFolderTimer(b)?this.startOpenFolderTimer(b.node):this.stopOpenFolderTimer(),this.updateDropHint())):(this.removeHover(),this.removeDropHint(),this.stopOpenFolderTimer()),!0},b.prototype.mustCaptureElement=function(a){return!a.is("input,select")},b.prototype.canMoveToArea=function(a){var b;return a?this.tree_widget.options.onCanMoveTo?(b=n.getName(a.position),this.tree_widget.options.onCanMoveTo(this.current_item.node,a.node,b)):!0:!1},b.prototype.mouseStop=function(a){return this.moveItem(a),this.clear(),this.removeHover(),this.removeDropHint(),this.removeHitAreas(),this.current_item&&(this.current_item.$element.removeClass("jqtree-moving"),this.current_item=null),this.is_dragging=!1,!1},b.prototype.refresh=function(){return this.removeHitAreas(),this.current_item&&(this.generateHitAreas(),this.current_item=this.tree_widget._getNodeElementForNode(this.current_item.node),this.is_dragging)?this.current_item.$element.addClass("jqtree-moving"):void 0},b.prototype.removeHitAreas=function(){return this.hit_areas=[]},b.prototype.clear=function(){return this.drag_element.remove(),this.drag_element=null},b.prototype.removeDropHint=function(){return this.previous_ghost?this.previous_ghost.remove():void 0},b.prototype.removeHover=function(){return this.hovered_area=null},b.prototype.generateHitAreas=function(){var a;return a=new h(this.tree_widget.tree,this.current_item.node,this.getTreeDimensions().bottom),this.hit_areas=a.generate()},b.prototype.findHoveredArea=function(a,b){var c,d,e,f,g;if(d=this.getTreeDimensions(),ad.right||b>d.bottom)return null;for(f=0,e=this.hit_areas.length;e>f;)if(g=f+e>>1,c=this.hit_areas[g],bc.bottom))return c;f=g+1}return null},b.prototype.mustOpenFolderTimer=function(a){var b;return b=a.node,b.isFolder()&&!b.is_open&&a.position===n.INSIDE},b.prototype.updateDropHint=function(){var a;if(this.hovered_area)return this.removeDropHint(),a=this.tree_widget._getNodeElementForNode(this.hovered_area.node),this.previous_ghost=a.addDropHint(this.hovered_area.position)},b.prototype.startOpenFolderTimer=function(a){var b;return b=function(b){return function(){return b.tree_widget._openNode(a,b.tree_widget.options.slide,function(){return b.refresh(),b.updateDropHint()})}}(this),this.stopOpenFolderTimer(),this.open_folder_timer=setTimeout(b,this.tree_widget.options.openFolderDelay)},b.prototype.stopOpenFolderTimer=function(){return this.open_folder_timer?(clearTimeout(this.open_folder_timer),this.open_folder_timer=null):void 0},b.prototype.moveItem=function(a){var b,c,d,e,f,g;return this.hovered_area&&this.hovered_area.position!==n.NONE&&this.canMoveToArea(this.hovered_area)&&(d=this.current_item.node,g=this.hovered_area.node,e=this.hovered_area.position,f=d.parent,e===n.INSIDE&&(this.hovered_area.node.is_open=!0),b=function(a){return function(){return a.tree_widget.tree.moveNode(d,g,e),a.tree_widget.element.empty(),a.tree_widget._refreshElements()}}(this),c=this.tree_widget._triggerEvent("tree.move",{move_info:{moved_node:d,target_node:g,position:n.getName(e),previous_parent:f,do_move:b,original_event:a.original_event}}),!c.isDefaultPrevented())?b():void 0},b.prototype.getTreeDimensions=function(){var a;return a=this.tree_widget.element.offset(),{left:a.left,top:a.top,right:a.left+this.tree_widget.element.width(),bottom:a.top+this.tree_widget.element.height()+16}},b}(),s=function(){function b(a){this.tree=a}return b.prototype.iterate=function(){var b,c;return b=!0,(c=function(d){return function(e,f){var g,h,i,j,k,l,m,n;if(k=(e.is_open||!e.element)&&e.hasChildren(),e.element){if(g=a(e.element),!g.is(":visible"))return;b&&(d.handleFirstNode(e,g),b=!1),e.hasChildren()?e.is_open?d.handleOpenFolder(e,g)||(k=!1):d.handleClosedFolder(e,f,g):d.handleNode(e,f,g)}if(k){for(i=e.children.length,n=e.children,j=l=0,m=n.length;m>l;j=++l)h=n[j],j===i-1?c(e.children[j],null):c(e.children[j],e.children[j+1]);if(e.is_open)return d.handleAfterOpenFolder(e,f,g)}}}(this))(this.tree,null)},b.prototype.handleNode=function(a,b,c){},b.prototype.handleOpenFolder=function(a,b){},b.prototype.handleClosedFolder=function(a,b,c){},b.prototype.handleAfterOpenFolder=function(a,b,c){},b.prototype.handleFirstNode=function(a,b){},b}(),h=function(b){function c(a,b,d){c.__super__.constructor.call(this,a),this.current_node=b,this.tree_bottom=d}return B(c,b),c.prototype.generate=function(){return this.positions=[],this.last_top=0,this.iterate(),this.generateHitAreas(this.positions)},c.prototype.getTop=function(a){return a.offset().top},c.prototype.addPosition=function(a,b,c){var d;return d={top:c,node:a,position:b},this.positions.push(d),this.last_top=c},c.prototype.handleNode=function(a,b,c){var d;return d=this.getTop(c),a===this.current_node?this.addPosition(a,n.NONE,d):this.addPosition(a,n.INSIDE,d),b===this.current_node||a===this.current_node?this.addPosition(a,n.NONE,d):this.addPosition(a,n.AFTER,d)},c.prototype.handleOpenFolder=function(a,b){return a===this.current_node?!1:(a.children[0]!==this.current_node&&this.addPosition(a,n.INSIDE,this.getTop(b)),!0)},c.prototype.handleClosedFolder=function(a,b,c){var d;return d=this.getTop(c),a===this.current_node?this.addPosition(a,n.NONE,d):(this.addPosition(a,n.INSIDE,d),b!==this.current_node?this.addPosition(a,n.AFTER,d):void 0)},c.prototype.handleFirstNode=function(b,c){return b!==this.current_node?this.addPosition(b,n.BEFORE,this.getTop(a(b.element))):void 0},c.prototype.handleAfterOpenFolder=function(a,b,c){return a===this.current_node.node||b===this.current_node.node?this.addPosition(a,n.NONE,this.last_top):this.addPosition(a,n.AFTER,this.last_top)},c.prototype.generateHitAreas=function(a){var b,c,d,e,f,g;for(e=-1,b=[],c=[],f=0,g=a.length;g>f;f++)d=a[f],d.top!==e&&b.length&&(b.length&&this.generateHitAreasForGroup(c,b,e,d.top),e=d.top,b=[]),b.push(d);return this.generateHitAreasForGroup(c,b,e,this.tree_bottom),c},c.prototype.generateHitAreasForGroup=function(a,b,c,d){var e,f,g,h,i;for(i=Math.min(b.length,4),e=Math.round((d-c)/i),f=c,g=0;i>g;)h=b[g],a.push({top:f,bottom:f+e,node:h.node,position:h.position}),f+=e,g+=1;return null},c}(s),d=function(){function b(b,c,d,e){this.offset_x=c,this.offset_y=d,this.$element=a(''+b.name+""),this.$element.css("position","absolute"),e.append(this.$element)}return b.prototype.move=function(a,b){return this.$element.offset({left:a-this.offset_x,top:b-this.offset_y})},b.prototype.remove=function(){return this.$element.remove()},b}(),g=function(){function b(b,c,d){this.$element=c,this.node=b,this.$ghost=a(''),d===n.AFTER?this.moveAfter():d===n.BEFORE?this.moveBefore():d===n.INSIDE&&(b.isFolder()&&b.is_open?this.moveInsideOpenFolder():this.moveInside())}return b.prototype.remove=function(){return this.$ghost.remove()},b.prototype.moveAfter=function(){return this.$element.after(this.$ghost)},b.prototype.moveBefore=function(){return this.$element.before(this.$ghost)},b.prototype.moveInsideOpenFolder=function(){return a(this.node.children[0].element).before(this.$ghost)},b.prototype.moveInside=function(){return this.$element.after(this.$ghost),this.$ghost.addClass("jqtree-inside")},b}(),b=function(){function b(b){var c,d;c=b.children(".jqtree-element"),d=b.width()-4,this.$hint=a(''),c.append(this.$hint),this.$hint.css({width:d,height:c.height()-4})}return b.prototype.remove=function(){return this.$hint.remove()},b}(),p=function(){function b(a){this.tree_widget=a,this.previous_top=-1,this._initScrollParent()}return b.prototype._initScrollParent=function(){var b,c,d;return c=function(b){return function(){var c,d,e,f,g,h;if(c=["overflow","overflow-y"],(e=function(b){var d,e,f,g;for(e=0,f=c.length;f>e;e++)if(d=c[e],"auto"===(g=a.css(b,d))||"scroll"===g)return!0;return!1})(b.tree_widget.$el[0]))return b.tree_widget.$el;for(h=b.tree_widget.$el.parents(),f=0,g=h.length;g>f;f++)if(d=h[f],e(d))return a(d);return null}}(this),d=function(a){return function(){return a.scroll_parent_top=0,a.$scroll_parent=null}}(this),"fixed"===this.tree_widget.$el.css("position")&&d(),b=c(),b&&b.length&&"HTML"!==b[0].tagName?(this.$scroll_parent=b,this.scroll_parent_top=this.$scroll_parent.offset().top):d()},b.prototype.checkScrolling=function(){var a;return a=this.tree_widget.dnd_handler.hovered_area,a&&a.top!==this.previous_top?(this.previous_top=a.top,this.$scroll_parent?this._handleScrollingWithScrollParent(a):this._handleScrollingWithDocument(a)):void 0},b.prototype._handleScrollingWithScrollParent=function(a){var b;return b=this.scroll_parent_top+this.$scroll_parent[0].offsetHeight-a.bottom,20>b?(this.$scroll_parent[0].scrollTop+=20,this.tree_widget.refreshHitAreas(),this.previous_top=-1):a.top-this.scroll_parent_top<20?(this.$scroll_parent[0].scrollTop-=20,this.tree_widget.refreshHitAreas(),this.previous_top=-1):void 0},b.prototype._handleScrollingWithDocument=function(b){var c;return c=b.top-a(document).scrollTop(),20>c?a(document).scrollTop(a(document).scrollTop()-20):a(window).height()-(b.bottom-a(document).scrollTop())<20?a(document).scrollTop(a(document).scrollTop()+20):void 0},b.prototype.scrollTo=function(b){var c;return this.$scroll_parent?this.$scroll_parent[0].scrollTop=b:(c=this.tree_widget.$el.offset().top,a(document).scrollTop(b+c))},b.prototype.isScrolledIntoView=function(b){var c,d,e,f,g;return c=a(b),this.$scroll_parent?(g=0,f=this.$scroll_parent.height(),e=c.offset().top-this.scroll_parent_top,d=e+c.height()):(g=a(window).scrollTop(),f=g+a(window).height(),e=c.offset().top,d=e+c.height()),f>=d&&e>=g},b}(),j=function(){function b(b){this.tree_widget=b,b.options.keyboardSupport&&a(document).bind("keydown.jqtree",a.proxy(this.handleKeyDown,this))}var c,d,e,f;return d=37,f=38,e=39,c=40,b.prototype.deinit=function(){return a(document).unbind("keydown.jqtree")},b.prototype.handleKeyDown=function(b){var g,h,i,j,k,l,m;if(this.tree_widget.options.keyboardSupport){if(a(document.activeElement).is("textarea,input,select"))return!0;if(g=this.tree_widget.getSelectedNode(),m=function(b){return function(c){return c?(b.tree_widget.selectNode(c),b.tree_widget.scroll_handler&&!b.tree_widget.scroll_handler.isScrolledIntoView(a(c.element).find(".jqtree-element"))&&b.tree_widget.scrollToNode(c),!1):!0}}(this),i=function(a){return function(){return m(a.getNextNode(g))}}(this),l=function(a){return function(){return m(a.getPreviousNode(g))}}(this),k=function(a){return function(){return g.isFolder()&&!g.is_open?(a.tree_widget.openNode(g),!1):!0}}(this),j=function(a){return function(){return g.isFolder()&&g.is_open?(a.tree_widget.closeNode(g),!1):!0}}(this),!g)return!0;switch(h=b.which){case c:return i();case f:return l();case e:return k();case d:return j()}}},b.prototype.getNextNode=function(a,b){var c;return null==b&&(b=!0),b&&a.hasChildren()&&a.is_open?a.children[0]:a.parent?(c=a.getNextSibling(),c?c:this.getNextNode(a.parent,!1)):null},b.prototype.getPreviousNode=function(a){var b;return a.parent?(b=a.getPreviousSibling(),b?b.hasChildren()&&b.is_open?this.getLastChild(b):b:a.parent.parent?a.parent:null):null},b.prototype.getLastChild=function(a){var b;return a.hasChildren()?(b=a.children[a.children.length-1],b.hasChildren()&&b.is_open?this.getLastChild(b):b):null},b}()}).call(this)}.apply(a,arguments)})}(this),define("mockup-patterns-tree",["jquery","underscore","pat-base","mockup-utils","jqtree"],function(a,b,c,d){"use strict";var e=c.extend({name:"tree",trigger:".pat-tree",parser:"mockup",defaults:{dragAndDrop:!1,autoOpen:!1,selectable:!0,keyboardSupport:!0,onLoad:null},init:function(){var b=this;for(var c in b.options){var e=b.defaults[c];void 0!==e&&"boolean"==typeof e&&(b.options[c]=d.bool(b.options[c]))}if(b.options.dragAndDrop&&void 0===b.options.onCanMoveTo&&(b.options.onCanMoveTo=function(a,b,c){return void 0===b.folder||b.folder===!0}),b.options.data&&"string"==typeof b.options.data&&(b.options.data=a.parseJSON(b.options.data)),null!==b.options.onLoad){var f=a.extend({},b.options);a.getJSON(f.dataUrl,function(a){f.data=a,delete f.dataUrl,b.tree=b.$el.tree(f),b.options.onLoad(b)})}else b.tree=b.$el.tree(b.options)}});return e}),define("mockup-patterns-relateditems",["jquery","underscore","pat-base","mockup-patterns-select2","mockup-utils","mockup-patterns-tree","translate"],function(a,b,c,d,e,f,g){"use strict";var h=c.extend({name:"relateditems",trigger:".pat-relateditems",parser:"mockup",browsing:!1,currentPath:null,defaults:{vocabularyUrl:null,width:"100%",multiple:!0,tokenSeparators:[","," "],separator:",",orderable:!0,cache:!0,mode:"search",closeOnSelect:!1,basePath:"/",homeText:g("home"),folderTypes:["Folder"],selectableTypes:null,attributes:["UID","Title","portal_type","path","getIcon"],dropdownCssClass:"pattern-relateditems-dropdown",maximumSelectionSize:-1,resultTemplate:'',resultTemplateSelector:null,selectionTemplate:' <% if (typeof getIcon !== "undefined" && getIcon) { %><% } %> <%= Title %> <%= path %>',selectionTemplateSelector:null,breadCrumbsTemplate:' <%= searchText %><%= items %>',breadCrumbsTemplateSelector:null,breadCrumbTemplate:'/<%= text %>',breadCrumbTemplateSelector:null,escapeMarkup:function(a){return a},setupAjax:function(){var a=this;return a.query.valid?a.query.selectAjax():{}}},applyTemplate:function(c,d){var e,f=this;f.options[c+"TemplateSelector"]?(e=a(f.options[c+"TemplateSelector"]).html(),e||(e=f.options[c+"Template"])):e=f.options[c+"Template"];var g=a.extend(!0,{},f.options,d);return g._item=d,b.template(e)(g)},activateBrowsing:function(){var a=this;a.browsing=!0,a.setBreadCrumbs()},deactivateBrowsing:function(){var a=this;a.browsing=!1,a.setBreadCrumbs()},browseTo:function(a){var b=this;b.emit("before-browse"),b.currentPath=a,"/"===a&&"search"===b.options.mode?b.deactivateBrowsing():b.activateBrowsing(),b.$el.select2("close"),b.$el.select2("open"),b.emit("after-browse")},setBreadCrumbs:function(){var c,d=this,e=d.currentPath?d.currentPath:d.options.basePath;if("/"===e){var h="";"search"===d.options.mode&&(h=""+g("entire site")+""),c=d.applyTemplate("breadCrumbs",{items:h,searchText:g("Search:")})}else{var i=e.split("/"),j="",k="";b.each(i,function(a){if(""!==a){var b={};j=j+"/"+a,b.text=a,b.path=j,k+=d.applyTemplate("breadCrumb",b)}}),c=d.applyTemplate("breadCrumbs",{items:k,searchText:g("Search:")})}var l=a(c);a("a.crumb",l).on("click",function(b){return b.preventDefault(),d.browseTo(a(this).attr("href")),!1});var m=a(".pattern-relateditems-tree-select",l),n=m.parent(),o=a(".tree-container",n),p=a(".pat-tree",n),q=null,r=new f(p,{data:[],dataFilter:function(a){var c=[];return b.each(a.results,function(a){var b={label:a.Title,id:a.UID,path:a.path,folder:-1!==d.options.folderTypes.indexOf(a.portal_type)};c.push(b)}),c},onCreateLi:function(a,b){a._loaded&&0===a.children.length&&b.find(".jqtree-title").append(''+g("(empty)")+""),b.append(''),b.find(".pattern-relateditems-result-browse").click(function(b){b.preventDefault(),d.currentPath=a.path,d.browseTo(d.currentPath),o.fadeOut()})}});r.$el.bind("tree.select",function(a){var b=a.node;b&&!b._loaded&&(d.currentPath=b.path,q=b,r.$el.tree("loadDataFromUrl",d.treeQuery.getUrl(),b,function(){r.$el.tree("openNode",b)}),b._loaded=!0)}),r.$el.bind("tree.dblclick",function(a){a.node&&(d.currentPath=a.node.path,d.browseTo(d.currentPath),o.fadeOut())}),r.$el.bind("tree.refresh",function(){q&&r.$el.tree("selectNode",q)}),a("a.pattern-relateditems-tree-cancel",o).click(function(a){return a.preventDefault(),o.fadeOut(),!1}),m.on("click",function(a){return a.preventDefault(),d.browsing=!0,d.currentPath="/",d.$el.select2("close"),o.fadeIn(),r.$el.tree("loadDataFromUrl",d.treeQuery.getUrl()),!1}),d.$el.on("select2-opening",function(){o.fadeOut()}),d.$browsePath.html(l)},selectItem:function(a){var b=this;b.emit("selecting");var c=b.$el.select2("data");c.push(a),b.$el.select2("data",c,!0),a.selected=!0,b.emit("selected")},deselectItem:function(a){var c=this;c.emit("deselecting");var d=c.$el.select2("data");b.each(d,function(b,c){b.UID===a.UID&&d.splice(c,1)}),c.$el.select2("data",d,!0),a.selected=!1,c.emit("deselected")},isSelectable:function(a){var c=this;return null===c.options.selectableTypes?!0:b.indexOf(c.options.selectableTypes,a.portal_type)>-1},init:function(){var c=this;c.query=new e.QueryHelper(a.extend(!0,{},c.options,{pattern:c})),c.treeQuery=new e.QueryHelper(a.extend(!0,{},c.options,{pattern:c,baseCriteria:[{i:"portal_type",o:"plone.app.querystring.operation.list.contains",v:c.options.folderTypes}]})),c.options.ajax=c.options.setupAjax.apply(c),c.$el.wrap(''),c.$container=c.$el.parents(".pattern-relateditems-container"),c.$container.width(c.options.width),d.prototype.initializeValues.call(c),d.prototype.initializeTags.call(c),c.options.formatSelection=function(a,b){return c.applyTemplate("selection",a)},d.prototype.initializeOrdering.call(c),c.options.formatResult=function(d){if(d.folderish=d.portal_type&&-1!==b.indexOf(c.options.folderTypes,d.portal_type)?!0:!1,d.selectable=c.isSelectable(d),void 0===d.selected){var e=c.$el.select2("data");d.selected=!1,b.each(e,function(a){a.UID===d.UID&&(d.selected=!0)})}var f=a(c.applyTemplate("result",d));return a(".pattern-relateditems-result-select",f).on("click",function(b){if(b.preventDefault(),a(this).is(".selectable")){var e=a(this).parents(".pattern-relateditems-result");if(e.is(".pattern-relateditems-active"))e.removeClass("pattern-relateditems-active"),c.deselectItem(d);else if(c.selectItem(d),e.addClass("pattern-relateditems-active"),c.options.maximumSelectionSize>0){var f=c.$select2.select2("data");f.length>=c.options.maximumSelectionSize&&c.$select2.select2("close")}}}),a(".pattern-relateditems-result-browse",f).on("click",function(b){b.preventDefault(),b.stopPropagation();var d=a(this).data("path");c.browseTo(d)}),a(f)},c.options.initSelection=function(b,d){var e=a(b).val();if(""!==e){var f=e.split(c.options.separator);c.query.search("UID","plone.app.querystring.operation.list.contains",f,function(a){var b=a.results.reduce(function(a,b){return a[b.UID]=b,a},{});d(f.map(function(a){return b[a]}).filter(function(a){return void 0!==a}))},!1)}},c.options.id=function(a){return a.UID},d.prototype.initializeSelect2.call(c),c.$browsePath=a(''),c.$container.prepend(c.$browsePath),"search"===c.options.mode?(c.deactivateBrowsing(),c.browsing=!1):(c.activateBrowsing(),c.browsing=!0),c.$el.on("select2-selecting",function(a){a.preventDefault()})}});return h}),function(a){define("tinymce",[],function(){return function(){return function(a,b){"use strict";function c(a,b){for(var c,d=[],f=0;fd;d++)if(f=c[d],f&&f.func.call(f.scope,a)===!1&&a.preventDefault(),a.isImmediatePropagationStopped())return}var g,h,i,j,k,l=this,m={};h=f+(+new Date).toString(32),j="onmouseenter"in document.documentElement,i="onfocusin"in document.documentElement,k={mouseenter:"mouseover",mouseleave:"mouseout"},g=1,l.domLoaded=!1,l.events=m,l.bind=function(b,f,n,o){function p(a){e(c(a||x.event),q)}var q,r,s,t,u,v,w,x=window;if(b&&3!==b.nodeType&&8!==b.nodeType){for(b[h]?q=b[h]:(q=g++,b[h]=q,m[q]={}),o=o||b,f=f.split(" "),s=f.length;s--;)t=f[s],v=p,u=w=!1,"DOMContentLoaded"===t&&(t="ready"),l.domLoaded&&"ready"===t&&"complete"==b.readyState?n.call(o,c({type:t})):(j||(u=k[t],u&&(v=function(a){var b,d;if(b=a.currentTarget,d=a.relatedTarget,d&&b.contains)d=b.contains(d);else for(;d&&d!==b;)d=d.parentNode;d||(a=c(a||x.event),a.type="mouseout"===a.type?"mouseleave":"mouseenter",a.target=b,e(a,q))})),i||"focusin"!==t&&"focusout"!==t||(w=!0,u="focusin"===t?"focus":"blur",v=function(a){a=c(a||x.event),a.type="focus"===a.type?"focusin":"focusout",e(a,q)}),r=m[q][t],r?"ready"===t&&l.domLoaded?n({type:t}):r.push({func:n,scope:o}):(m[q][t]=r=[{func:n,scope:o}],r.fakeName=u,r.capture=w,r.nativeHandler=v,"ready"===t?d(b,v,l):a(b,u||t,v,w)));return b=r=0,n}},l.unbind=function(a,c,d){var e,f,g,i,j,k;if(!a||3===a.nodeType||8===a.nodeType)return l;if(e=a[h]){if(k=m[e],c){for(c=c.split(" "),g=c.length;g--;)if(j=c[g],f=k[j]){if(d)for(i=f.length;i--;)if(f[i].func===d){var n=f.nativeHandler,o=f.fakeName,p=f.capture;f=f.slice(0,i).concat(f.slice(i+1)),f.nativeHandler=n,f.fakeName=o,f.capture=p,k[j]=f}d&&0!==f.length||(delete k[j],b(a,f.fakeName||j,f.nativeHandler,f.capture))}}else{for(j in k)f=k[j],b(a,f.fakeName||j,f.nativeHandler,f.capture);k={}}for(j in k)return l;delete m[e];try{delete a[h]}catch(q){a[h]=null}}return l},l.fire=function(a,b,d){var f;if(!a||3===a.nodeType||8===a.nodeType)return l;d=c(null,d),d.type=b,d.target=a;do f=a[h],f&&e(d,f),a=a.parentNode||a.ownerDocument||a.defaultView||a.parentWindow;while(a&&!d.isPropagationStopped());return l},l.clean=function(a){var b,c,d=l.unbind;if(!a||3===a.nodeType||8===a.nodeType)return l;if(a[h]&&d(a),a.getElementsByTagName||(a=a.document),a&&a.getElementsByTagName)for(d(a),c=a.getElementsByTagName("*"),b=c.length;b--;)a=c[b],a[h]&&d(a);return l},l.destroy=function(){m={}},l.cancel=function(a){return a&&(a.preventDefault(),a.stopImmediatePropagation()),!1}}var f="mce-data-",g=/^(?:mouse|contextmenu)|click/,h={keyLocation:1,layerX:1,layerY:1,returnValue:1,webkitMovementX:1,webkitMovementY:1};return e.Event=new e,e.Event.bind(window,"ready",function(){}),e}),d("tinymce/dom/Sizzle",[],function(){function a(a,b,c,d){var e,f,g,h,i,j,l,n,o,p;if((b?b.ownerDocument||b:O)!==G&&F(b),b=b||G,c=c||[],!a||"string"!=typeof a)return c;if(1!==(h=b.nodeType)&&9!==h)return[];if(I&&!d){if(e=ra.exec(a))if(g=e[1]){if(9===h){if(f=b.getElementById(g),!f||!f.parentNode)return c;if(f.id===g)return c.push(f),c}else if(b.ownerDocument&&(f=b.ownerDocument.getElementById(g))&&M(b,f)&&f.id===g)return c.push(f),c}else{if(e[2])return _.apply(c,b.getElementsByTagName(a)),c;if((g=e[3])&&v.getElementsByClassName)return _.apply(c,b.getElementsByClassName(g)),c}if(v.qsa&&(!J||!J.test(a))){if(n=l=N,o=b,p=9===h&&a,1===h&&"object"!==b.nodeName.toLowerCase()){for(j=z(a),(l=b.getAttribute("id"))?n=l.replace(ta,"\\$&"):b.setAttribute("id",n),n="[id='"+n+"'] ",i=j.length;i--;)j[i]=n+m(j[i]);o=sa.test(a)&&k(b.parentNode)||b,p=j.join(",")}if(p)try{return _.apply(c,o.querySelectorAll(p)),c}catch(q){}finally{l||b.removeAttribute("id")}}}return B(a.replace(ha,"$1"),b,c,d)}function c(){function a(c,d){return b.push(c+" ")>w.cacheLength&&delete a[b.shift()],a[c+" "]=d}var b=[];return a}function d(a){return a[N]=!0,a}function e(a){var b=G.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function f(a,b){for(var c=a.split("|"),d=a.length;d--;)w.attrHandle[c[d]]=b}function g(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||W)-(~a.sourceIndex||W);if(d)return d;if(c)for(;c=c.nextSibling;)if(c===b)return-1;return a?1:-1}function h(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function i(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function j(a){return d(function(b){return b=+b,d(function(c,d){for(var e,f=a([],c.length,b),g=f.length;g--;)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function k(a){return a&&typeof a.getElementsByTagName!==V&&a}function l(){}function m(a){for(var b=0,c=a.length,d="";c>b;b++)d+=a[b].value;return d}function n(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=Q++;return b.first?function(b,c,f){for(;b=b[d];)if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j=[P,f];if(g){for(;b=b[d];)if((1===b.nodeType||e)&&a(b,c,g))return!0}else for(;b=b[d];)if(1===b.nodeType||e){if(i=b[N]||(b[N]={}),(h=i[d])&&h[0]===P&&h[1]===f)return j[2]=h[2];if(i[d]=j,j[2]=a(b,c,g))return!0}}}function o(a){return a.length>1?function(b,c,d){for(var e=a.length;e--;)if(!a[e](b,c,d))return!1;return!0}:a[0]}function p(b,c,d){for(var e=0,f=c.length;f>e;e++)a(b,c[e],d);return d}function q(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function r(a,b,c,e,f,g){return e&&!e[N]&&(e=r(e)),f&&!f[N]&&(f=r(f,g)),d(function(d,g,h,i){var j,k,l,m=[],n=[],o=g.length,r=d||p(b||"*",h.nodeType?[h]:h,[]),s=!a||!d&&b?r:q(r,m,a,h,i),t=c?f||(d?a:o||e)?[]:g:s;if(c&&c(s,t,h,i),e)for(j=q(t,n),e(j,[],h,i),k=j.length;k--;)(l=j[k])&&(t[n[k]]=!(s[n[k]]=l));if(d){if(f||a){if(f){for(j=[],k=t.length;k--;)(l=t[k])&&j.push(s[k]=l);f(null,t=[],j,i)}for(k=t.length;k--;)(l=t[k])&&(j=f?ba.call(d,l):m[k])>-1&&(d[j]=!(g[j]=l))}}else t=q(t===g?t.splice(o,t.length):t),f?f(null,g,t,i):_.apply(g,t)})}function s(a){for(var b,c,d,e=a.length,f=w.relative[a[0].type],g=f||w.relative[" "],h=f?1:0,i=n(function(a){return a===b},g,!0),j=n(function(a){return ba.call(b,a)>-1},g,!0),k=[function(a,c,d){return!f&&(d||c!==C)||((b=c).nodeType?i(a,c,d):j(a,c,d))}];e>h;h++)if(c=w.relative[a[h].type])k=[n(o(k),c)];else{if(c=w.filter[a[h].type].apply(null,a[h].matches),c[N]){for(d=++h;e>d&&!w.relative[a[d].type];d++);return r(h>1&&o(k),h>1&&m(a.slice(0,h-1).concat({value:" "===a[h-2].type?"*":""})).replace(ha,"$1"),c,d>h&&s(a.slice(h,d)),e>d&&s(a=a.slice(d)),e>d&&m(a))}k.push(c)}return o(k)}function t(b,c){var e=c.length>0,f=b.length>0,g=function(d,g,h,i,j){var k,l,m,n=0,o="0",p=d&&[],r=[],s=C,t=d||f&&w.find.TAG("*",j),u=P+=null==s?1:Math.random()||.1,v=t.length;for(j&&(C=g!==G&&g);o!==v&&null!=(k=t[o]);o++){if(f&&k){for(l=0;m=b[l++];)if(m(k,g,h)){i.push(k);break}j&&(P=u)}e&&((k=!m&&k)&&n--,d&&p.push(k))}if(n+=o,e&&o!==n){for(l=0;m=c[l++];)m(p,r,g,h);if(d){if(n>0)for(;o--;)p[o]||r[o]||(r[o]=Z.call(i));r=q(r)}_.apply(i,r),j&&!d&&r.length>0&&n+c.length>1&&a.uniqueSort(i)}return j&&(P=u,C=s),p};return e?d(g):g}var u,v,w,x,y,z,A,B,C,D,E,F,G,H,I,J,K,L,M,N="sizzle"+-new Date,O=window.document,P=0,Q=0,R=c(),S=c(),T=c(),U=function(a,b){return a===b&&(E=!0),0},V=typeof b,W=1<<31,X={}.hasOwnProperty,Y=[],Z=Y.pop,$=Y.push,_=Y.push,aa=Y.slice,ba=Y.indexOf||function(a){for(var b=0,c=this.length;c>b;b++)if(this[b]===a)return b;return-1},ca="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",da="[\\x20\\t\\r\\n\\f]",ea="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",fa="\\["+da+"*("+ea+")(?:"+da+"*([*^$|!~]?=)"+da+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+ea+"))|)"+da+"*\\]",ga=":("+ea+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+fa+")*)|.*)\\)|)",ha=new RegExp("^"+da+"+|((?:^|[^\\\\])(?:\\\\.)*)"+da+"+$","g"),ia=new RegExp("^"+da+"*,"+da+"*"),ja=new RegExp("^"+da+"*([>+~]|"+da+")"+da+"*"),ka=new RegExp("="+da+"*([^\\]'\"]*?)"+da+"*\\]","g"),la=new RegExp(ga),ma=new RegExp("^"+ea+"$"),na={ID:new RegExp("^#("+ea+")"),CLASS:new RegExp("^\\.("+ea+")"),TAG:new RegExp("^("+ea+"|[*])"),ATTR:new RegExp("^"+fa),PSEUDO:new RegExp("^"+ga),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+da+"*(even|odd|(([+-]|)(\\d*)n|)"+da+"*(?:([+-]|)"+da+"*(\\d+)|))"+da+"*\\)|)","i"),bool:new RegExp("^(?:"+ca+")$","i"),
-needsContext:new RegExp("^"+da+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+da+"*((?:-\\d)?\\d*)"+da+"*\\)|)(?=[^-]|$)","i")},oa=/^(?:input|select|textarea|button)$/i,pa=/^h\d$/i,qa=/^[^{]+\{\s*\[native \w/,ra=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,sa=/[+~]/,ta=/'|\\/g,ua=new RegExp("\\\\([\\da-f]{1,6}"+da+"?|("+da+")|.)","ig"),va=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)};try{_.apply(Y=aa.call(O.childNodes),O.childNodes),Y[O.childNodes.length].nodeType}catch(wa){_={apply:Y.length?function(a,b){$.apply(a,aa.call(b))}:function(a,b){for(var c=a.length,d=0;a[c++]=b[d++];);a.length=c-1}}}v=a.support={},y=a.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},F=a.setDocument=function(a){var b,c=a?a.ownerDocument||a:O,d=c.defaultView;return c!==G&&9===c.nodeType&&c.documentElement?(G=c,H=c.documentElement,I=!y(c),d&&d!==d.top&&(d.addEventListener?d.addEventListener("unload",function(){F()},!1):d.attachEvent&&d.attachEvent("onunload",function(){F()})),v.attributes=e(function(a){return a.className="i",!a.getAttribute("className")}),v.getElementsByTagName=e(function(a){return a.appendChild(c.createComment("")),!a.getElementsByTagName("*").length}),v.getElementsByClassName=qa.test(c.getElementsByClassName),v.getById=e(function(a){return H.appendChild(a).id=N,!c.getElementsByName||!c.getElementsByName(N).length}),v.getById?(w.find.ID=function(a,b){if(typeof b.getElementById!==V&&I){var c=b.getElementById(a);return c&&c.parentNode?[c]:[]}},w.filter.ID=function(a){var b=a.replace(ua,va);return function(a){return a.getAttribute("id")===b}}):(delete w.find.ID,w.filter.ID=function(a){var b=a.replace(ua,va);return function(a){var c=typeof a.getAttributeNode!==V&&a.getAttributeNode("id");return c&&c.value===b}}),w.find.TAG=v.getElementsByTagName?function(a,b){return typeof b.getElementsByTagName!==V?b.getElementsByTagName(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){for(;c=f[e++];)1===c.nodeType&&d.push(c);return d}return f},w.find.CLASS=v.getElementsByClassName&&function(a,b){return I?b.getElementsByClassName(a):void 0},K=[],J=[],(v.qsa=qa.test(c.querySelectorAll))&&(e(function(a){a.innerHTML="",a.querySelectorAll("[msallowcapture^='']").length&&J.push("[*^$]="+da+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||J.push("\\["+da+"*(?:value|"+ca+")"),a.querySelectorAll(":checked").length||J.push(":checked")}),e(function(a){var b=c.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&J.push("name"+da+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||J.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),J.push(",.*:")})),(v.matchesSelector=qa.test(L=H.matches||H.webkitMatchesSelector||H.mozMatchesSelector||H.oMatchesSelector||H.msMatchesSelector))&&e(function(a){v.disconnectedMatch=L.call(a,"div"),L.call(a,"[s!='']:x"),K.push("!=",ga)}),J=J.length&&new RegExp(J.join("|")),K=K.length&&new RegExp(K.join("|")),b=qa.test(H.compareDocumentPosition),M=b||qa.test(H.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)for(;b=b.parentNode;)if(b===a)return!0;return!1},U=b?function(a,b){if(a===b)return E=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!v.sortDetached&&b.compareDocumentPosition(a)===d?a===c||a.ownerDocument===O&&M(O,a)?-1:b===c||b.ownerDocument===O&&M(O,b)?1:D?ba.call(D,a)-ba.call(D,b):0:4&d?-1:1)}:function(a,b){if(a===b)return E=!0,0;var d,e=0,f=a.parentNode,h=b.parentNode,i=[a],j=[b];if(!f||!h)return a===c?-1:b===c?1:f?-1:h?1:D?ba.call(D,a)-ba.call(D,b):0;if(f===h)return g(a,b);for(d=a;d=d.parentNode;)i.unshift(d);for(d=b;d=d.parentNode;)j.unshift(d);for(;i[e]===j[e];)e++;return e?g(i[e],j[e]):i[e]===O?-1:j[e]===O?1:0},c):G},a.matches=function(b,c){return a(b,null,null,c)},a.matchesSelector=function(b,c){if((b.ownerDocument||b)!==G&&F(b),c=c.replace(ka,"='$1']"),!(!v.matchesSelector||!I||K&&K.test(c)||J&&J.test(c)))try{var d=L.call(b,c);if(d||v.disconnectedMatch||b.document&&11!==b.document.nodeType)return d}catch(e){}return a(c,G,null,[b]).length>0},a.contains=function(a,b){return(a.ownerDocument||a)!==G&&F(a),M(a,b)},a.attr=function(a,c){(a.ownerDocument||a)!==G&&F(a);var d=w.attrHandle[c.toLowerCase()],e=d&&X.call(w.attrHandle,c.toLowerCase())?d(a,c,!I):b;return e!==b?e:v.attributes||!I?a.getAttribute(c):(e=a.getAttributeNode(c))&&e.specified?e.value:null},a.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},a.uniqueSort=function(a){var b,c=[],d=0,e=0;if(E=!v.detectDuplicates,D=!v.sortStable&&a.slice(0),a.sort(U),E){for(;b=a[e++];)b===a[e]&&(d=c.push(e));for(;d--;)a.splice(c[d],1)}return D=null,a},x=a.getText=function(a){var b,c="",d=0,e=a.nodeType;if(e){if(1===e||9===e||11===e){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=x(a)}else if(3===e||4===e)return a.nodeValue}else for(;b=a[d++];)c+=x(b);return c},w=a.selectors={cacheLength:50,createPseudo:d,match:na,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(ua,va),a[3]=(a[3]||a[4]||a[5]||"").replace(ua,va),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(b){return b[1]=b[1].toLowerCase(),"nth"===b[1].slice(0,3)?(b[3]||a.error(b[0]),b[4]=+(b[4]?b[5]+(b[6]||1):2*("even"===b[3]||"odd"===b[3])),b[5]=+(b[7]+b[8]||"odd"===b[3])):b[3]&&a.error(b[0]),b},PSEUDO:function(a){var b,c=!a[6]&&a[2];return na.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&la.test(c)&&(b=z(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(ua,va).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=R[a+" "];return b||(b=new RegExp("(^|"+da+")"+a+"("+da+"|$)"))&&R(a,function(a){return b.test("string"==typeof a.className&&a.className||typeof a.getAttribute!==V&&a.getAttribute("class")||"")})},ATTR:function(b,c,d){return function(e){var f=a.attr(e,b);return null==f?"!="===c:c?(f+="","="===c?f===d:"!="===c?f!==d:"^="===c?d&&0===f.indexOf(d):"*="===c?d&&f.indexOf(d)>-1:"$="===c?d&&f.slice(-d.length)===d:"~="===c?(" "+f+" ").indexOf(d)>-1:"|="===c?f===d||f.slice(0,d.length+1)===d+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h;if(q){if(f){for(;p;){for(l=b;l=l[p];)if(h?l.nodeName.toLowerCase()===r:1===l.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){for(k=q[N]||(q[N]={}),j=k[a]||[],n=j[0]===P&&j[1],m=j[0]===P&&j[2],l=n&&q.childNodes[n];l=++n&&l&&l[p]||(m=n=0)||o.pop();)if(1===l.nodeType&&++m&&l===b){k[a]=[P,n,m];break}}else if(s&&(j=(b[N]||(b[N]={}))[a])&&j[0]===P)m=j[1];else for(;(l=++n&&l&&l[p]||(m=n=0)||o.pop())&&((h?l.nodeName.toLowerCase()!==r:1!==l.nodeType)||!++m||(s&&((l[N]||(l[N]={}))[a]=[P,m]),l!==b)););return m-=e,m===d||m%d===0&&m/d>=0}}},PSEUDO:function(b,c){var e,f=w.pseudos[b]||w.setFilters[b.toLowerCase()]||a.error("unsupported pseudo: "+b);return f[N]?f(c):f.length>1?(e=[b,b,"",c],w.setFilters.hasOwnProperty(b.toLowerCase())?d(function(a,b){for(var d,e=f(a,c),g=e.length;g--;)d=ba.call(a,e[g]),a[d]=!(b[d]=e[g])}):function(a){return f(a,0,e)}):f}},pseudos:{not:d(function(a){var b=[],c=[],e=A(a.replace(ha,"$1"));return e[N]?d(function(a,b,c,d){for(var f,g=e(a,null,d,[]),h=a.length;h--;)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,d,f){return b[0]=a,e(b,null,f,c),!c.pop()}}),has:d(function(b){return function(c){return a(b,c).length>0}}),contains:d(function(a){return a=a.replace(ua,va),function(b){return(b.textContent||b.innerText||x(b)).indexOf(a)>-1}}),lang:d(function(b){return ma.test(b||"")||a.error("unsupported lang: "+b),b=b.replace(ua,va).toLowerCase(),function(a){var c;do if(c=I?a.lang:a.getAttribute("xml:lang")||a.getAttribute("lang"))return c=c.toLowerCase(),c===b||0===c.indexOf(b+"-");while((a=a.parentNode)&&1===a.nodeType);return!1}}),target:function(a){var b=window.location&&window.location.hash;return b&&b.slice(1)===a.id},root:function(a){return a===H},focus:function(a){return a===G.activeElement&&(!G.hasFocus||G.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!w.pseudos.empty(a)},header:function(a){return pa.test(a.nodeName)},input:function(a){return oa.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:j(function(){return[0]}),last:j(function(a,b){return[b-1]}),eq:j(function(a,b,c){return[0>c?c+b:c]}),even:j(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:j(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:j(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:j(function(a,b,c){for(var d=0>c?c+b:c;++d2&&"ID"===(g=f[0]).type&&v.getById&&9===b.nodeType&&I&&w.relative[f[1].type]){if(b=(w.find.ID(g.matches[0].replace(ua,va),b)||[])[0],!b)return c;j&&(b=b.parentNode),a=a.slice(f.shift().value.length)}for(e=na.needsContext.test(a)?0:f.length;e--&&(g=f[e],!w.relative[h=g.type]);)if((i=w.find[h])&&(d=i(g.matches[0].replace(ua,va),sa.test(f[0].type)&&k(b.parentNode)||b))){if(f.splice(e,1),a=d.length&&m(f),!a)return _.apply(c,d),c;break}}return(j||A(a,l))(d,b,!I,c,sa.test(a)&&k(b.parentNode)||b),c},v.sortStable=N.split("").sort(U).join("")===N,v.detectDuplicates=!!E,F(),v.sortDetached=e(function(a){return 1&a.compareDocumentPosition(G.createElement("div"))}),e(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||f("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),v.attributes&&e(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||f("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),e(function(a){return null==a.getAttribute("disabled")})||f(ca,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),a}),d("tinymce/Env",[],function(){var a,b,c,d,e,f,g,h,i,j,k=navigator,l=k.userAgent;a=window.opera&&window.opera.buildNumber,i=/Android/.test(l),b=/WebKit/.test(l),c=!b&&!a&&/MSIE/gi.test(l)&&/Explorer/gi.test(k.appName),c=c&&/MSIE (\w+)\./.exec(l)[1],d=-1==l.indexOf("Trident/")||-1==l.indexOf("rv:")&&-1==k.appName.indexOf("Netscape")?!1:11,e=-1==l.indexOf("Edge/")||c||d?!1:12,c=c||d||e,f=!b&&!d&&/Gecko/.test(l),g=-1!=l.indexOf("Mac"),h=/(iPad|iPhone)/.test(l),j="FormData"in window&&"FileReader"in window&&"URL"in window&&!!URL.createObjectURL,e&&(b=!1);var m=!h||j||l.match(/AppleWebKit\/(\d*)/)[1]>=534;return{opera:a,webkit:b,ie:c,gecko:f,mac:g,iOS:h,android:i,contentEditable:m,transparentSrc:"data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7",caretAfter:8!=c,range:window.getSelection&&"Range"in window,documentMode:c&&!e?document.documentMode||7:10,fileApi:j}}),d("tinymce/util/Arr",[],function(){function a(a){var b,c,d=a;if(!h(a))for(d=[],b=0,c=a.length;c>b;b++)d[b]=a[b];return d}function c(a,c,d){var e,f;if(!a)return 0;if(d=d||a,a.length!==b){for(e=0,f=a.length;f>e;e++)if(c.call(d,a[e],e,a)===!1)return 0}else for(e in a)if(a.hasOwnProperty(e)&&c.call(d,a[e],e,a)===!1)return 0;return 1}function d(a,b){var d=[];return c(a,function(c,e){d.push(b(c,e,a))}),d}function e(a,b){var d=[];return c(a,function(a){(!b||b(a))&&d.push(a)}),d}function f(a,b){var c,d;if(a)for(c=0,d=a.length;d>c;c++)if(a[c]===b)return c;return-1}function g(a,b,c,d){var e=0;for(arguments.length<3&&(c=a[0],e=1);ed;d++){c=h[d];for(f in c)c.hasOwnProperty(f)&&(g=c[f],g!==b&&(a[f]=g))}return a}function i(a,b,d,e){e=e||this,a&&(d&&(a=a[d]),c.each(a,function(a,c){return b.call(e,a,c,d)===!1?!1:void i(a,b,d,e)}))}function j(a,b){var c,d;for(b=b||window,a=a.split("."),c=0;cc&&(b=b[a[c]],b);c++);return b}function l(a,b){return!a||e(a,"array")?a:c.map(a.split(b||","),d)}function m(b){var c=a.cacheSuffix;return c&&(b+=(-1===b.indexOf("?")?"?":"&")+c),b}var n=/^\s*|\s*$/g;return{trim:d,isArray:c.isArray,is:e,toArray:c.toArray,makeMap:f,each:c.each,map:c.map,grep:c.filter,inArray:c.indexOf,extend:h,create:g,walk:i,createNS:j,resolve:k,explode:l,_addCacheSuffix:m}}),d("tinymce/dom/DomQuery",["tinymce/dom/EventUtils","tinymce/dom/Sizzle","tinymce/util/Tools","tinymce/Env"],function(a,c,d,e){function f(a){return"undefined"!=typeof a}function g(a){return"string"==typeof a}function h(a){return a&&a==a.window}function i(a,b){var c,d,e;for(b=b||x,e=b.createElement("div"),c=b.createDocumentFragment(),e.innerHTML=a;d=e.firstChild;)c.appendChild(d);return c}function j(a,b,c,d){var e;if(g(b))b=i(b,r(a[0]));else if(b.length&&!b.nodeType){if(b=m.makeArray(b),d)for(e=b.length-1;e>=0;e--)j(a,b[e],c,d);else for(e=0;ee&&(g=a[e],b.call(g,e,g)!==!1);e++);return a}function q(a,b){var c=[];return p(a,function(a,d){b(d,a)&&c.push(d)}),c}function r(a){return a?9==a.nodeType?a:a.ownerDocument:x}function s(a,c,d){var e=[],f=a[c];for("string"!=typeof d&&d instanceof m&&(d=d[0]);f&&9!==f.nodeType;){if(d!==b){if(f===d)break;if("string"==typeof d&&m(f).is(d))break}1===f.nodeType&&e.push(f),f=f[c]}return e}function t(a,c,d,e){var f=[];for(e instanceof m&&(e=e[0]);a;a=a[c])if(!d||a.nodeType===d){if(e!==b){if(a===e)break;if("string"==typeof e&&m(a).is(e))break}f.push(a)}return f}function u(a,b,c){for(a=a[b];a;a=a[b])if(a.nodeType==c)return a;return null}function v(a,b,c){p(c,function(c,d){a[c]=a[c]||{},a[c][b]=d})}var w,x=document,y=Array.prototype.push,z=Array.prototype.slice,A=/^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,B=a.Event,C=d.makeMap("fillOpacity fontWeight lineHeight opacity orphans widows zIndex zoom"," "),D=d.makeMap("checked compact declare defer disabled ismap multiple nohref noshade nowrap readonly selected"," "),E={"for":"htmlFor","class":"className",readonly:"readOnly"},F={"float":"cssFloat"},G={},H={},I=/^\s*|\s*$/g;return m.fn=m.prototype={constructor:m,selector:"",context:null,length:0,init:function(a,b){var c,d,e=this;if(!a)return e;if(a.nodeType)return e.context=e[0]=a,e.length=1,e;if(b&&b.nodeType)e.context=b;else{if(b)return m(a).attr(b);e.context=b=document}if(g(a)){if(e.selector=a,c="<"===a.charAt(0)&&">"===a.charAt(a.length-1)&&a.length>=3?[null,a,null]:A.exec(a),!c)return m(b).find(a);if(c[1])for(d=i(a,r(b)).firstChild;d;)y.call(e,d),d=d.nextSibling;else{if(d=r(b).getElementById(c[2]),!d)return e;if(d.id!==c[2])return e.find(a);e.length=1,e[0]=d}}else this.add(a,!1);return e},toArray:function(){return d.toArray(this)},add:function(a,b){var c,d,e=this;if(g(a))return e.add(m(a));if(b!==!1)for(c=m.unique(e.toArray().concat(m.makeArray(a))),e.length=c.length,d=0;db;b++)m.find(a,this[b],d);return m(d)},filter:function(a){return m("function"==typeof a?q(this.toArray(),function(b,c){return a(c,b)}):m.filter(a,this.toArray()))},closest:function(a){var b=[];return a instanceof m&&(a=a[0]),this.each(function(c,d){for(;d;){if("string"==typeof a&&m(d).is(a)){b.push(d);break}if(d==a){b.push(d);break}d=d.parentNode}}),m(b)},offset:function(a){var b,c,d,e,f=0,g=0;return a?this.css(a):(b=this[0],b&&(c=b.ownerDocument,d=c.documentElement,b.getBoundingClientRect&&(e=b.getBoundingClientRect(),f=e.left+(d.scrollLeft||c.body.scrollLeft)-d.clientLeft,g=e.top+(d.scrollTop||c.body.scrollTop)-d.clientTop)),{left:f,top:g})},push:y,sort:[].sort,splice:[].splice},d.extend(m,{extend:d.extend,makeArray:function(a){return h(a)||a.nodeType?[a]:d.toArray(a)},inArray:n,isArray:d.isArray,each:p,trim:o,grep:q,find:c,expr:c.selectors,unique:c.uniqueSort,text:c.getText,contains:c.contains,filter:function(a,b,c){var d=b.length;for(c&&(a=":not("+a+")");d--;)1!=b[d].nodeType&&b.splice(d,1);return b=1===b.length?m.find.matchesSelector(b[0],a)?[b[0]]:[]:m.find.matches(a,b)}}),p({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return s(a,"parentNode")},next:function(a){return u(a,"nextSibling",1)},prev:function(a){return u(a,"previousSibling",1)},children:function(a){return t(a.firstChild,"nextSibling",1)},contents:function(a){return d.toArray(("iframe"===a.nodeName?a.contentDocument||a.contentWindow.document:a).childNodes)}},function(a,b){m.fn[a]=function(c){var d=this,e=[];return d.each(function(){var a=b.call(e,this,c,e);a&&(m.isArray(a)?e.push.apply(e,a):e.push(a))}),this.length>1&&(e=m.unique(e),0===a.indexOf("parents")&&(e=e.reverse())),e=m(e),c?e.filter(c):e}}),p({parentsUntil:function(a,b){return s(a,"parentNode",b)},nextUntil:function(a,b){return t(a,"nextSibling",1,b).slice(1)},prevUntil:function(a,b){return t(a,"previousSibling",1,b).slice(1)}},function(a,b){m.fn[a]=function(c,d){var e=this,f=[];return e.each(function(){var a=b.call(f,this,c,f);a&&(m.isArray(a)?f.push.apply(f,a):f.push(a))}),this.length>1&&(f=m.unique(f),(0===a.indexOf("parents")||"prevUntil"===a)&&(f=f.reverse())),f=m(f),d?f.filter(d):f}}),m.fn.is=function(a){return!!a&&this.filter(a).length>0},m.fn.init.prototype=m.fn,m.overrideDefaults=function(a){function b(d,e){return c=c||a(),0===arguments.length&&(d=c.element),e||(e=c.context),new b.fn.init(d,e)}var c;return m.extend(b,this),b},e.ie&&e.ie<8&&(v(G,"get",{maxlength:function(a){var b=a.maxLength;return 2147483647===b?w:b},size:function(a){var b=a.size;return 20===b?w:b},"class":function(a){return a.className},style:function(a){var b=a.style.cssText;return 0===b.length?w:b}}),v(G,"set",{"class":function(a,b){a.className=b},style:function(a,b){a.style.cssText=b}})),e.ie&&e.ie<9&&(F["float"]="styleFloat",v(H,"set",{opacity:function(a,b){var c=a.style;null===b||""===b?c.removeAttribute("filter"):(c.zoom=1,c.filter="alpha(opacity="+100*b+")")}})),m.attrHooks=G,m.cssHooks=H,m}),d("tinymce/html/Styles",[],function(){return function(a,b){function c(a,b,c,d){function e(a){return a=parseInt(a,10).toString(16),a.length>1?a:"0"+a}return"#"+e(b)+e(c)+e(d)}var d,e,f,g,h,i=/rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/gi,j=/(?:url(?:(?:\(\s*\"([^\"]+)\"\s*\))|(?:\(\s*\'([^\']+)\'\s*\))|(?:\(\s*([^)\s]+)\s*\))))|(?:\'([^\']+)\')|(?:\"([^\"]+)\")/gi,k=/\s*([^:]+):\s*([^;]+);?/g,l=/\s+$/,m={},n="\ufeff";for(a=a||{},b&&(g=b.getValidStyles(),h=b.getInvalidStyles()),f=("\\\" \\' \\; \\: ; : "+n).split(" "),e=0;e-1&&c||(t[a+b]=-1==e?i[0]:i.join(" "),delete t[a+"-top"+b],delete t[a+"-right"+b],delete t[a+"-bottom"+b],delete t[a+"-left"+b])}}function f(a){var b,c=t[a];if(c){for(c=c.split(" "),b=c.length;b--;)if(c[b]!==c[0])return!1;return t[a]=c[0],!0}}function g(a,b,c,d){f(b)&&f(c)&&f(d)&&(t[a]=t[b]+" "+t[c]+" "+t[d],delete t[b],delete t[c],delete t[d])}function h(a){return s=!0,m[a]}function n(a,b){return s&&(a=a.replace(/\uFEFF[0-9]/g,function(a){return m[a]})),b||(a=a.replace(/\\([\'\";:])/g,"$1")),a}function o(b,c,d,e,f,g){if(f=f||g)return f=n(f),"'"+f.replace(/\'/g,"\\'")+"'";if(c=n(c||d||e),!a.allow_script_urls){var h=c.replace(/[\s\r\n]+/,"");if(/(java|vb)script:/i.test(h))return"";if(!a.allow_svg_data_urls&&/^data:image\/svg/i.test(h))return""}return u&&(c=u.call(v,c,"style")),"url('"+c.replace(/\'/g,"\\'")+"')"}var p,q,r,s,t={},u=a.url_converter,v=a.url_converter_scope||this;if(b){for(b=b.replace(/[\u0000-\u001F]/g,""),b=b.replace(/\\[\"\';:\uFEFF]/g,h).replace(/\"[^\"]+\"|\'[^\']+\'/g,function(a){return a.replace(/[;:]/g,h)});p=k.exec(b);){if(q=p[1].replace(l,"").toLowerCase(),r=p[2].replace(l,""),r=r.replace(/\\[0-9a-f]+/g,function(a){return String.fromCharCode(parseInt(a.substr(1),16))}),q&&r.length>0){if(!a.allow_script_urls&&("behavior"==q||/expression\s*\(|\/\*|\*\//.test(r)))continue;"font-weight"===q&&"700"===r?r="bold":("color"===q||"background-color"===q)&&(r=r.toLowerCase()),r=r.replace(i,c),r=r.replace(j,o),t[q]=s?n(r,!0):r}k.lastIndex=p.index+p[0].length}d("border","",!0),d("border","-width"),d("border","-color"),d("border","-style"),d("padding",""),d("margin",""),g("border","border-width","border-style","border-color"),"medium none"===t.border&&delete t.border,"none"===t["border-image"]&&delete t["border-image"]}return t},serialize:function(a,b){function c(b){var c,e,f,h;if(c=g[b])for(e=0,f=c.length;f>e;e++)b=c[e],h=a[b],h!==d&&h.length>0&&(j+=(j.length>0?" ":"")+b+": "+h+";")}function e(a,b){var c;return c=h["*"],c&&c[a]?!1:(c=h[b],c&&c[a]?!1:!0)}var f,i,j="";if(b&&g)c("*"),c(b);else for(f in a)i=a[f],i!==d&&i.length>0&&(!h||e(f,b))&&(j+=(j.length>0?" ":"")+f+": "+i+";");return j}}}}),d("tinymce/dom/TreeWalker",[],function(){return function(a,b){function c(a,c,d,e){var f,g;if(a){if(!e&&a[c])return a[c];if(a!=b){if(f=a[d])return f;for(g=a.parentNode;g&&g!=b;g=g.parentNode)if(f=g[d])return f}}}var d=a;this.current=function(){return d},this.next=function(a){return d=c(d,"firstChild","nextSibling",a)},this.prev=function(a){return d=c(d,"lastChild","previousSibling",a)}}}),d("tinymce/dom/Range",["tinymce/util/Tools"],function(a){function b(c){function d(){return J.createDocumentFragment()}function e(a,b){x(N,a,b)}function f(a,b){x(O,a,b)}function g(a){e(a.parentNode,U(a))}function h(a){e(a.parentNode,U(a)+1)}function i(a){f(a.parentNode,U(a))}function j(a){f(a.parentNode,U(a)+1)}function k(a){a?(I[R]=I[Q],I[S]=I[P]):(I[Q]=I[R],I[P]=I[S]),I.collapsed=N}function l(a){g(a),j(a)}function m(a){e(a,0),f(a,1===a.nodeType?a.childNodes.length:a.nodeValue.length)}function n(a,b){var c=I[Q],d=I[P],e=I[R],f=I[S],g=b.startContainer,h=b.startOffset,i=b.endContainer,j=b.endOffset;return 0===a?w(c,d,g,h):1===a?w(e,f,g,h):2===a?w(e,f,i,j):3===a?w(c,d,i,j):void 0}function o(){y(M)}function p(){return y(K)}function q(){return y(L)}function r(a){var b,d,e=this[Q],f=this[P];3!==e.nodeType&&4!==e.nodeType||!e.nodeValue?(e.childNodes.length>0&&(d=e.childNodes[f]),d?e.insertBefore(a,d):3==e.nodeType?c.insertAfter(a,e):e.appendChild(a)):f?f>=e.nodeValue.length?c.insertAfter(a,e):(b=e.splitText(f),e.parentNode.insertBefore(a,b)):e.parentNode.insertBefore(a,e)}function s(a){var b=I.extractContents();I.insertNode(a),a.appendChild(b),I.selectNode(a)}function t(){return T(new b(c),{startContainer:I[Q],startOffset:I[P],endContainer:I[R],endOffset:I[S],collapsed:I.collapsed,commonAncestorContainer:I.commonAncestorContainer})}function u(a,b){var c;if(3==a.nodeType)return a;if(0>b)return a;for(c=a.firstChild;c&&b>0;)--b,c=c.nextSibling;return c?c:a}function v(){return I[Q]==I[R]&&I[P]==I[S]}function w(a,b,d,e){var f,g,h,i,j,k;if(a==d)return b==e?0:e>b?-1:1;for(f=d;f&&f.parentNode!=a;)f=f.parentNode;if(f){for(g=0,h=a.firstChild;h!=f&&b>g;)g++,h=h.nextSibling;return g>=b?-1:1}for(f=a;f&&f.parentNode!=d;)f=f.parentNode;if(f){for(g=0,h=d.firstChild;h!=f&&e>g;)g++,h=h.nextSibling;return e>g?-1:1}for(i=c.findCommonAncestor(a,d),j=a;j&&j.parentNode!=i;)j=j.parentNode;for(j||(j=i),k=d;k&&k.parentNode!=i;)k=k.parentNode;if(k||(k=i),j==k)return 0;for(h=i.firstChild;h;){if(h==j)return-1;if(h==k)return 1;h=h.nextSibling}}function x(a,b,d){var e,f;for(a?(I[Q]=b,I[P]=d):(I[R]=b,I[S]=d),e=I[R];e.parentNode;)e=e.parentNode;for(f=I[Q];f.parentNode;)f=f.parentNode;f==e?w(I[Q],I[P],I[R],I[S])>0&&I.collapse(a):I.collapse(a),I.collapsed=v(),I.commonAncestorContainer=c.findCommonAncestor(I[Q],I[R])}function y(a){var b,c,d,e,f,g,h,i=0,j=0;if(I[Q]==I[R])return z(a);
-
-for(b=I[R],c=b.parentNode;c;b=c,c=c.parentNode){if(c==I[Q])return A(b,a);++i}for(b=I[Q],c=b.parentNode;c;b=c,c=c.parentNode){if(c==I[R])return B(b,a);++j}for(d=j-i,e=I[Q];d>0;)e=e.parentNode,d--;for(f=I[R];0>d;)f=f.parentNode,d++;for(g=e.parentNode,h=f.parentNode;g!=h;g=g.parentNode,h=h.parentNode)e=g,f=h;return C(e,f,a)}function z(a){var b,c,e,f,g,h,i,j,k;if(a!=M&&(b=d()),I[P]==I[S])return b;if(3==I[Q].nodeType){if(c=I[Q].nodeValue,e=c.substring(I[P],I[S]),a!=L&&(f=I[Q],j=I[P],k=I[S]-I[P],0===j&&k>=f.nodeValue.length-1?f.parentNode.removeChild(f):f.deleteData(j,k),I.collapse(N)),a==M)return;return e.length>0&&b.appendChild(J.createTextNode(e)),b}for(f=u(I[Q],I[P]),g=I[S]-I[P];f&&g>0;)h=f.nextSibling,i=G(f,a),b&&b.appendChild(i),--g,f=h;return a!=L&&I.collapse(N),b}function A(a,b){var c,e,f,g,h,i;if(b!=M&&(c=d()),e=D(a,b),c&&c.appendChild(e),f=U(a),g=f-I[P],0>=g)return b!=L&&(I.setEndBefore(a),I.collapse(O)),c;for(e=a.previousSibling;g>0;)h=e.previousSibling,i=G(e,b),c&&c.insertBefore(i,c.firstChild),--g,e=h;return b!=L&&(I.setEndBefore(a),I.collapse(O)),c}function B(a,b){var c,e,f,g,h,i;for(b!=M&&(c=d()),f=E(a,b),c&&c.appendChild(f),e=U(a),++e,g=I[S]-e,f=a.nextSibling;f&&g>0;)h=f.nextSibling,i=G(f,b),c&&c.appendChild(i),--g,f=h;return b!=L&&(I.setStartAfter(a),I.collapse(N)),c}function C(a,b,c){var e,f,g,h,i,j,k;for(c!=M&&(f=d()),e=E(a,c),f&&f.appendChild(e),g=U(a),h=U(b),++g,i=h-g,j=a.nextSibling;i>0;)k=j.nextSibling,e=G(j,c),f&&f.appendChild(e),j=k,--i;return e=D(b,c),f&&f.appendChild(e),c!=L&&(I.setStartAfter(a),I.collapse(N)),f}function D(a,b){var c,d,e,f,g,h=u(I[R],I[S]-1),i=h!=I[R];if(h==a)return F(h,i,O,b);for(c=h.parentNode,d=F(c,O,O,b);c;){for(;h;)e=h.previousSibling,f=F(h,i,O,b),b!=M&&d.insertBefore(f,d.firstChild),i=N,h=e;if(c==a)return d;h=c.previousSibling,c=c.parentNode,g=F(c,O,O,b),b!=M&&g.appendChild(d),d=g}}function E(a,b){var c,d,e,f,g,h=u(I[Q],I[P]),i=h!=I[Q];if(h==a)return F(h,i,N,b);for(c=h.parentNode,d=F(c,O,N,b);c;){for(;h;)e=h.nextSibling,f=F(h,i,N,b),b!=M&&d.appendChild(f),i=N,h=e;if(c==a)return d;h=c.nextSibling,c=c.parentNode,g=F(c,O,N,b),b!=M&&g.appendChild(d),d=g}}function F(a,b,d,e){var f,g,h,i,j;if(b)return G(a,e);if(3==a.nodeType){if(f=a.nodeValue,d?(i=I[P],g=f.substring(i),h=f.substring(0,i)):(i=I[S],g=f.substring(0,i),h=f.substring(i)),e!=L&&(a.nodeValue=h),e==M)return;return j=c.clone(a,O),j.nodeValue=g,j}if(e!=M)return c.clone(a,O)}function G(a,b){return b!=M?b==L?c.clone(a,N):a:void a.parentNode.removeChild(a)}function H(){return c.create("body",null,q()).outerText}var I=this,J=c.doc,K=0,L=1,M=2,N=!0,O=!1,P="startOffset",Q="startContainer",R="endContainer",S="endOffset",T=a.extend,U=c.nodeIndex;return T(I,{startContainer:J,startOffset:0,endContainer:J,endOffset:0,collapsed:N,commonAncestorContainer:J,START_TO_START:0,START_TO_END:1,END_TO_END:2,END_TO_START:3,setStart:e,setEnd:f,setStartBefore:g,setStartAfter:h,setEndBefore:i,setEndAfter:j,collapse:k,selectNode:l,selectNodeContents:m,compareBoundaryPoints:n,deleteContents:o,extractContents:p,cloneContents:q,insertNode:r,surroundContents:s,cloneRange:t,toStringIE:H}),I}return b.prototype.toString=function(){return this.toStringIE()},b}),d("tinymce/html/Entities",["tinymce/util/Tools"],function(a){function b(a){var b;return b=document.createElement("div"),b.innerHTML=a,b.textContent||b.innerText||a}function c(a,b){var c,d,f,g={};if(a){for(a=a.split(","),b=b||10,c=0;c\"\u0060\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,i=/[<>&\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,j=/[<>&\"\']/g,k=/([a-z0-9]+);?|&([a-z0-9]+);/gi,l={128:"€",130:"‚",131:"ƒ",132:"„",133:"…",134:"†",135:"‡",136:"ˆ",137:"‰",138:"Š",139:"‹",140:"Œ",142:"Ž",145:"‘",146:"’",147:"“",148:"”",149:"•",150:"–",151:"—",152:"˜",153:"™",154:"š",155:"›",156:"œ",158:"ž",159:"Ÿ"};e={'"':""","'":"'","<":"<",">":">","&":"&","`":"`"},f={"<":"<",">":">","&":"&",""":'"',"'":"'"},d=c("50,nbsp,51,iexcl,52,cent,53,pound,54,curren,55,yen,56,brvbar,57,sect,58,uml,59,copy,5a,ordf,5b,laquo,5c,not,5d,shy,5e,reg,5f,macr,5g,deg,5h,plusmn,5i,sup2,5j,sup3,5k,acute,5l,micro,5m,para,5n,middot,5o,cedil,5p,sup1,5q,ordm,5r,raquo,5s,frac14,5t,frac12,5u,frac34,5v,iquest,60,Agrave,61,Aacute,62,Acirc,63,Atilde,64,Auml,65,Aring,66,AElig,67,Ccedil,68,Egrave,69,Eacute,6a,Ecirc,6b,Euml,6c,Igrave,6d,Iacute,6e,Icirc,6f,Iuml,6g,ETH,6h,Ntilde,6i,Ograve,6j,Oacute,6k,Ocirc,6l,Otilde,6m,Ouml,6n,times,6o,Oslash,6p,Ugrave,6q,Uacute,6r,Ucirc,6s,Uuml,6t,Yacute,6u,THORN,6v,szlig,70,agrave,71,aacute,72,acirc,73,atilde,74,auml,75,aring,76,aelig,77,ccedil,78,egrave,79,eacute,7a,ecirc,7b,euml,7c,igrave,7d,iacute,7e,icirc,7f,iuml,7g,eth,7h,ntilde,7i,ograve,7j,oacute,7k,ocirc,7l,otilde,7m,ouml,7n,divide,7o,oslash,7p,ugrave,7q,uacute,7r,ucirc,7s,uuml,7t,yacute,7u,thorn,7v,yuml,ci,fnof,sh,Alpha,si,Beta,sj,Gamma,sk,Delta,sl,Epsilon,sm,Zeta,sn,Eta,so,Theta,sp,Iota,sq,Kappa,sr,Lambda,ss,Mu,st,Nu,su,Xi,sv,Omicron,t0,Pi,t1,Rho,t3,Sigma,t4,Tau,t5,Upsilon,t6,Phi,t7,Chi,t8,Psi,t9,Omega,th,alpha,ti,beta,tj,gamma,tk,delta,tl,epsilon,tm,zeta,tn,eta,to,theta,tp,iota,tq,kappa,tr,lambda,ts,mu,tt,nu,tu,xi,tv,omicron,u0,pi,u1,rho,u2,sigmaf,u3,sigma,u4,tau,u5,upsilon,u6,phi,u7,chi,u8,psi,u9,omega,uh,thetasym,ui,upsih,um,piv,812,bull,816,hellip,81i,prime,81j,Prime,81u,oline,824,frasl,88o,weierp,88h,image,88s,real,892,trade,89l,alefsym,8cg,larr,8ch,uarr,8ci,rarr,8cj,darr,8ck,harr,8dl,crarr,8eg,lArr,8eh,uArr,8ei,rArr,8ej,dArr,8ek,hArr,8g0,forall,8g2,part,8g3,exist,8g5,empty,8g7,nabla,8g8,isin,8g9,notin,8gb,ni,8gf,prod,8gh,sum,8gi,minus,8gn,lowast,8gq,radic,8gt,prop,8gu,infin,8h0,ang,8h7,and,8h8,or,8h9,cap,8ha,cup,8hb,int,8hk,there4,8hs,sim,8i5,cong,8i8,asymp,8j0,ne,8j1,equiv,8j4,le,8j5,ge,8k2,sub,8k3,sup,8k4,nsub,8k6,sube,8k7,supe,8kl,oplus,8kn,otimes,8l5,perp,8m5,sdot,8o8,lceil,8o9,rceil,8oa,lfloor,8ob,rfloor,8p9,lang,8pa,rang,9ea,loz,9j0,spades,9j3,clubs,9j5,hearts,9j6,diams,ai,OElig,aj,oelig,b0,Scaron,b1,scaron,bo,Yuml,m6,circ,ms,tilde,802,ensp,803,emsp,809,thinsp,80c,zwnj,80d,zwj,80e,lrm,80f,rlm,80j,ndash,80k,mdash,80o,lsquo,80p,rsquo,80q,sbquo,80s,ldquo,80t,rdquo,80u,bdquo,810,dagger,811,Dagger,81g,permil,81p,lsaquo,81q,rsaquo,85c,euro",32);var m={encodeRaw:function(a,b){return a.replace(b?h:i,function(a){return e[a]||a})},encodeAllRaw:function(a){return(""+a).replace(j,function(a){return e[a]||a})},encodeNumeric:function(a,b){return a.replace(b?h:i,function(a){return a.length>1?""+(1024*(a.charCodeAt(0)-55296)+(a.charCodeAt(1)-56320)+65536)+";":e[a]||""+a.charCodeAt(0)+";"})},encodeNamed:function(a,b,c){return c=c||d,a.replace(b?h:i,function(a){return e[a]||c[a]||a})},getEncodeFunc:function(a,b){function f(a,c){return a.replace(c?h:i,function(a){return e[a]||b[a]||""+a.charCodeAt(0)+";"||a})}function j(a,c){return m.encodeNamed(a,c,b)}return b=c(b)||d,a=g(a.replace(/\+/g,",")),a.named&&a.numeric?f:a.named?b?j:m.encodeNamed:a.numeric?m.encodeNumeric:m.encodeRaw},decode:function(a){return a.replace(k,function(a,c){return c?(c="x"===c.charAt(0).toLowerCase()?parseInt(c.substr(1),16):parseInt(c,10),c>65535?(c-=65536,String.fromCharCode(55296+(c>>10),56320+(1023&c))):l[c]||String.fromCharCode(c)):f[a]||d[a]||b(a)})}};return m}),d("tinymce/dom/StyleSheetLoader",["tinymce/util/Tools"],function(a){return function(b,c){function d(a){b.getElementsByTagName("head")[0].appendChild(a)}function e(c,e,i){function j(){for(var a=s.passed,b=a.length;b--;)a[b]();s.status=2,s.passed=[],s.failed=[]}function k(){for(var a=s.failed,b=a.length;b--;)a[b]();s.status=3,s.passed=[],s.failed=[]}function l(){var a=navigator.userAgent.match(/WebKit\/(\d*)/);return!!(a&&a[1]<536)}function m(a,b){a()||((new Date).getTime()-r0)return q=b.createElement("style"),q.textContent='@import "'+c+'"',o(),void d(q);n()}d(p),p.href=c}}var f,g=0,h={};c=c||{},f=c.maxLoadTime||5e3,this.load=e}}),d("tinymce/dom/DOMUtils",["tinymce/dom/Sizzle","tinymce/dom/DomQuery","tinymce/html/Styles","tinymce/dom/EventUtils","tinymce/dom/TreeWalker","tinymce/dom/Range","tinymce/html/Entities","tinymce/Env","tinymce/util/Tools","tinymce/dom/StyleSheetLoader"],function(a,c,d,e,f,g,h,i,j,k){function l(a,b){var c,d={},e=b.keep_values;return c={set:function(c,d,e){b.url_converter&&(d=b.url_converter.call(b.url_converter_scope||a,d,e,c[0])),c.attr("data-mce-"+e,d).attr(e,d)},get:function(a,b){return a.attr("data-mce-"+b)||a.attr(b)}},d={style:{set:function(a,b){return null!==b&&"object"==typeof b?void a.css(b):(e&&a.attr("data-mce-style",b),void a.attr("style",b))},get:function(b){var c=b.attr("data-mce-style")||b.attr("style");return c=a.serializeStyle(a.parseStyle(c),b[0].nodeName)}}},e&&(d.href=d.src=c),d}function m(a,b){var f,g=this;g.doc=a,g.win=window,g.files={},g.counter=0,g.stdMode=!r||a.documentMode>=8,g.boxModel=!r||"CSS1Compat"==a.compatMode||g.stdMode,g.styleSheetLoader=new k(a),g.boundEvents=[],g.settings=b=b||{},g.schema=b.schema,g.styles=new d({url_converter:b.url_converter,url_converter_scope:b.url_converter_scope},b.schema),g.fixDoc(a),g.events=b.ownEvents?new e(b.proxy):e.Event,g.attrHooks=l(g,b),f=b.schema?b.schema.getBlockElements():{},g.$=c.overrideDefaults(function(){return{context:a,element:g.getRoot()}}),g.isBlock=function(a){if(!a)return!1;var b=a.nodeType;return b?!(1!==b||!f[a.nodeName]):!!f[a]}}var n=j.each,o=j.is,p=j.grep,q=j.trim,r=i.ie,s=/^([a-z0-9],?)+$/i,t=/^[ \t\r\n]*$/;return m.prototype={$$:function(a){return"string"==typeof a&&(a=this.get(a)),this.$(a)},root:null,fixDoc:function(a){var b,c=this.settings;if(r&&c.schema){"abbr article aside audio canvas details figcaption figure footer header hgroup mark menu meter nav output progress section summary time video".replace(/\w+/g,function(b){a.createElement(b)});for(b in c.schema.getCustomElements())a.createElement(b)}},clone:function(a,b){var c,d,e=this;return!r||1!==a.nodeType||b?a.cloneNode(b):(d=e.doc,b?c.firstChild:(c=d.createElement(a.nodeName),n(e.getAttribs(a),function(b){e.setAttrib(c,b.nodeName,e.getAttrib(a,b.nodeName))}),c))},getRoot:function(){var a=this;return a.settings.root_element||a.doc.body},getViewPort:function(a){var b,c;return a=a?a:this.win,b=a.document,c=this.boxModel?b.documentElement:b.body,{x:a.pageXOffset||c.scrollLeft,y:a.pageYOffset||c.scrollTop,w:a.innerWidth||c.clientWidth,h:a.innerHeight||c.clientHeight}},getRect:function(a){var b,c,d=this;return a=d.get(a),b=d.getPos(a),c=d.getSize(a),{x:b.x,y:b.y,w:c.w,h:c.h}},getSize:function(a){var b,c,d=this;return a=d.get(a),b=d.getStyle(a,"width"),c=d.getStyle(a,"height"),-1===b.indexOf("px")&&(b=0),-1===c.indexOf("px")&&(c=0),{w:parseInt(b,10)||a.offsetWidth||a.clientWidth,h:parseInt(c,10)||a.offsetHeight||a.clientHeight}},getParent:function(a,b,c){return this.getParents(a,b,c,!1)},getParents:function(a,c,d,e){var f,g=this,h=[];for(a=g.get(a),e=e===b,d=d||("BODY"!=g.getRoot().nodeName?g.getRoot().parentNode:null),o(c,"string")&&(f=c,c="*"===c?function(a){return 1==a.nodeType}:function(a){return g.is(a,f)});a&&a!=d&&a.nodeType&&9!==a.nodeType;){if(!c||c(a)){if(!e)return a;h.push(a)}a=a.parentNode}return e?h:null},get:function(a){var b;return a&&this.doc&&"string"==typeof a&&(b=a,a=this.doc.getElementById(a),a&&a.id!==b)?this.doc.getElementsByName(b)[1]:a},getNext:function(a,b){return this._findSib(a,b,"nextSibling")},getPrev:function(a,b){return this._findSib(a,b,"previousSibling")},select:function(b,c){var d=this;return a(b,d.get(c)||d.settings.root_element||d.doc,[])},is:function(c,d){var e;if(c.length===b){if("*"===d)return 1==c.nodeType;if(s.test(d)){for(d=d.toLowerCase().split(/,/),c=c.nodeName.toLowerCase(),e=d.length-1;e>=0;e--)if(d[e]==c)return!0;return!1}}if(c.nodeType&&1!=c.nodeType)return!1;var f=c.nodeType?[c]:c;return a(d,f[0].ownerDocument||f[0],null,f).length>0},add:function(a,b,c,d,e){var f=this;return this.run(a,function(a){var g;return g=o(b,"string")?f.doc.createElement(b):b,f.setAttribs(g,c),d&&(d.nodeType?g.appendChild(d):f.setHTML(g,d)),e?g:a.appendChild(g)})},create:function(a,b,c){return this.add(this.doc.createElement(a),a,b,c,1)},createHTML:function(a,b,c){var d,e="";e+="<"+a;for(d in b)b.hasOwnProperty(d)&&null!==b[d]&&"undefined"!=typeof b[d]&&(e+=" "+d+'="'+this.encode(b[d])+'"');return"undefined"!=typeof c?e+">"+c+""+a+">":e+" />"},createFragment:function(a){var b,c,d,e=this.doc;for(d=e.createElement("div"),b=e.createDocumentFragment(),a&&(d.innerHTML=a);c=d.firstChild;)b.appendChild(c);return b},remove:function(a,b){return a=this.$$(a),b?a.each(function(){for(var a;a=this.firstChild;)3==a.nodeType&&0===a.data.length?this.removeChild(a):this.parentNode.insertBefore(a,this)}).remove():a.remove(),a.length>1?a.toArray():a[0]},setStyle:function(a,b,c){a=this.$$(a).css(b,c),this.settings.update_styles&&a.attr("data-mce-style",null)},getStyle:function(a,c,d){return a=this.$$(a),d?a.css(c):(c=c.replace(/-(\D)/g,function(a,b){return b.toUpperCase()}),"float"==c&&(c=i.ie&&i.ie<12?"styleFloat":"cssFloat"),a[0]&&a[0].style?a[0].style[c]:b)},setStyles:function(a,b){a=this.$$(a).css(b),this.settings.update_styles&&a.attr("data-mce-style",null)},removeAllAttribs:function(a){return this.run(a,function(a){var b,c=a.attributes;for(b=c.length-1;b>=0;b--)a.removeAttributeNode(c.item(b))})},setAttrib:function(a,b,c){var d,e,f=this,g=f.settings;""===c&&(c=null),a=f.$$(a),d=a.attr(b),a.length&&(e=f.attrHooks[b],e&&e.set?e.set(a,c,b):a.attr(b,c),d!=c&&g.onSetAttrib&&g.onSetAttrib({attrElm:a,attrName:b,attrValue:c}))},setAttribs:function(a,b){var c=this;c.$$(a).each(function(a,d){n(b,function(a,b){c.setAttrib(d,b,a)})})},getAttrib:function(a,b,c){var d,e,f=this;return a=f.$$(a),a.length&&(d=f.attrHooks[b],e=d&&d.get?d.get(a,b):a.attr(b)),"undefined"==typeof e&&(e=c||""),e},getPos:function(a,b){var d,e,f=this,g=0,h=0,i=f.doc,j=i.body;if(a=f.get(a),b=b||j,a){if(b===j&&a.getBoundingClientRect&&"static"===c(j).css("position"))return e=a.getBoundingClientRect(),b=f.boxModel?i.documentElement:j,g=e.left+(i.documentElement.scrollLeft||j.scrollLeft)-b.clientLeft,h=e.top+(i.documentElement.scrollTop||j.scrollTop)-b.clientTop,{x:g,y:h};for(d=a;d&&d!=b&&d.nodeType;)g+=d.offsetLeft||0,h+=d.offsetTop||0,d=d.offsetParent;for(d=a.parentNode;d&&d!=b&&d.nodeType;)g-=d.scrollLeft||0,h-=d.scrollTop||0,d=d.parentNode}return{x:g,y:h}},parseStyle:function(a){return this.styles.parse(a)},serializeStyle:function(a,b){return this.styles.serialize(a,b)},addStyle:function(a){var b,c,d=this,e=d.doc;if(d!==m.DOM&&e===document){var f=m.DOM.addedStyles;if(f=f||[],f[a])return;f[a]=!0,m.DOM.addedStyles=f}c=e.getElementById("mceDefaultStyles"),c||(c=e.createElement("style"),c.id="mceDefaultStyles",c.type="text/css",b=e.getElementsByTagName("head")[0],b.firstChild?b.insertBefore(c,b.firstChild):b.appendChild(c)),c.styleSheet?c.styleSheet.cssText+=a:c.appendChild(e.createTextNode(a))},loadCSS:function(a){var b,c=this,d=c.doc;return c!==m.DOM&&d===document?void m.DOM.loadCSS(a):(a||(a=""),b=d.getElementsByTagName("head")[0],void n(a.split(","),function(a){var e;a=j._addCacheSuffix(a),c.files[a]||(c.files[a]=!0,e=c.create("link",{rel:"stylesheet",href:a}),r&&d.documentMode&&d.recalc&&(e.onload=function(){d.recalc&&d.recalc(),e.onload=null}),b.appendChild(e))}))},addClass:function(a,b){this.$$(a).addClass(b)},removeClass:function(a,b){this.toggleClass(a,b,!1)},hasClass:function(a,b){return this.$$(a).hasClass(b)},toggleClass:function(a,b,d){this.$$(a).toggleClass(b,d).each(function(){""===this.className&&c(this).attr("class",null)})},show:function(a){this.$$(a).show()},hide:function(a){this.$$(a).hide()},isHidden:function(a){return"none"==this.$$(a).css("display")},uniqueId:function(a){return(a?a:"mce_")+this.counter++},setHTML:function(a,b){a=this.$$(a),r?a.each(function(a,d){if(d.canHaveHTML!==!1){for(;d.firstChild;)d.removeChild(d.firstChild);try{d.innerHTML="
"+b,d.removeChild(d.firstChild)}catch(e){c("").html("
"+b).contents().slice(1).appendTo(d)}return b}}):a.html(b)},getOuterHTML:function(a){return a=this.get(a),1==a.nodeType&&"outerHTML"in a?a.outerHTML:c("
").append(c(a).clone()).html()},setOuterHTML:function(a,b){var d=this;d.$$(a).each(function(){try{if("outerHTML"in this)return void(this.outerHTML=b)}catch(a){}d.remove(c(this).html(b),!0)})},decode:h.decode,encode:h.encodeAllRaw,insertAfter:function(a,b){return b=this.get(b),this.run(a,function(a){var c,d;return c=b.parentNode,d=b.nextSibling,d?c.insertBefore(a,d):c.appendChild(a),a})},replace:function(a,b,c){var d=this;return d.run(b,function(b){return o(b,"array")&&(a=a.cloneNode(!0)),c&&n(p(b.childNodes),function(b){a.appendChild(b)}),b.parentNode.replaceChild(a,b)})},rename:function(a,b){var c,d=this;return a.nodeName!=b.toUpperCase()&&(c=d.create(b),n(d.getAttribs(a),function(b){d.setAttrib(c,b.nodeName,d.getAttrib(a,b.nodeName))}),d.replace(c,a,1)),c||a},findCommonAncestor:function(a,b){for(var c,d=a;d;){for(c=b;c&&d!=c;)c=c.parentNode;if(d==c)break;d=d.parentNode}return!d&&a.ownerDocument?a.ownerDocument.documentElement:d},toHex:function(a){return this.styles.toHex(j.trim(a))},run:function(a,b,c){var d,e=this;return"string"==typeof a&&(a=e.get(a)),a?(c=c||this,a.nodeType||!a.length&&0!==a.length?b.call(c,a):(d=[],n(a,function(a,f){a&&("string"==typeof a&&(a=e.get(a)),d.push(b.call(c,a,f)))}),d)):!1},getAttribs:function(a){var b;if(a=this.get(a),!a)return[];if(r){if(b=[],"OBJECT"==a.nodeName)return a.attributes;"OPTION"===a.nodeName&&this.getAttrib(a,"selected")&&b.push({specified:1,nodeName:"selected"});var c=/<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi;return a.cloneNode(!1).outerHTML.replace(c,"").replace(/[\w:\-]+/gi,function(a){b.push({specified:1,nodeName:a})}),b}return a.attributes},isEmpty:function(a,b){var c,d,e,g,h,i=this,j=0;if(a=a.firstChild){g=new f(a,a.parentNode),b=b||(i.schema?i.schema.getNonEmptyElements():null);do{if(e=a.nodeType,1===e){if(a.getAttribute("data-mce-bogus"))continue;if(h=a.nodeName.toLowerCase(),b&&b[h]){if("br"===h){j++;continue}return!1}for(d=i.getAttribs(a),c=d.length;c--;)if(h=d[c].nodeName,"name"===h||"data-mce-bookmark"===h)return!1}if(8==e)return!1;if(3===e&&!t.test(a.nodeValue))return!1}while(a=g.next())}return 1>=j},createRng:function(){var a=this.doc;return a.createRange?a.createRange():new g(this)},nodeIndex:function(a,b){var c,d,e=0;if(a)for(c=a.nodeType,a=a.previousSibling;a;a=a.previousSibling)d=a.nodeType,(!b||3!=d||d!=c&&a.nodeValue.length)&&(e++,c=d);return e},split:function(a,b,c){function d(a){function b(a){var b=a.previousSibling&&"SPAN"==a.previousSibling.nodeName,c=a.nextSibling&&"SPAN"==a.nextSibling.nodeName;return b&&c}var c,e=a.childNodes,f=a.nodeType;if(1!=f||"bookmark"!=a.getAttribute("data-mce-type")){for(c=e.length-1;c>=0;c--)d(e[c]);if(9!=f){if(3==f&&a.nodeValue.length>0){var g=q(a.nodeValue).length;if(!h.isBlock(a.parentNode)||g>0||0===g&&b(a))return}else if(1==f&&(e=a.childNodes,1==e.length&&e[0]&&1==e[0].nodeType&&"bookmark"==e[0].getAttribute("data-mce-type")&&a.parentNode.insertBefore(e[0],a),e.length||/^(br|hr|input|img)$/i.test(a.nodeName)))return;h.remove(a)}return a}}var e,f,g,h=this,i=h.createRng();return a&&b?(i.setStart(a.parentNode,h.nodeIndex(a)),i.setEnd(b.parentNode,h.nodeIndex(b)),e=i.extractContents(),i=h.createRng(),i.setStart(b.parentNode,h.nodeIndex(b)+1),i.setEnd(a.parentNode,h.nodeIndex(a)+1),f=i.extractContents(),g=a.parentNode,g.insertBefore(d(e),a),c?g.replaceChild(c,b):g.insertBefore(b,a),g.insertBefore(d(f),a),h.remove(a),c||b):void 0},bind:function(a,b,c,d){var e=this;if(j.isArray(a)){for(var f=a.length;f--;)a[f]=e.bind(a[f],b,c,d);return a}return!e.settings.collect||a!==e.doc&&a!==e.win||e.boundEvents.push([a,b,c,d]),e.events.bind(a,b,c,d||e)},unbind:function(a,b,c){var d,e=this;if(j.isArray(a)){for(d=a.length;d--;)a[d]=e.unbind(a[d],b,c);return a}if(e.boundEvents&&(a===e.doc||a===e.win))for(d=e.boundEvents.length;d--;){var f=e.boundEvents[d];a!=f[0]||b&&b!=f[1]||c&&c!=f[2]||this.events.unbind(f[0],f[1],f[2])}return this.events.unbind(a,b,c)},fire:function(a,b,c){return this.events.fire(a,b,c)},getContentEditable:function(a){var b;return a&&1==a.nodeType?(b=a.getAttribute("data-mce-contenteditable"),b&&"inherit"!==b?b:"inherit"!==a.contentEditable?a.contentEditable:null):null},getContentEditableParent:function(a){for(var b=this.getRoot(),c=null;a&&a!==b&&(c=this.getContentEditable(a),null===c);a=a.parentNode);return c},destroy:function(){var b=this;if(b.boundEvents){for(var c=b.boundEvents.length;c--;){var d=b.boundEvents[c];this.events.unbind(d[0],d[1],d[2])}b.boundEvents=null}a.setDocument&&a.setDocument(),b.win=b.doc=b.root=b.events=b.frag=null},isChildOf:function(a,b){for(;a;){if(b===a)return!0;a=a.parentNode}return!1},dumpRng:function(a){return"startContainer: "+a.startContainer.nodeName+", startOffset: "+a.startOffset+", endContainer: "+a.endContainer.nodeName+", endOffset: "+a.endOffset},_findSib:function(a,b,c){var d=this,e=b;if(a)for("string"==typeof e&&(e=function(a){return d.is(a,b)}),a=a[c];a;a=a[c])if(e(a))return a;return null}},m.DOM=new m(document),m}),d("tinymce/dom/ScriptLoader",["tinymce/dom/DOMUtils","tinymce/util/Tools"],function(a,b){function c(){function a(a,c){function e(){i.remove(h),g&&(g.onreadystatechange=g.onload=g=null),c()}function f(){"undefined"!=typeof console&&console.log&&console.log("Failed to load: "+a)}var g,h,i=d;h=i.uniqueId(),g=document.createElement("script"),g.id=h,g.type="text/javascript",g.src=b._addCacheSuffix(a),"onreadystatechange"in g?g.onreadystatechange=function(){/loaded|complete/.test(g.readyState)&&e()}:g.onload=e,g.onerror=f,(document.getElementsByTagName("head")[0]||document.body).appendChild(g)}var c,g=0,h=1,i=2,j={},k=[],l={},m=[],n=0;this.isDone=function(a){return j[a]==i},this.markDone=function(a){j[a]=i},this.add=this.load=function(a,b,d){var e=j[a];e==c&&(k.push(a),j[a]=g),b&&(l[a]||(l[a]=[]),l[a].push({func:b,scope:d||this}))},this.loadQueue=function(a,b){this.loadScripts(k,a,b)},this.loadScripts=function(b,d,g){function k(a){e(l[a],function(a){a.func.call(a.scope)}),l[a]=c}var o;m.push({func:d,scope:g||this}),(o=function(){var c=f(b);b.length=0,e(c,function(b){return j[b]==i?void k(b):void(j[b]!=h&&(j[b]=h,n++,a(b,function(){j[b]=i,n--,k(b),o()})))}),n||(e(m,function(a){a.func.call(a.scope)}),m.length=0)})()}}var d=a.DOM,e=b.each,f=b.grep;return c.ScriptLoader=new c,c}),d("tinymce/AddOnManager",["tinymce/dom/ScriptLoader","tinymce/util/Tools"],function(a,c){function d(){var a=this;a.items=[],a.urls={},a.lookup={}}var e=c.each;return d.prototype={get:function(a){return this.lookup[a]?this.lookup[a].instance:b},dependencies:function(a){var b;return this.lookup[a]&&(b=this.lookup[a].dependencies),b||[]},requireLangPack:function(b,c){var e=d.language;if(e&&d.languageLoad!==!1){if(c)if(c=","+c+",",-1!=c.indexOf(","+e.substr(0,2)+","))e=e.substr(0,2);else if(-1==c.indexOf(","+e+","))return;a.ScriptLoader.add(this.urls[b]+"/langs/"+e+".js")}},add:function(a,b,c){return this.items.push(b),this.lookup[a]={instance:b,dependencies:c},b},createUrl:function(a,b){return"object"==typeof b?b:{prefix:a.prefix,resource:b,suffix:a.suffix}},addComponents:function(b,c){var d=this.urls[b];e(c,function(b){a.ScriptLoader.add(d+"/"+b)})},load:function(c,f,g,h){function i(){var d=j.dependencies(c);e(d,function(a){var c=j.createUrl(f,a);j.load(c.resource,c,b,b)}),g&&g.call(h?h:a)}var j=this,k=f;j.urls[c]||("object"==typeof f&&(k=f.prefix+f.resource+f.suffix),0!==k.indexOf("/")&&-1==k.indexOf("://")&&(k=d.baseURL+"/"+k),j.urls[c]=k.substring(0,k.lastIndexOf("/")),j.lookup[c]?i():a.ScriptLoader.add(k,i,h))}},d.PluginManager=new d,d.ThemeManager=new d,d}),d("tinymce/dom/RangeUtils",["tinymce/util/Tools","tinymce/dom/TreeWalker"],function(a,b){function c(a,b){var c=a.childNodes;return b--,b>c.length-1?b=c.length-1:0>b&&(b=0),c[b]||a}function d(a){this.walk=function(b,d){function f(a){var b;return b=a[0],3===b.nodeType&&b===q&&r>=b.nodeValue.length&&a.splice(0,1),b=a[a.length-1],0===t&&a.length>0&&b===s&&3===b.nodeType&&a.splice(a.length-1,1),a}function g(a,b,c){for(var d=[];a&&a!=c;a=a[b])d.push(a);return d}function h(a,b){do{if(a.parentNode==b)return a;a=a.parentNode}while(a)}function i(a,b,c){var e=c?"nextSibling":"previousSibling";for(m=a,n=m.parentNode;m&&m!=b;m=n)n=m.parentNode,o=g(m==a?m:m[e],e),o.length&&(c||o.reverse(),d(f(o)))}var j,k,l,m,n,o,p,q=b.startContainer,r=b.startOffset,s=b.endContainer,t=b.endOffset;if(p=a.select("td.mce-item-selected,th.mce-item-selected"),p.length>0)return void e(p,function(a){d([a])});if(1==q.nodeType&&q.hasChildNodes()&&(q=q.childNodes[r]),1==s.nodeType&&s.hasChildNodes()&&(s=c(s,t)),q==s)return d(f([q]));for(j=a.findCommonAncestor(q,s),m=q;m;m=m.parentNode){if(m===s)return i(q,j,!0);if(m===j)break}for(m=s;m;m=m.parentNode){if(m===q)return i(s,j);if(m===j)break}k=h(q,j)||q,l=h(s,j)||s,i(q,k,!0),o=g(k==q?k:k.nextSibling,"nextSibling",l==s?l.nextSibling:l),o.length&&d(f(o)),i(s,l)},this.split=function(a){function b(a,b){return a.splitText(b)}var c=a.startContainer,d=a.startOffset,e=a.endContainer,f=a.endOffset;return c==e&&3==c.nodeType?d>0&&d
d?(f-=d,c=e=b(e,f).previousSibling,f=e.nodeValue.length,d=0):f=0):(3==c.nodeType&&d>0&&d0&&f0)return j=m,k=c?m.nodeValue.length:0,void(e=!0);if(a.isBlock(m)||n[m.nodeName.toLowerCase()])return;h=m}f&&h&&(j=h,e=!0,k=0)}var j,k,l,m,n,o,p,q=a.getRoot();if(j=c[(d?"start":"end")+"Container"],k=c[(d?"start":"end")+"Offset"],p=1==j.nodeType&&k===j.childNodes.length,n=a.schema.getNonEmptyElements(),o=d,1==j.nodeType&&k>j.childNodes.length-1&&(o=!1),9===j.nodeType&&(j=a.getRoot(),k=0),j===q){if(o&&(m=j.childNodes[k>0?k-1:0],m&&(n[m.nodeName]||"TABLE"==m.nodeName)))return;if(j.hasChildNodes()&&(k=Math.min(!o&&k>0?k-1:k,j.childNodes.length-1),j=j.childNodes[k],k=0,j.hasChildNodes()&&!/TABLE/.test(j.nodeName))){m=j,l=new b(j,q);do{if(3===m.nodeType&&m.nodeValue.length>0){k=o?0:m.nodeValue.length,j=m,e=!0;break}if(n[m.nodeName.toLowerCase()]){k=a.nodeIndex(m),j=m.parentNode,"IMG"!=m.nodeName||o||k++,e=!0;break}}while(m=o?l.next():l.prev())}}f&&(3===j.nodeType&&0===k&&i(!0),1===j.nodeType&&(m=j.childNodes[k],m||(m=j.childNodes[k-1]),!m||"BR"!==m.nodeName||h(m,"A")||g(m)||g(m,!0)||i(!0,m))),o&&!f&&3===j.nodeType&&k===j.nodeValue.length&&i(!1),e&&c["set"+(d?"Start":"End")](j,k)}var e,f;return f=c.collapsed,d(!0),f||d(),e&&f&&c.collapse(!0),e}}var e=a.each;return d.compareRanges=function(a,b){if(a&&b){if(!a.item&&!a.duplicate)return a.startContainer==b.startContainer&&a.startOffset==b.startOffset;if(a.item&&b.item&&a.item(0)===b.item(0))return!0;if(a.isEqual&&b.isEqual&&b.isEqual(a))return!0}return!1},d.getCaretRangeFromPoint=function(a,b,c){var d,e;if(c.caretPositionFromPoint)e=c.caretPositionFromPoint(a,b),d=c.createRange(),d.setStart(e.offsetNode,e.offset),d.collapse(!0);else if(c.caretRangeFromPoint)d=c.caretRangeFromPoint(a,b);else if(c.body.createTextRange){d=c.body.createTextRange();try{d.moveToPoint(a,b),d.collapse(!0)}catch(f){d.collapse(b=a.childNodes.length&&(b=a.childNodes.length-1),a=a.childNodes[b]),a},d}),d("tinymce/NodeChange",["tinymce/dom/RangeUtils","tinymce/Env"],function(a,b){return function(c){function d(a){var b,d;if(d=c.$(a).parentsUntil(c.getBody()).add(a),d.length===f.length){for(b=d.length;b>=0&&d[b]===f[b];b--);if(-1===b)return f=d,!0}return f=d,!1}var e,f=[];"onselectionchange"in c.getDoc()||c.on("NodeChange Click MouseUp KeyUp Focus",function(b){var d,f;d=c.selection.getRng(),f={startContainer:d.startContainer,startOffset:d.startOffset,endContainer:d.endContainer,endOffset:d.endOffset},"nodechange"!=b.type&&a.compareRanges(f,e)||c.fire("SelectionChange"),e=f}),c.on("contextmenu",function(){c.fire("SelectionChange")}),c.on("SelectionChange",function(){var a=c.selection.getStart(!0);(b.range||!c.selection.isCollapsed())&&!d(a)&&c.dom.isChildOf(a,c.getBody())&&c.nodeChanged({selectionChange:!0})}),c.on("MouseUp",function(a){a.isDefaultPrevented()||("IMG"==c.selection.getNode().nodeName?setTimeout(function(){c.nodeChanged()},0):c.nodeChanged())}),this.nodeChanged=function(a){var b,d,e,f=c.selection;c.initialized&&f&&!c.settings.disable_nodechange&&!c.settings.readonly&&(e=c.getBody(),b=f.getStart()||e,b=b.ownerDocument!=c.getDoc()?c.getBody():b,"IMG"==b.nodeName&&f.isCollapsed()&&(b=b.parentNode),d=[],c.dom.getParent(b,function(a){return a===e?!0:void d.push(a)}),a=a||{},a.element=b,a.parents=d,c.fire("NodeChange",a))}}}),d("tinymce/html/Node",[],function(){function a(a,b,c){var d,e,f=c?"lastChild":"firstChild",g=c?"prev":"next";if(a[f])return a[f];if(a!==b){if(d=a[g])return d;for(e=a.parent;e&&e!==b;e=e.parent)if(d=e[g])return d}}function b(a,b){this.name=a,this.type=b,1===b&&(this.attributes=[],this.attributes.map={})}var c=/^[ \t\r\n]*$/,d={"#text":3,"#comment":8,"#cdata":4,"#pi":7,"#doctype":10,"#document-fragment":11};return b.prototype={replace:function(a){var b=this;return a.parent&&a.remove(),b.insert(a,b),b.remove(),b},attr:function(a,b){var c,d,e,f=this;if("string"!=typeof a){for(d in a)f.attr(d,a[d]);return f}if(c=f.attributes){if(b!==e){if(null===b){if(a in c.map)for(delete c.map[a],d=c.length;d--;)if(c[d].name===a)return c=c.splice(d,1),f;return f}if(a in c.map){for(d=c.length;d--;)if(c[d].name===a){c[d].value=b;break}}else c.push({name:a,value:b});return c.map[a]=b,f}return c.map[a]}},clone:function(){var a,c,d,e,f,g=this,h=new b(g.name,g.type);if(d=g.attributes){for(f=[],f.map={},a=0,c=d.length;c>a;a++)e=d[a],"id"!==e.name&&(f[f.length]={name:e.name,value:e.value},f.map[e.name]=e.value);h.attributes=f}return h.value=g.value,h.shortEnded=g.shortEnded,h},wrap:function(a){var b=this;return b.parent.insert(a,b),a.append(b),b},unwrap:function(){var a,b,c=this;for(a=c.firstChild;a;)b=a.next,c.insert(a,c,!0),a=b;c.remove()},remove:function(){var a=this,b=a.parent,c=a.next,d=a.prev;return b&&(b.firstChild===a?(b.firstChild=c,c&&(c.prev=null)):d.next=c,b.lastChild===a?(b.lastChild=d,d&&(d.next=null)):c.prev=d,a.parent=a.next=a.prev=null),a},append:function(a){var b,c=this;return a.parent&&a.remove(),b=c.lastChild,b?(b.next=a,a.prev=b,c.lastChild=a):c.lastChild=c.firstChild=a,a.parent=c,a},insert:function(a,b,c){var d;return a.parent&&a.remove(),d=b.parent||this,c?(b===d.firstChild?d.firstChild=a:b.prev.next=a,a.prev=b.prev,a.next=b,b.prev=a):(b===d.lastChild?d.lastChild=a:b.next.prev=a,a.next=b.next,a.prev=b,b.next=a),a.parent=d,a},getAll:function(b){var c,d=this,e=[];for(c=d.firstChild;c;c=a(c,d))c.name===b&&e.push(c);
-
-return e},empty:function(){var b,c,d,e=this;if(e.firstChild){for(b=[],d=e.firstChild;d;d=a(d,e))b.push(d);for(c=b.length;c--;)d=b[c],d.parent=d.firstChild=d.lastChild=d.next=d.prev=null}return e.firstChild=e.lastChild=null,e},isEmpty:function(b){var d,e,f=this,g=f.firstChild;if(g)do{if(1===g.type){if(g.attributes.map["data-mce-bogus"])continue;if(b[g.name])return!1;for(d=g.attributes.length;d--;)if(e=g.attributes[d].name,"name"===e||0===e.indexOf("data-mce-bookmark"))return!1}if(8===g.type)return!1;if(3===g.type&&!c.test(g.value))return!1}while(g=a(g,f));return!0},walk:function(b){return a(this,null,b)}},b.create=function(a,c){var e,f;if(e=new b(a,d[a]||1),c)for(f in c)e.attr(f,c[f]);return e},b}),d("tinymce/html/Schema",["tinymce/util/Tools"],function(a){function b(a,b){return a?a.split(b||" "):[]}function c(a){function c(a,c,d){function e(a,b){var c,d,e={};for(c=0,d=a.length;d>c;c++)e[a[c]]=b||{};return e}var h,i,j,k=arguments;for(d=d||[],c=c||"","string"==typeof d&&(d=b(d)),i=3;if;f++)e.attributes[c[f]]={},e.attributesOrder.push(c[f])}var g,i,j,k,l,m,n={};return e[a]?e[a]:(g=b("id accesskey class dir lang style tabindex title"),i=b("address blockquote div dl fieldset form h1 h2 h3 h4 h5 h6 hr menu ol p pre table ul"),j=b("a abbr b bdo br button cite code del dfn em embed i iframe img input ins kbd label map noscript object q s samp script select small span strong sub sup textarea u var #text #comment"),"html4"!=a&&(g.push.apply(g,b("contenteditable contextmenu draggable dropzone hidden spellcheck translate")),i.push.apply(i,b("article aside details dialog figure header footer hgroup section nav")),j.push.apply(j,b("audio canvas command datalist mark meter output picture progress time wbr video ruby bdi keygen"))),"html5-strict"!=a&&(g.push("xml:lang"),m=b("acronym applet basefont big font strike tt"),j.push.apply(j,m),h(m,function(a){c(a,"",j)}),l=b("center dir isindex noframes"),i.push.apply(i,l),k=[].concat(i,j),h(l,function(a){c(a,"",k)})),k=k||[].concat(i,j),c("html","manifest","head body"),c("head","","base command link meta noscript script style title"),c("title hr noscript br"),c("base","href target"),c("link","href rel media hreflang type sizes hreflang"),c("meta","name http-equiv content charset"),c("style","media type scoped"),c("script","src async defer type charset"),c("body","onafterprint onbeforeprint onbeforeunload onblur onerror onfocus onhashchange onload onmessage onoffline ononline onpagehide onpageshow onpopstate onresize onscroll onstorage onunload",k),c("address dt dd div caption","",k),c("h1 h2 h3 h4 h5 h6 pre p abbr code var samp kbd sub sup i b u bdo span legend em strong small s cite dfn","",j),c("blockquote","cite",k),c("ol","reversed start type","li"),c("ul","","li"),c("li","value",k),c("dl","","dt dd"),c("a","href target rel media hreflang type",j),c("q","cite",j),c("ins del","cite datetime",k),c("img","src sizes srcset alt usemap ismap width height"),c("iframe","src name width height",k),c("embed","src type width height"),c("object","data type typemustmatch name usemap form width height",k,"param"),c("param","name value"),c("map","name",k,"area"),c("area","alt coords shape href target rel media hreflang type"),c("table","border","caption colgroup thead tfoot tbody tr"+("html4"==a?" col":"")),c("colgroup","span","col"),c("col","span"),c("tbody thead tfoot","","tr"),c("tr","","td th"),c("td","colspan rowspan headers",k),c("th","colspan rowspan headers scope abbr",k),c("form","accept-charset action autocomplete enctype method name novalidate target",k),c("fieldset","disabled form name",k,"legend"),c("label","form for",j),c("input","accept alt autocomplete checked dirname disabled form formaction formenctype formmethod formnovalidate formtarget height list max maxlength min multiple name pattern readonly required size src step type value width"),c("button","disabled form formaction formenctype formmethod formnovalidate formtarget name type value","html4"==a?k:j),c("select","disabled form multiple name required size","option optgroup"),c("optgroup","disabled label","option"),c("option","disabled label selected value"),c("textarea","cols dirname disabled form maxlength name readonly required rows wrap"),c("menu","type label",k,"li"),c("noscript","",k),"html4"!=a&&(c("wbr"),c("ruby","",j,"rt rp"),c("figcaption","",k),c("mark rt rp summary bdi","",j),c("canvas","width height",k),c("video","src crossorigin poster preload autoplay mediagroup loop muted controls width height buffered",k,"track source"),c("audio","src crossorigin preload autoplay mediagroup loop muted controls buffered volume",k,"track source"),c("picture","","img source"),c("source","src srcset type media sizes"),c("track","kind src srclang label default"),c("datalist","",j,"option"),c("article section nav aside header footer","",k),c("hgroup","","h1 h2 h3 h4 h5 h6"),c("figure","",k,"figcaption"),c("time","datetime",j),c("dialog","open",k),c("command","type label icon disabled checked radiogroup command"),c("output","for form name",j),c("progress","value max",j),c("meter","value min max low high optimum",j),c("details","open",k,"summary"),c("keygen","autofocus challenge disabled form keytype name")),"html5-strict"!=a&&(d("script","language xml:space"),d("style","xml:space"),d("object","declare classid code codebase codetype archive standby align border hspace vspace"),d("embed","align name hspace vspace"),d("param","valuetype type"),d("a","charset name rev shape coords"),d("br","clear"),d("applet","codebase archive code object alt name width height align hspace vspace"),d("img","name longdesc align border hspace vspace"),d("iframe","longdesc frameborder marginwidth marginheight scrolling align"),d("font basefont","size color face"),d("input","usemap align"),d("select","onchange"),d("textarea"),d("h1 h2 h3 h4 h5 h6 div p legend caption","align"),d("ul","type compact"),d("li","type"),d("ol dl menu dir","compact"),d("pre","width xml:space"),d("hr","align noshade size width"),d("isindex","prompt"),d("table","summary width frame rules cellspacing cellpadding align bgcolor"),d("col","width align char charoff valign"),d("colgroup","width align char charoff valign"),d("thead","align char charoff valign"),d("tr","align char charoff valign bgcolor"),d("th","axis align char charoff valign nowrap bgcolor width height"),d("form","accept"),d("td","abbr axis scope align char charoff valign nowrap bgcolor width height"),d("tfoot","align char charoff valign"),d("tbody","align char charoff valign"),d("area","nohref"),d("body","background bgcolor text link vlink alink")),"html4"!=a&&(d("input button select textarea","autofocus"),d("input textarea","placeholder"),d("a","download"),d("link script img","crossorigin"),d("iframe","sandbox seamless allowfullscreen")),h(b("a form meter progress dfn"),function(a){n[a]&&delete n[a].children[a]}),delete n.caption.children.table,delete n.script,e[a]=n,n)}function d(a,b){var c;return a&&(c={},"string"==typeof a&&(a={"*":a}),h(a,function(a,d){c[d]=c[d.toUpperCase()]="map"==b?g(a,/[, ]/):j(a,/[, ]/)})),c}var e={},f={},g=a.makeMap,h=a.each,i=a.extend,j=a.explode,k=a.inArray;return function(a){function f(b,c,d){var f=a[b];return f?f=g(f,/[, ]/,g(f.toUpperCase(),/[, ]/)):(f=e[b],f||(f=g(c," ",g(c.toUpperCase()," ")),f=i(f,d),e[b]=f)),f}function l(a){return new RegExp("^"+a.replace(/([?+*])/g,".$1")+"$")}function m(a){var c,d,e,f,h,i,j,m,n,o,p,q,r,s,t,u,v,w,x,y=/^([#+\-])?([^\[!\/]+)(?:\/([^\[!]+))?(?:(!?)\[([^\]]+)\])?$/,z=/^([!\-])?(\w+::\w+|[^=:<]+)?(?:([=:<])(.*))?$/,A=/[*?+]/;if(a)for(a=b(a,","),F["@"]&&(u=F["@"].attributes,v=F["@"].attributesOrder),c=0,d=a.length;d>c;c++)if(h=y.exec(a[c])){if(s=h[1],n=h[2],t=h[3],m=h[5],q={},r=[],i={attributes:q,attributesOrder:r},"#"===s&&(i.paddEmpty=!0),"-"===s&&(i.removeEmpty=!0),"!"===h[4]&&(i.removeEmptyAttrs=!0),u){for(w in u)q[w]=u[w];r.push.apply(r,v)}if(m)for(m=b(m,"|"),e=0,f=m.length;f>e;e++)if(h=z.exec(m[e])){if(j={},p=h[1],o=h[2].replace(/::/g,":"),s=h[3],x=h[4],"!"===p&&(i.attributesRequired=i.attributesRequired||[],i.attributesRequired.push(o),j.required=!0),"-"===p){delete q[o],r.splice(k(r,o),1);continue}s&&("="===s&&(i.attributesDefault=i.attributesDefault||[],i.attributesDefault.push({name:o,value:x}),j.defaultValue=x),":"===s&&(i.attributesForced=i.attributesForced||[],i.attributesForced.push({name:o,value:x}),j.forcedValue=x),"<"===s&&(j.validValues=g(x,"?"))),A.test(o)?(i.attributePatterns=i.attributePatterns||[],j.pattern=l(o),i.attributePatterns.push(j)):(q[o]||r.push(o),q[o]=j)}u||"@"!=n||(u=q,v=r),t&&(i.outputName=n,F[t]=i),A.test(n)?(i.pattern=l(n),H.push(i)):F[n]=i}}function n(a){F={},H=[],m(a),h(t,function(a,b){G[b]=a.children})}function o(a){var c=/^(~)?(.+)$/;a&&(e.text_block_elements=e.block_elements=null,h(b(a,","),function(a){var b=c.exec(a),d="~"===b[1],e=d?"span":"div",f=b[2];if(G[f]=G[e],I[f]=e,d||(z[f.toUpperCase()]={},z[f]={}),!F[f]){var g=F[e];g=i({},g),delete g.removeEmptyAttrs,delete g.removeEmpty,F[f]=g}h(G,function(a,b){a[e]&&(G[b]=a=i({},G[b]),a[f]=a[e])})}))}function p(c){var d=/^([+\-]?)(\w+)\[([^\]]+)\]$/;e[a.schema]=null,c&&h(b(c,","),function(a){var c,e,f=d.exec(a);f&&(e=f[1],c=e?G[f[2]]:G[f[2]]={"#comment":{}},c=G[f[2]],h(b(f[3],"|"),function(a){"-"===e?delete c[a]:c[a]={}}))})}function q(a){var b,c=F[a];if(c)return c;for(b=H.length;b--;)if(c=H[b],c.pattern.test(a))return c}var r,s,t,u,v,w,x,y,z,A,B,C,D,E=this,F={},G={},H=[],I={},J={};a=a||{},t=c(a.schema),a.verify_html===!1&&(a.valid_elements="*[*]"),r=d(a.valid_styles),s=d(a.invalid_styles,"map"),y=d(a.valid_classes,"map"),u=f("whitespace_elements","pre script noscript style textarea video audio iframe object"),v=f("self_closing_elements","colgroup dd dt li option p td tfoot th thead tr"),w=f("short_ended_elements","area base basefont br col frame hr img input isindex link meta param embed source wbr track"),x=f("boolean_attributes","checked compact declare defer disabled ismap multiple nohref noresize noshade nowrap readonly selected autoplay loop controls"),A=f("non_empty_elements","td th iframe video audio object script",w),B=f("move_caret_before_on_enter_elements","table",A),C=f("text_block_elements","h1 h2 h3 h4 h5 h6 p div address pre form blockquote center dir fieldset header footer article section hgroup aside nav figure"),z=f("block_elements","hr table tbody thead tfoot th tr td li ol ul caption dl dt dd noscript menu isindex option datalist select optgroup",C),D=f("text_inline_elements","span strong b em i font strike u var cite dfn code mark q sup sub samp"),h((a.special||"script noscript style textarea").split(" "),function(a){J[a]=new RegExp(""+a+"[^>]*>","gi")}),a.valid_elements?n(a.valid_elements):(h(t,function(a,b){F[b]={attributes:a.attributes,attributesOrder:a.attributesOrder},G[b]=a.children}),"html5"!=a.schema&&h(b("strong/b em/i"),function(a){a=b(a,"/"),F[a[1]].outputName=a[0]}),F.img.attributesDefault=[{name:"alt",value:""}],h(b("ol ul sub sup blockquote span font a table tbody tr strong em b i"),function(a){F[a]&&(F[a].removeEmpty=!0)}),h(b("p h1 h2 h3 h4 h5 h6 th td pre div address caption"),function(a){F[a].paddEmpty=!0}),h(b("span"),function(a){F[a].removeEmptyAttrs=!0})),o(a.custom_elements),p(a.valid_children),m(a.extended_valid_elements),p("+ol[ul|ol],+ul[ul|ol]"),a.invalid_elements&&h(j(a.invalid_elements),function(a){F[a]&&delete F[a]}),q("span")||m("span[!data-mce-type|*]"),E.children=G,E.getValidStyles=function(){return r},E.getInvalidStyles=function(){return s},E.getValidClasses=function(){return y},E.getBoolAttrs=function(){return x},E.getBlockElements=function(){return z},E.getTextBlockElements=function(){return C},E.getTextInlineElements=function(){return D},E.getShortEndedElements=function(){return w},E.getSelfClosingElements=function(){return v},E.getNonEmptyElements=function(){return A},E.getMoveCaretBeforeOnEnterElements=function(){return B},E.getWhiteSpaceElements=function(){return u},E.getSpecialElements=function(){return J},E.isValidChild=function(a,b){var c=G[a];return!(!c||!c[b])},E.isValid=function(a,b){var c,d,e=q(a);if(e){if(!b)return!0;if(e.attributes[b])return!0;if(c=e.attributePatterns)for(d=c.length;d--;)if(c[d].pattern.test(a))return!0}return!1},E.getElementRule=q,E.getCustomElements=function(){return I},E.addValidElements=m,E.setValidElements=n,E.addCustomElements=o,E.addValidChildren=p,E.elements=F}}),d("tinymce/html/SaxParser",["tinymce/html/Schema","tinymce/html/Entities","tinymce/util/Tools"],function(a,b,c){function d(a,b,c){var d,e,f,g,h=1;for(g=a.getShortEndedElements(),f=/<([!?\/])?([A-Za-z0-9\-_\:\.]+)((?:\s+[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*|\/|\s+)>/g,f.lastIndex=d=c;e=f.exec(b);){if(d=f.lastIndex,"/"===e[1])h--;else if(!e[1]){if(e[2]in g)continue;h++}if(0===h)break}return d}function e(e,g){function h(){}var i=this;e=e||{},i.schema=g=g||new a,e.fix_self_closing!==!1&&(e.fix_self_closing=!0),f("comment cdata text start end pi doctype".split(" "),function(a){a&&(i[a]=e[a]||h)}),i.parse=function(a){function f(a){var b,c;for(b=N.length;b--&&N[b].name!==a;);if(b>=0){for(c=N.length-1;c>=b;c--)a=N[c],a.valid&&L.end(a.name);N.length=b}}function h(a,b,c,d,f){var g,h,i=/[\s\u0000-\u001F]+/g;if(b=b.toLowerCase(),c=b in s?b:P(c||d||f||""),u&&!p&&0!==b.indexOf("data-")){if(g=z[b],!g&&A){for(h=A.length;h--&&(g=A[h],!g.pattern.test(b)););-1===h&&(g=null)}if(!g)return;if(g.validValues&&!(c in g.validValues))return}if(Q[b]&&!e.allow_script_urls){var j=c.replace(i,"");try{j=decodeURIComponent(j)}catch(k){j=unescape(j)}if(R.test(j))return;if(!e.allow_html_data_urls&&S.test(j)&&!/^data:image\//i.test(j))return}l.map[b]=c,l.push({name:b,value:c})}var i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,A,B,C,D,E,F,G,H,I,J,K,L=this,M=0,N=[],O=0,P=b.decode,Q=c.makeMap("src,href,data,background,formaction,poster"),R=/((java|vb)script|mhtml):/i,S=/^data:/i;for(G=new RegExp("<(?:(?:!--([\\w\\W]*?)-->)|(?:!\\[CDATA\\[([\\w\\W]*?)\\]\\]>)|(?:!DOCTYPE([\\w\\W]*?)>)|(?:\\?([^\\s\\/<>]+) ?([\\w\\W]*?)[?/]>)|(?:\\/([^>]+)>)|(?:([A-Za-z0-9\\-_\\:\\.]+)((?:\\s+[^\"'>]+(?:(?:\"[^\"]*\")|(?:'[^']*')|[^>]*))*|\\/|\\s+)>))","g"),H=/([\w:\-]+)(?:\s*=\s*(?:(?:\"((?:[^\"])*)\")|(?:\'((?:[^\'])*)\')|([^>\s]+)))?/g,r=g.getShortEndedElements(),F=e.self_closing_elements||g.getSelfClosingElements(),s=g.getBoolAttrs(),u=e.validate,q=e.remove_internals,K=e.fix_self_closing,I=g.getSpecialElements();i=G.exec(a);){if(M0&&N[N.length-1].name===j&&f(j),!u||(v=g.getElementRule(j))){if(w=!0,u&&(z=v.attributes,A=v.attributePatterns),(y=i[8])?(p=-1!==y.indexOf("data-mce-type"),p&&q&&(w=!1),l=[],l.map={},y.replace(H,h)):(l=[],l.map={}),u&&!p){if(B=v.attributesRequired,C=v.attributesDefault,D=v.attributesForced,E=v.removeEmptyAttrs,E&&!l.length&&(w=!1),D)for(m=D.length;m--;)x=D[m],o=x.name,J=x.value,"{$uid}"===J&&(J="mce_"+O++),l.map[o]=J,l.push({name:o,value:J});if(C)for(m=C.length;m--;)x=C[m],o=x.name,o in l.map||(J=x.value,"{$uid}"===J&&(J="mce_"+O++),l.map[o]=J,l.push({name:o,value:J}));if(B){for(m=B.length;m--&&!(B[m]in l.map););-1===m&&(w=!1)}if(x=l.map["data-mce-bogus"]){if("all"===x){M=d(g,a,G.lastIndex),G.lastIndex=M;continue}w=!1}}w&&L.start(j,l,t)}else w=!1;if(k=I[j]){k.lastIndex=M=i.index+i[0].length,(i=k.exec(a))?(w&&(n=a.substr(M,i.index-M)),M=i.index+i[0].length):(n=a.substr(M),M=a.length),w&&(n.length>0&&L.text(n,!0),L.end(j)),G.lastIndex=M;continue}t||(y&&y.indexOf("/")==y.length-1?w&&L.end(j):N.push({name:j,valid:w}))}else(j=i[1])?(">"===j.charAt(0)&&(j=" "+j),e.allow_conditional_comments||"[if"!==j.substr(0,3)||(j=" "+j),L.comment(j)):(j=i[2])?L.cdata(j):(j=i[3])?L.doctype(j):(j=i[4])&&L.pi(j,i[5]);M=i.index+i[0].length}for(M=0;m--)j=N[m],j.valid&&L.end(j.name)}}var f=c.each;return e.findEndTag=d,e}),d("tinymce/html/DomParser",["tinymce/html/Node","tinymce/html/Schema","tinymce/html/SaxParser","tinymce/util/Tools"],function(a,b,c,d){var e=d.makeMap,f=d.each,g=d.explode,h=d.extend;return function(d,i){function j(b){var c,d,f,g,h,j,l,m,n,o,p,q,r,s,t;for(p=e("tr,td,th,tbody,thead,tfoot,table"),o=i.getNonEmptyElements(),q=i.getTextBlockElements(),r=i.getSpecialElements(),c=0;c1){for(g.reverse(),h=j=k.filterNode(g[0].clone()),n=0;n0)return void(b.value=d);if(c=b.next){if(3==c.type&&c.value.length){b=b.prev;continue}if(!f[c.name]&&"script"!=c.name&&"style"!=c.name){b=b.prev;continue}}e=b.prev,b.remove(),b=e}}function q(a){var b,c={};for(b in a)"li"!==b&&"p"!=b&&(c[b]=a[b]);return c}var r,s,t,u,v,w,x,y,z,A,B,C,D,E,F,G,H,I,J,K,L,M=[];if(f=f||{},n={},o={},C=h(e("script,style,head,html,body,title,meta,param"),i.getBlockElements()),K=i.getNonEmptyElements(),J=i.children,B=d.validate,L="forced_root_block"in f?f.forced_root_block:d.forced_root_block,I=i.getWhiteSpaceElements(),D=/^[ \t\r\n]+/,F=/[ \t\r\n]+$/,G=/[ \t\r\n]+/g,H=/^[ \t\r\n]+$/,r=new c({validate:B,allow_script_urls:d.allow_script_urls,allow_conditional_comments:d.allow_conditional_comments,self_closing_elements:q(i.getSelfClosingElements()),cdata:function(a){t.append(k("#cdata",4)).value=a},text:function(a,b){var c;E||(a=a.replace(G," "),t.lastChild&&C[t.lastChild.name]&&(a=a.replace(D,""))),0!==a.length&&(c=k("#text",3),c.raw=!!b,t.append(c).value=a)},comment:function(a){t.append(k("#comment",8)).value=a},pi:function(a,b){t.append(k(a,7)).value=b,p(t)},doctype:function(a){var b;b=t.append(k("#doctype",10)),b.value=a,p(t)},start:function(a,b,c){var d,e,f,g,h;if(f=B?i.getElementRule(a):{}){for(d=k(f.outputName||a,1),d.attributes=b,d.shortEnded=c,t.append(d),h=J[t.name],h&&J[d.name]&&!h[d.name]&&M.push(d),e=m.length;e--;)g=m[e].name,g in b.map&&(z=o[g],z?z.push(d):o[g]=[d]);C[a]&&p(d),c||(t=d),!E&&I[a]&&(E=!0)}},end:function(b){var c,d,e,f,g;if(d=B?i.getElementRule(b):{}){if(C[b]&&!E){if(c=t.firstChild,c&&3===c.type)if(e=c.value.replace(D,""),e.length>0)c.value=e,c=c.next;else for(f=c.next,c.remove(),c=f;c&&3===c.type;)e=c.value,f=c.next,(0===e.length||H.test(e))&&(c.remove(),c=f),c=f;if(c=t.lastChild,c&&3===c.type)if(e=c.value.replace(F,""),e.length>0)c.value=e,c=c.prev;else for(f=c.prev,c.remove(),c=f;c&&3===c.type;)e=c.value,f=c.prev,(0===e.length||H.test(e))&&(c.remove(),c=f),c=f}if(E&&I[b]&&(E=!1),(d.removeEmpty||d.paddEmpty)&&t.isEmpty(K))if(d.paddEmpty)t.empty().append(new a("#text","3")).value=" ";else if(!t.attributes.map.name&&!t.attributes.map.id)return g=t.parent,C[t.name]?t.empty().remove():t.unwrap(),void(t=g);t=t.parent}}},i),s=t=new a(f.context||d.root_name,11),r.parse(b),B&&M.length&&(f.context?f.invalid=!0:j(M)),L&&("body"==s.name||f.isRootContent)&&g(),!f.invalid){for(A in n){for(z=l[A],u=n[A],x=u.length;x--;)u[x].parent||u.splice(x,1);for(v=0,w=z.length;w>v;v++)z[v](u,A,f)}for(v=0,w=m.length;w>v;v++)if(z=m[v],z.name in o){for(u=o[z.name],x=u.length;x--;)u[x].parent||u.splice(x,1);for(x=0,y=z.callbacks.length;y>x;x++)z.callbacks[x](u,z.name,f)}}return s},d.remove_trailing_brs&&k.addNodeFilter("br",function(b){var c,d,e,f,g,j,k,l,m=b.length,n=h({},i.getBlockElements()),o=i.getNonEmptyElements();for(n.body=1,c=0;m>c;c++)if(d=b[c],e=d.parent,n[d.parent.name]&&d===e.lastChild){for(g=d.prev;g;){if(j=g.name,"span"!==j||"bookmark"!==g.attr("data-mce-type")){if("br"!==j)break;if("br"===j){d=null;break}}g=g.prev}d&&(d.remove(),e.isEmpty(o)&&(k=i.getElementRule(e.name),k&&(k.removeEmpty?e.remove():k.paddEmpty&&(e.empty().append(new a("#text",3)).value=" "))))}else{for(f=d;e&&e.firstChild===f&&e.lastChild===f&&(f=e,!n[e.name]);)e=e.parent;f===e&&(l=new a("#text",3),l.value=" ",d.replace(l))}}),d.allow_html_in_named_anchor||k.addAttributeFilter("id,name",function(a){for(var b,c,d,e,f=a.length;f--;)if(e=a[f],"a"===e.name&&e.firstChild&&!e.attr("href")){d=e.parent,b=e.lastChild;do c=b.prev,d.insert(b,e),b=c;while(b)}}),d.validate&&i.getValidClasses()&&k.addAttributeFilter("class",function(a){for(var b,c,d,e,f,g,h,j=a.length,k=i.getValidClasses();j--;){for(b=a[j],c=b.attr("class").split(" "),f="",d=0;d0&&(m=i[i.length-1],m.length>0&&"\n"!==m&&i.push("\n")),i.push("<",a),b)for(j=0,k=b.length;k>j;j++)l=b[j],i.push(" ",l.name,'="',g(l.value,!0),'"');i[i.length]=!c||h?">":" />",c&&d&&f[a]&&i.length>0&&(m=i[i.length-1],m.length>0&&"\n"!==m&&i.push("\n"))},end:function(a){var b;i.push("",a,">"),d&&f[a]&&i.length>0&&(b=i[i.length-1],b.length>0&&"\n"!==b&&i.push("\n"))},text:function(a,b){a.length>0&&(i[i.length]=b?a:g(a))},cdata:function(a){i.push("")},comment:function(a){i.push("")},pi:function(a,b){b?i.push("",a," ",g(b),"?>"):i.push("",a,"?>"),d&&i.push("\n")},doctype:function(a){i.push("",d?"\n":"")},reset:function(){i.length=0},getContent:function(){return i.join("").replace(/\n$/,"")}}}}),d("tinymce/html/Serializer",["tinymce/html/Writer","tinymce/html/Schema"],function(a,b){return function(c,d){var e=this,f=new a(c);c=c||{},c.validate="validate"in c?c.validate:!0,e.schema=d=d||new b,e.writer=f,e.serialize=function(a){function b(a){var c,h,i,j,k,l,m,n,o,p=e[a.type];if(p)p(a);else{if(c=a.name,h=a.shortEnded,i=a.attributes,g&&i&&i.length>1){for(l=[],l.map={},o=d.getElementRule(a.name),m=0,n=o.attributesOrder.length;n>m;m++)j=o.attributesOrder[m],j in i.map&&(k=i.map[j],l.map[j]=k,l.push({name:j,value:k}));for(m=0,n=i.length;n>m;m++)j=i[m].name,j in l.map||(k=i.map[j],l.map[j]=k,l.push({name:j,value:k}));i=l}if(f.start(a.name,i,h),!h){if(a=a.firstChild)do b(a);while(a=a.next);f.end(c)}}}var e,g;return g=c.validate,e={3:function(a){f.text(a.value,a.raw)},8:function(a){f.comment(a.value)},7:function(a){f.pi(a.name,a.value)},10:function(a){f.doctype(a.value)},4:function(a){f.cdata(a.value)},11:function(a){if(a=a.firstChild)do b(a);while(a=a.next)}},f.reset(),1!=a.type||c.inner?e[11](a):b(a),f.getContent()}}}),d("tinymce/dom/Serializer",["tinymce/dom/DOMUtils","tinymce/html/DomParser","tinymce/html/Entities","tinymce/html/Serializer","tinymce/html/Node","tinymce/html/Schema","tinymce/Env","tinymce/util/Tools"],function(a,b,c,d,e,f,g,h){var i=h.each,j=h.trim,k=a.DOM;return function(a,e){var h,l,m;return e&&(h=e.dom,l=e.schema),h=h||k,l=l||new f(a),a.entity_encoding=a.entity_encoding||"named",a.remove_trailing_brs="remove_trailing_brs"in a?a.remove_trailing_brs:!0,m=new b(a,l),m.addAttributeFilter("data-mce-tabindex",function(a,b){for(var c,d=a.length;d--;)c=a[d],c.attr("tabindex",c.attributes.map["data-mce-tabindex"]),c.attr(b,null)}),m.addAttributeFilter("src,href,style",function(b,c){for(var d,e,f,g=b.length,i="data-mce-"+c,j=a.url_converter,k=a.url_converter_scope;g--;)d=b[g],e=d.attributes.map[i],e!==f?(d.attr(c,e.length>0?e:null),d.attr(i,null)):(e=d.attributes.map[c],"style"===c?e=h.serializeStyle(h.parseStyle(e),d.name):j&&(e=j.call(k,e,c,d.name)),d.attr(c,e.length>0?e:null))}),m.addAttributeFilter("class",function(a){for(var b,c,d=a.length;d--;)b=a[d],c=b.attr("class"),c&&(c=b.attr("class").replace(/(?:^|\s)mce-item-\w+(?!\S)/g,""),b.attr("class",c.length>0?c:null))}),m.addAttributeFilter("data-mce-type",function(a,b,c){for(var d,e=a.length;e--;)d=a[e],"bookmark"!==d.attributes.map["data-mce-type"]||c.cleanup||d.remove()}),m.addNodeFilter("noscript",function(a){for(var b,d=a.length;d--;)b=a[d].firstChild,b&&(b.value=c.decode(b.value))}),m.addNodeFilter("script,style",function(a,b){function c(a){return a.replace(/()/g,"\n").replace(/^[\r\n]*|[\r\n]*$/g,"").replace(/^\s*(()?|\s*\/\/\s*\]\]>(-->)?|\/\/\s*(-->)?|\]\]>|\/\*\s*-->\s*\*\/|\s*-->\s*)\s*$/g,"")}for(var d,e,f,g=a.length;g--;)d=a[g],e=d.firstChild?d.firstChild.value:"","script"===b?(f=d.attr("type"),f&&d.attr("type","mce-no/type"==f?null:f.replace(/^mce\-/,"")),e.length>0&&(d.firstChild.value="// ")):e.length>0&&(d.firstChild.value="")}),m.addNodeFilter("#comment",function(a){for(var b,c=a.length;c--;)b=a[c],0===b.value.indexOf("[CDATA[")?(b.name="#cdata",b.type=4,b.value=b.value.replace(/^\[CDATA\[|\]\]$/g,"")):0===b.value.indexOf("mce:protected ")&&(b.name="#text",b.type=3,b.raw=!0,b.value=unescape(b.value).substr(14))}),m.addNodeFilter("xml:namespace,input",function(a,b){for(var c,d=a.length;d--;)c=a[d],7===c.type?c.remove():1===c.type&&("input"!==b||"type"in c.attributes.map||c.attr("type","text"))}),a.fix_list_elements&&m.addNodeFilter("ul,ol",function(a){for(var b,c,d=a.length;d--;)b=a[d],c=b.parent,("ul"===c.name||"ol"===c.name)&&b.prev&&"li"===b.prev.name&&b.prev.append(b)}),m.addAttributeFilter("data-mce-src,data-mce-href,data-mce-style,data-mce-selected,data-mce-expando,data-mce-type,data-mce-resize",function(a,b){for(var c=a.length;c--;)a[c].attr(b,null)}),{schema:l,addNodeFilter:m.addNodeFilter,addAttributeFilter:m.addAttributeFilter,serialize:function(b,c){var e,f,k,n,o,p=this;return g.ie&&h.select("script,style,select,map").length>0?(o=b.innerHTML,b=b.cloneNode(!1),h.setHTML(b,o)):b=b.cloneNode(!0),e=b.ownerDocument.implementation,e.createHTMLDocument&&(f=e.createHTMLDocument(""),i("BODY"==b.nodeName?b.childNodes:[b],function(a){f.body.appendChild(f.importNode(a,!0))}),b="BODY"!=b.nodeName?f.body.firstChild:f.body,k=h.doc,h.doc=f),c=c||{},c.format=c.format||"html",c.selection&&(c.forced_root_block=""),c.no_events||(c.node=b,p.onPreProcess(c)),n=new d(a,l),c.content=n.serialize(m.parse(j(c.getInner?b.innerHTML:h.getOuterHTML(b)),c)),c.cleanup||(c.content=c.content.replace(/\uFEFF/g,"")),c.no_events||p.onPostProcess(c),k&&(h.doc=k),c.node=null,c.content},addRules:function(a){l.addValidElements(a)},setRules:function(a){l.setValidElements(a)},onPreProcess:function(a){e&&e.fire("PreProcess",a)},onPostProcess:function(a){e&&e.fire("PostProcess",a)}}}}),d("tinymce/dom/TridentSelection",[],function(){function a(a){function b(b,c){var d,e,f,g,h,i,j,k,l=0,m=-1;if(d=b.duplicate(),d.collapse(c),k=d.parentElement(),k.ownerDocument===a.dom.doc){for(;"false"===k.contentEditable;)k=k.parentNode;if(!k.hasChildNodes())return{node:k,inside:1};for(g=k.children,e=g.length-1;e>=l;)if(j=Math.floor((l+e)/2),h=g[j],d.moveToElementText(h),m=d.compareEndPoints(c?"StartToStart":"EndToEnd",b),m>0)e=j-1;else{if(!(0>m))return{node:h};l=j+1}if(0>m)for(h?d.collapse(!1):(d.moveToElementText(k),d.collapse(!0),h=k,f=!0),i=0;0!==d.compareEndPoints(c?"StartToStart":"StartToEnd",b)&&0!==d.move("character",1)&&k==d.parentElement();)i++;else for(d.collapse(!0),i=0;0!==d.compareEndPoints(c?"StartToStart":"StartToEnd",b)&&0!==d.move("character",-1)&&k==d.parentElement();)i++;return{node:h,position:m,offset:i,inside:f}}}function c(){function c(a){var c,d,e,f,g,h=b(k,a),i=0;if(c=h.node,d=h.offset,h.inside&&!c.hasChildNodes())return void l[a?"setStart":"setEnd"](c,0);if(d===f)return void l[a?"setStartBefore":"setEndAfter"](c);if(h.position<0){if(e=h.inside?c.firstChild:c.nextSibling,!e)return void l[a?"setStartAfter":"setEndAfter"](c);if(!d)return void(3==e.nodeType?l[a?"setStart":"setEnd"](e,0):l[a?"setStartBefore":"setEndBefore"](e));for(;e;){if(3==e.nodeType&&(g=e.nodeValue,i+=g.length,i>=d)){c=e,i-=d,i=g.length-i;break}e=e.nextSibling}}else{if(e=c.previousSibling,!e)return l[a?"setStartBefore":"setEndBefore"](c);if(!d)return void(3==c.nodeType?l[a?"setStart":"setEnd"](e,c.nodeValue.length):l[a?"setStartAfter":"setEndAfter"](e));for(;e;){if(3==e.nodeType&&(i+=e.nodeValue.length,i>=d)){c=e,i-=d;break}e=e.previousSibling}}l[a?"setStart":"setEnd"](c,i)}var f,g,h,i,j,k=a.getRng(),l=e.createRng();if(f=k.item?k.item(0):k.parentElement(),f.ownerDocument!=e.doc)return l;if(g=a.isCollapsed(),k.item)return l.setStart(f.parentNode,e.nodeIndex(f)),l.setEnd(l.startContainer,l.startOffset+1),l;try{c(!0),g||c()}catch(m){if(-2147024809!=m.number)throw m;j=d.getBookmark(2),h=k.duplicate(),h.collapse(!0),f=h.parentElement(),g||(h=k.duplicate(),h.collapse(!1),i=h.parentElement(),i.innerHTML=i.innerHTML),f.innerHTML=f.innerHTML,d.moveToBookmark(j),k=a.getRng(),c(!0),g||c()}return l}var d=this,e=a.dom,f=!1;this.getBookmark=function(c){function d(a){var b,c,d,f,g=[];for(b=a.parentNode,c=e.getRoot().parentNode;b!=c&&9!==b.nodeType;){for(d=b.children,f=d.length;f--;)if(a===d[f]){g.push(f);break}a=b,b=b.parentNode}return g}function f(a){var c;return c=b(g,a),c?{position:c.position,offset:c.offset,indexes:d(c.node),inside:c.inside}:void 0}var g=a.getRng(),h={};return 2===c&&(g.item?h.start={ctrl:!0,indexes:d(g.item(0))}:(h.start=f(!0),a.isCollapsed()||(h.end=f()))),h},this.moveToBookmark=function(a){function b(a){var b,c,d,f;for(b=e.getRoot(),c=a.length-1;c>=0;c--)f=b.children,d=a[c],d<=f.length-1&&(b=f[d]);return b}function c(c){var e,g,h,i,j=a[c?"start":"end"];j&&(e=j.position>0,g=f.createTextRange(),g.moveToElementText(b(j.indexes)),i=j.offset,i!==h?(g.collapse(j.inside||e),g.moveStart("character",e?-i:i)):g.collapse(c),d.setEndPoint(c?"StartToStart":"EndToStart",g),c&&d.collapse(!0))}var d,f=e.doc.body;a.start&&(a.start.ctrl?(d=f.createControlRange(),
-d.addElement(b(a.start.indexes)),d.select()):(d=f.createTextRange(),c(!0),c(),d.select()))},this.addRange=function(b){function c(a){var b,c,g,l,m;g=e.create("a"),b=a?h:j,c=a?i:k,l=d.duplicate(),(b==o||b==o.documentElement)&&(b=p,c=0),3==b.nodeType?(b.parentNode.insertBefore(g,b),l.moveToElementText(g),l.moveStart("character",c),e.remove(g),d.setEndPoint(a?"StartToStart":"EndToEnd",l)):(m=b.childNodes,m.length?(c>=m.length?e.insertAfter(g,m[m.length-1]):b.insertBefore(g,m[c]),l.moveToElementText(g)):b.canHaveHTML&&(b.innerHTML="