From f4d35138020480d3f0f09f568a771bb19296fed1 Mon Sep 17 00:00:00 2001
From: Andrew Ozz || || || ||
- 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) + '>';
- }
-
- if ((startBlock && /^(PRE|DIV)$/.test(startBlock.nodeName)) || !forcedRootBlockName) {
- text = Utils.filter(text, [
- [/\n/g, " )$/, forcedRootBlockStartHtml + '$1'],
- [/\n/g, " ') != -1) {
- text = forcedRootBlockStartHtml + text;
- }
- }
-
- 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;
-
- 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;
- }
- }
-
- /**
- * 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;
-
- rects = rng.getClientRects();
- if (rects.length) {
- return rects[0];
- }
-
- if (!rng.collapsed || container.nodeType != 1) {
- return;
- }
-
- node = container.childNodes[lastRng.startOffset];
-
- // Skip empty whitespace nodes
- while (node && node.nodeType == 3 && !node.data.length) {
- node = node.nextSibling;
- }
-
- if (!node) {
- return;
- }
-
- // Check if the location is | a b a b a b a b )$/,t+"$1"],[/\n/g," ")!=-1&&(e=t+e)),s(e)}function u(){function t(e){var t,n,i,o=e.startContainer;if(t=e.getClientRects(),t.length)return t[0];if(e.collapsed&&1==o.nodeType){for(i=o.childNodes[_.startOffset];i&&3==i.nodeType&&!i.data.length;)i=i.nextSibling;if(i)return"BR"==i.tagName&&(n=r.doc.createTextNode("\ufeff"),i.parentNode.insertBefore(n,i),e=r.createRng(),e.setStartBefore(n),e.setEndAfter(n),t=e.getClientRects(),r.remove(n)),t.length?t[0]:void 0}}var n,r=a.dom,i=a.getBody(),o=a.dom.getViewPort(a.getWin()),s=o.y,l=20;if(_=a.selection.getRng(),a.inline&&(n=a.selection.getScrollContainer(),n&&n.scrollTop>0&&(s=n.scrollTop)),_.getClientRects){var u=t(_);if(u)l=s+(u.top-r.getPos(i).y);else{l=s;var c=_.startContainer;c&&(3==c.nodeType&&c.parentNode!=i&&(c=c.parentNode),1==c.nodeType&&(l=r.getPos(c,n||i).y))}}N=r.add(a.getBody(),"div",{id:"mcepastebin",contentEditable:!0,"data-mce-bogus":"all",style:"position: absolute; top: "+l+"px;width: 10px; height: 10px; overflow: hidden; opacity: 0"},A),(e.ie||e.gecko)&&r.setStyle(N,"left","rtl"==r.getStyle(i,"direction",!0)?65535:-65535),r.bind(N,"beforedeactivate focusin focusout",function(e){e.stopPropagation()}),N.focus(),a.selection.select(N,!0)}function c(){if(N){for(var e;e=a.dom.get("mcepastebin");)a.dom.remove(e),a.dom.unbind(e);_&&a.selection.setRng(_)}N=_=null}function d(){var e,t,n,r,i="";for(e=a.dom.select("div[id=mcepastebin]"),t=0;t || || || || abcabc123 abc 123 text 1CHOPtext 2 text 1 text 2 text 1 text 2 a b abcabc123 abc 123 text 1CHOPtext 2 text 1 text 2 text 1 text 2 a b | [a x| | | [a x| x x a x x a .
- *
- * @method start
- * @param {String} name Name of the element.
- * @param {Array} attrs Optional attribute array or undefined if it hasn't any.
- * @param {Boolean} empty Optional empty state if the tag should end like .
+ *
+ * @method start
+ * @param {String} name Name of the element.
+ * @param {Array} attrs Optional attribute array or undefined if it hasn't any.
+ * @param {Boolean} empty Optional empty state if the tag should end like ';
+ function showDialog() {
+ var gridHtml, x, y, win;
- var charmap = getCharMap();
- var width = Math.min(charmap.length, 25);
- var height = Math.ceil(charmap.length / width);
- for (y = 0; y < height; y++) {
- gridHtml += '
';
- 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(' ');
- }
- }
- };
+ var charMapPanel = {
+ type: 'container',
+ html: gridHtml,
+ onclick: function (e) {
+ var target = e.target;
- 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
- }
- ]
- }
- ],
- buttons: [
- {text: "Close", onclick: function() {
- win.close();
- }}
- ]
- });
- }
+ if (/^(TD|DIV)$/.test(target.nodeName)) {
+ var charDiv = getParentTd(target).firstChild;
+ if (charDiv && charDiv.hasAttribute('data-chr')) {
+ insertChar(charDiv.getAttribute('data-chr'));
- editor.addCommand('mceShowCharmap', showDialog);
+ if (!e.ctrlKey) {
+ win.close();
+ }
+ }
+ }
+ },
+ onmouseover: function (e) {
+ var td = getParentTd(e.target);
- editor.addButton('charmap', {
- icon: 'charmap',
- tooltip: 'Special character',
- cmd: 'mceShowCharmap'
- });
+ 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(' ');
+ }
+ }
+ };
- editor.addMenuItem('charmap', {
- icon: 'charmap',
- text: 'Special character',
- cmd: 'mceShowCharmap',
- context: 'insert'
- });
+ 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
+ }
+ ]
+ }
+ ],
+ buttons: [
+ {
+ text: "Close", onclick: function () {
+ win.close();
+ }
+ }
+ ]
+ });
+ }
- return {
- getCharMap: getCharMap,
- insertChar: insertChar
- };
-});
+ editor.addCommand('mceShowCharmap', showDialog);
+
+ editor.addButton('charmap', {
+ icon: 'charmap',
+ tooltip: 'Special character',
+ cmd: 'mceShowCharmap'
+ });
+
+ editor.addMenuItem('charmap', {
+ icon: 'charmap',
+ text: 'Special character',
+ cmd: 'mceShowCharmap',
+ context: 'insert'
+ });
+
+ return {
+ getCharMap: getCharMap,
+ insertChar: insertChar
+ };
+ });
+
+ return function () { };
+ }
+);
+dem('tinymce.plugins.charmap.Plugin')();
+})();
diff --git a/src/wp-includes/js/tinymce/plugins/charmap/plugin.min.js b/src/wp-includes/js/tinymce/plugins/charmap/plugin.min.js
index 0589cd1272..487770b98a 100644
--- a/src/wp-includes/js/tinymce/plugins/charmap/plugin.min.js
+++ b/src/wp-includes/js/tinymce/plugins/charmap/plugin.min.js
@@ -1 +1 @@
-tinymce.PluginManager.add("charmap",function(e){function t(){return[["160","no-break space"],["173","soft hyphen"],["34","quotation mark"],["162","cent sign"],["8364","euro sign"],["163","pound sign"],["165","yen sign"],["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"],["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"],["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"],["180","acute accent"],["184","cedilla"],["170","feminine ordinal indicator"],["186","masculine ordinal indicator"],["8224","dagger"],["8225","double dagger"],["192","A - grave"],["193","A - acute"],["194","A - circumflex"],["195","A - tilde"],["196","A - diaeresis"],["197","A - ring above"],["256","A - macron"],["198","ligature AE"],["199","C - cedilla"],["200","E - grave"],["201","E - acute"],["202","E - circumflex"],["203","E - diaeresis"],["274","E - macron"],["204","I - grave"],["205","I - acute"],["206","I - circumflex"],["207","I - diaeresis"],["298","I - macron"],["208","ETH"],["209","N - tilde"],["210","O - grave"],["211","O - acute"],["212","O - circumflex"],["213","O - tilde"],["214","O - diaeresis"],["216","O - slash"],["332","O - macron"],["338","ligature OE"],["352","S - caron"],["217","U - grave"],["218","U - acute"],["219","U - circumflex"],["220","U - diaeresis"],["362","U - macron"],["221","Y - acute"],["376","Y - diaeresis"],["562","Y - macron"],["222","THORN"],["224","a - grave"],["225","a - acute"],["226","a - circumflex"],["227","a - tilde"],["228","a - diaeresis"],["229","a - ring above"],["257","a - macron"],["230","ligature ae"],["231","c - cedilla"],["232","e - grave"],["233","e - acute"],["234","e - circumflex"],["235","e - diaeresis"],["275","e - macron"],["236","i - grave"],["237","i - acute"],["238","i - circumflex"],["239","i - diaeresis"],["299","i - macron"],["240","eth"],["241","n - tilde"],["242","o - grave"],["243","o - acute"],["244","o - circumflex"],["245","o - tilde"],["246","o - diaeresis"],["248","o slash"],["333","o macron"],["339","ligature oe"],["353","s - caron"],["249","u - grave"],["250","u - acute"],["251","u - circumflex"],["252","u - diaeresis"],["363","u - macron"],["253","y - acute"],["254","thorn"],["255","y - diaeresis"],["563","y - macron"],["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"],["8501","alef symbol"],["982","pi symbol"],["8476","real part symbol"],["978","upsilon - hook symbol"],["8472","Weierstrass p"],["8465","imaginary part"],["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"]]}function n(e){return tinymce.util.Tools.grep(e,function(e){return l(e)&&2==e.length})}function r(e){return l(e)?[].concat(n(e)):"function"==typeof e?e():[]}function i(t){var n=e.settings;return n.charmap&&(t=r(n.charmap)),n.charmap_append?[].concat(t).concat(r(n.charmap_append)):t}function o(){return i(t())}function a(t){e.fire("insertCustomChar",{chr:t}).chr,e.execCommand("mceInsertContent",!1,t)}function s(){function t(e){for(;e;){if("TD"==e.nodeName)return e;e=e.parentNode}}var n,r,i,s;n='';
+ function getParentTd(elm) {
+ while (elm) {
+ if (elm.nodeName == 'TD') {
+ return elm;
+ }
- for (x = 0; x < width; x++) {
- var index = y * width + x;
- if (index < charmap.length) {
- var chr = charmap[index];
- var chrText = chr ? String.fromCharCode(parseInt(chr[0], 10)) : ' ';
+ elm = elm.parentNode;
+ }
+ }
- gridHtml += (
- ' ';
+ }
- if (!e.ctrlKey) {
- win.close();
- }
- }
- }
- },
- onmouseover: function(e) {
- var td = getParentTd(e.target);
+ gridHtml += ' '
- );
- } else {
- gridHtml += '';
- }
- }
+ gridHtml = ' ';
- gridHtml += '';
- }
+ var charmap = getCharMap();
+ var width = Math.min(charmap.length, 25);
+ var height = Math.ceil(charmap.length / width);
+ for (y = 0; y < height; y++) {
+ gridHtml += '
';
+ for (x = 0; x < width; x++) {
+ var index = y * width + x;
+ if (index < charmap.length) {
+ var chr = charmap[index];
+ var chrText = chr ? String.fromCharCode(parseInt(chr[0], 10)) : ' ';
- var charMapPanel = {
- type: 'container',
- html: gridHtml,
- onclick: function(e) {
- var target = e.target;
+ gridHtml += (
+ '';
- gridHtml += ' ' +
+ ' '
+ );
+ } else {
+ gridHtml += '';
+ }
+ }
- if (/^(TD|DIV)$/.test(target.nodeName)) {
- if (getParentTd(target).firstChild) {
- insertChar(target.getAttribute('data-chr'));
+ gridHtml += ' ';var l=o(),u=Math.min(l.length,25),c=Math.ceil(l.length/u);for(i=0;i
";var h={type:"container",html:n,onclick:function(e){var n=e.target;/^(TD|DIV)$/.test(n.nodeName)&&t(n).firstChild&&(a(n.getAttribute("data-chr")),e.ctrlKey||s.close())},onmouseover:function(e){var n=t(e.target);n&&n.firstChild?(s.find("#preview").text(n.firstChild.firstChild.data),s.find("#previewTitle").text(n.title)):(s.find("#preview").text(" "),s.find("#previewTitle").text(" "))}};s=e.windowManager.open({title:"Special character",spacing:10,padding:10,items:[h,{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}]}],buttons:[{text:"Close",onclick:function(){s.close()}}]})}var l=tinymce.util.Tools.isArray;return e.addCommand("mceShowCharmap",s),e.addButton("charmap",{icon:"charmap",tooltip:"Special character",cmd:"mceShowCharmap"}),e.addMenuItem("charmap",{icon:"charmap",text:"Special character",cmd:"mceShowCharmap",context:"insert"}),{getCharMap:o,insertChar:a}});
\ No newline at end of file
+!function(){var a={},b=function(b){for(var c=a[b],e=c.deps,f=c.defn,g=e.length,h=new Array(g),i=0;i"}n+=""}n+=" "}c+=""}c+="
');
- });
+/**
+ * Plugin.js
+ *
+ * Released under LGPL License.
+ * Copyright (c) 1999-2017 Ephox Corp. All rights reserved
+ *
+ * License: http://www.tinymce.com/license
+ * Contributing: http://www.tinymce.com/contributing
+ */
- editor.addButton('hr', {
- icon: 'hr',
- tooltip: 'Horizontal line',
- cmd: 'InsertHorizontalRule'
- });
+/**
+ * This class contains all core logic for the hr plugin.
+ *
+ * @class tinymce.hr.Plugin
+ * @private
+ */
+define(
+ 'tinymce.plugins.hr.Plugin',
+ [
+ 'tinymce.core.PluginManager'
+ ],
+ function (PluginManager) {
+ PluginManager.add('hr', function (editor) {
+ editor.addCommand('InsertHorizontalRule', function () {
+ editor.execCommand('mceInsertContent', false, '
');
+ });
- editor.addMenuItem('hr', {
- icon: 'hr',
- text: 'Horizontal line',
- cmd: 'InsertHorizontalRule',
- context: 'insert'
- });
-});
+ editor.addButton('hr', {
+ icon: 'hr',
+ tooltip: 'Horizontal line',
+ cmd: 'InsertHorizontalRule'
+ });
+
+ editor.addMenuItem('hr', {
+ icon: 'hr',
+ text: 'Horizontal line',
+ cmd: 'InsertHorizontalRule',
+ context: 'insert'
+ });
+ });
+
+ return function () { };
+ }
+);
+dem('tinymce.plugins.hr.Plugin')();
+})();
diff --git a/src/wp-includes/js/tinymce/plugins/hr/plugin.min.js b/src/wp-includes/js/tinymce/plugins/hr/plugin.min.js
index ca36c92751..6c0dfa4478 100644
--- a/src/wp-includes/js/tinymce/plugins/hr/plugin.min.js
+++ b/src/wp-includes/js/tinymce/plugins/hr/plugin.min.js
@@ -1 +1 @@
-tinymce.PluginManager.add("hr",function(e){e.addCommand("InsertHorizontalRule",function(){e.execCommand("mceInsertContent",!1,"
")}),e.addButton("hr",{icon:"hr",tooltip:"Horizontal line",cmd:"InsertHorizontalRule"}),e.addMenuItem("hr",{icon:"hr",text:"Horizontal line",cmd:"InsertHorizontalRule",context:"insert"})});
\ No newline at end of file
+!function(){var a={},b=function(b){for(var c=a[b],e=c.deps,f=c.defn,g=e.length,h=new Array(g),i=0;i}jC=}2U+|*tCBQ3y*8o6C_l8#
/g,
- /
$/i // Trailing BR elements
- ]);
+ html = filter(html, [
+ /^[\s\S]*]*>\s*|\s*<\/body[^>]*>[\s\S]*$/ig, // Remove anything but the contents within the BODY element
+ /|/g, // Inner fragments (tables from excel on mac)
+ [/( ?)\u00a0<\/span>( ?)/g, trimSpaces],
+ /
/g,
+ /
$/i // Trailing BR elements
+ ]);
- return html;
- }
+ return html;
+ }
- // TODO: Should be in some global class
- function createIdGenerator(prefix) {
- var count = 0;
+ // TODO: Should be in some global class
+ function createIdGenerator(prefix) {
+ var count = 0;
- return function() {
- return prefix + (count++);
- };
- }
+ return function () {
+ return prefix + (count++);
+ };
+ }
- return {
- filter: filter,
- innerText: innerText,
- trimHtml: trimHtml,
- createIdGenerator: createIdGenerator
- };
-});
+ var isMsEdge = function () {
+ return navigator.userAgent.indexOf(' Edge/') !== -1;
+ };
-// Included from: js/tinymce/plugins/paste/classes/SmartPaste.js
+ return {
+ filter: filter,
+ innerText: innerText,
+ trimHtml: trimHtml,
+ createIdGenerator: createIdGenerator,
+ isMsEdge: isMsEdge
+ };
+ }
+);
+/**
+ * CutCopy.js
+ *
+ * Released under LGPL License.
+ * Copyright (c) 1999-2017 Ephox Corp. All rights reserved
+ *
+ * License: http://www.tinymce.com/license
+ * Contributing: http://www.tinymce.com/contributing
+ */
+
+define(
+ 'tinymce.plugins.paste.core.CutCopy',
+ [
+ 'tinymce.core.Env',
+ 'tinymce.plugins.paste.core.InternalHtml',
+ 'tinymce.plugins.paste.core.Utils'
+ ],
+ function (Env, InternalHtml, Utils) {
+ var noop = function () {
+ };
+
+ var hasWorkingClipboardApi = function (clipboardData) {
+ // iOS supports the clipboardData API but it doesn't do anything for cut operations
+ // Edge 15 has a broken HTML Clipboard API see https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/11780845/
+ return Env.iOS === false && clipboardData !== undefined && typeof clipboardData.setData === 'function' && Utils.isMsEdge() !== true;
+ };
+
+ var setHtml5Clipboard = function (clipboardData, html, text) {
+ if (hasWorkingClipboardApi(clipboardData)) {
+ try {
+ clipboardData.clearData();
+ clipboardData.setData('text/html', html);
+ clipboardData.setData('text/plain', text);
+ clipboardData.setData(InternalHtml.internalHtmlMime(), html);
+ return true;
+ } catch (e) {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ };
+
+ var setClipboardData = function (evt, data, fallback, done) {
+ if (setHtml5Clipboard(evt.clipboardData, data.html, data.text)) {
+ evt.preventDefault();
+ done();
+ } else {
+ fallback(data.html, done);
+ }
+ };
+
+ var fallback = function (editor) {
+ return function (html, done) {
+ var markedHtml = InternalHtml.mark(html);
+ var outer = editor.dom.create('div', { contenteditable: "false" });
+ var inner = editor.dom.create('div', { contenteditable: "true" }, markedHtml);
+ editor.dom.setStyles(outer, {
+ position: 'fixed',
+ left: '-3000px',
+ width: '1000px',
+ overflow: 'hidden'
+ });
+ outer.appendChild(inner);
+ editor.dom.add(editor.getBody(), outer);
+
+ var range = editor.selection.getRng();
+ inner.focus();
+
+ var offscreenRange = editor.dom.createRng();
+ offscreenRange.selectNodeContents(inner);
+ editor.selection.setRng(offscreenRange);
+
+ setTimeout(function () {
+ outer.parentNode.removeChild(outer);
+ editor.selection.setRng(range);
+ done();
+ }, 0);
+ };
+ };
+
+ var getData = function (editor) {
+ return {
+ html: editor.selection.getContent(),
+ text: editor.selection.getContent({ format: 'text' })
+ };
+ };
+
+ var cut = function (editor) {
+ return function (evt) {
+ if (editor.selection.isCollapsed() === false) {
+ setClipboardData(evt, getData(editor), fallback(editor), function () {
+ editor.execCommand('Delete');
+ });
+ }
+ };
+ };
+
+ var copy = function (editor) {
+ return function (evt) {
+ if (editor.selection.isCollapsed() === false) {
+ setClipboardData(evt, getData(editor), fallback(editor), noop);
+ }
+ };
+ };
+
+ var register = function (editor) {
+ editor.on('cut', cut(editor));
+ editor.on('copy', copy(editor));
+ };
+
+ return {
+ register: register
+ };
+ }
+);
+/**
+ * ResolveGlobal.js
+ *
+ * Released under LGPL License.
+ * Copyright (c) 1999-2017 Ephox Corp. All rights reserved
+ *
+ * License: http://www.tinymce.com/license
+ * Contributing: http://www.tinymce.com/contributing
+ */
+
+define(
+ 'tinymce.core.html.Entities',
+ [
+ 'global!tinymce.util.Tools.resolve'
+ ],
+ function (resolve) {
+ return resolve('tinymce.html.Entities');
+ }
+);
+
+/**
+ * Newlines.js
+ *
+ * Released under LGPL License.
+ * Copyright (c) 1999-2017 Ephox Corp. All rights reserved
+ *
+ * License: http://www.tinymce.com/license
+ * Contributing: http://www.tinymce.com/contributing
+ */
+
+/**
+ * Newlines class contains utilities to convert newlines (\n or \r\n) tp BRs or to a combination of the specified block element and BRs
+ *
+ * @class tinymce.Newlines
+ * @private
+ */
+define(
+ 'tinymce.plugins.paste.core.Newlines',
+ [
+ 'tinymce.core.html.Entities'
+ ],
+ function (Entities) {
+
+ var isPlainText = function (text) {
+ // so basically any tag that is not one of the "p, div, br", or is one of them, but is followed
+ // by some additional characters qualifies the text as not a plain text (having some HTML tags)
+ return !/<(?:(?!\/?(?:div|p|br))[^>]*|(?:div|p|br)\s+\w[^>]+)>/.test(text);
+ };
+
+
+ var toBRs = function (text) {
+ return text.replace(/\r?\n/g, '
');
+ };
+
+
+ var openContainer = function (rootTag, rootAttrs) {
+ var key, attrs = [];
+ var tag = '<' + rootTag;
+
+ if (typeof rootAttrs === 'object') {
+ for (key in rootAttrs) {
+ if (rootAttrs.hasOwnProperty(key)) {
+ attrs.push(key + '="' + Entities.encodeAllRaw(rootAttrs[key]) + '"');
+ }
+ }
+
+ if (attrs.length) {
+ tag += ' ' + attrs.join(' ');
+ }
+ }
+ return tag + '>';
+ };
+
+
+ var toBlockElements = function (text, rootTag, rootAttrs) {
+ var pieces = text.split(/\r?\n/);
+ var i = 0, len = pieces.length;
+ var stack = [];
+ var blocks = [];
+ var tagOpen = openContainer(rootTag, rootAttrs);
+ var tagClose = '' + rootTag + '>';
+ var isLast, newlineFollows, isSingleNewline;
+
+ // if single-line text then nothing to do
+ if (pieces.length === 1) {
+ return text;
+ }
+
+ for (; i < len; i++) {
+ isLast = i === len - 1;
+ newlineFollows = !isLast && !pieces[i + 1];
+ isSingleNewline = !pieces[i] && !stack.length;
+
+ stack.push(pieces[i] ? pieces[i] : ' ');
+
+ if (isLast || newlineFollows || isSingleNewline) {
+ blocks.push(stack.join('
'));
+ stack = [];
+ }
+
+ if (newlineFollows) {
+ i++; // extra progress for extra newline
+ }
+ }
+
+ return blocks.length === 1 ? blocks[0] : tagOpen + blocks.join(tagClose + tagOpen) + tagClose;
+ };
+
+
+ var convert = function (text, rootTag, rootAttrs) {
+ return rootTag ? toBlockElements(text, rootTag, rootAttrs) : toBRs(text);
+ };
+
+
+ return {
+ isPlainText: isPlainText,
+ convert: convert,
+ toBRs: toBRs,
+ toBlockElements: toBlockElements
+ };
+ }
+);
/**
* SmartPaste.js
*
* Released under LGPL License.
- * Copyright (c) 1999-2016 Ephox Corp. All rights reserved
+ * Copyright (c) 1999-2017 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
@@ -260,86 +690,88 @@ define("tinymce/pasteplugin/Utils", [
* @class tinymce.pasteplugin.SmartPaste
* @private
*/
-define("tinymce/pasteplugin/SmartPaste", [
- "tinymce/util/Tools"
-], function (Tools) {
- var isAbsoluteUrl = function (url) {
- return /^https?:\/\/[\w\?\-\/+=.&%@~#]+$/i.test(url);
- };
+define(
+ 'tinymce.plugins.paste.core.SmartPaste',
+ [
+ 'tinymce.core.util.Tools'
+ ],
+ function (Tools) {
+ var isAbsoluteUrl = function (url) {
+ return /^https?:\/\/[\w\?\-\/+=.&%@~#]+$/i.test(url);
+ };
- var isImageUrl = function (url) {
- return isAbsoluteUrl(url) && /.(gif|jpe?g|png)$/.test(url);
- };
+ var isImageUrl = function (url) {
+ return isAbsoluteUrl(url) && /.(gif|jpe?g|png)$/.test(url);
+ };
- var createImage = function (editor, url, pasteHtml) {
- editor.undoManager.extra(function () {
- pasteHtml(editor, url);
- }, function () {
- editor.insertContent('');
- });
+ var createImage = function (editor, url, pasteHtml) {
+ editor.undoManager.extra(function () {
+ pasteHtml(editor, url);
+ }, function () {
+ editor.insertContent('
');
+ });
- return true;
- };
+ return true;
+ };
- var createLink = function (editor, url, pasteHtml) {
- editor.undoManager.extra(function () {
- pasteHtml(editor, url);
- }, function () {
- editor.execCommand('mceInsertLink', false, url);
- });
+ var createLink = function (editor, url, pasteHtml) {
+ editor.undoManager.extra(function () {
+ pasteHtml(editor, url);
+ }, function () {
+ editor.execCommand('mceInsertLink', false, url);
+ });
- return true;
- };
+ return true;
+ };
- var linkSelection = function (editor, html, pasteHtml) {
- return editor.selection.isCollapsed() === false && isAbsoluteUrl(html) ? createLink(editor, html, pasteHtml) : false;
- };
+ var linkSelection = function (editor, html, pasteHtml) {
+ return editor.selection.isCollapsed() === false && isAbsoluteUrl(html) ? createLink(editor, html, pasteHtml) : false;
+ };
- var insertImage = function (editor, html, pasteHtml) {
- return isImageUrl(html) ? createImage(editor, html, pasteHtml) : false;
- };
+ var insertImage = function (editor, html, pasteHtml) {
+ return isImageUrl(html) ? createImage(editor, html, pasteHtml) : false;
+ };
- var pasteHtml = function (editor, html) {
- editor.insertContent(html, {
- merge: editor.settings.paste_merge_formats !== false,
- paste: true
- });
+ var pasteHtml = function (editor, html) {
+ editor.insertContent(html, {
+ merge: editor.settings.paste_merge_formats !== false,
+ paste: true
+ });
- return true;
- };
+ return true;
+ };
- var smartInsertContent = function (editor, html) {
- Tools.each([
- linkSelection,
- insertImage,
- pasteHtml
- ], function (action) {
- return action(editor, html, pasteHtml) !== true;
- });
- };
+ var smartInsertContent = function (editor, html) {
+ Tools.each([
+ linkSelection,
+ insertImage,
+ pasteHtml
+ ], function (action) {
+ return action(editor, html, pasteHtml) !== true;
+ });
+ };
- var insertContent = function (editor, html) {
- if (editor.settings.smart_paste === false) {
- pasteHtml(editor, html);
- } else {
- smartInsertContent(editor, html);
- }
- };
+ var insertContent = function (editor, html) {
+ if (editor.settings.smart_paste === false) {
+ pasteHtml(editor, html);
+ } else {
+ smartInsertContent(editor, html);
+ }
+ };
- return {
- isImageUrl: isImageUrl,
- isAbsoluteUrl: isAbsoluteUrl,
- insertContent: insertContent
- };
-});
-
-// Included from: js/tinymce/plugins/paste/classes/Clipboard.js
+ return {
+ isImageUrl: isImageUrl,
+ isAbsoluteUrl: isAbsoluteUrl,
+ insertContent: insertContent
+ };
+ }
+);
/**
* Clipboard.js
*
* Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
+ * Copyright (c) 1999-2017 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
@@ -364,692 +796,727 @@ define("tinymce/pasteplugin/SmartPaste", [
* @class tinymce.pasteplugin.Clipboard
* @private
*/
-define("tinymce/pasteplugin/Clipboard", [
- "tinymce/Env",
- "tinymce/dom/RangeUtils",
- "tinymce/util/VK",
- "tinymce/pasteplugin/Utils",
- "tinymce/pasteplugin/SmartPaste",
- "tinymce/util/Delay"
-], function(Env, RangeUtils, VK, Utils, SmartPaste, Delay) {
- return function(editor) {
- var self = this, pasteBinElm, lastRng, keyboardPasteTimeStamp = 0, draggingInternally = false;
- var pasteBinDefaultContent = '%MCEPASTEBIN%', keyboardPastePlainTextState;
- var mceInternalUrlPrefix = 'data:text/mce-internal,';
- var uniqueId = Utils.createIdGenerator("mceclip");
-
- /**
- * 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;
-
- args = editor.fire('BeforePastePreProcess', {content: html}); // Internal event used by Quirks
- args = editor.fire('PastePreProcess', args);
- html = args.content;
-
- 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;
- }
-
- if (!args.isDefaultPrevented()) {
- SmartPaste.insertContent(editor, html);
- }
- }
- }
-
- /**
- * 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');
-
- var startBlock = editor.dom.getParent(editor.selection.getStart(), editor.dom.isBlock);
-
- // Create start block html for example
"]
- ]);
- } else {
- text = Utils.filter(text, [
- [/\n\n/g, "
"]
- ]);
-
- if (text.indexOf('
- // 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;
- }
- }
- }
- }
-
- // 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);
-
- // 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);
- }
-
- if (lastRng) {
- editor.selection.setRng(lastRng);
- }
- }
-
- 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;
-
- // Since WebKit/Chrome might clone the paste bin when pasting
- // for example:
"},d=function(a){var b=a.dom.select("*[data-mce-id]");return b[0]},e=function(a,b,e){a.undoManager.transact(function(){var f,g;a.insertContent(c(b,e)),f=d(a),f.removeAttribute("data-mce-id"),g=a.dom.select("td,th",f),a.selection.setCursorLocation(g[0],0)})},f=function(a,b){a.execCommand("FormatBlock",!1,b)},g=function(b,c,d){var e,f;e=b.editorUpload.blobCache,f=e.create(a.uuid("mceu"),d,c),e.add(f),b.insertContent(b.dom.createHTML("img",{src:f.blobUri()}))},h=function(a){a.selection.collapse(!1)},i=function(a){a.focus(),b.unlinkSelection(a),h(a)},j=function(a,b,c){a.focus(),a.dom.setAttrib(b,"href",c),h(a)},k=function(a,b){a.execCommand("mceInsertLink",!1,{href:b}),h(a)},l=function(a,b){var c=a.dom.getParent(a.selection.getStart(),"a[href]");c?j(a,c,b):k(a,b)},m=function(a,b){0===b.trim().length?i(a):l(a,b)};return{insertTable:e,formatBlock:f,insertBlob:g,createLink:m,unlink:i}}),g("v",[],function(){var a=function(a){return/^www\.|\.(com|org|edu|gov|uk|net|ca|de|jp|fr|au|us|ru|ch|it|nl|se|no|es|mil)$/i.test(a.trim())},b=function(a){return/^https?:\/\//.test(a.trim())};return{isDomainLike:a,isAbsolute:b}}),g("l",["g","j","s","p","v"],function(a,b,c,d,e){var f=function(a){a.find("textbox").eq(0).each(function(a){a.focus()})},g=function(c,d){var e=b.create(a.extend({type:"form",layout:"flex",direction:"row",padding:5,name:c,spacing:3},d));return e.on("show",function(){f(e)}),e},h=function(a,b){return b?a.show():a.hide()},i=function(a,b){return new c(function(c){a.windowManager.confirm("The URL you entered seems to be an external link. Do you want to add the required http:// prefix?",function(a){var d=a===!0?"http://"+b:b;c(d)})})},j=function(a,b){return!e.isAbsolute(b)&&e.isDomainLike(b)?i(a,b):c.resolve(b)},k=function(a,b){var c={},e=function(){a.focus(),d.unlink(a),b()},f=function(a){var b=a.meta;b&&b.attach&&(c={href:this.value(),attach:b.attach})},i=function(b){if(b.control===this){var c,d="";c=a.dom.getParent(a.selection.getStart(),"a[href]"),c&&(d=a.dom.getAttrib(c,"href")),this.fromJSON({linkurl:d}),h(this.find("#unlink"),c),this.find("#linkurl")[0].focus()}};return g("quicklink",{items:[{type:"button",name:"unlink",icon:"unlink",onclick:e,tooltip:"Remove link"},{type:"filepicker",name:"linkurl",placeholder:"Paste or type a link",filetype:"file",onchange:f},{type:"button",icon:"checkmark",subtype:"primary",tooltip:"Ok",onclick:"submit"}],onshow:i,onsubmit:function(e){j(a,e.data.linkurl).then(function(e){a.undoManager.transact(function(){e===c.href&&(c.attach(),c={}),d.createLink(a,e)}),b()})}})};return{createQuickLinkForm:k}}),g("m",["q","r"],function(a,b){var c=function(a,b){return{rect:a,position:b}},d=function(a,b){return{x:b.x,y:b.y,w:a.w,h:a.h}},e=function(b,e,f,g,h){var i,j,k;return i=a.findBestRelativePosition(h,f,g,b),f=a.clamp(f,g),i?(j=a.relativePosition(h,f,i),k=d(h,j),c(k,i)):(f=a.intersect(g,f),f?(i=a.findBestRelativePosition(h,f,g,e))?(j=a.relativePosition(h,f,i),k=d(h,j),c(k,i)):(k=d(h,f),c(k,i)):null)},f=function(a,b,c){return e(["cr-cl","cl-cr"],["bc-tc","bl-tl","br-tr"],a,b,c)},g=function(a,b,c){return e(["tc-bc","bc-tc","tl-bl","bl-tl","tr-br","br-tr"],["bc-tc","bl-tl","br-tr"],a,b,c)},h=function(a,c,d,e){var f;return"function"==typeof a?(f=a({elementRect:b.toClientRect(c),contentAreaRect:b.toClientRect(d),panelRect:b.toClientRect(e)}),b.fromClientRect(f)):e},i=function(a){return a.panelRect};return{calcInsert:f,calc:g,userConstrain:h,defaultHandler:i}}),g("c",["g","j","i","k","l","f","m","5"],function(a,b,c,d,e,f,g,h){return function(){var i,j,k=["bold","italic","|","quicklink","h2","h3","blockquote"],l=["quickimage","quicktable"],m=function(b,c){return a.map(c,function(a){return d.create(b,a.id,a.items)})},n=function(a){return h.getToolbarItemsOr(a,"selection_toolbar",k)},o=function(a){return h.getToolbarItemsOr(a,"insert_toolbar",l)},p=function(a){return a.items().length>0},q=function(c,f){var g=m(c,f).concat([d.create(c,"text",n(c)),d.create(c,"insert",o(c)),e.createQuickLinkForm(c,B)]);return b.create({type:"floatpanel",role:"dialog",classes:"tinymce tinymce-inline arrow",ariaLabel:"Inline toolbar",layout:"flex",direction:"column",align:"stretch",autohide:!1,autofix:!0,fixed:!0,border:1,items:a.grep(g,p),oncancel:function(){c.focus()}})},r=function(a){a&&a.show()},s=function(a,b){a.moveTo(b.x,b.y)},t=function(b,c){c=c?c.substr(0,2):"",a.each({t:"down",b:"up",c:"center"},function(a,d){b.classes.toggle("arrow-"+a,d===c.substr(0,1))}),"cr"===c?(b.classes.toggle("arrow-left",!0),b.classes.toggle("arrow-right",!1)):"cl"===c?(b.classes.toggle("arrow-left",!0),b.classes.toggle("arrow-right",!0)):a.each({l:"left",r:"right"},function(a,d){b.classes.toggle("arrow-"+a,d===c.substr(1,1))})},u=function(a,b){var c=a.items().filter("#"+b);return c.length>0&&(c[0].show(),a.reflow(),!0)},v=function(a,b,d,e){var i,k,l,m;return m=h.getHandlerOr(d,"inline_toolbar_position_handler",g.defaultHandler),i=f.getContentAreaRect(d),k=c.DOM.getRect(a.getEl()),l="insert"===b?g.calcInsert(e,i,k):g.calc(e,i,k),!!l&&(k=l.rect,j=e,s(a,g.userConstrain(m,e,i,k)),t(a,l.position),!0)},w=function(a,b,c,d){return r(a),a.items().hide(),u(a,b)?void(v(a,b,c,d)===!1&&B(a)):void B(a)},x=function(){return i.items().filter("form:visible").length>0},y=function(a,b){if(i){if(i.items().hide(),!u(i,b))return void B(i);var d,e,k,l;r(i),i.items().hide(),u(i,b),l=h.getHandlerOr(a,"inline_toolbar_position_handler",g.defaultHandler),d=f.getContentAreaRect(a),e=c.DOM.getRect(i.getEl()),k=g.calc(j,d,e),k&&(e=k.rect,s(i,g.userConstrain(l,j,d,e)),t(i,k.position))}},z=function(a,b,c,d){i||(i=q(a,d),i.renderTo(document.body).reflow().moveTo(c.x,c.y),a.nodeChanged()),w(i,b,a,c)},A=function(a,b,c){i&&v(i,b,a,c)},B=function(){i&&i.hide()},C=function(){i&&i.find("toolbar:visible").eq(0).each(function(a){a.focus(!0)})},D=function(){i&&(i.remove(),i=null)},E=function(){return i&&i.visible()&&x()};return{show:z,showForm:y,reposition:A,inForm:E,hide:B,focus:C,remove:D}}}),g("n",["s"],function(a){var b=function(b){return new a(function(a){var c=new FileReader;c.onloadend=function(){a(c.result.split(",")[1])},c.readAsDataURL(b)})};return{blobToBase64:b}}),g("o",["s"],function(a){var b=function(){return new a(function(a){var b;b=document.createElement("input"),b.type="file",b.style.position="fixed",b.style.left=0,b.style.top=0,b.style.opacity=.001,document.body.appendChild(b),b.onchange=function(b){a(Array.prototype.slice.call(b.target.files))},b.click(),b.parentNode.removeChild(b)})};return{pickFile:b}}),g("b",["c","n","o","p"],function(a,b,c,d){var e=function(a){for(var b=function(b){return function(){d.formatBlock(a,b)}},c=1;c<6;c++){var e="h"+c;a.addButton(e,{text:e.toUpperCase(),tooltip:"Heading "+c,stateSelector:e,onclick:b(e),onPostRender:function(){var a=this.getEl().firstChild.firstChild;a.style.fontWeight="bold"}})}},f=function(a,f){a.addButton("quicklink",{icon:"link",tooltip:"Insert/Edit link",stateSelector:"a[href]",onclick:function(){f.showForm(a,"quicklink")}}),a.addButton("quickimage",{icon:"image",tooltip:"Insert image",onclick:function(){c.pickFile().then(function(c){var e=c[0];b.blobToBase64(e).then(function(b){d.insertBlob(a,b,e)})})}}),a.addButton("quicktable",{icon:"table",tooltip:"Insert table",onclick:function(){f.hide(),d.insertTable(a,2,2)}}),e(a)};return{addToEditor:f}}),g("0",["1","2","3","4","5","6","7","8","9","a","b","c"],function(a,b,c,d,e,f,g,h,i,j,k,l){var m=function(a){var b=a.selection.getNode(),c=a.dom.getParents(b);return c},n=function(a,b,c,d){var e=function(c){return a.dom.is(c,b)};return{predicate:e,id:c,items:d}},o=function(a){var b=a.contextToolbars;return d.flatten([b?b:[],n(a,"img","image","alignleft aligncenter alignright")])},p=function(a,b){var c,d,e;return d=m(a),e=h.fromContextToolbars(b),c=g.match(a,[f.element(d[0],e),i.textSelection("text"),i.emptyTextBlock(d,"insert"),f.parent(d,e)]),c&&c.rect?c:null},q=function(a,b){var c=function(){var c=o(a),d=p(a,c);d?b.show(a,d.id,d.rect,c):b.hide()};return function(){a.removed||c()}},r=function(a,b){return function(){var c=o(a),d=p(a,c);d&&b.reposition(a,d.id,d.rect)}},s=function(a,b,c){return function(){a.removed||b.inForm()||c()}},t=function(a,b){var d=c.throttle(q(a,b),0),e=c.throttle(s(a,b,q(a,b)),0);a.on("blur hide ObjectResizeStart",b.hide),a.on("click",d),a.on("nodeChange mouseup",e),a.on("ResizeEditor keyup",d),a.on("ResizeWindow",r(a,b)),a.on("remove",b.remove),a.shortcuts.add("Alt+F10","",b.focus)},u=function(a,b){a.shortcuts.remove("meta+k"),a.shortcuts.add("meta+k","",function(){var c=o(a),d=d=g.match(a,[i.textSelection("quicklink")]);d&&b.show(a,d.id,d.rect,c)})},v=function(a,b){return j.load(a,function(){t(a,b),u(a,b)}),{}},w=function(a){throw new Error(a)};return a.add("inlite",function(a){var b=new l;k.addToEditor(a,b);var c=function(){return a.inline?v(a,b):w("inlite theme only supports inline mode.")};return{renderUI:c}}),b.appendTo(window.tinymce?window.tinymce:{}),function(){}}),d("0")()}();
\ No newline at end of file
diff --git a/src/wp-includes/js/tinymce/themes/modern/theme.js b/src/wp-includes/js/tinymce/themes/modern/theme.js
index 15ed07c7a7..a3a97cce84 100644
--- a/src/wp-includes/js/tinymce/themes/modern/theme.js
+++ b/src/wp-includes/js/tinymce/themes/modern/theme.js
@@ -81,616 +81,149 @@ var defineGlobal = function (id, ref) {
define(id, [], function () { return ref; });
};
/*jsc
-["tinymce.modern.Theme","global!tinymce.Env","global!tinymce.EditorManager","global!tinymce.ThemeManager","tinymce.modern.modes.Iframe","tinymce.modern.modes.Inline","tinymce.modern.ui.Resize","tinymce.modern.ui.ProgressState","global!tinymce.util.Tools","global!tinymce.ui.Factory","global!tinymce.DOM","tinymce.modern.ui.Toolbar","tinymce.modern.ui.Menubar","tinymce.modern.ui.ContextToolbars","tinymce.modern.ui.A11y","tinymce.modern.ui.Sidebar","tinymce.modern.ui.SkinLoaded","global!tinymce.ui.FloatPanel","global!tinymce.ui.Throbber","global!tinymce.util.Delay","global!tinymce.geom.Rect"]
+["tinymce.themes.modern.Theme","global!window","tinymce.core.AddOnManager","tinymce.core.EditorManager","tinymce.core.Env","tinymce.core.ui.Api","tinymce.themes.modern.modes.Iframe","tinymce.themes.modern.modes.Inline","tinymce.themes.modern.ui.ProgressState","tinymce.themes.modern.ui.Resize","global!tinymce.util.Tools.resolve","tinymce.core.dom.DOMUtils","tinymce.core.ui.Factory","tinymce.core.util.Tools","tinymce.themes.modern.ui.A11y","tinymce.themes.modern.ui.Branding","tinymce.themes.modern.ui.ContextToolbars","tinymce.themes.modern.ui.Menubar","tinymce.themes.modern.ui.Sidebar","tinymce.themes.modern.ui.SkinLoaded","tinymce.themes.modern.ui.Toolbar","tinymce.core.ui.FloatPanel","tinymce.core.ui.Throbber","tinymce.core.util.Delay","tinymce.core.geom.Rect"]
jsc*/
-defineGlobal("global!tinymce.Env", tinymce.Env);
-defineGlobal("global!tinymce.EditorManager", tinymce.EditorManager);
-defineGlobal("global!tinymce.ThemeManager", tinymce.ThemeManager);
-defineGlobal("global!tinymce.util.Tools", tinymce.util.Tools);
-defineGlobal("global!tinymce.ui.Factory", tinymce.ui.Factory);
-defineGlobal("global!tinymce.DOM", tinymce.DOM);
+defineGlobal("global!window", window);
+defineGlobal("global!tinymce.util.Tools.resolve", tinymce.util.Tools.resolve);
/**
- * Toolbar.js
+ * ResolveGlobal.js
*
* Released under LGPL License.
- * Copyright (c) 1999-2016 Ephox Corp. All rights reserved
+ * Copyright (c) 1999-2017 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
-define('tinymce.modern.ui.Toolbar', [
- 'global!tinymce.util.Tools',
- 'global!tinymce.ui.Factory'
-], function (Tools, Factory) {
- var defaultToolbar = "undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | " +
- "bullist numlist outdent indent | link image";
-
- var createToolbar = function (editor, items, size) {
- var toolbarItems = [], buttonGroup;
-
- if (!items) {
- return;
- }
-
- Tools.each(items.split(/[ ,]/), function(item) {
- var itemName;
-
- var bindSelectorChanged = function () {
- var selection = editor.selection;
-
- if (item.settings.stateSelector) {
- selection.selectorChanged(item.settings.stateSelector, function(state) {
- item.active(state);
- }, true);
- }
-
- if (item.settings.disabledStateSelector) {
- selection.selectorChanged(item.settings.disabledStateSelector, function(state) {
- item.disabled(state);
- });
- }
- };
-
- if (item == "|") {
- buttonGroup = null;
- } else {
- if (Factory.has(item)) {
- item = {type: item, size: size};
- toolbarItems.push(item);
- buttonGroup = null;
- } else {
- if (!buttonGroup) {
- buttonGroup = {type: 'buttongroup', items: []};
- toolbarItems.push(buttonGroup);
- }
-
- if (editor.buttons[item]) {
- // TODO: Move control creation to some UI class
- itemName = item;
- item = editor.buttons[itemName];
-
- if (typeof item == "function") {
- item = item();
- }
-
- item.type = item.type || 'button';
- item.size = size;
-
- item = Factory.create(item);
- buttonGroup.items.push(item);
-
- if (editor.initialized) {
- bindSelectorChanged();
- } else {
- editor.on('init', bindSelectorChanged);
- }
- }
- }
- }
- });
-
- return {
- type: 'toolbar',
- layout: 'flow',
- items: toolbarItems
- };
- };
-
- /**
- * Creates the toolbars from config and returns a toolbar array.
- *
- * @param {String} size Optional toolbar item size.
- * @return {Array} Array with toolbars.
- */
- var createToolbars = function (editor, size) {
- var toolbars = [], settings = editor.settings;
-
- var addToolbar = function (items) {
- if (items) {
- toolbars.push(createToolbar(editor, items, size));
- return true;
- }
- };
-
- // Convert toolbar array to multiple options
- if (Tools.isArray(settings.toolbar)) {
- // Empty toolbar array is the same as a disabled toolbar
- if (settings.toolbar.length === 0) {
- return;
- }
-
- Tools.each(settings.toolbar, function(toolbar, i) {
- settings["toolbar" + (i + 1)] = toolbar;
- });
-
- delete settings.toolbar;
- }
-
- // Generate toolbar 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];
-
- // 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;
- }
- }
-
- return html;
- }
-
- /**
- * 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 items = {};
-
- 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) {
- items['text/plain'] = legacyText;
- }
- }
- }
-
- if (dataTransfer.types) {
- for (var i = 0; i < dataTransfer.types.length; i++) {
- var contentType = dataTransfer.types[i];
- items[contentType] = dataTransfer.getData(contentType);
- }
- }
- }
-
- return items;
- }
-
- /**
- * 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 hasHtmlOrText(content) {
- return hasContentType(content, 'text/html') || hasContentType(content, 'text/plain');
- }
-
- function getBase64FromUri(uri) {
- var idx;
-
- idx = uri.indexOf(',');
- if (idx !== -1) {
- return uri.substr(idx + 1);
- }
-
- return null;
- }
-
- function isValidDataUriImage(settings, imgElm) {
- return settings.images_dataimg_filter ? settings.images_dataimg_filter(imgElm) : true;
- }
-
- function pasteImage(rng, reader, blob) {
- if (rng) {
- editor.selection.setRng(rng);
- rng = null;
- }
-
- var dataUri = reader.result;
- var base64 = getBase64FromUri(dataUri);
-
- var img = new Image();
- img.src = dataUri;
-
- // TODO: Move the bulk of the cache logic to EditorUpload
- if (isValidDataUriImage(editor.settings, img)) {
- var blobCache = editor.editorUpload.blobCache;
- var blobInfo, existingBlobInfo;
-
- existingBlobInfo = blobCache.findFirst(function(cachedBlobInfo) {
- return cachedBlobInfo.base64() === base64;
- });
-
- if (!existingBlobInfo) {
- blobInfo = blobCache.create(uniqueId(), blob, base64);
- blobCache.add(blobInfo);
- } else {
- blobInfo = existingBlobInfo;
- }
-
- pasteHtml('
');
- } else {
- pasteHtml('
');
- }
- }
-
- /**
- * 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 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 processItems(items) {
- var i, item, reader, hadImage = false;
-
- if (items) {
- for (i = 0; i < items.length; i++) {
- item = items[i];
-
- if (/^image\/(jpeg|png|gif|bmp)$/.test(item.type)) {
- var blob = item.getAsFile ? item.getAsFile() : item;
-
- reader = new FileReader();
- reader.onload = pasteImage.bind(null, rng, reader, blob);
- reader.readAsDataURL(blob);
-
- e.preventDefault();
- hadImage = true;
- }
- }
- }
-
- return hadImage;
- }
-
- if (editor.settings.paste_data_images && dataTransfer) {
- return processItems(dataTransfer.items) || processItems(dataTransfer.files);
- }
- }
-
- /**
- * 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;
-
- return navigator.userAgent.indexOf('Android') != -1 && clipboardData && clipboardData.items && clipboardData.items.length === 0;
- }
-
- function getCaretRangeFromEvent(e) {
- return RangeUtils.getCaretRangeFromPoint(e.clientX, e.clientY, editor.getDoc());
- }
-
- function hasContentType(clipboardContent, mimeType) {
- return mimeType in clipboardContent && clipboardContent[mimeType].length > 0;
- }
-
- function isKeyboardPasteEvent(e) {
- return (VK.metaKeyPressed(e) && e.keyCode == 86) || (e.shiftKey && e.keyCode == 45);
- }
-
- function registerEventHandlers() {
- editor.on('keydown', function(e) {
- function removePasteBinOnKeyUp(e) {
- // Ctrl+V or Shift+Insert
- if (isKeyboardPasteEvent(e) && !e.isDefaultPrevented()) {
- removePasteBin();
- }
- }
-
- // Ctrl+V or Shift+Insert
- if (isKeyboardPasteEvent(e) && !e.isDefaultPrevented()) {
- keyboardPastePlainTextState = e.shiftKey && e.keyCode == 86;
-
- // 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;
- }
-
- // Prevent undoManager keydown handler from making an undo level with the pastebin in it
- e.stopImmediatePropagation();
-
- keyboardPasteTimeStamp = new Date().getTime();
-
- // 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;
- }
-
- removePasteBin();
- createPasteBin();
-
- // 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 insertClipboardContent(clipboardContent, isKeyBoardPaste, plainTextMode) {
- var content;
-
- // Grab HTML from Clipboard API or paste bin as a fallback
- if (hasContentType(clipboardContent, 'text/html')) {
- content = clipboardContent['text/html'];
- } else {
- content = getPasteBinHtml();
-
- // If paste bin is empty try using plain text mode
- // since that is better than nothing right
- if (content == pasteBinDefaultContent) {
- plainTextMode = true;
- }
- }
-
- content = Utils.trimHtml(content);
-
- // 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;
- }
-
- removePasteBin();
-
- // If we got nothing from clipboard API and pastebin then we could try the last resort: plain/text
- if (!content.length) {
- plainTextMode = true;
- }
-
- // 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 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 (plainTextMode) {
- pasteText(content);
- } else {
- pasteHtml(content);
- }
- }
-
- var getLastRng = function() {
- return lastRng || editor.selection.getRng();
- };
-
- 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;
-
- var isKeyBoardPaste = (new Date().getTime() - keyboardPasteTimeStamp - clipboardDelay) < 1000;
- var plainTextMode = self.pasteFormat == "text" || keyboardPastePlainTextState;
-
- keyboardPastePlainTextState = false;
-
- if (e.isDefaultPrevented() || isBrokenAndroidClipboardEvent(e)) {
- removePasteBin();
- return;
- }
-
- if (!hasHtmlOrText(clipboardContent) && pasteImageData(e, getLastRng())) {
- removePasteBin();
- return;
- }
-
- // Not a keyboard paste prevent default paste and try to grab the clipboard contents using different APIs
- if (!isKeyBoardPaste) {
- e.preventDefault();
- }
-
- // Try IE only method if paste isn't a keyboard paste
- if (Env.ie && (!isKeyBoardPaste || e.ieFake)) {
- createPasteBin();
-
- editor.dom.bind(pasteBinElm, 'paste', function(e) {
- e.stopPropagation();
- });
-
- editor.getDoc().execCommand('Paste', false, null);
- clipboardContent["text/html"] = getPasteBinHtml();
- }
-
- // If clipboard API has HTML then use that directly
- if (hasContentType(clipboardContent, 'text/html')) {
- e.preventDefault();
- insertClipboardContent(clipboardContent, isKeyBoardPaste, plainTextMode);
- } else {
- Delay.setEditorTimeout(editor, function() {
- insertClipboardContent(clipboardContent, isKeyBoardPaste, plainTextMode);
- }, 0);
- }
- });
-
- editor.on('dragstart dragend', function(e) {
- draggingInternally = e.type == 'dragstart';
- });
-
- function isPlainTextFileUrl(content) {
- var plainTextContent = content['text/plain'];
- return plainTextContent ? plainTextContent.indexOf('file://') === 0 : false;
- }
-
- editor.on('drop', function(e) {
- var dropContent, rng;
-
- rng = getCaretRangeFromEvent(e);
-
- if (e.isDefaultPrevented() || draggingInternally) {
- return;
- }
-
- dropContent = getDataTransferItems(e.dataTransfer);
-
- if ((!hasHtmlOrText(dropContent) || isPlainTextFileUrl(dropContent)) && pasteImageData(e, rng)) {
- return;
- }
-
- if (rng && editor.settings.paste_filter_drop !== false) {
- var content = dropContent['mce-internal'] || dropContent['text/html'] || dropContent['text/plain'];
-
- if (content) {
- e.preventDefault();
-
- // FF 45 doesn't paint a caret when dragging in text in due to focus call by execCommand
- Delay.setEditorTimeout(editor, function() {
- editor.undoManager.transact(function() {
- if (dropContent['mce-internal']) {
- editor.execCommand('Delete');
- }
-
- editor.selection.setRng(rng);
-
- content = Utils.trimHtml(content);
-
- if (!dropContent['text/html']) {
- pasteText(content);
- } else {
- pasteHtml(content);
- }
- });
- });
- }
- }
- });
-
- editor.on('dragover dragend', function(e) {
- if (editor.settings.paste_data_images) {
- e.preventDefault();
- }
- });
- }
-
- self.pasteHtml = pasteHtml;
- self.pasteText = pasteText;
- self.pasteImageData = pasteImageData;
-
- editor.on('preInit', function() {
- registerEventHandlers();
-
- // 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;
- }
-
- function remove(node) {
- if (!node.attr('data-mce-object') && src !== Env.transparentSrc) {
- node.remove();
- }
- }
-
- function isWebKitFakeUrl(src) {
- return src.indexOf("webkit-fake-url") === 0;
- }
-
- function isDataUri(src) {
- return src.indexOf("data:") === 0;
- }
-
- if (!editor.settings.paste_data_images && isPasteInsert(args)) {
- var i = nodes.length;
-
- while (i--) {
- var src = nodes[i].attributes.map.src;
-
- if (!src) {
- continue;
- }
-
- // 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
+define(
+ 'tinymce.plugins.paste.core.Clipboard',
+ [
+ 'tinymce.core.dom.RangeUtils',
+ 'tinymce.core.Env',
+ 'tinymce.core.util.Delay',
+ 'tinymce.core.util.Tools',
+ 'tinymce.core.util.VK',
+ 'tinymce.plugins.paste.core.CutCopy',
+ 'tinymce.plugins.paste.core.InternalHtml',
+ 'tinymce.plugins.paste.core.Newlines',
+ 'tinymce.plugins.paste.core.SmartPaste',
+ 'tinymce.plugins.paste.core.Utils'
+ ],
+ function (RangeUtils, Env, Delay, Tools, VK, CutCopy, InternalHtml, Newlines, SmartPaste, Utils) {
+ return function (editor) {
+ var self = this, pasteBinElm, lastRng, keyboardPasteTimeStamp = 0, draggingInternally = false;
+ var pasteBinDefaultContent = '%MCEPASTEBIN%', keyboardPastePlainTextState;
+ var mceInternalUrlPrefix = 'data:text/mce-internal,';
+ var uniqueId = Utils.createIdGenerator("mceclip");
+
+ /**
+ * 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.
+ * @param {Boolean?} internalFlag Optional true/false flag if the contents is internal or external.
+ */
+ function pasteHtml(html, internalFlag) {
+ var args, dom = editor.dom, internal;
+
+ internal = internalFlag || InternalHtml.isMarked(html);
+ html = InternalHtml.unmark(html);
+
+ args = editor.fire('BeforePastePreProcess', { content: html, internal: internal }); // Internal event used by Quirks
+ args = editor.fire('PastePreProcess', args);
+ html = args.content;
+
+ 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, internal: internal });
+ dom.remove(tempBody);
+ html = args.node.innerHTML;
+ }
+
+ if (!args.isDefaultPrevented()) {
+ SmartPaste.insertContent(editor, html);
+ }
+ }
+ }
+
+ /**
+ * 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');
+ text = Newlines.convert(text, editor.settings.forced_root_block, editor.settings.forced_root_block_attrs);
+
+ pasteHtml(text, false);
+ }
+
+ /**
+ * 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;
+ }
+ }
+
+ /**
+ * 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;
+
+ rects = rng.getClientRects();
+ if (rects.length) {
+ return rects[0];
+ }
+
+ if (!rng.collapsed || container.nodeType != 1) {
+ return;
+ }
+
+ node = container.childNodes[lastRng.startOffset];
+
+ // Skip empty whitespace nodes
+ while (node && node.nodeType == 3 && !node.data.length) {
+ node = node.nextSibling;
+ }
+
+ if (!node) {
+ return;
+ }
+
+ // 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;
+ }
+ }
+ }
+ }
+
+ // 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);
+
+ // 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);
+ }
+
+ if (lastRng) {
+ editor.selection.setRng(lastRng);
+ }
+ }
+
+ 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;
+
+ // Since WebKit/Chrome might clone the paste bin when pasting
+ // for example:
"},r=function(e){var t=e.dom.select("*[data-mce-id]");return t[0]},i=function(e,t,i){e.undoManager.transact(function(){var o,a;e.insertContent(n(t,i)),o=r(e),o.removeAttribute("data-mce-id"),a=e.dom.select("td,th",o),e.selection.setCursorLocation(a[0],0)})},o=function(e,t){e.execCommand("FormatBlock",!1,t)},a=function(t,n,r){var i,o;i=t.editorUpload.blobCache,o=i.create(e.uuid("mceu"),r,n),i.add(o),t.insertContent(t.dom.createHTML("img",{src:o.blobUri()}))},s=function(e){e.selection.collapse(!1)},l=function(e){e.focus(),t.unlinkSelection(e),s(e)},u=function(e,t,n){e.focus(),e.dom.setAttrib(t,"href",n),s(e)},c=function(e,t){e.execCommand("mceInsertLink",!1,{href:t}),s(e)},d=function(e,t){var n=e.dom.getParent(e.selection.getStart(),"a[href]");n?u(e,n,t):c(e,t)},f=function(e,t){0===t.trim().length?l(e):d(e,t)};return{insertTable:i,formatBlock:o,insertBlob:a,createLink:f,unlink:l}}),a("r",[],function(){var e=function(e){return/^www\.|\.(com|org|edu|gov|uk|net|ca|de|jp|fr|au|us|ru|ch|it|nl|se|no|es|mil)$/i.test(e.trim())},t=function(e){return/^https?:\/\//.test(e.trim())};return{isDomainLike:e,isAbsolute:t}}),a("g",["c","d","o","m","r"],function(e,t,n,r,i){var o=function(e){e.find("textbox").eq(0).each(function(e){e.focus()})},a=function(n,r){var i=t.create(e.extend({type:"form",layout:"flex",direction:"row",padding:5,name:n,spacing:3},r));return i.on("show",function(){o(i)}),i},s=function(e,t){return t?e.show():e.hide()},l=function(e,t){return new n(function(n){e.windowManager.confirm("The URL you entered seems to be an external link. Do you want to add the required http:// prefix?",function(e){var r=e===!0?"http://"+t:t;n(r)})})},u=function(e,t){return!i.isAbsolute(t)&&i.isDomainLike(t)?l(e,t):n.resolve(t)},c=function(e,t){var n={},i=function(){e.focus(),r.unlink(e),t()},o=function(e){var t=e.meta;t&&t.attach&&(n={href:this.value(),attach:t.attach})},l=function(t){if(t.control===this){var n,r="";n=e.dom.getParent(e.selection.getStart(),"a[href]"),n&&(r=e.dom.getAttrib(n,"href")),this.fromJSON({linkurl:r}),s(this.find("#unlink"),n),this.find("#linkurl")[0].focus()}};return a("quicklink",{items:[{type:"button",name:"unlink",icon:"unlink",onclick:i,tooltip:"Remove link"},{type:"filepicker",name:"linkurl",placeholder:"Paste or type a link",filetype:"file",onchange:o},{type:"button",icon:"checkmark",subtype:"primary",tooltip:"Ok",onclick:"submit"}],onshow:l,onsubmit:function(i){u(e,i.data.linkurl).then(function(i){e.undoManager.transact(function(){i===n.href&&(n.attach(),n={}),r.createLink(e,i)}),t()})}})};return{createQuickLinkForm:c}}),s("s",tinymce.geom.Rect),a("t",[],function(){var e=function(e){return{x:e.left,y:e.top,w:e.width,h:e.height}},t=function(e){return{left:e.x,top:e.y,width:e.w,height:e.h,right:e.x+e.w,bottom:e.y+e.h}};return{fromClientRect:e,toClientRect:t}}),a("h",["e","s","t"],function(e,t,n){var r=function(t){var n=e.getViewPort();return{x:t.x+n.x,y:t.y+n.y,w:t.w,h:t.h}},i=function(e){var t=e.getBoundingClientRect();return r({x:t.left,y:t.top,w:Math.max(e.clientWidth,e.offsetWidth),h:Math.max(e.clientHeight,e.offsetHeight)})},o=function(e,t){return i(t)},a=function(e){return i(e.getElement().ownerDocument.body)},s=function(e){return i(e.getContentAreaContainer()||e.getBody())},l=function(e){var t=e.selection.getBoundingClientRect();return t?r(n.fromClientRect(t)):null};return{getElementRect:o,getPageAreaRect:a,getContentAreaRect:s,getSelectionRect:l}}),a("i",["s","t"],function(e,t){var n=function(e,t){return{rect:e,position:t}},r=function(e,t){return{x:t.x,y:t.y,w:e.w,h:e.h}},i=function(t,i,o,a,s){var l,u,c;return l=e.findBestRelativePosition(s,o,a,t),o=e.clamp(o,a),l?(u=e.relativePosition(s,o,l),c=r(s,u),n(c,l)):(o=e.intersect(a,o),o?(l=e.findBestRelativePosition(s,o,a,i))?(u=e.relativePosition(s,o,l),c=r(s,u),n(c,l)):(c=r(s,o),n(c,l)):null)},o=function(e,t,n){return i(["cr-cl","cl-cr"],["bc-tc","bl-tl","br-tr"],e,t,n)},a=function(e,t,n){return i(["tc-bc","bc-tc","tl-bl","bl-tl","tr-br","br-tr"],["bc-tc","bl-tl","br-tr"],e,t,n)},s=function(e,n,r,i){var o;return"function"==typeof e?(o=e({elementRect:t.toClientRect(n),contentAreaRect:t.toClientRect(r),panelRect:t.toClientRect(i)}),t.fromClientRect(o)):i},l=function(e){return e.panelRect};return{calcInsert:o,calc:a,userConstrain:s,defaultHandler:l}}),a("a",["j"],function(e){var t=function(e,t){if(t(e))return!0;throw new Error("Default value doesn't match requested type.")},n=function(e){return function(n,r,i){var o=n.settings;return t(i,e),r in o&&e(o[r])?o[r]:i}},r=function(e,t){return e.split(t).filter(function(e){return e.length>0})},i=function(t,n){var i=function(e){return"string"==typeof e?r(e,/[ ,]/):e},o=function(e,t){return e===!1?[]:t};return e.isArray(t)?t:e.isString(t)?i(t):e.isBoolean(t)?o(t,n):n},o=function(e){return function(n,r,o){var a=r in n.settings?n.settings[r]:o;return t(o,e),i(a,o)}};return{getStringOr:n(e.isString),getBoolOr:n(e.isBoolean),getNumberOr:n(e.isNumber),getHandlerOr:n(e.isFunction),getToolbarItemsOr:o(e.isArray)}}),a("3",["c","d","e","f","g","h","i","a"],function(e,t,n,r,i,o,a,s){return function(){var l,u,c=["bold","italic","|","quicklink","h2","h3","blockquote"],d=["quickimage","quicktable"],f=function(t,n){return e.map(n,function(e){return r.create(t,e.id,e.items)})},p=function(e){return s.getToolbarItemsOr(e,"selection_toolbar",c)},h=function(e){return s.getToolbarItemsOr(e,"insert_toolbar",d)},m=function(e){return e.items().length>0},g=function(n,o){var a=f(n,o).concat([r.create(n,"text",p(n)),r.create(n,"insert",h(n)),i.createQuickLinkForm(n,k)]);return t.create({type:"floatpanel",role:"dialog",classes:"tinymce tinymce-inline arrow",ariaLabel:"Inline toolbar",layout:"flex",direction:"column",align:"stretch",autohide:!1,autofix:!0,fixed:!0,border:1,items:e.grep(a,m),oncancel:function(){n.focus()}})},v=function(e){e&&e.show()},y=function(e,t){e.moveTo(t.x,t.y)},b=function(t,n){n=n?n.substr(0,2):"",e.each({t:"down",b:"up",c:"center"},function(e,r){t.classes.toggle("arrow-"+e,r===n.substr(0,1))}),"cr"===n?(t.classes.toggle("arrow-left",!0),t.classes.toggle("arrow-right",!1)):"cl"===n?(t.classes.toggle("arrow-left",!0),t.classes.toggle("arrow-right",!0)):e.each({l:"left",r:"right"},function(e,r){t.classes.toggle("arrow-"+e,r===n.substr(1,1))})},C=function(e,t){var n=e.items().filter("#"+t);return n.length>0&&(n[0].show(),e.reflow(),!0)},x=function(e,t,r,i){var l,c,d,f;return f=s.getHandlerOr(r,"inline_toolbar_position_handler",a.defaultHandler),l=o.getContentAreaRect(r),c=n.getRect(e.getEl()),d="insert"===t?a.calcInsert(i,l,c):a.calc(i,l,c),!!d&&(c=d.rect,u=i,y(e,a.userConstrain(f,i,l,c)),b(e,d.position),!0)},w=function(e,t,n,r){return v(e),e.items().hide(),C(e,t)?void(x(e,t,n,r)===!1&&k(e)):void k(e)},E=function(){return l.items().filter("form:visible").length>0},N=function(e,t){if(l){if(l.items().hide(),!C(l,t))return void k(l);var r,i,c,d;v(l),l.items().hide(),C(l,t),d=s.getHandlerOr(e,"inline_toolbar_position_handler",a.defaultHandler),r=o.getContentAreaRect(e),i=n.getRect(l.getEl()),c=a.calc(u,r,i),c&&(i=c.rect,y(l,a.userConstrain(d,u,r,i)),b(l,c.position))}},_=function(e,t,n,r){l||(l=g(e,r),l.renderTo(document.body).reflow().moveTo(n.x,n.y),e.nodeChanged()),w(l,t,e,n)},S=function(e,t,n){l&&x(l,t,e,n)},k=function(){l&&l.hide()},T=function(){l&&l.find("toolbar:visible").eq(0).each(function(e){e.focus(!0)})},R=function(){l&&(l.remove(),l=null)},A=function(){return l&&l.visible()&&E()};return{show:_,showForm:N,reposition:S,inForm:A,hide:k,focus:T,remove:R}}}),a("k",["o"],function(e){var t=function(t){return new e(function(e){var n=new FileReader;n.onloadend=function(){e(n.result.split(",")[1])},n.readAsDataURL(t)})};return{blobToBase64:t}}),a("l",["o"],function(e){var t=function(){return new e(function(e){var t;t=document.createElement("input"),t.type="file",t.style.position="fixed",t.style.left=0,t.style.top=0,t.style.opacity=.001,document.body.appendChild(t),t.onchange=function(t){e(Array.prototype.slice.call(t.target.files))},t.click(),t.parentNode.removeChild(t)})};return{pickFile:t}}),a("4",["3","k","l","m"],function(e,t,n,r){var i=function(e){for(var t=function(t){return function(){r.formatBlock(e,t)}},n=1;n<6;n++){var i="h"+n;e.addButton(i,{text:i.toUpperCase(),tooltip:"Heading "+n,stateSelector:i,onclick:t(i),onPostRender:function(){var e=this.getEl().firstChild.firstChild;e.style.fontWeight="bold"}})}},o=function(e,o){e.addButton("quicklink",{icon:"link",tooltip:"Insert/Edit link",stateSelector:"a[href]",onclick:function(){o.showForm(e,"quicklink")}}),e.addButton("quickimage",{icon:"image",tooltip:"Insert image",onclick:function(){n.pickFile().then(function(n){var i=n[0];t.blobToBase64(i).then(function(t){r.insertBlob(e,t,i)})})}}),e.addButton("quicktable",{icon:"table",tooltip:"Insert table",onclick:function(){o.hide(),r.insertTable(e,2,2)}}),i(e)};return{addToEditor:o}}),s("n",tinymce.EditorManager),a("5",["n","e"],function(e,t){var n=function(e,t){var n=function(){e._skinLoaded=!0,e.fire("SkinLoaded"),t()};e.initialized?n():e.on("init",n)},r=function(t){var n=e.baseURL+"/skins/";return t?n+t:n+"lightgray"},i=function(e,t){return e.documentBaseURI.toAbsolute(t)},o=function(e,o){var a=e.settings,s=a.skin_url?i(e,a.skin_url):r(a.skin),l=function(){n(e,o)};t.styleSheetLoader.load(s+"/skin.min.css",l),e.contentCSS.push(s+"/content.inline.min.css")};return{load:o}}),a("8",[],function(){var e=function(e,t){return{id:e,rect:t}},t=function(e,t){for(var n=0;n 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];
+
+ // 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;
+ }
+ }
+
+ return html;
+ }
+
+ /**
+ * 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 items = {};
+
+ 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) {
+ items['text/plain'] = legacyText;
+ }
+ }
+ }
+
+ if (dataTransfer.types) {
+ for (var i = 0; i < dataTransfer.types.length; i++) {
+ var contentType = dataTransfer.types[i];
+ items[contentType] = dataTransfer.getData(contentType);
+ }
+ }
+ }
+
+ return items;
+ }
+
+ /**
+ * 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) {
+ var content = getDataTransferItems(clipboardEvent.clipboardData || editor.getDoc().dataTransfer);
+
+ // Edge 15 has a broken HTML Clipboard API see https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/11877517/
+ return Utils.isMsEdge() ? Tools.extend(content, { 'text/html': '' }) : content;
+ }
+
+ function hasHtmlOrText(content) {
+ return hasContentType(content, 'text/html') || hasContentType(content, 'text/plain');
+ }
+
+ function getBase64FromUri(uri) {
+ var idx;
+
+ idx = uri.indexOf(',');
+ if (idx !== -1) {
+ return uri.substr(idx + 1);
+ }
+
+ return null;
+ }
+
+ function isValidDataUriImage(settings, imgElm) {
+ return settings.images_dataimg_filter ? settings.images_dataimg_filter(imgElm) : true;
+ }
+
+ function pasteImage(rng, reader, blob) {
+ if (rng) {
+ editor.selection.setRng(rng);
+ rng = null;
+ }
+
+ var dataUri = reader.result;
+ var base64 = getBase64FromUri(dataUri);
+
+ var img = new Image();
+ img.src = dataUri;
+
+ // TODO: Move the bulk of the cache logic to EditorUpload
+ if (isValidDataUriImage(editor.settings, img)) {
+ var blobCache = editor.editorUpload.blobCache;
+ var blobInfo, existingBlobInfo;
+
+ existingBlobInfo = blobCache.findFirst(function (cachedBlobInfo) {
+ return cachedBlobInfo.base64() === base64;
+ });
+
+ if (!existingBlobInfo) {
+ blobInfo = blobCache.create(uniqueId(), blob, base64);
+ blobCache.add(blobInfo);
+ } else {
+ blobInfo = existingBlobInfo;
+ }
+
+ pasteHtml('
', false);
+ } else {
+ pasteHtml('
', false);
+ }
+ }
+
+ /**
+ * 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 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 processItems(items) {
+ var i, item, reader, hadImage = false;
+
+ if (items) {
+ for (i = 0; i < items.length; i++) {
+ item = items[i];
+
+ if (/^image\/(jpeg|png|gif|bmp)$/.test(item.type)) {
+ var blob = item.getAsFile ? item.getAsFile() : item;
+
+ reader = new FileReader();
+ reader.onload = pasteImage.bind(null, rng, reader, blob);
+ reader.readAsDataURL(blob);
+
+ e.preventDefault();
+ hadImage = true;
+ }
+ }
+ }
+
+ return hadImage;
+ }
+
+ if (editor.settings.paste_data_images && dataTransfer) {
+ return processItems(dataTransfer.items) || processItems(dataTransfer.files);
+ }
+ }
+
+ /**
+ * 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;
+
+ return navigator.userAgent.indexOf('Android') != -1 && clipboardData && clipboardData.items && clipboardData.items.length === 0;
+ }
+
+ function getCaretRangeFromEvent(e) {
+ return RangeUtils.getCaretRangeFromPoint(e.clientX, e.clientY, editor.getDoc());
+ }
+
+ function hasContentType(clipboardContent, mimeType) {
+ return mimeType in clipboardContent && clipboardContent[mimeType].length > 0;
+ }
+
+ function isKeyboardPasteEvent(e) {
+ return (VK.metaKeyPressed(e) && e.keyCode == 86) || (e.shiftKey && e.keyCode == 45);
+ }
+
+ function registerEventHandlers() {
+ editor.on('keydown', function (e) {
+ function removePasteBinOnKeyUp(e) {
+ // Ctrl+V or Shift+Insert
+ if (isKeyboardPasteEvent(e) && !e.isDefaultPrevented()) {
+ removePasteBin();
+ }
+ }
+
+ // Ctrl+V or Shift+Insert
+ if (isKeyboardPasteEvent(e) && !e.isDefaultPrevented()) {
+ keyboardPastePlainTextState = e.shiftKey && e.keyCode == 86;
+
+ // 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;
+ }
+
+ // Prevent undoManager keydown handler from making an undo level with the pastebin in it
+ e.stopImmediatePropagation();
+
+ keyboardPasteTimeStamp = new Date().getTime();
+
+ // 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;
+ }
+
+ removePasteBin();
+ createPasteBin();
+
+ // 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 insertClipboardContent(clipboardContent, isKeyBoardPaste, plainTextMode, internal) {
+ var content, isPlainTextHtml;
+
+ // Grab HTML from Clipboard API or paste bin as a fallback
+ if (hasContentType(clipboardContent, 'text/html')) {
+ content = clipboardContent['text/html'];
+ } else {
+ content = getPasteBinHtml();
+
+ // If paste bin is empty try using plain text mode
+ // since that is better than nothing right
+ if (content == pasteBinDefaultContent) {
+ plainTextMode = true;
+ }
+ }
+
+ content = Utils.trimHtml(content);
+
+ // 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;
+ }
+
+ removePasteBin();
+
+ isPlainTextHtml = internal === false && Newlines.isPlainText(content);
+
+ // If we got nothing from clipboard API and pastebin or the content is a plain text (with only
+ // some BRs, Ps or DIVs as newlines) then we fallback to plain/text
+ if (!content.length || isPlainTextHtml) {
+ plainTextMode = true;
+ }
+
+
+
+ // 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') && isPlainTextHtml) {
+ content = clipboardContent['text/plain'];
+ } else {
+ content = Utils.innerText(content);
+ }
+ }
+
+ // 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 (plainTextMode) {
+ pasteText(content);
+ } else {
+ pasteHtml(content, internal);
+ }
+ }
+
+ var getLastRng = function () {
+ return lastRng || editor.selection.getRng();
+ };
+
+ 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;
+
+ var isKeyBoardPaste = (new Date().getTime() - keyboardPasteTimeStamp - clipboardDelay) < 1000;
+ var plainTextMode = self.pasteFormat == "text" || keyboardPastePlainTextState;
+ var internal = hasContentType(clipboardContent, InternalHtml.internalHtmlMime());
+
+ keyboardPastePlainTextState = false;
+
+ if (e.isDefaultPrevented() || isBrokenAndroidClipboardEvent(e)) {
+ removePasteBin();
+ return;
+ }
+
+ if (!hasHtmlOrText(clipboardContent) && pasteImageData(e, getLastRng())) {
+ removePasteBin();
+ return;
+ }
+
+ // Not a keyboard paste prevent default paste and try to grab the clipboard contents using different APIs
+ if (!isKeyBoardPaste) {
+ e.preventDefault();
+ }
+
+ // Try IE only method if paste isn't a keyboard paste
+ if (Env.ie && (!isKeyBoardPaste || e.ieFake) && !hasContentType(clipboardContent, 'text/html')) {
+ createPasteBin();
+
+ editor.dom.bind(pasteBinElm, 'paste', function (e) {
+ e.stopPropagation();
+ });
+
+ editor.getDoc().execCommand('Paste', false, null);
+ clipboardContent["text/html"] = getPasteBinHtml();
+ }
+
+ // If clipboard API has HTML then use that directly
+ if (hasContentType(clipboardContent, 'text/html')) {
+ e.preventDefault();
+ insertClipboardContent(clipboardContent, isKeyBoardPaste, plainTextMode, internal);
+ } else {
+ Delay.setEditorTimeout(editor, function () {
+ insertClipboardContent(clipboardContent, isKeyBoardPaste, plainTextMode, internal);
+ }, 0);
+ }
+ });
+
+ editor.on('dragstart dragend', function (e) {
+ draggingInternally = e.type == 'dragstart';
+ });
+
+ function isPlainTextFileUrl(content) {
+ var plainTextContent = content['text/plain'];
+ return plainTextContent ? plainTextContent.indexOf('file://') === 0 : false;
+ }
+
+ editor.on('drop', function (e) {
+ var dropContent, rng;
+
+ rng = getCaretRangeFromEvent(e);
+
+ if (e.isDefaultPrevented() || draggingInternally) {
+ return;
+ }
+
+ dropContent = getDataTransferItems(e.dataTransfer);
+ var internal = hasContentType(dropContent, InternalHtml.internalHtmlMime());
+
+ if ((!hasHtmlOrText(dropContent) || isPlainTextFileUrl(dropContent)) && pasteImageData(e, rng)) {
+ return;
+ }
+
+ if (rng && editor.settings.paste_filter_drop !== false) {
+ var content = dropContent['mce-internal'] || dropContent['text/html'] || dropContent['text/plain'];
+
+ if (content) {
+ e.preventDefault();
+
+ // FF 45 doesn't paint a caret when dragging in text in due to focus call by execCommand
+ Delay.setEditorTimeout(editor, function () {
+ editor.undoManager.transact(function () {
+ if (dropContent['mce-internal']) {
+ editor.execCommand('Delete');
+ }
+
+ editor.selection.setRng(rng);
+
+ content = Utils.trimHtml(content);
+
+ if (!dropContent['text/html']) {
+ pasteText(content);
+ } else {
+ pasteHtml(content, internal);
+ }
+ });
+ });
+ }
+ }
+ });
+
+ editor.on('dragover dragend', function (e) {
+ if (editor.settings.paste_data_images) {
+ e.preventDefault();
+ }
+ });
+ }
+
+ self.pasteHtml = pasteHtml;
+ self.pasteText = pasteText;
+ self.pasteImageData = pasteImageData;
+
+ editor.on('preInit', function () {
+ registerEventHandlers();
+
+ // 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;
+ }
+
+ function remove(node) {
+ if (!node.attr('data-mce-object') && src !== Env.transparentSrc) {
+ node.remove();
+ }
+ }
+
+ function isWebKitFakeUrl(src) {
+ return src.indexOf("webkit-fake-url") === 0;
+ }
+
+ function isDataUri(src) {
+ return src.indexOf("data:") === 0;
+ }
+
+ if (!editor.settings.paste_data_images && isPasteInsert(args)) {
+ var i = nodes.length;
+
+ while (i--) {
+ var src = nodes[i].attributes.map.src;
+
+ if (!src) {
+ continue;
+ }
+
+ // 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]);
+ }
+ }
+ }
+ });
+ });
+ };
+ }
+);
+
+/**
+ * ResolveGlobal.js
+ *
+ * Released under LGPL License.
+ * Copyright (c) 1999-2017 Ephox Corp. All rights reserved
+ *
+ * License: http://www.tinymce.com/license
+ * Contributing: http://www.tinymce.com/contributing
+ */
+
+define(
+ 'tinymce.core.html.Serializer',
+ [
+ 'global!tinymce.util.Tools.resolve'
+ ],
+ function (resolve) {
+ return resolve('tinymce.html.Serializer');
+ }
+);
+
+/**
+ * ResolveGlobal.js
+ *
+ * Released under LGPL License.
+ * Copyright (c) 1999-2017 Ephox Corp. All rights reserved
+ *
+ * License: http://www.tinymce.com/license
+ * Contributing: http://www.tinymce.com/contributing
+ */
+
+define(
+ 'tinymce.core.html.Node',
+ [
+ 'global!tinymce.util.Tools.resolve'
+ ],
+ function (resolve) {
+ return resolve('tinymce.html.Node');
+ }
+);
/**
* WordFilter.js
*
* Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
+ * Copyright (c) 1999-2017 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
@@ -1061,492 +1528,494 @@ define("tinymce/pasteplugin/Clipboard", [
* @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 (
- (/]+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;
-
- function getText(node) {
- var txt = '';
-
- if (node.type === 3) {
- return node.value;
- }
-
- if ((node = node.firstChild)) {
- do {
- txt += getText(node);
- } while ((node = node.next));
- }
-
- return txt;
- }
-
- function trimListStart(node, regExp) {
- if (node.type === 3) {
- if (regExp.test(node.value)) {
- node.value = node.value.replace(regExp, '');
- return false;
- }
- }
-
- if ((node = node.firstChild)) {
- do {
- if (!trimListStart(node, regExp)) {
- return false;
- }
- } while ((node = node.next));
- }
-
- return true;
- }
-
- function removeIgnoredNodes(node) {
- if (node._listIgnore) {
- node.remove();
- return;
- }
-
- if ((node = node.firstChild)) {
- do {
- removeIgnoredNodes(node);
- } while ((node = node.next));
- }
- }
-
- 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+/);
- }
-
- // 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);
-
- child = child.walk();
- if (child !== null) {
- while (typeof child !== 'undefined' && child.parent !== node) {
- child = child.walk();
- }
- }
- }
-
- 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;
- }
- }
- }
-
- 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;
-
- case "horiz-align":
- name = "text-align";
- break;
-
- case "vert-align":
- name = "vertical-align";
- break;
-
- case "font-color":
- case "mso-foreground":
- name = "color";
- break;
-
- case "mso-background":
- case "mso-highlight":
- name = "background";
- break;
-
- case "font-weight":
- case "font-style":
- if (value != "normal") {
- outputStyles[name] = value;
- }
- return;
-
- case "mso-element":
- // Remove track changes code
- if (/^(comment|comment-list)$/i.test(value)) {
- node.remove();
- return;
- }
-
- break;
- }
-
- if (name.indexOf('mso-comment') === 0) {
- node.remove();
- return;
- }
-
- // Never allow mso- prefixed names
- if (name.indexOf('mso-') === 0) {
- return;
- }
-
- // Output only valid styles
- if (retainStyleProperties == "all" || (validStyles && validStyles[name])) {
- outputStyles[name] = value;
- }
- });
-
- // Convert bold style to "b" element
- if (/(bold)/i.test(outputStyles["font-weight"])) {
- delete outputStyles["font-weight"];
- node.wrap(new Node("b", 1));
- }
-
- // Convert italic style to "i" element
- if (/(italic)/i.test(outputStyles["font-style"])) {
- delete outputStyles["font-style"];
- node.wrap(new Node("i", 1));
- }
-
- // Serialize the styles and see if there is something left to keep
- outputStyles = editor.dom.serializeStyle(outputStyles, node.name);
- if (outputStyles) {
- return outputStyles;
- }
-
- return null;
- }
-
- if (settings.paste_enable_default_filters === false) {
- return;
- }
-
- // Detect is the contents is Word junk HTML
- if (isWordContent(e.content)) {
- e.wordContent = true; // Mark it for other processors
-
- // Remove basic Word junk
- content = Utils.filter(content, [
- // Word comments like conditional comments etc
- //gi,
-
- // 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,
-
- // Convert into for line-though
- [/<(\/?)s>/gi, "<$1strike>"],
-
- // Replace nsbp entites to char since it's easier to handle
- [/ /gi, "\u00a0"],
-
- // 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") : "";
- }
- ]
- ]);
-
- 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]'
- });
-
- // 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 (!rule.attributes.style) {
- rule.attributes.style = {};
- rule.attributesOrder.push("style");
- }
- });
-
- // Parse HTML into DOM structure
- var domParser = new DomParser({}, schema);
-
- // Filter styles to remove "mso" specific styles and convert some of them
- domParser.addAttributeFilter('style', function(nodes) {
- var i = nodes.length, node;
-
- while (i--) {
- node = nodes[i];
- node.attr('style', filterStyles(node, node.attr('style')));
-
- // Remove pointess spans
- if (node.name == 'span' && node.parent && !node.attributes.length) {
- node.unwrap();
- }
- }
- });
-
- // Check the class attribute for comments or del items and remove those
- domParser.addAttributeFilter('class', function(nodes) {
- var i = nodes.length, node, className;
-
- while (i--) {
- node = nodes[i];
-
- className = node.attr('class');
- if (/^(MsoCommentReference|MsoCommentText|msoDel)$/i.test(className)) {
- node.remove();
- }
-
- node.attr('class', null);
- }
- });
-
- // 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;
-
- while (i--) {
- nodes[i].remove();
- }
- });
-
- // Keep some of the links and anchors
- domParser.addNodeFilter('a', function(nodes) {
- var i = nodes.length, node, href, name;
-
- while (i--) {
- node = nodes[i];
- href = node.attr('href');
- name = node.attr('name');
-
- if (href && href.indexOf('#_msocom_') != -1) {
- node.remove();
- continue;
- }
-
- if (href && href.indexOf('file://') === 0) {
- href = href.split('#')[1];
- if (href) {
- href = '#' + href;
- }
- }
-
- 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;
- }
-
- node.attr({
- href: href,
- name: name
- });
- }
- }
- });
-
- // Parse into DOM structure
- var rootNode = domParser.parse(content);
-
- // Process DOM
- if (settings.paste_convert_word_fake_lists !== false) {
- convertFakeListsToProperLists(rootNode);
- }
-
- // Serialize DOM back to HTML
- e.content = new Serializer({
- validate: settings.validate
- }, schema).serialize(rootNode);
- }
- });
- }
-
- WordFilter.isWordContent = isWordContent;
-
- return WordFilter;
-});
-
-// Included from: js/tinymce/plugins/paste/classes/Quirks.js
+define(
+ 'tinymce.plugins.paste.core.WordFilter',
+ [
+ 'tinymce.core.util.Tools',
+ 'tinymce.core.html.DomParser',
+ 'tinymce.core.html.Schema',
+ 'tinymce.core.html.Serializer',
+ 'tinymce.core.html.Node',
+ 'tinymce.plugins.paste.core.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;
+
+ function getText(node) {
+ var txt = '';
+
+ if (node.type === 3) {
+ return node.value;
+ }
+
+ if ((node = node.firstChild)) {
+ do {
+ txt += getText(node);
+ } while ((node = node.next));
+ }
+
+ return txt;
+ }
+
+ function trimListStart(node, regExp) {
+ if (node.type === 3) {
+ if (regExp.test(node.value)) {
+ node.value = node.value.replace(regExp, '');
+ return false;
+ }
+ }
+
+ if ((node = node.firstChild)) {
+ do {
+ if (!trimListStart(node, regExp)) {
+ return false;
+ }
+ } while ((node = node.next));
+ }
+
+ return true;
+ }
+
+ function removeIgnoredNodes(node) {
+ if (node._listIgnore) {
+ node.remove();
+ return;
+ }
+
+ if ((node = node.firstChild)) {
+ do {
+ removeIgnoredNodes(node);
+ } while ((node = node.next));
+ }
+ }
+
+ 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+/);
+ }
+
+ // 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);
+
+ child = child.walk();
+ if (child !== null) {
+ while (typeof child !== 'undefined' && child.parent !== node) {
+ child = child.walk();
+ }
+ }
+ }
+
+ 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;
+ }
+ }
+ }
+
+ 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;
+
+ case "horiz-align":
+ name = "text-align";
+ break;
+
+ case "vert-align":
+ name = "vertical-align";
+ break;
+
+ case "font-color":
+ case "mso-foreground":
+ name = "color";
+ break;
+
+ case "mso-background":
+ case "mso-highlight":
+ name = "background";
+ break;
+
+ case "font-weight":
+ case "font-style":
+ if (value != "normal") {
+ outputStyles[name] = value;
+ }
+ return;
+
+ case "mso-element":
+ // Remove track changes code
+ if (/^(comment|comment-list)$/i.test(value)) {
+ node.remove();
+ return;
+ }
+
+ break;
+ }
+
+ if (name.indexOf('mso-comment') === 0) {
+ node.remove();
+ return;
+ }
+
+ // Never allow mso- prefixed names
+ if (name.indexOf('mso-') === 0) {
+ return;
+ }
+
+ // Output only valid styles
+ if (retainStyleProperties == "all" || (validStyles && validStyles[name])) {
+ outputStyles[name] = value;
+ }
+ });
+
+ // Convert bold style to "b" element
+ if (/(bold)/i.test(outputStyles["font-weight"])) {
+ delete outputStyles["font-weight"];
+ node.wrap(new Node("b", 1));
+ }
+
+ // Convert italic style to "i" element
+ if (/(italic)/i.test(outputStyles["font-style"])) {
+ delete outputStyles["font-style"];
+ node.wrap(new Node("i", 1));
+ }
+
+ // Serialize the styles and see if there is something left to keep
+ outputStyles = editor.dom.serializeStyle(outputStyles, node.name);
+ if (outputStyles) {
+ return outputStyles;
+ }
+
+ return null;
+ }
+
+ if (settings.paste_enable_default_filters === false) {
+ return;
+ }
+
+ // Detect is the contents is Word junk HTML
+ if (isWordContent(e.content)) {
+ e.wordContent = true; // Mark it for other processors
+
+ // Remove basic Word junk
+ content = Utils.filter(content, [
+ // Word comments like conditional comments etc
+ //gi,
+
+ // 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,
+
+ // Convert into for line-though
+ [/<(\/?)s>/gi, "<$1strike>"],
+
+ // Replace nsbp entites to char since it's easier to handle
+ [/ /gi, "\u00a0"],
+
+ // 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") : "";
+ }
+ ]
+ ]);
+
+ 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]'
+ });
+
+ // 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 (!rule.attributes.style) {
+ rule.attributes.style = {};
+ rule.attributesOrder.push("style");
+ }
+ });
+
+ // Parse HTML into DOM structure
+ var domParser = new DomParser({}, schema);
+
+ // Filter styles to remove "mso" specific styles and convert some of them
+ domParser.addAttributeFilter('style', function (nodes) {
+ var i = nodes.length, node;
+
+ while (i--) {
+ node = nodes[i];
+ node.attr('style', filterStyles(node, node.attr('style')));
+
+ // Remove pointess spans
+ if (node.name == 'span' && node.parent && !node.attributes.length) {
+ node.unwrap();
+ }
+ }
+ });
+
+ // Check the class attribute for comments or del items and remove those
+ domParser.addAttributeFilter('class', function (nodes) {
+ var i = nodes.length, node, className;
+
+ while (i--) {
+ node = nodes[i];
+
+ className = node.attr('class');
+ if (/^(MsoCommentReference|MsoCommentText|msoDel)$/i.test(className)) {
+ node.remove();
+ }
+
+ node.attr('class', null);
+ }
+ });
+
+ // 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;
+
+ while (i--) {
+ nodes[i].remove();
+ }
+ });
+
+ // Keep some of the links and anchors
+ domParser.addNodeFilter('a', function (nodes) {
+ var i = nodes.length, node, href, name;
+
+ while (i--) {
+ node = nodes[i];
+ href = node.attr('href');
+ name = node.attr('name');
+
+ if (href && href.indexOf('#_msocom_') != -1) {
+ node.remove();
+ continue;
+ }
+
+ if (href && href.indexOf('file://') === 0) {
+ href = href.split('#')[1];
+ if (href) {
+ href = '#' + href;
+ }
+ }
+
+ 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;
+ }
+
+ node.attr({
+ href: href,
+ name: name
+ });
+ }
+ }
+ });
+
+ // Parse into DOM structure
+ var rootNode = domParser.parse(content);
+
+ // Process DOM
+ if (settings.paste_convert_word_fake_lists !== false) {
+ convertFakeListsToProperLists(rootNode);
+ }
+
+ // Serialize DOM back to HTML
+ e.content = new Serializer({
+ validate: settings.validate
+ }, schema).serialize(rootNode);
+ }
+ });
+ }
+
+ WordFilter.isWordContent = isWordContent;
+
+ return WordFilter;
+ }
+);
/**
* Quirks.js
@@ -1566,304 +2035,313 @@ define("tinymce/pasteplugin/WordFilter", [
* @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";
+define(
+ 'tinymce.plugins.paste.core.Quirks',
+ [
+ 'tinymce.core.Env',
+ 'tinymce.core.util.Tools',
+ 'tinymce.plugins.paste.core.WordFilter',
+ 'tinymce.plugins.paste.core.Utils'
+ ],
+ function (Env, Tools, WordFilter, Utils) {
+ "use strict";
- return function(editor) {
- function addPreProcessFilter(filterFunc) {
- editor.on('BeforePastePreProcess', function(e) {
- e.content = filterFunc(e.content);
- });
- }
+ return function (editor) {
+ function addPreProcessFilter(filterFunc) {
+ editor.on('BeforePastePreProcess', function (e) {
+ e.content = filterFunc(e.content);
+ });
+ }
- function addPostProcessFilter(filterFunc) {
- editor.on('PastePostProcess', function(e) {
- filterFunc(e.node);
- });
- }
+ function addPostProcessFilter(filterFunc) {
+ editor.on('PastePostProcess', function (e) {
+ filterFunc(e.node);
+ });
+ }
- /**
- * 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:
- *
[\\s\\r\\n]+|
)*(<\\/?(' + blockElements.join('|') + ')[^>]*>)(?:
[\\s\\r\\n]+|
)*',
- 'g'
- );
+ var explorerBlocksRegExp = new RegExp(
+ '(?:
[\\s\\r\\n]+|
)*(<\\/?(' + blockElements.join('|') + ')[^>]*>)(?:
[\\s\\r\\n]+|
)*',
+ 'g'
+ );
- // Remove BR:s from:
- html = Utils.filter(html, [
- [explorerBlocksRegExp, '$1']
- ]);
+ // Remove BR:s from:
+ html = Utils.filter(html, [
+ [explorerBlocksRegExp, '$1']
+ ]);
- // 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
- ]);
+ // 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
+ ]);
- return html;
- }
+ return html;
+ }
- /**
- * 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;
- }
+ /**
+ * 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;
+ }
- // Filter away styles that isn't matching the target node
- var webKitStyles = editor.settings.paste_webkit_styles;
+ // Filter away styles that isn't matching the target node
+ var webKitStyles = editor.settings.paste_webkit_styles;
- if (editor.settings.paste_remove_styles_if_webkit === false || webKitStyles == "all") {
- return content;
- }
+ if (editor.settings.paste_remove_styles_if_webkit === false || webKitStyles == "all") {
+ return content;
+ }
- if (webKitStyles) {
- webKitStyles = webKitStyles.split(/[, ]/);
- }
+ if (webKitStyles) {
+ webKitStyles = webKitStyles.split(/[, ]/);
+ }
- // Keep specific styles that doesn't match the current node computed style
- if (webKitStyles) {
- var dom = editor.dom, node = editor.selection.getNode();
+ // Keep specific styles that doesn't match the current node computed style
+ if (webKitStyles) {
+ var dom = editor.dom, node = editor.selection.getNode();
- content = content.replace(/(<[^>]+) style="([^"]*)"([^>]*>)/gi, function(all, before, value, after) {
- var inputStyles = dom.parseStyle(value, 'span'), outputStyles = {};
+ content = content.replace(/(<[^>]+) style="([^"]*)"([^>]*>)/gi, function (all, before, value, after) {
+ var inputStyles = dom.parseStyle(value, 'span'), outputStyles = {};
- if (webKitStyles === "none") {
- return before + after;
- }
+ if (webKitStyles === "none") {
+ return before + after;
+ }
- for (var i = 0; i < webKitStyles.length; i++) {
- var inputValue = inputStyles[webKitStyles[i]], currentValue = dom.getStyle(node, webKitStyles[i], true);
+ for (var i = 0; i < webKitStyles.length; i++) {
+ var inputValue = inputStyles[webKitStyles[i]], currentValue = dom.getStyle(node, webKitStyles[i], true);
- if (/color/.test(webKitStyles[i])) {
- inputValue = dom.toHex(inputValue);
- currentValue = dom.toHex(currentValue);
- }
+ if (/color/.test(webKitStyles[i])) {
+ inputValue = dom.toHex(inputValue);
+ currentValue = dom.toHex(currentValue);
+ }
- if (currentValue != inputValue) {
- outputStyles[webKitStyles[i]] = inputValue;
- }
- }
+ if (currentValue != inputValue) {
+ outputStyles[webKitStyles[i]] = inputValue;
+ }
+ }
- outputStyles = dom.serializeStyle(outputStyles, 'span');
- if (outputStyles) {
- return before + ' style="' + outputStyles + '"' + after;
- }
+ outputStyles = dom.serializeStyle(outputStyles, 'span');
+ if (outputStyles) {
+ return before + ' style="' + outputStyles + '"' + after;
+ }
- return before + after;
- });
- } else {
- // Remove all external styles
- content = content.replace(/(<[^>]+) style="([^"]*)"([^>]*>)/gi, '$1$3');
- }
+ return before + after;
+ });
+ } else {
+ // Remove all external styles
+ content = content.replace(/(<[^>]+) style="([^"]*)"([^>]*>)/gi, '$1$3');
+ }
- // Keep internal styles
- content = content.replace(/(<[^>]+) data-mce-style="([^"]+)"([^>]*>)/gi, function(all, before, value, after) {
- return before + ' style="' + value + '"' + after;
- });
+ // Keep internal styles
+ content = content.replace(/(<[^>]+) data-mce-style="([^"]+)"([^>]*>)/gi, function (all, before, value, after) {
+ return before + ' style="' + value + '"' + after;
+ });
- return content;
- }
+ return content;
+ }
- function removeUnderlineAndFontInAnchor(root) {
- editor.$('a', root).find('font,u').each(function(i, node) {
- editor.dom.remove(node, true);
- });
- }
+ function removeUnderlineAndFontInAnchor(root) {
+ editor.$('a', root).find('font,u').each(function (i, node) {
+ editor.dom.remove(node, true);
+ });
+ }
- // Sniff browsers and apply fixes since we can't feature detect
- if (Env.webkit) {
- addPreProcessFilter(removeWebKitStyles);
- }
-
- if (Env.ie) {
- addPreProcessFilter(removeExplorerBrElementsAfterBlocks);
- addPostProcessFilter(removeUnderlineAndFontInAnchor);
- }
- };
-});
-
-// Included from: js/tinymce/plugins/paste/classes/Plugin.js
+ // Sniff browsers and apply fixes since we can't feature detect
+ if (Env.webkit) {
+ addPreProcessFilter(removeWebKitStyles);
+ }
+ if (Env.ie) {
+ addPreProcessFilter(removeExplorerBrElementsAfterBlocks);
+ addPostProcessFilter(removeUnderlineAndFontInAnchor);
+ }
+ };
+ }
+);
/**
* Plugin.js
*
* Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
+ * Copyright (c) 1999-2017 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
/**
- * This class contains the tinymce plugin logic for the paste plugin.
+ * This class contains all core logic for the paste plugin.
*
- * @class tinymce.pasteplugin.Plugin
+ * @class tinymce.paste.Plugin
* @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.plugins.paste.Plugin',
+ [
+ 'tinymce.core.PluginManager',
+ 'tinymce.plugins.paste.core.Clipboard',
+ 'tinymce.plugins.paste.core.CutCopy',
+ 'tinymce.plugins.paste.core.Quirks',
+ 'tinymce.plugins.paste.core.WordFilter'
+ ],
+ function (PluginManager, Clipboard, CutCopy, Quirks, WordFilter) {
+ var userIsInformed;
- PluginManager.add('paste', function(editor) {
- var self = this, clipboard, settings = editor.settings;
+ PluginManager.add('paste', function (editor) {
+ var self = this, clipboard, settings = editor.settings;
- function isUserInformedAboutPlainText() {
- return userIsInformed || editor.settings.paste_plaintext_inform === false;
- }
+ function isUserInformedAboutPlainText() {
+ return userIsInformed || editor.settings.paste_plaintext_inform === false;
+ }
- function togglePlainTextPaste() {
- if (clipboard.pasteFormat == "text") {
- clipboard.pasteFormat = "html";
- editor.fire('PastePlainTextToggle', {state: false});
- } else {
- clipboard.pasteFormat = "text";
- editor.fire('PastePlainTextToggle', {state: true});
+ function togglePlainTextPaste() {
+ if (clipboard.pasteFormat == "text") {
+ clipboard.pasteFormat = "html";
+ editor.fire('PastePlainTextToggle', { state: false });
+ } else {
+ clipboard.pasteFormat = "text";
+ editor.fire('PastePlainTextToggle', { state: true });
- if (!isUserInformedAboutPlainText()) {
- var message = editor.translate('Paste is now in plain text mode. Contents will now ' +
- 'be pasted as plain text until you toggle this option off.');
+ if (!isUserInformedAboutPlainText()) {
+ var message = editor.translate('Paste is now in plain text mode. Contents will now ' +
+ 'be pasted as plain text until you toggle this option off.');
- editor.notificationManager.open({
- text: message,
- type: 'info'
- });
+ editor.notificationManager.open({
+ text: message,
+ type: 'info'
+ });
- userIsInformed = true;
- }
- }
+ userIsInformed = true;
+ }
+ }
- editor.focus();
- }
+ editor.focus();
+ }
- function stateChange() {
- var self = this;
+ function stateChange() {
+ var self = this;
- self.active(clipboard.pasteFormat === 'text');
+ self.active(clipboard.pasteFormat === 'text');
- editor.on('PastePlainTextToggle', function (e) {
- self.active(e.state);
- });
- }
+ editor.on('PastePlainTextToggle', function (e) {
+ self.active(e.state);
+ });
+ }
- // draw back if power version is requested and registered
- if (/(^|[ ,])powerpaste([, ]|$)/.test(settings.plugins) && PluginManager.get('powerpaste')) {
- /*eslint no-console:0 */
- if (typeof console !== "undefined" && console.log) {
- console.log("PowerPaste is incompatible with Paste plugin! Remove 'paste' from the 'plugins' option.");
- }
- return;
- }
+ // draw back if power version is requested and registered
+ if (/(^|[ ,])powerpaste([, ]|$)/.test(settings.plugins) && PluginManager.get('powerpaste')) {
+ /*eslint no-console:0 */
+ if (typeof console !== "undefined" && console.log) {
+ console.log("PowerPaste is incompatible with Paste plugin! Remove 'paste' from the 'plugins' option.");
+ }
+ return;
+ }
- self.clipboard = clipboard = new Clipboard(editor);
- self.quirks = new Quirks(editor);
- self.wordFilter = new WordFilter(editor);
+ 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 (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);
- });
- }
+ if (settings.paste_preprocess) {
+ editor.on('PastePreProcess', function (e) {
+ settings.paste_preprocess.call(self, self, e);
+ });
+ }
- if (settings.paste_postprocess) {
- editor.on('PastePostProcess', function(e) {
- settings.paste_postprocess.call(self, self, e);
- });
- }
+ if (settings.paste_postprocess) {
+ editor.on('PastePostProcess', function (e) {
+ settings.paste_postprocess.call(self, self, e);
+ });
+ }
- editor.addCommand('mceInsertClipboardContent', function(ui, value) {
- if (value.content) {
- self.clipboard.pasteHtml(value.content);
- }
+ editor.addCommand('mceInsertClipboardContent', function (ui, value) {
+ if (value.content) {
+ self.clipboard.pasteHtml(value.content, value.internal);
+ }
- if (value.text) {
- self.clipboard.pasteText(value.text);
- }
- });
+ if (value.text) {
+ self.clipboard.pasteText(value.text);
+ }
+ });
- // Block all drag/drop events
- if (editor.settings.paste_block_drop) {
- editor.on('dragend dragover draggesture dragdrop drop drag', function(e) {
- e.preventDefault();
- e.stopPropagation();
- });
- }
+ // Block all drag/drop events
+ if (editor.settings.paste_block_drop) {
+ editor.on('dragend dragover draggesture dragdrop drop drag', function (e) {
+ e.preventDefault();
+ e.stopPropagation();
+ });
+ }
- // Prevent users from dropping data images on Gecko
- if (!editor.settings.paste_data_images) {
- editor.on('drop', function(e) {
- var dataTransfer = e.dataTransfer;
+ // 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();
- }
- });
- }
+ if (dataTransfer && dataTransfer.files && dataTransfer.files.length > 0) {
+ e.preventDefault();
+ }
+ });
+ }
- editor.addCommand('mceTogglePlainTextPaste', togglePlainTextPaste);
+ editor.addCommand('mceTogglePlainTextPaste', togglePlainTextPaste);
- editor.addButton('pastetext', {
- icon: 'pastetext',
- tooltip: 'Paste as text',
- onclick: togglePlainTextPaste,
- onPostRender: stateChange
- });
+ editor.addButton('pastetext', {
+ icon: 'pastetext',
+ tooltip: 'Paste as text',
+ onclick: togglePlainTextPaste,
+ onPostRender: stateChange
+ });
- editor.addMenuItem('pastetext', {
- text: 'Paste as text',
- selectable: true,
- active: clipboard.pasteFormat,
- onclick: togglePlainTextPaste,
- onPostRender: stateChange
- });
- });
-});
+ editor.addMenuItem('pastetext', {
+ text: 'Paste as text',
+ selectable: true,
+ active: clipboard.pasteFormat,
+ onclick: togglePlainTextPaste,
+ onPostRender: stateChange
+ });
-expose(["tinymce/pasteplugin/Utils"]);
-})(window);
\ No newline at end of file
+ CutCopy.register(editor);
+ });
+
+ return function () { };
+ }
+);
+dem('tinymce.plugins.paste.Plugin')();
+})();
diff --git a/src/wp-includes/js/tinymce/plugins/paste/plugin.min.js b/src/wp-includes/js/tinymce/plugins/paste/plugin.min.js
index 7cefb4ab50..3a5859558b 100644
--- a/src/wp-includes/js/tinymce/plugins/paste/plugin.min.js
+++ b/src/wp-includes/js/tinymce/plugins/paste/plugin.min.js
@@ -1 +1 @@
-!function(e,t){"use strict";function n(e,t){for(var n,r=[],o=0;o
/g,/
$/i])}function a(e){var t=0;return function(){return e+t++}}return{filter:r,innerText:i,trimHtml:o,createIdGenerator:a}}),r("tinymce/pasteplugin/SmartPaste",["tinymce/util/Tools"],function(e){var t=function(e){return/^https?:\/\/[\w\?\-\/+=.&%@~#]+$/i.test(e)},n=function(e){return t(e)&&/.(gif|jpe?g|png)$/.test(e)},r=function(e,t,n){return e.undoManager.extra(function(){n(e,t)},function(){e.insertContent('')}),!0},i=function(e,t,n){return e.undoManager.extra(function(){n(e,t)},function(){e.execCommand("mceInsertLink",!1,t)}),!0},o=function(e,n,r){return!(e.selection.isCollapsed()!==!1||!t(n))&&i(e,n,r)},a=function(e,t,i){return!!n(t)&&r(e,t,i)},s=function(e,t){return e.insertContent(t,{merge:e.settings.paste_merge_formats!==!1,paste:!0}),!0},l=function(t,n){e.each([o,a,s],function(e){return e(t,n,s)!==!0})},u=function(e,t){e.settings.smart_paste===!1?s(e,t):l(e,t)};return{isImageUrl:n,isAbsoluteUrl:t,insertContent:u}}),r("tinymce/pasteplugin/Clipboard",["tinymce/Env","tinymce/dom/RangeUtils","tinymce/util/VK","tinymce/pasteplugin/Utils","tinymce/pasteplugin/SmartPaste","tinymce/util/Delay"],function(e,t,n,r,i,o){return function(a){function s(e){var t,n=a.dom;if(t=a.fire("BeforePastePreProcess",{content:e}),t=a.fire("PastePreProcess",t),e=t.content,!t.isDefaultPrevented()){if(a.hasEventListeners("PastePostProcess")&&!t.isDefaultPrevented()){var r=n.add(a.getBody(),"div",{style:"display:none"},e);t=a.fire("PastePostProcess",{node:r}),n.remove(r),e=t.node.innerHTML}t.isDefaultPrevented()||i.insertContent(a,e)}}function l(e){e=a.dom.encode(e).replace(/\r\n/g,"\n");var t,n=a.dom.getParent(a.selection.getStart(),a.dom.isBlock),i=a.settings.forced_root_block;i&&(t=a.dom.createHTML(i,a.settings.forced_root_block_attrs),t=t.substr(0,t.length-3)+">"),n&&/^(PRE|DIV)$/.test(n.nodeName)||!i?e=r.filter(e,[[/\n/g,"
"]]):(e=r.filter(e,[[/\n\n/g,""+t],[/^(.*<\/p>)(
"]]),e.indexOf("')}function y(e,t){function n(n){var r,i,o,a=!1;if(n)for(r=0;r
/gi,""),h=c.paste_retain_style_properties,h&&(m=e.makeMap(h.split(/[, ]/))),c.paste_enable_default_filters!==!1&&a(d.content)){d.wordContent=!0,g=o.filter(g,[//gi,/<(!|script[^>]*>.*?<\/script(?=[>\s])|\/?(\?xml(:\w+)?|img|meta|link|style|\w:\w+)(?=[\s\/>]))[^>]*>/gi,[/<(\/?)s>/gi,"<$1strike>"],[/ /gi,"\xa0"],[/([\s\u00a0]*)<\/span>/gi,function(e,t){return t.length>0?t.replace(/./," ").slice(Math.floor(t.length/2)).split("").join("\xa0"):""}]]);var v=c.paste_word_valid_elements;v||(v="-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");var y=new n({valid_elements:v,valid_children:"-li[p]"});e.each(y.elements,function(e){e.attributes["class"]||(e.attributes["class"]={},e.attributesOrder.push("class")),e.attributes.style||(e.attributes.style={},e.attributesOrder.push("style"))});var b=new t({},y);b.addAttributeFilter("style",function(e){for(var t,n=e.length;n--;)t=e[n],t.attr("style",p(t,t.attr("style"))),"span"==t.name&&t.parent&&!t.attributes.length&&t.unwrap()}),b.addAttributeFilter("class",function(e){for(var t,n,r=e.length;r--;)t=e[r],n=t.attr("class"),/^(MsoCommentReference|MsoCommentText|msoDel)$/i.test(n)&&t.remove(),t.attr("class",null)}),b.addNodeFilter("del",function(e){for(var t=e.length;t--;)e[t].remove()}),b.addNodeFilter("a",function(e){for(var t,n,r,i=e.length;i--;)if(t=e[i],n=t.attr("href"),r=t.attr("name"),n&&n.indexOf("#_msocom_")!=-1)t.remove();else if(n&&0===n.indexOf("file://")&&(n=n.split("#")[1],n&&(n="#"+n)),n||r){if(r&&!/^_?(?:toc|edn|ftn)/i.test(r)){t.unwrap();continue}t.attr({href:n,name:r})}else t.unwrap()});var C=b.parse(g);c.paste_convert_word_fake_lists!==!1&&f(C),d.content=new r({validate:c.validate},y).serialize(C)}})}return u.isWordContent=a,u}),r("tinymce/pasteplugin/Quirks",["tinymce/Env","tinymce/util/Tools","tinymce/pasteplugin/WordFilter","tinymce/pasteplugin/Utils"],function(e,t,n,r){return function(i){function o(e){i.on("BeforePastePreProcess",function(t){t.content=e(t.content)})}function a(e){i.on("PastePostProcess",function(t){e(t.node)})}function s(e){if(!n.isWordContent(e))return e;var o=[];t.each(i.schema.getBlockElements(),function(e,t){o.push(t)});var a=new RegExp("(?:
[\\s\\r\\n]+|
)*(<\\/?("+o.join("|")+")[^>]*>)(?:
[\\s\\r\\n]+|
)*","g");return e=r.filter(e,[[a,"$1"]]),e=r.filter(e,[[/
/g,"
"],[/
/g," "],[/
/g,"
"]])}function l(e){if(n.isWordContent(e))return e;var t=i.settings.paste_webkit_styles;if(i.settings.paste_remove_styles_if_webkit===!1||"all"==t)return e;if(t&&(t=t.split(/[, ]/)),t){var r=i.dom,o=i.selection.getNode();e=e.replace(/(<[^>]+) style="([^"]*)"([^>]*>)/gi,function(e,n,i,a){var s=r.parseStyle(i,"span"),l={};if("none"===t)return n+a;for(var u=0;u
/g,/
$/i])}function g(a){var b=0;return function(){return a+b++}}var h=function(){return navigator.userAgent.indexOf(" Edge/")!==-1};return{filter:d,innerText:e,trimHtml:f,createIdGenerator:g,isMsEdge:h}}),g("3",["8","c","d"],function(a,b,c){var d=function(){},e=function(b){return a.iOS===!1&&void 0!==b&&"function"==typeof b.setData&&c.isMsEdge()!==!0},f=function(a,c,d){if(!e(a))return!1;try{return a.clearData(),a.setData("text/html",c),a.setData("text/plain",d),a.setData(b.internalHtmlMime(),c),!0}catch(a){return!1}},g=function(a,b,c,d){f(a.clipboardData,b.html,b.text)?(a.preventDefault(),d()):c(b.html,d)},h=function(a){return function(c,d){var e=b.mark(c),f=a.dom.create("div",{contenteditable:"false"}),g=a.dom.create("div",{contenteditable:"true"},e);a.dom.setStyles(f,{position:"fixed",left:"-3000px",width:"1000px",overflow:"hidden"}),f.appendChild(g),a.dom.add(a.getBody(),f);var h=a.selection.getRng();g.focus();var i=a.dom.createRng();i.selectNodeContents(g),a.selection.setRng(i),setTimeout(function(){f.parentNode.removeChild(f),a.selection.setRng(h),d()},0)}},i=function(a){return{html:a.selection.getContent(),text:a.selection.getContent({format:"text"})}},j=function(a){return function(b){a.selection.isCollapsed()===!1&&g(b,i(a),h(a),function(){a.execCommand("Delete")})}},k=function(a){return function(b){a.selection.isCollapsed()===!1&&g(b,i(a),h(a),d)}},l=function(a){a.on("cut",j(a)),a.on("copy",k(a))};return{register:l}}),g("k",["6"],function(a){return a("tinymce.html.Entities")}),g("e",["k"],function(a){var b=function(a){return!/<(?:(?!\/?(?:div|p|br))[^>]*|(?:div|p|br)\s+\w[^>]+)>/.test(a)},c=function(a){return a.replace(/\r?\n/g,"
")},d=function(b,c){var d,e=[],f="<"+b;if("object"==typeof c){for(d in c)c.hasOwnProperty(d)&&e.push(d+'="'+a.encodeAllRaw(c[d])+'"');e.length&&(f+=" "+e.join(" "))}return f+">"},e=function(a,b,c){var e,f,g,h=a.split(/\r?\n/),i=0,j=h.length,k=[],l=[],m=d(b,c),n=""+b+">";if(1===h.length)return a;for(;i')}),!0},e=function(a,b,c){return a.undoManager.extra(function(){c(a,b)},function(){a.execCommand("mceInsertLink",!1,b)}),!0},f=function(a,c,d){return!(a.selection.isCollapsed()!==!1||!b(c))&&e(a,c,d)},g=function(a,b,e){return!!c(b)&&d(a,b,e)},h=function(a,b){return a.insertContent(b,{merge:a.settings.paste_merge_formats!==!1,paste:!0}),!0},i=function(b,c){a.each([f,g,h],function(a){return a(b,c,h)!==!0})},j=function(a,b){a.settings.smart_paste===!1?h(a,b):i(a,b)};return{isImageUrl:c,isAbsoluteUrl:b,insertContent:j}}),g("2",["7","8","9","a","b","3","c","e","f","d"],function(a,b,c,d,e,f,g,h,i,j){return function(f){function k(a,b){var c,d,e=f.dom;if(d=b||g.isMarked(a),a=g.unmark(a),c=f.fire("BeforePastePreProcess",{content:a,internal:d}),c=f.fire("PastePreProcess",c),a=c.content,!c.isDefaultPrevented()){if(f.hasEventListeners("PastePostProcess")&&!c.isDefaultPrevented()){var h=e.add(f.getBody(),"div",{style:"display:none"},a);c=f.fire("PastePostProcess",{node:h,internal:d}),e.remove(h),a=c.node.innerHTML}c.isDefaultPrevented()||i.insertContent(f,a)}}function l(a){a=f.dom.encode(a).replace(/\r\n/g,"\n"),a=h.convert(a,f.settings.forced_root_block,f.settings.forced_root_block_attrs),k(a,!1)}function m(){function a(a){var b,c,e,f=a.startContainer;if(b=a.getClientRects(),b.length)return b[0];if(a.collapsed&&1==f.nodeType){for(e=f.childNodes[C.startOffset];e&&3==e.nodeType&&!e.data.length;)e=e.nextSibling;if(e)return"BR"==e.tagName&&(c=d.doc.createTextNode("\ufeff"),e.parentNode.insertBefore(c,e),a=d.createRng(),a.setStartBefore(c),a.setEndAfter(c),b=a.getClientRects(),d.remove(c)),b.length?b[0]:void 0}}var c,d=f.dom,e=f.getBody(),g=f.dom.getViewPort(f.getWin()),h=g.y,i=20;if(C=f.selection.getRng(),f.inline&&(c=f.selection.getScrollContainer(),c&&c.scrollTop>0&&(h=c.scrollTop)),C.getClientRects){var j=a(C);if(j)i=h+(j.top-d.getPos(e).y);else{i=h;var k=C.startContainer;k&&(3==k.nodeType&&k.parentNode!=e&&(k=k.parentNode),1==k.nodeType&&(i=d.getPos(k,c||e).y))}}B=d.add(f.getBody(),"div",{id:"mcepastebin",contentEditable:!0,"data-mce-bogus":"all",style:"position: absolute; top: "+i+"px;width: 10px; height: 10px; overflow: hidden; opacity: 0"},H),(b.ie||b.gecko)&&d.setStyle(B,"left","rtl"==d.getStyle(e,"direction",!0)?65535:-65535),d.bind(B,"beforedeactivate focusin focusout",function(a){a.stopPropagation()}),B.focus(),f.selection.select(B,!0)}function n(){if(B){for(var a;a=f.dom.get("mcepastebin");)f.dom.remove(a),f.dom.unbind(a);C&&f.selection.setRng(C)}B=C=null}function o(){var a,b,c,d,e="";for(a=f.dom.select("div[id=mcepastebin]"),b=0;b
',!1)}function v(a,b){function c(c){var d,e,f,g=!1;if(c)for(d=0;d
/gi,""),o=k.paste_retain_style_properties,o&&(p=a.makeMap(o.split(/[, ]/))),k.paste_enable_default_filters!==!1&&g(l.content)){l.wordContent=!0,q=f.filter(q,[//gi,/<(!|script[^>]*>.*?<\/script(?=[>\s])|\/?(\?xml(:\w+)?|img|meta|link|style|\w:\w+)(?=[\s\/>]))[^>]*>/gi,[/<(\/?)s>/gi,"<$1strike>"],[/ /gi,"\xa0"],[/([\s\u00a0]*)<\/span>/gi,function(a,b){return b.length>0?b.replace(/./," ").slice(Math.floor(b.length/2)).split("").join("\xa0"):""}]]);var r=k.paste_word_valid_elements;r||(r="-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");var s=new c({valid_elements:r,valid_children:"-li[p]"});a.each(s.elements,function(a){a.attributes["class"]||(a.attributes["class"]={},a.attributesOrder.push("class")),a.attributes.style||(a.attributes.style={},a.attributesOrder.push("style"))});var t=new b({},s);t.addAttributeFilter("style",function(a){for(var b,c=a.length;c--;)b=a[c],b.attr("style",n(b,b.attr("style"))),"span"==b.name&&b.parent&&!b.attributes.length&&b.unwrap()}),t.addAttributeFilter("class",function(a){for(var b,c,d=a.length;d--;)b=a[d],c=b.attr("class"),/^(MsoCommentReference|MsoCommentText|msoDel)$/i.test(c)&&b.remove(),b.attr("class",null)}),t.addNodeFilter("del",function(a){for(var b=a.length;b--;)a[b].remove()}),t.addNodeFilter("a",function(a){for(var b,c,d,e=a.length;e--;)if(b=a[e],c=b.attr("href"),d=b.attr("name"),c&&c.indexOf("#_msocom_")!=-1)b.remove();else if(c&&0===c.indexOf("file://")&&(c=c.split("#")[1],c&&(c="#"+c)),c||d){if(d&&!/^_?(?:toc|edn|ftn)/i.test(d)){b.unwrap();continue}b.attr({href:c,name:d})}else b.unwrap()});var u=t.parse(q);k.paste_convert_word_fake_lists!==!1&&m(u),l.content=new d({validate:k.validate},s).serialize(u)}})}return j.isWordContent=g,j}),g("4",["8","a","5","d"],function(a,b,c,d){"use strict";return function(e){function f(a){e.on("BeforePastePreProcess",function(b){b.content=a(b.content)})}function g(a){e.on("PastePostProcess",function(b){a(b.node)})}function h(a){if(!c.isWordContent(a))return a;var f=[];b.each(e.schema.getBlockElements(),function(a,b){f.push(b)});var g=new RegExp("(?:
[\\s\\r\\n]+|
)*(<\\/?("+f.join("|")+")[^>]*>)(?:
[\\s\\r\\n]+|
)*","g");return a=d.filter(a,[[g,"$1"]]),a=d.filter(a,[[/
/g,"
"],[/
/g," "],[/
/g,"
"]])}function i(a){if(c.isWordContent(a))return a;var b=e.settings.paste_webkit_styles;if(e.settings.paste_remove_styles_if_webkit===!1||"all"==b)return a;if(b&&(b=b.split(/[, ]/)),b){var d=e.dom,f=e.selection.getNode();a=a.replace(/(<[^>]+) style="([^"]*)"([^>]*>)/gi,function(a,c,e,g){var h=d.parseStyle(e,"span"),i={};if("none"===b)return c+g;for(var j=0;j' +
- ' '
- );
- }
+ function mapColors(type) {
+ var i, colors = [], colorMap;
- colors = mapColors(type);
- colors.push({
- text: tinymce.translate("No color"),
- color: "transparent"
- });
+ colorMap = [
+ "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"
+ ];
- html = '';
- last = colors.length - 1;
+ colorMap = editor.settings.textcolor_map || colorMap;
+ colorMap = editor.settings[type + '_map'] || colorMap;
- for (y = 0; y < rows[type]; y++) {
- html += '
';
- if (tinymce.DOM.getParent(e.target, '.mce-custom-color-btn')) {
- buttonCtrl.hidePanel();
+ return html;
+ }
- editor.settings.color_picker_callback.call(editor, function(value) {
- var tableElm = buttonCtrl.panel.getEl().getElementsByTagName('table')[0];
- var customColorCells, div, i;
+ function applyFormat(format, value) {
+ editor.undoManager.transact(function () {
+ editor.focus();
+ editor.formatter.apply(format, { value: value });
+ editor.nodeChanged();
+ });
+ }
- customColorCells = tinymce.map(tableElm.rows[tableElm.rows.length - 1].childNodes, function(elm) {
- return elm.firstChild;
- });
+ function removeFormat(format) {
+ editor.undoManager.transact(function () {
+ editor.focus();
+ editor.formatter.remove(format, { value: null }, null, true);
+ editor.nodeChanged();
+ });
+ }
- for (i = 0; i < customColorCells.length; i++) {
- div = customColorCells[i];
- if (!div.getAttribute('data-mce-color')) {
- break;
- }
- }
+ function onPanelClick(e) {
+ var buttonCtrl = this.parent(), value, type;
- // Shift colors to the right
- // TODO: Might need to be the left on RTL
- if (i == cols[type]) {
- for (i = 0; i < cols[type] - 1; i++) {
- setDivColor(customColorCells[i], customColorCells[i + 1].getAttribute('data-mce-color'));
- }
- }
+ type = buttonCtrl.settings.origin;
- setDivColor(div, value);
- selectColor(value);
- }, getCurrentColor(buttonCtrl.settings.format));
- }
+ function selectColor(value) {
+ buttonCtrl.hidePanel();
+ buttonCtrl.color(value);
+ applyFormat(buttonCtrl.settings.format, value);
+ }
- value = e.target.getAttribute('data-mce-color');
- if (value) {
- if (this.lastId) {
- document.getElementById(this.lastId).setAttribute('aria-selected', false);
- }
+ function resetColor() {
+ buttonCtrl.hidePanel();
+ buttonCtrl.resetColor();
+ removeFormat(buttonCtrl.settings.format);
+ }
- e.target.setAttribute('aria-selected', true);
- this.lastId = e.target.id;
+ function setDivColor(div, value) {
+ div.style.background = value;
+ div.setAttribute('data-mce-color', value);
+ }
- if (value == 'transparent') {
- resetColor();
- } else {
- selectColor(value);
- }
- } else if (value !== null) {
- buttonCtrl.hidePanel();
- }
- }
+ if (DOMUtils.DOM.getParent(e.target, '.mce-custom-color-btn')) {
+ buttonCtrl.hidePanel();
- function onButtonClick() {
- var self = this;
+ editor.settings.color_picker_callback.call(editor, function (value) {
+ var tableElm = buttonCtrl.panel.getEl().getElementsByTagName('table')[0];
+ var customColorCells, div, i;
- if (self._color) {
- applyFormat(self.settings.format, self._color);
- } else {
- removeFormat(self.settings.format);
- }
- }
+ customColorCells = Tools.map(tableElm.rows[tableElm.rows.length - 1].childNodes, function (elm) {
+ return elm.firstChild;
+ });
- editor.addButton('forecolor', {
- type: 'colorbutton',
- tooltip: 'Text color',
- format: 'forecolor',
- panel: {
- origin: 'forecolor',
- role: 'application',
- ariaRemember: true,
- html: renderColorPicker,
- onclick: onPanelClick
- },
- onclick: onButtonClick
- });
+ for (i = 0; i < customColorCells.length; i++) {
+ div = customColorCells[i];
+ if (!div.getAttribute('data-mce-color')) {
+ break;
+ }
+ }
- editor.addButton('backcolor', {
- type: 'colorbutton',
- tooltip: 'Background color',
- format: 'hilitecolor',
- panel: {
- origin: 'backcolor',
- role: 'application',
- ariaRemember: true,
- html: renderColorPicker,
- onclick: onPanelClick
- },
- onclick: onButtonClick
- });
-});
+ // Shift colors to the right
+ // TODO: Might need to be the left on RTL
+ if (i == cols[type]) {
+ for (i = 0; i < cols[type] - 1; i++) {
+ setDivColor(customColorCells[i], customColorCells[i + 1].getAttribute('data-mce-color'));
+ }
+ }
+
+ setDivColor(div, value);
+ selectColor(value);
+ }, getCurrentColor(buttonCtrl.settings.format));
+ }
+
+ value = e.target.getAttribute('data-mce-color');
+ if (value) {
+ if (this.lastId) {
+ document.getElementById(this.lastId).setAttribute('aria-selected', false);
+ }
+
+ e.target.setAttribute('aria-selected', true);
+ this.lastId = e.target.id;
+
+ if (value == 'transparent') {
+ resetColor();
+ } else {
+ selectColor(value);
+ }
+ } else if (value !== null) {
+ buttonCtrl.hidePanel();
+ }
+ }
+
+ function onButtonClick() {
+ var self = this;
+
+ if (self._color) {
+ applyFormat(self.settings.format, self._color);
+ } else {
+ removeFormat(self.settings.format);
+ }
+ }
+
+ editor.addButton('forecolor', {
+ type: 'colorbutton',
+ tooltip: 'Text color',
+ format: 'forecolor',
+ panel: {
+ origin: 'forecolor',
+ role: 'application',
+ ariaRemember: true,
+ html: renderColorPicker,
+ onclick: onPanelClick
+ },
+ onclick: onButtonClick
+ });
+
+ editor.addButton('backcolor', {
+ type: 'colorbutton',
+ tooltip: 'Background color',
+ format: 'hilitecolor',
+ panel: {
+ origin: 'backcolor',
+ role: 'application',
+ ariaRemember: true,
+ html: renderColorPicker,
+ onclick: onPanelClick
+ },
+ onclick: onButtonClick
+ });
+ });
+
+ return function () { };
+ }
+);
+dem('tinymce.plugins.textcolor.Plugin')();
+})();
diff --git a/src/wp-includes/js/tinymce/plugins/textcolor/plugin.min.js b/src/wp-includes/js/tinymce/plugins/textcolor/plugin.min.js
index 19e9a64873..4b2f012c08 100644
--- a/src/wp-includes/js/tinymce/plugins/textcolor/plugin.min.js
+++ b/src/wp-includes/js/tinymce/plugins/textcolor/plugin.min.js
@@ -1 +1 @@
-tinymce.PluginManager.add("textcolor",function(e){function t(t){var n;return e.dom.getParents(e.selection.getStart(),function(e){var r;(r=e.style["forecolor"==t?"color":"background-color"])&&(n=r)}),n}function n(t){var n,r,i=[];for(r=["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"],r=e.settings.textcolor_map||r,r=e.settings[t+"_map"]||r,n=0;n';
+ for (i = 0; i < colorMap.length; i += 2) {
+ colors.push({
+ text: colorMap[i + 1],
+ color: '#' + colorMap[i]
+ });
+ }
- for (x = 0; x < cols[type]; x++) {
- i = y * cols[type] + x;
+ return colors;
+ }
- if (i > last) {
- html += ' ';
- }
+ type = self.settings.origin;
- if (editor.settings.color_picker_callback) {
- html += (
- '';
- } else {
- color = colors[i];
- html += getColorCellHtml(color.color, color.text);
- }
- }
+ function renderColorPicker() {
+ var self = this, colors, color, html, last, x, y, i, id = self._id, count = 0, type;
- html += ' ' +
- ' '
- );
+ function getColorCellHtml(color, title) {
+ var isNoColor = color == 'transparent';
- html += '' +
- '' +
- ' ' +
- '';
+ return (
+ ' ';
- }
+ html = '' +
+ ' '
+ );
+ }
- for (x = 0; x < cols[type]; x++) {
- html += getColorCellHtml('', 'Custom color');
- }
+ colors = mapColors(type);
+ colors.push({
+ text: translate("No color"),
+ color: "transparent"
+ });
- html += '';
+ last = colors.length - 1;
- html += '
';
+ for (y = 0; y < rows[type]; y++) {
+ html += '';
- return html;
- }
+ for (x = 0; x < cols[type]; x++) {
+ i = y * cols[type] + x;
- function applyFormat(format, value) {
- editor.undoManager.transact(function() {
- editor.focus();
- editor.formatter.apply(format, {value: value});
- editor.nodeChanged();
- });
- }
+ if (i > last) {
+ html += ' ';
+ }
- function onPanelClick(e) {
- var buttonCtrl = this.parent(), value, type;
+ if (editor.settings.color_picker_callback) {
+ html += (
+ '';
+ } else {
+ color = colors[i];
+ html += getColorCellHtml(color.color, color.text);
+ }
+ }
- function removeFormat(format) {
- editor.undoManager.transact(function() {
- editor.focus();
- editor.formatter.remove(format, {value: null}, null, true);
- editor.nodeChanged();
- });
- }
+ html += ' ' +
+ ' '
+ );
- type = buttonCtrl.settings.origin;
+ html += '' +
+ '' +
+ ' ' +
+ '';
- function selectColor(value) {
- buttonCtrl.hidePanel();
- buttonCtrl.color(value);
- applyFormat(buttonCtrl.settings.format, value);
- }
+ for (x = 0; x < cols[type]; x++) {
+ html += getColorCellHtml('', 'Custom color');
+ }
- function resetColor() {
- buttonCtrl.hidePanel();
- buttonCtrl.resetColor();
- removeFormat(buttonCtrl.settings.format);
- }
+ html += ' ';
+ }
- function setDivColor(div, value) {
- div.style.background = value;
- div.setAttribute('data-mce-color', value);
- }
+ html += '',a=r.length-1,c=0;c",s=0;s
"}function i(t,n){e.undoManager.transact(function(){e.focus(),e.formatter.apply(t,{value:n}),e.nodeChanged()})}function o(t){e.undoManager.transact(function(){e.focus(),e.formatter.remove(t,{value:null},null,!0),e.nodeChanged()})}function a(n){function r(e){d.hidePanel(),d.color(e),i(d.settings.format,e)}function a(){d.hidePanel(),d.resetColor(),o(d.settings.format)}function s(e,t){e.style.background=t,e.setAttribute("data-mce-color",t)}var u,c,d=this.parent();c=d.settings.origin,tinymce.DOM.getParent(n.target,".mce-custom-color-btn")&&(d.hidePanel(),e.settings.color_picker_callback.call(e,function(e){var t,n,i,o=d.panel.getEl().getElementsByTagName("table")[0];for(t=tinymce.map(o.rows[o.rows.length-1].childNodes,function(e){return e.firstChild}),i=0;i":(i=r[d],o+=t(i.color,i.text));o+=""}if(e.settings.color_picker_callback){for(o+=' ",o+="",s=0;s ',h=c.length-1,j=0;j
"}function h(a,c){b.undoManager.transact(function(){b.focus(),b.formatter.apply(a,{value:c}),b.nodeChanged()})}function i(a){b.undoManager.transact(function(){b.focus(),b.formatter.remove(a,{value:null},null,!0),b.nodeChanged()})}function j(e){function f(a){n.hidePanel(),n.color(a),h(n.settings.format,a)}function g(){n.hidePanel(),n.resetColor(),i(n.settings.format)}function j(a,b){a.style.background=b,a.setAttribute("data-mce-color",b)}var k,m,n=this.parent();m=n.settings.origin,a.DOM.getParent(e.target,".mce-custom-color-btn")&&(n.hidePanel(),b.settings.color_picker_callback.call(b,function(a){var b,c,e,g=n.panel.getEl().getElementsByTagName("table")[0];for(b=d.map(g.rows[g.rows.length-1].childNodes,function(a){return a.firstChild}),e=0;e":(d=c[k],g+=a(d.color,d.text));g+=""}if(b.settings.color_picker_callback){for(g+=' ",g+="",i=0;i ';
- html += '';
+ html = '
';
- return html;
- };
+ return html;
+ };
- var getInsertedElement = function (editor) {
- var elms = editor.dom.select('*[data-mce-id]');
- return elms[0];
- };
+ var getInsertedElement = function (editor) {
+ var elms = editor.dom.select('*[data-mce-id]');
+ return elms[0];
+ };
- var insertTable = function (editor, cols, rows) {
- editor.undoManager.transact(function () {
- var tableElm, cellElm;
+ var insertTable = function (editor, cols, rows) {
+ editor.undoManager.transact(function () {
+ var tableElm, cellElm;
- editor.insertContent(createTableHtml(cols, rows));
+ editor.insertContent(createTableHtml(cols, rows));
- tableElm = getInsertedElement(editor);
- tableElm.removeAttribute('data-mce-id');
- cellElm = editor.dom.select('td,th', tableElm);
- editor.selection.setCursorLocation(cellElm[0], 0);
- });
- };
+ tableElm = getInsertedElement(editor);
+ tableElm.removeAttribute('data-mce-id');
+ cellElm = editor.dom.select('td,th', tableElm);
+ editor.selection.setCursorLocation(cellElm[0], 0);
+ });
+ };
- var formatBlock = function (editor, formatName) {
- editor.execCommand('FormatBlock', false, formatName);
- };
+ var formatBlock = function (editor, formatName) {
+ editor.execCommand('FormatBlock', false, formatName);
+ };
- var insertBlob = function (editor, base64, blob) {
- var blobCache, blobInfo;
+ var insertBlob = function (editor, base64, blob) {
+ var blobCache, blobInfo;
- blobCache = editor.editorUpload.blobCache;
- blobInfo = blobCache.create(Uuid.uuid('mceu'), blob, base64);
- blobCache.add(blobInfo);
+ blobCache = editor.editorUpload.blobCache;
+ blobInfo = blobCache.create(Uuid.uuid('mceu'), blob, base64);
+ blobCache.add(blobInfo);
- editor.insertContent(editor.dom.createHTML('img', {src: blobInfo.blobUri()}));
- };
+ editor.insertContent(editor.dom.createHTML('img', { src: blobInfo.blobUri() }));
+ };
- var collapseSelectionToEnd = function (editor) {
- editor.selection.collapse(false);
- };
+ var collapseSelectionToEnd = function (editor) {
+ editor.selection.collapse(false);
+ };
- var unlink = function (editor) {
- editor.focus();
- Unlink.unlinkSelection(editor);
- collapseSelectionToEnd(editor);
- };
+ var unlink = function (editor) {
+ editor.focus();
+ Unlink.unlinkSelection(editor);
+ collapseSelectionToEnd(editor);
+ };
- var changeHref = function (editor, elm, url) {
- editor.focus();
- editor.dom.setAttrib(elm, 'href', url);
- collapseSelectionToEnd(editor);
- };
+ var changeHref = function (editor, elm, url) {
+ editor.focus();
+ editor.dom.setAttrib(elm, 'href', url);
+ collapseSelectionToEnd(editor);
+ };
- var insertLink = function (editor, url) {
- editor.execCommand('mceInsertLink', false, {href: url});
- collapseSelectionToEnd(editor);
- };
+ var insertLink = function (editor, url) {
+ editor.execCommand('mceInsertLink', false, { href: url });
+ collapseSelectionToEnd(editor);
+ };
- var updateOrInsertLink = function (editor, url) {
- var elm = editor.dom.getParent(editor.selection.getStart(), 'a[href]');
- elm ? changeHref(editor, elm, url) : insertLink(editor, url);
- };
+ var updateOrInsertLink = function (editor, url) {
+ var elm = editor.dom.getParent(editor.selection.getStart(), 'a[href]');
+ elm ? changeHref(editor, elm, url) : insertLink(editor, url);
+ };
- var createLink = function (editor, url) {
- url.trim().length === 0 ? unlink(editor) : updateOrInsertLink(editor, url);
- };
+ var createLink = function (editor, url) {
+ url.trim().length === 0 ? unlink(editor) : updateOrInsertLink(editor, url);
+ };
- return {
- insertTable: insertTable,
- formatBlock: formatBlock,
- insertBlob: insertBlob,
- createLink: createLink,
- unlink: unlink
- };
-});
+ return {
+ insertTable: insertTable,
+ formatBlock: formatBlock,
+ insertBlob: insertBlob,
+ createLink: createLink,
+ unlink: unlink
+ };
+ }
+);
/**
* UrlType.js
@@ -617,21 +1313,25 @@ define('tinymce/inlite/core/Actions', [
* Contributing: http://www.tinymce.com/contributing
*/
-define('tinymce/inlite/core/UrlType', [
-], function () {
- var isDomainLike = function (href) {
- return /^www\.|\.(com|org|edu|gov|uk|net|ca|de|jp|fr|au|us|ru|ch|it|nl|se|no|es|mil)$/i.test(href.trim());
- };
+define(
+ 'tinymce.themes.inlite.core.UrlType',
+ [
+ ],
+ function () {
+ var isDomainLike = function (href) {
+ return /^www\.|\.(com|org|edu|gov|uk|net|ca|de|jp|fr|au|us|ru|ch|it|nl|se|no|es|mil)$/i.test(href.trim());
+ };
- var isAbsolute = function (href) {
- return /^https?:\/\//.test(href.trim());
- };
+ var isAbsolute = function (href) {
+ return /^https?:\/\//.test(href.trim());
+ };
- return {
- isDomainLike: isDomainLike,
- isAbsolute: isAbsolute
- };
-});
+ return {
+ isDomainLike: isDomainLike,
+ isAbsolute: isAbsolute
+ };
+ }
+);
@@ -645,225 +1345,128 @@ define('tinymce/inlite/core/UrlType', [
* Contributing: http://www.tinymce.com/contributing
*/
-define('tinymce/inlite/ui/Forms', [
- 'global!tinymce.util.Tools',
- 'global!tinymce.ui.Factory',
- 'global!tinymce.util.Promise',
- 'tinymce/inlite/core/Actions',
- 'tinymce/inlite/core/UrlType'
-], function (Tools, Factory, Promise, Actions, UrlType) {
- var focusFirstTextBox = function (form) {
- form.find('textbox').eq(0).each(function (ctrl) {
- ctrl.focus();
- });
- };
+define(
+ 'tinymce.themes.inlite.ui.Forms',
+ [
+ 'tinymce.core.util.Tools',
+ 'tinymce.core.ui.Factory',
+ 'tinymce.core.util.Promise',
+ 'tinymce.themes.inlite.core.Actions',
+ 'tinymce.themes.inlite.core.UrlType'
+ ],
+ function (Tools, Factory, Promise, Actions, UrlType) {
+ var focusFirstTextBox = function (form) {
+ form.find('textbox').eq(0).each(function (ctrl) {
+ ctrl.focus();
+ });
+ };
- var createForm = function (name, spec) {
- var form = Factory.create(
- Tools.extend({
- type: 'form',
- layout: 'flex',
- direction: 'row',
- padding: 5,
- name: name,
- spacing: 3
- }, spec)
- );
+ var createForm = function (name, spec) {
+ var form = Factory.create(
+ Tools.extend({
+ type: 'form',
+ layout: 'flex',
+ direction: 'row',
+ padding: 5,
+ name: name,
+ spacing: 3
+ }, spec)
+ );
- form.on('show', function () {
- focusFirstTextBox(form);
- });
+ form.on('show', function () {
+ focusFirstTextBox(form);
+ });
- return form;
- };
+ return form;
+ };
- var toggleVisibility = function (ctrl, state) {
- return state ? ctrl.show() : ctrl.hide();
- };
+ var toggleVisibility = function (ctrl, state) {
+ return state ? ctrl.show() : ctrl.hide();
+ };
- var askAboutPrefix = function (editor, href) {
- return new Promise(function (resolve) {
- editor.windowManager.confirm(
- 'The URL you entered seems to be an external link. Do you want to add the required http:// prefix?',
- function (result) {
- var output = result === true ? 'http://' + href : href;
- resolve(output);
- }
- );
- });
- };
+ var askAboutPrefix = function (editor, href) {
+ return new Promise(function (resolve) {
+ editor.windowManager.confirm(
+ 'The URL you entered seems to be an external link. Do you want to add the required http:// prefix?',
+ function (result) {
+ var output = result === true ? 'http://' + href : href;
+ resolve(output);
+ }
+ );
+ });
+ };
- var convertLinkToAbsolute = function (editor, href) {
- return !UrlType.isAbsolute(href) && UrlType.isDomainLike(href) ? askAboutPrefix(editor, href) : Promise.resolve(href);
- };
+ var convertLinkToAbsolute = function (editor, href) {
+ return !UrlType.isAbsolute(href) && UrlType.isDomainLike(href) ? askAboutPrefix(editor, href) : Promise.resolve(href);
+ };
- var createQuickLinkForm = function (editor, hide) {
- var attachState = {};
+ var createQuickLinkForm = function (editor, hide) {
+ var attachState = {};
- var unlink = function () {
- editor.focus();
- Actions.unlink(editor);
- hide();
- };
+ var unlink = function () {
+ editor.focus();
+ Actions.unlink(editor);
+ hide();
+ };
- var onChangeHandler = function (e) {
- var meta = e.meta;
+ var onChangeHandler = function (e) {
+ var meta = e.meta;
- if (meta && meta.attach) {
- attachState = {
- href: this.value(),
- attach: meta.attach
- };
- }
- };
+ if (meta && meta.attach) {
+ attachState = {
+ href: this.value(),
+ attach: meta.attach
+ };
+ }
+ };
- var onShowHandler = function (e) {
- if (e.control === this) {
- var elm, linkurl = '';
+ var onShowHandler = function (e) {
+ if (e.control === this) {
+ var elm, linkurl = '';
- elm = editor.dom.getParent(editor.selection.getStart(), 'a[href]');
- if (elm) {
- linkurl = editor.dom.getAttrib(elm, 'href');
- }
+ elm = editor.dom.getParent(editor.selection.getStart(), 'a[href]');
+ if (elm) {
+ linkurl = editor.dom.getAttrib(elm, 'href');
+ }
- this.fromJSON({
- linkurl: linkurl
- });
+ this.fromJSON({
+ linkurl: linkurl
+ });
- toggleVisibility(this.find('#unlink'), elm);
- this.find('#linkurl')[0].focus();
- }
- };
+ toggleVisibility(this.find('#unlink'), elm);
+ this.find('#linkurl')[0].focus();
+ }
+ };
- return createForm('quicklink', {
- items: [
- {type: 'button', name: 'unlink', icon: 'unlink', onclick: unlink, tooltip: 'Remove link'},
- {type: 'filepicker', name: 'linkurl', placeholder: 'Paste or type a link', filetype: 'file', onchange: onChangeHandler},
- {type: 'button', icon: 'checkmark', subtype: 'primary', tooltip: 'Ok', onclick: 'submit'}
- ],
- onshow: onShowHandler,
- onsubmit: function (e) {
- convertLinkToAbsolute(editor, e.data.linkurl).then(function (url) {
- editor.undoManager.transact(function () {
- if (url === attachState.href) {
- attachState.attach();
- attachState = {};
- }
+ return createForm('quicklink', {
+ items: [
+ { type: 'button', name: 'unlink', icon: 'unlink', onclick: unlink, tooltip: 'Remove link' },
+ { type: 'filepicker', name: 'linkurl', placeholder: 'Paste or type a link', filetype: 'file', onchange: onChangeHandler },
+ { type: 'button', icon: 'checkmark', subtype: 'primary', tooltip: 'Ok', onclick: 'submit' }
+ ],
+ onshow: onShowHandler,
+ onsubmit: function (e) {
+ convertLinkToAbsolute(editor, e.data.linkurl).then(function (url) {
+ editor.undoManager.transact(function () {
+ if (url === attachState.href) {
+ attachState.attach();
+ attachState = {};
+ }
- Actions.createLink(editor, url);
- });
+ Actions.createLink(editor, url);
+ });
- hide();
- });
- }
- });
- };
+ hide();
+ });
+ }
+ });
+ };
- return {
- createQuickLinkForm: createQuickLinkForm
- };
-});
-
-defineGlobal("global!tinymce.geom.Rect", tinymce.geom.Rect);
-/**
- * Convert.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2016 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
-
-define('tinymce/inlite/core/Convert', [
-], function () {
- var fromClientRect = function (clientRect) {
- return {
- x: clientRect.left,
- y: clientRect.top,
- w: clientRect.width,
- h: clientRect.height
- };
- };
-
- var toClientRect = function (geomRect) {
- return {
- left: geomRect.x,
- top: geomRect.y,
- width: geomRect.w,
- height: geomRect.h,
- right: geomRect.x + geomRect.w,
- bottom: geomRect.y + geomRect.h
- };
- };
-
- return {
- fromClientRect: fromClientRect,
- toClientRect: toClientRect
- };
-});
-
-/**
- * Measure.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2016 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
-
-define('tinymce/inlite/core/Measure', [
- 'global!tinymce.DOM',
- 'global!tinymce.geom.Rect',
- 'tinymce/inlite/core/Convert'
-], function (DOM, Rect, Convert) {
- var toAbsolute = function (rect) {
- var vp = DOM.getViewPort();
-
- return {
- x: rect.x + vp.x,
- y: rect.y + vp.y,
- w: rect.w,
- h: rect.h
- };
- };
-
- var measureElement = function (elm) {
- var clientRect = elm.getBoundingClientRect();
-
- return toAbsolute({
- x: clientRect.left,
- y: clientRect.top,
- w: Math.max(elm.clientWidth, elm.offsetWidth),
- h: Math.max(elm.clientHeight, elm.offsetHeight)
- });
- };
-
- var getElementRect = function (editor, elm) {
- return measureElement(elm);
- };
-
- var getPageAreaRect = function (editor) {
- return measureElement(editor.getElement().ownerDocument.body);
- };
-
- var getContentAreaRect = function (editor) {
- return measureElement(editor.getContentAreaContainer() || editor.getBody());
- };
-
- var getSelectionRect = function (editor) {
- var clientRect = editor.selection.getBoundingClientRect();
- return clientRect ? toAbsolute(Convert.fromClientRect(clientRect)) : null;
- };
-
- return {
- getElementRect: getElementRect,
- getPageAreaRect: getPageAreaRect,
- getContentAreaRect: getContentAreaRect,
- getSelectionRect: getSelectionRect
- };
-});
+ return {
+ createQuickLinkForm: createQuickLinkForm
+ };
+ }
+);
/**
* Layout.js
@@ -875,169 +1478,100 @@ define('tinymce/inlite/core/Measure', [
* Contributing: http://www.tinymce.com/contributing
*/
-define('tinymce/inlite/core/Layout', [
- 'global!tinymce.geom.Rect',
- 'tinymce/inlite/core/Convert'
-], function (Rect, Convert) {
- var result = function (rect, position) {
- return {
- rect: rect,
- position: position
- };
- };
+define(
+ 'tinymce.themes.inlite.core.Layout',
+ [
+ 'tinymce.core.geom.Rect',
+ 'tinymce.themes.inlite.core.Convert'
+ ],
+ function (Rect, Convert) {
+ var result = function (rect, position) {
+ return {
+ rect: rect,
+ position: position
+ };
+ };
- var moveTo = function (rect, toRect) {
- return {x: toRect.x, y: toRect.y, w: rect.w, h: rect.h};
- };
+ var moveTo = function (rect, toRect) {
+ return { x: toRect.x, y: toRect.y, w: rect.w, h: rect.h };
+ };
- var calcByPositions = function (testPositions1, testPositions2, targetRect, contentAreaRect, panelRect) {
- var relPos, relRect, outputPanelRect;
+ var calcByPositions = function (testPositions1, testPositions2, targetRect, contentAreaRect, panelRect) {
+ var relPos, relRect, outputPanelRect;
- relPos = Rect.findBestRelativePosition(panelRect, targetRect, contentAreaRect, testPositions1);
- targetRect = Rect.clamp(targetRect, contentAreaRect);
+ relPos = Rect.findBestRelativePosition(panelRect, targetRect, contentAreaRect, testPositions1);
+ targetRect = Rect.clamp(targetRect, contentAreaRect);
- if (relPos) {
- relRect = Rect.relativePosition(panelRect, targetRect, relPos);
- outputPanelRect = moveTo(panelRect, relRect);
- return result(outputPanelRect, relPos);
- }
+ if (relPos) {
+ relRect = Rect.relativePosition(panelRect, targetRect, relPos);
+ outputPanelRect = moveTo(panelRect, relRect);
+ return result(outputPanelRect, relPos);
+ }
- targetRect = Rect.intersect(contentAreaRect, targetRect);
- if (targetRect) {
- relPos = Rect.findBestRelativePosition(panelRect, targetRect, contentAreaRect, testPositions2);
- if (relPos) {
- relRect = Rect.relativePosition(panelRect, targetRect, relPos);
- outputPanelRect = moveTo(panelRect, relRect);
- return result(outputPanelRect, relPos);
- }
+ targetRect = Rect.intersect(contentAreaRect, targetRect);
+ if (targetRect) {
+ relPos = Rect.findBestRelativePosition(panelRect, targetRect, contentAreaRect, testPositions2);
+ if (relPos) {
+ relRect = Rect.relativePosition(panelRect, targetRect, relPos);
+ outputPanelRect = moveTo(panelRect, relRect);
+ return result(outputPanelRect, relPos);
+ }
- outputPanelRect = moveTo(panelRect, targetRect);
- return result(outputPanelRect, relPos);
- }
+ outputPanelRect = moveTo(panelRect, targetRect);
+ return result(outputPanelRect, relPos);
+ }
- return null;
- };
+ return null;
+ };
- var calcInsert = function (targetRect, contentAreaRect, panelRect) {
- return calcByPositions(
- ['cr-cl', 'cl-cr'],
- ['bc-tc', 'bl-tl', 'br-tr'],
- targetRect,
- contentAreaRect,
- panelRect
- );
- };
+ var calcInsert = function (targetRect, contentAreaRect, panelRect) {
+ return calcByPositions(
+ ['cr-cl', 'cl-cr'],
+ ['bc-tc', 'bl-tl', 'br-tr'],
+ targetRect,
+ contentAreaRect,
+ panelRect
+ );
+ };
- var calc = function (targetRect, contentAreaRect, panelRect) {
- return calcByPositions(
- ['tc-bc', 'bc-tc', 'tl-bl', 'bl-tl', 'tr-br', 'br-tr'],
- ['bc-tc', 'bl-tl', 'br-tr'],
- targetRect,
- contentAreaRect,
- panelRect
- );
- };
+ var calc = function (targetRect, contentAreaRect, panelRect) {
+ return calcByPositions(
+ ['tc-bc', 'bc-tc', 'tl-bl', 'bl-tl', 'tr-br', 'br-tr'],
+ ['bc-tc', 'bl-tl', 'br-tr'],
+ targetRect,
+ contentAreaRect,
+ panelRect
+ );
+ };
- var userConstrain = function (handler, targetRect, contentAreaRect, panelRect) {
- var userConstrainedPanelRect;
+ var userConstrain = function (handler, targetRect, contentAreaRect, panelRect) {
+ var userConstrainedPanelRect;
- if (typeof handler === 'function') {
- userConstrainedPanelRect = handler({
- elementRect: Convert.toClientRect(targetRect),
- contentAreaRect: Convert.toClientRect(contentAreaRect),
- panelRect: Convert.toClientRect(panelRect)
- });
+ if (typeof handler === 'function') {
+ userConstrainedPanelRect = handler({
+ elementRect: Convert.toClientRect(targetRect),
+ contentAreaRect: Convert.toClientRect(contentAreaRect),
+ panelRect: Convert.toClientRect(panelRect)
+ });
- return Convert.fromClientRect(userConstrainedPanelRect);
- }
+ return Convert.fromClientRect(userConstrainedPanelRect);
+ }
- return panelRect;
- };
+ return panelRect;
+ };
- var defaultHandler = function (rects) {
- return rects.panelRect;
- };
+ var defaultHandler = function (rects) {
+ return rects.panelRect;
+ };
- return {
- calcInsert: calcInsert,
- calc: calc,
- userConstrain: userConstrain,
- defaultHandler: defaultHandler
- };
-});
-
-/**
- * EditorSettings.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2016 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
-
-define('tinymce/inlite/alien/EditorSettings', [
- 'tinymce/inlite/alien/Type'
-], function (Type) {
- var validDefaultOrDie = function (value, predicate) {
- if (predicate(value)) {
- return true;
- }
-
- throw new Error('Default value doesn\'t match requested type.');
- };
-
- var getByTypeOr = function (predicate) {
- return function (editor, name, defaultValue) {
- var settings = editor.settings;
- validDefaultOrDie(defaultValue, predicate);
- return name in settings && predicate(settings[name]) ? settings[name] : defaultValue;
- };
- };
-
- var splitNoEmpty = function (str, delim) {
- return str.split(delim).filter(function (item) {
- return item.length > 0;
- });
- };
-
- var itemsToArray = function (value, defaultValue) {
- var stringToItemsArray = function (value) {
- return typeof value === 'string' ? splitNoEmpty(value, /[ ,]/) : value;
- };
-
- var boolToItemsArray = function (value, defaultValue) {
- return value === false ? [ ] : defaultValue;
- };
-
- if (Type.isArray(value)) {
- return value;
- } else if (Type.isString(value)) {
- return stringToItemsArray(value);
- } else if (Type.isBoolean(value)) {
- return boolToItemsArray(value, defaultValue);
- }
-
- return defaultValue;
- };
-
- var getToolbarItemsOr = function (predicate) {
- return function (editor, name, defaultValue) {
- var value = name in editor.settings ? editor.settings[name] : defaultValue;
- validDefaultOrDie(defaultValue, predicate);
- return itemsToArray(value, defaultValue);
- };
- };
-
- return {
- // TODO: Add Option based getString, getBool if merged with core
- getStringOr: getByTypeOr(Type.isString),
- getBoolOr: getByTypeOr(Type.isBoolean),
- getNumberOr: getByTypeOr(Type.isNumber),
- getHandlerOr: getByTypeOr(Type.isFunction),
- getToolbarItemsOr: getToolbarItemsOr(Type.isArray)
- };
-});
+ return {
+ calcInsert: calcInsert,
+ calc: calc,
+ userConstrain: userConstrain,
+ defaultHandler: defaultHandler
+ };
+ }
+);
/**
* Panel.js
@@ -1049,237 +1583,241 @@ define('tinymce/inlite/alien/EditorSettings', [
* Contributing: http://www.tinymce.com/contributing
*/
-define('tinymce/inlite/ui/Panel', [
- 'global!tinymce.util.Tools',
- 'global!tinymce.ui.Factory',
- 'global!tinymce.DOM',
- 'tinymce/inlite/ui/Toolbar',
- 'tinymce/inlite/ui/Forms',
- 'tinymce/inlite/core/Measure',
- 'tinymce/inlite/core/Layout',
- 'tinymce/inlite/alien/EditorSettings'
-], function (Tools, Factory, DOM, Toolbar, Forms, Measure, Layout, EditorSettings) {
- return function () {
- var DEFAULT_TEXT_SELECTION_ITEMS = ['bold', 'italic', '|', 'quicklink', 'h2', 'h3', 'blockquote'];
- var DEFAULT_INSERT_TOOLBAR_ITEMS = ['quickimage', 'quicktable'];
- var panel, currentRect;
+define(
+ 'tinymce.themes.inlite.ui.Panel',
+ [
+ 'tinymce.core.util.Tools',
+ 'tinymce.core.ui.Factory',
+ 'tinymce.core.dom.DOMUtils',
+ 'tinymce.themes.inlite.ui.Toolbar',
+ 'tinymce.themes.inlite.ui.Forms',
+ 'tinymce.themes.inlite.core.Measure',
+ 'tinymce.themes.inlite.core.Layout',
+ 'tinymce.themes.inlite.alien.EditorSettings'
+ ],
+ function (Tools, Factory, DOMUtils, Toolbar, Forms, Measure, Layout, EditorSettings) {
+ return function () {
+ var DEFAULT_TEXT_SELECTION_ITEMS = ['bold', 'italic', '|', 'quicklink', 'h2', 'h3', 'blockquote'];
+ var DEFAULT_INSERT_TOOLBAR_ITEMS = ['quickimage', 'quicktable'];
+ var panel, currentRect;
- var createToolbars = function (editor, toolbars) {
- return Tools.map(toolbars, function (toolbar) {
- return Toolbar.create(editor, toolbar.id, toolbar.items);
- });
- };
+ var createToolbars = function (editor, toolbars) {
+ return Tools.map(toolbars, function (toolbar) {
+ return Toolbar.create(editor, toolbar.id, toolbar.items);
+ });
+ };
- var getTextSelectionToolbarItems = function (editor) {
- return EditorSettings.getToolbarItemsOr(editor, 'selection_toolbar', DEFAULT_TEXT_SELECTION_ITEMS);
- };
+ var getTextSelectionToolbarItems = function (editor) {
+ return EditorSettings.getToolbarItemsOr(editor, 'selection_toolbar', DEFAULT_TEXT_SELECTION_ITEMS);
+ };
- var getInsertToolbarItems = function (editor) {
- return EditorSettings.getToolbarItemsOr(editor, 'insert_toolbar', DEFAULT_INSERT_TOOLBAR_ITEMS);
- };
+ var getInsertToolbarItems = function (editor) {
+ return EditorSettings.getToolbarItemsOr(editor, 'insert_toolbar', DEFAULT_INSERT_TOOLBAR_ITEMS);
+ };
- var hasToolbarItems = function (toolbar) {
- return toolbar.items().length > 0;
- };
+ var hasToolbarItems = function (toolbar) {
+ return toolbar.items().length > 0;
+ };
- var create = function (editor, toolbars) {
- var items = createToolbars(editor, toolbars).concat([
- Toolbar.create(editor, 'text', getTextSelectionToolbarItems(editor)),
- Toolbar.create(editor, 'insert', getInsertToolbarItems(editor)),
- Forms.createQuickLinkForm(editor, hide)
- ]);
+ var create = function (editor, toolbars) {
+ var items = createToolbars(editor, toolbars).concat([
+ Toolbar.create(editor, 'text', getTextSelectionToolbarItems(editor)),
+ Toolbar.create(editor, 'insert', getInsertToolbarItems(editor)),
+ Forms.createQuickLinkForm(editor, hide)
+ ]);
- return Factory.create({
- type: 'floatpanel',
- role: 'dialog',
- classes: 'tinymce tinymce-inline arrow',
- ariaLabel: 'Inline toolbar',
- layout: 'flex',
- direction: 'column',
- align: 'stretch',
- autohide: false,
- autofix: true,
- fixed: true,
- border: 1,
- items: Tools.grep(items, hasToolbarItems),
- oncancel: function() {
- editor.focus();
- }
- });
- };
+ return Factory.create({
+ type: 'floatpanel',
+ role: 'dialog',
+ classes: 'tinymce tinymce-inline arrow',
+ ariaLabel: 'Inline toolbar',
+ layout: 'flex',
+ direction: 'column',
+ align: 'stretch',
+ autohide: false,
+ autofix: true,
+ fixed: true,
+ border: 1,
+ items: Tools.grep(items, hasToolbarItems),
+ oncancel: function () {
+ editor.focus();
+ }
+ });
+ };
- var showPanel = function (panel) {
- if (panel) {
- panel.show();
- }
- };
+ var showPanel = function (panel) {
+ if (panel) {
+ panel.show();
+ }
+ };
- var movePanelTo = function (panel, pos) {
- panel.moveTo(pos.x, pos.y);
- };
+ var movePanelTo = function (panel, pos) {
+ panel.moveTo(pos.x, pos.y);
+ };
- var togglePositionClass = function (panel, relPos) {
- relPos = relPos ? relPos.substr(0, 2) : '';
+ var togglePositionClass = function (panel, relPos) {
+ relPos = relPos ? relPos.substr(0, 2) : '';
- Tools.each({
- t: 'down',
- b: 'up',
- c: 'center'
- }, function(cls, pos) {
- panel.classes.toggle('arrow-' + cls, pos === relPos.substr(0, 1));
- });
+ Tools.each({
+ t: 'down',
+ b: 'up',
+ c: 'center'
+ }, function (cls, pos) {
+ panel.classes.toggle('arrow-' + cls, pos === relPos.substr(0, 1));
+ });
- if (relPos === 'cr') {
- panel.classes.toggle('arrow-left', true);
- panel.classes.toggle('arrow-right', false);
- } else if (relPos === 'cl') {
- panel.classes.toggle('arrow-left', true);
- panel.classes.toggle('arrow-right', true);
- } else {
- Tools.each({
- l: 'left',
- r: 'right'
- }, function(cls, pos) {
- panel.classes.toggle('arrow-' + cls, pos === relPos.substr(1, 1));
- });
- }
- };
+ if (relPos === 'cr') {
+ panel.classes.toggle('arrow-left', true);
+ panel.classes.toggle('arrow-right', false);
+ } else if (relPos === 'cl') {
+ panel.classes.toggle('arrow-left', true);
+ panel.classes.toggle('arrow-right', true);
+ } else {
+ Tools.each({
+ l: 'left',
+ r: 'right'
+ }, function (cls, pos) {
+ panel.classes.toggle('arrow-' + cls, pos === relPos.substr(1, 1));
+ });
+ }
+ };
- var showToolbar = function (panel, id) {
- var toolbars = panel.items().filter('#' + id);
+ var showToolbar = function (panel, id) {
+ var toolbars = panel.items().filter('#' + id);
- if (toolbars.length > 0) {
- toolbars[0].show();
- panel.reflow();
- return true;
- }
+ if (toolbars.length > 0) {
+ toolbars[0].show();
+ panel.reflow();
+ return true;
+ }
- return false;
- };
+ return false;
+ };
- var repositionPanelAt = function (panel, id, editor, targetRect) {
- var contentAreaRect, panelRect, result, userConstainHandler;
+ var repositionPanelAt = function (panel, id, editor, targetRect) {
+ var contentAreaRect, panelRect, result, userConstainHandler;
- userConstainHandler = EditorSettings.getHandlerOr(editor, 'inline_toolbar_position_handler', Layout.defaultHandler);
- contentAreaRect = Measure.getContentAreaRect(editor);
- panelRect = DOM.getRect(panel.getEl());
+ userConstainHandler = EditorSettings.getHandlerOr(editor, 'inline_toolbar_position_handler', Layout.defaultHandler);
+ contentAreaRect = Measure.getContentAreaRect(editor);
+ panelRect = DOMUtils.DOM.getRect(panel.getEl());
- if (id === 'insert') {
- result = Layout.calcInsert(targetRect, contentAreaRect, panelRect);
- } else {
- result = Layout.calc(targetRect, contentAreaRect, panelRect);
- }
+ if (id === 'insert') {
+ result = Layout.calcInsert(targetRect, contentAreaRect, panelRect);
+ } else {
+ result = Layout.calc(targetRect, contentAreaRect, panelRect);
+ }
- if (result) {
- panelRect = result.rect;
- currentRect = targetRect;
- movePanelTo(panel, Layout.userConstrain(userConstainHandler, targetRect, contentAreaRect, panelRect));
- togglePositionClass(panel, result.position);
- return true;
- } else {
- return false;
- }
- };
+ if (result) {
+ panelRect = result.rect;
+ currentRect = targetRect;
+ movePanelTo(panel, Layout.userConstrain(userConstainHandler, targetRect, contentAreaRect, panelRect));
+ togglePositionClass(panel, result.position);
+ return true;
+ } else {
+ return false;
+ }
+ };
- var showPanelAt = function (panel, id, editor, targetRect) {
- showPanel(panel);
- panel.items().hide();
+ var showPanelAt = function (panel, id, editor, targetRect) {
+ showPanel(panel);
+ panel.items().hide();
- if (!showToolbar(panel, id)) {
- hide(panel);
- return;
- }
+ if (!showToolbar(panel, id)) {
+ hide(panel);
+ return;
+ }
- if (repositionPanelAt(panel, id, editor, targetRect) === false) {
- hide(panel);
- }
- };
+ if (repositionPanelAt(panel, id, editor, targetRect) === false) {
+ hide(panel);
+ }
+ };
- var hasFormVisible = function () {
- return panel.items().filter('form:visible').length > 0;
- };
+ var hasFormVisible = function () {
+ return panel.items().filter('form:visible').length > 0;
+ };
- var showForm = function (editor, id) {
- if (panel) {
- panel.items().hide();
+ var showForm = function (editor, id) {
+ if (panel) {
+ panel.items().hide();
- if (!showToolbar(panel, id)) {
- hide(panel);
- return;
- }
+ if (!showToolbar(panel, id)) {
+ hide(panel);
+ return;
+ }
- var contentAreaRect, panelRect, result, userConstainHandler;
+ var contentAreaRect, panelRect, result, userConstainHandler;
- showPanel(panel);
- panel.items().hide();
- showToolbar(panel, id);
+ showPanel(panel);
+ panel.items().hide();
+ showToolbar(panel, id);
- userConstainHandler = EditorSettings.getHandlerOr(editor, 'inline_toolbar_position_handler', Layout.defaultHandler);
- contentAreaRect = Measure.getContentAreaRect(editor);
- panelRect = DOM.getRect(panel.getEl());
+ userConstainHandler = EditorSettings.getHandlerOr(editor, 'inline_toolbar_position_handler', Layout.defaultHandler);
+ contentAreaRect = Measure.getContentAreaRect(editor);
+ panelRect = DOMUtils.DOM.getRect(panel.getEl());
- result = Layout.calc(currentRect, contentAreaRect, panelRect);
+ result = Layout.calc(currentRect, contentAreaRect, panelRect);
- if (result) {
- panelRect = result.rect;
- movePanelTo(panel, Layout.userConstrain(userConstainHandler, currentRect, contentAreaRect, panelRect));
- togglePositionClass(panel, result.position);
- }
- }
- };
+ if (result) {
+ panelRect = result.rect;
+ movePanelTo(panel, Layout.userConstrain(userConstainHandler, currentRect, contentAreaRect, panelRect));
+ togglePositionClass(panel, result.position);
+ }
+ }
+ };
- var show = function (editor, id, targetRect, toolbars) {
- if (!panel) {
- panel = create(editor, toolbars);
- panel.renderTo(document.body).reflow().moveTo(targetRect.x, targetRect.y);
- editor.nodeChanged();
- }
+ var show = function (editor, id, targetRect, toolbars) {
+ if (!panel) {
+ panel = create(editor, toolbars);
+ panel.renderTo(document.body).reflow().moveTo(targetRect.x, targetRect.y);
+ editor.nodeChanged();
+ }
- showPanelAt(panel, id, editor, targetRect);
- };
+ showPanelAt(panel, id, editor, targetRect);
+ };
- var reposition = function (editor, id, targetRect) {
- if (panel) {
- repositionPanelAt(panel, id, editor, targetRect);
- }
- };
+ var reposition = function (editor, id, targetRect) {
+ if (panel) {
+ repositionPanelAt(panel, id, editor, targetRect);
+ }
+ };
- var hide = function () {
- if (panel) {
- panel.hide();
- }
- };
+ var hide = function () {
+ if (panel) {
+ panel.hide();
+ }
+ };
- var focus = function () {
- if (panel) {
- panel.find('toolbar:visible').eq(0).each(function (item) {
- item.focus(true);
- });
- }
- };
+ var focus = function () {
+ if (panel) {
+ panel.find('toolbar:visible').eq(0).each(function (item) {
+ item.focus(true);
+ });
+ }
+ };
- var remove = function () {
- if (panel) {
- panel.remove();
- panel = null;
- }
- };
+ var remove = function () {
+ if (panel) {
+ panel.remove();
+ panel = null;
+ }
+ };
- var inForm = function () {
- return panel && panel.visible() && hasFormVisible();
- };
+ var inForm = function () {
+ return panel && panel.visible() && hasFormVisible();
+ };
- return {
- show: show,
- showForm: showForm,
- reposition: reposition,
- inForm: inForm,
- hide: hide,
- focus: focus,
- remove: remove
- };
- };
-});
+ return {
+ show: show,
+ showForm: showForm,
+ reposition: reposition,
+ inForm: inForm,
+ hide: hide,
+ focus: focus,
+ remove: remove
+ };
+ };
+ }
+);
/**
* Conversions.js
@@ -1291,25 +1829,29 @@ define('tinymce/inlite/ui/Panel', [
* Contributing: http://www.tinymce.com/contributing
*/
-define('tinymce/inlite/file/Conversions', [
- 'global!tinymce.util.Promise'
-], function (Promise) {
- var blobToBase64 = function (blob) {
- return new Promise(function(resolve) {
- var reader = new FileReader();
+define(
+ 'tinymce.themes.inlite.file.Conversions',
+ [
+ 'tinymce.core.util.Promise'
+ ],
+ function (Promise) {
+ var blobToBase64 = function (blob) {
+ return new Promise(function (resolve) {
+ var reader = new FileReader();
- reader.onloadend = function() {
- resolve(reader.result.split(',')[1]);
- };
+ reader.onloadend = function () {
+ resolve(reader.result.split(',')[1]);
+ };
- reader.readAsDataURL(blob);
- });
- };
+ reader.readAsDataURL(blob);
+ });
+ };
- return {
- blobToBase64: blobToBase64
- };
-});
+ return {
+ blobToBase64: blobToBase64
+ };
+ }
+);
@@ -1323,34 +1865,38 @@ define('tinymce/inlite/file/Conversions', [
* Contributing: http://www.tinymce.com/contributing
*/
-define('tinymce/inlite/file/Picker', [
- 'global!tinymce.util.Promise'
-], function (Promise) {
- var pickFile = function () {
- return new Promise(function (resolve) {
- var fileInput;
+define(
+ 'tinymce.themes.inlite.file.Picker',
+ [
+ 'tinymce.core.util.Promise'
+ ],
+ function (Promise) {
+ var pickFile = function () {
+ return new Promise(function (resolve) {
+ var fileInput;
- fileInput = document.createElement("input");
- fileInput.type = "file";
- fileInput.style.position = 'fixed';
- fileInput.style.left = 0;
- fileInput.style.top = 0;
- fileInput.style.opacity = 0.001;
- document.body.appendChild(fileInput);
+ fileInput = document.createElement("input");
+ fileInput.type = "file";
+ fileInput.style.position = 'fixed';
+ fileInput.style.left = 0;
+ fileInput.style.top = 0;
+ fileInput.style.opacity = 0.001;
+ document.body.appendChild(fileInput);
- fileInput.onchange = function(e) {
- resolve(Array.prototype.slice.call(e.target.files));
- };
+ fileInput.onchange = function (e) {
+ resolve(Array.prototype.slice.call(e.target.files));
+ };
- fileInput.click();
- fileInput.parentNode.removeChild(fileInput);
- });
- };
+ fileInput.click();
+ fileInput.parentNode.removeChild(fileInput);
+ });
+ };
- return {
- pickFile: pickFile
- };
-});
+ return {
+ pickFile: pickFile
+ };
+ }
+);
@@ -1364,333 +1910,80 @@ define('tinymce/inlite/file/Picker', [
* Contributing: http://www.tinymce.com/contributing
*/
-define('tinymce/inlite/ui/Buttons', [
- 'tinymce/inlite/ui/Panel',
- 'tinymce/inlite/file/Conversions',
- 'tinymce/inlite/file/Picker',
- 'tinymce/inlite/core/Actions'
-], function (Panel, Conversions, Picker, Actions) {
- var addHeaderButtons = function (editor) {
- var formatBlock = function (name) {
- return function () {
- Actions.formatBlock(editor, name);
- };
- };
+define(
+ 'tinymce.themes.inlite.ui.Buttons',
+ [
+ 'tinymce.themes.inlite.ui.Panel',
+ 'tinymce.themes.inlite.file.Conversions',
+ 'tinymce.themes.inlite.file.Picker',
+ 'tinymce.themes.inlite.core.Actions'
+ ],
+ function (Panel, Conversions, Picker, Actions) {
+ var addHeaderButtons = function (editor) {
+ var formatBlock = function (name) {
+ return function () {
+ Actions.formatBlock(editor, name);
+ };
+ };
- for (var i = 1; i < 6; i++) {
- var name = 'h' + i;
+ for (var i = 1; i < 6; i++) {
+ var name = 'h' + i;
- editor.addButton(name, {
- text: name.toUpperCase(),
- tooltip: 'Heading ' + i,
- stateSelector: name,
- onclick: formatBlock(name),
- onPostRender: function () {
- // TODO: Remove this hack that produces bold H1-H6 when we have proper icons
- var span = this.getEl().firstChild.firstChild;
- span.style.fontWeight = 'bold';
- }
- });
- }
- };
+ editor.addButton(name, {
+ text: name.toUpperCase(),
+ tooltip: 'Heading ' + i,
+ stateSelector: name,
+ onclick: formatBlock(name),
+ onPostRender: function () {
+ // TODO: Remove this hack that produces bold H1-H6 when we have proper icons
+ var span = this.getEl().firstChild.firstChild;
+ span.style.fontWeight = 'bold';
+ }
+ });
+ }
+ };
- var addToEditor = function (editor, panel) {
- editor.addButton('quicklink', {
- icon: 'link',
- tooltip: 'Insert/Edit link',
- stateSelector: 'a[href]',
- onclick: function () {
- panel.showForm(editor, 'quicklink');
- }
- });
+ var addToEditor = function (editor, panel) {
+ editor.addButton('quicklink', {
+ icon: 'link',
+ tooltip: 'Insert/Edit link',
+ stateSelector: 'a[href]',
+ onclick: function () {
+ panel.showForm(editor, 'quicklink');
+ }
+ });
- editor.addButton('quickimage', {
- icon: 'image',
- tooltip: 'Insert image',
- onclick: function () {
- Picker.pickFile().then(function (files) {
- var blob = files[0];
+ editor.addButton('quickimage', {
+ icon: 'image',
+ tooltip: 'Insert image',
+ onclick: function () {
+ Picker.pickFile().then(function (files) {
+ var blob = files[0];
- Conversions.blobToBase64(blob).then(function (base64) {
- Actions.insertBlob(editor, base64, blob);
- });
- });
- }
- });
+ Conversions.blobToBase64(blob).then(function (base64) {
+ Actions.insertBlob(editor, base64, blob);
+ });
+ });
+ }
+ });
- editor.addButton('quicktable', {
- icon: 'table',
- tooltip: 'Insert table',
- onclick: function () {
- panel.hide();
- Actions.insertTable(editor, 2, 2);
- }
- });
+ editor.addButton('quicktable', {
+ icon: 'table',
+ tooltip: 'Insert table',
+ onclick: function () {
+ panel.hide();
+ Actions.insertTable(editor, 2, 2);
+ }
+ });
- addHeaderButtons(editor);
- };
+ addHeaderButtons(editor);
+ };
- return {
- addToEditor: addToEditor
- };
-});
-
-defineGlobal("global!tinymce.EditorManager", tinymce.EditorManager);
-/**
- * SkinLoader.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2016 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
-
-define('tinymce/inlite/core/SkinLoader', [
- 'global!tinymce.EditorManager',
- 'global!tinymce.DOM'
-], function (EditorManager, DOM) {
- var fireSkinLoaded = function (editor, callback) {
- var done = function () {
- editor._skinLoaded = true;
- editor.fire('SkinLoaded');
- callback();
- };
-
- if (editor.initialized) {
- done();
- } else {
- editor.on('init', done);
- }
- };
-
- var urlFromName = function (name) {
- var prefix = EditorManager.baseURL + '/skins/';
- return name ? prefix + name : prefix + 'lightgray';
- };
-
- var toAbsoluteUrl = function (editor, url) {
- return editor.documentBaseURI.toAbsolute(url);
- };
-
- var load = function (editor, callback) {
- var settings = editor.settings;
- var skinUrl = settings.skin_url ? toAbsoluteUrl(editor, settings.skin_url) : urlFromName(settings.skin);
-
- var done = function () {
- fireSkinLoaded(editor, callback);
- };
-
- DOM.styleSheetLoader.load(skinUrl + '/skin.min.css', done);
- editor.contentCSS.push(skinUrl + '/content.inline.min.css');
- };
-
- return {
- load: load
- };
-});
-
-
-
-/**
- * Matcher.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2016 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
-
-define('tinymce/inlite/core/Matcher', [
-], function () {
- // result :: String, Rect -> Matcher.result
- var result = function (id, rect) {
- return {
- id: id,
- rect: rect
- };
- };
-
- // match :: Editor, [(Editor -> Matcher.result | Null)] -> Matcher.result | Null
- var match = function (editor, matchers) {
- for (var i = 0; i < matchers.length; i++) {
- var f = matchers[i];
- var result = f(editor);
-
- if (result) {
- return result;
- }
- }
-
- return null;
- };
-
- return {
- match: match,
- result: result
- };
-});
-
-/**
- * SelectionMatcher.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2016 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
-
-define('tinymce/inlite/core/SelectionMatcher', [
- 'tinymce/inlite/core/Matcher',
- 'tinymce/inlite/core/Measure'
-], function (Matcher, Measure) {
- // textSelection :: String -> (Editor -> Matcher.result | Null)
- var textSelection = function (id) {
- return function (editor) {
- if (!editor.selection.isCollapsed()) {
- return Matcher.result(id, Measure.getSelectionRect(editor));
- }
-
- return null;
- };
- };
-
- // emptyTextBlock :: [Elements], String -> (Editor -> Matcher.result | Null)
- var emptyTextBlock = function (elements, id) {
- return function (editor) {
- var i, textBlockElementsMap = editor.schema.getTextBlockElements();
-
- for (i = 0; i < elements.length; i++) {
- if (elements[i].nodeName === 'TABLE') {
- return null;
- }
- }
-
- for (i = 0; i < elements.length; i++) {
- if (elements[i].nodeName in textBlockElementsMap) {
- if (editor.dom.isEmpty(elements[i])) {
- return Matcher.result(id, Measure.getSelectionRect(editor));
- }
-
- return null;
- }
- }
-
- return null;
- };
- };
-
- return {
- textSelection: textSelection,
- emptyTextBlock: emptyTextBlock
- };
-});
-
-/**
- * ElementMatcher.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2016 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
-
-define('tinymce/inlite/core/ElementMatcher', [
- 'tinymce/inlite/core/Matcher',
- 'tinymce/inlite/core/Measure'
-], function (Matcher, Measure) {
- // element :: Element, [PredicateId] -> (Editor -> Matcher.result | Null)
- var element = function (element, predicateIds) {
- return function (editor) {
- for (var i = 0; i < predicateIds.length; i++) {
- if (predicateIds[i].predicate(element)) {
- return Matcher.result(predicateIds[i].id, Measure.getElementRect(editor, element));
- }
- }
-
- return null;
- };
- };
-
- // parent :: [Elements], [PredicateId] -> (Editor -> Matcher.result | Null)
- var parent = function (elements, predicateIds) {
- return function (editor) {
- for (var i = 0; i < elements.length; i++) {
- for (var x = 0; x < predicateIds.length; x++) {
- if (predicateIds[x].predicate(elements[i])) {
- return Matcher.result(predicateIds[x].id, Measure.getElementRect(editor, elements[i]));
- }
- }
- }
-
- return null;
- };
- };
-
- return {
- element: element,
- parent: parent
- };
-});
-
-/**
- * Arr.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2016 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
-
-define('tinymce/inlite/alien/Arr', [
-], function () {
- var flatten = function (arr) {
- return arr.reduce(function (results, item) {
- return Array.isArray(item) ? results.concat(flatten(item)) : results.concat(item);
- }, []);
- };
-
- return {
- flatten: flatten
- };
-});
-
-/**
- * PredicateId.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2016 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
-
-define('tinymce/inlite/core/PredicateId', [
- 'global!tinymce.util.Tools'
-], function (Tools) {
- var create = function (id, predicate) {
- return {
- id: id,
- predicate: predicate
- };
- };
-
- // fromContextToolbars :: [ContextToolbar] -> [PredicateId]
- var fromContextToolbars = function (toolbars) {
- return Tools.map(toolbars, function (toolbar) {
- return create(toolbar.id, toolbar.predicate);
- });
- };
-
- return {
- create: create,
- fromContextToolbars: fromContextToolbars
- };
-});
+ return {
+ addToEditor: addToEditor
+ };
+ }
+);
/**
* Theme.js
@@ -1702,157 +1995,167 @@ define('tinymce/inlite/core/PredicateId', [
* Contributing: http://www.tinymce.com/contributing
*/
-define('tinymce/inlite/Theme', [
- 'global!tinymce.ThemeManager',
- 'global!tinymce.util.Delay',
- 'tinymce/inlite/ui/Panel',
- 'tinymce/inlite/ui/Buttons',
- 'tinymce/inlite/core/SkinLoader',
- 'tinymce/inlite/core/SelectionMatcher',
- 'tinymce/inlite/core/ElementMatcher',
- 'tinymce/inlite/core/Matcher',
- 'tinymce/inlite/alien/Arr',
- 'tinymce/inlite/alien/EditorSettings',
- 'tinymce/inlite/core/PredicateId'
-], function(ThemeManager, Delay, Panel, Buttons, SkinLoader, SelectionMatcher, ElementMatcher, Matcher, Arr, EditorSettings, PredicateId) {
- var getSelectionElements = function (editor) {
- var node = editor.selection.getNode();
- var elms = editor.dom.getParents(node);
- return elms;
- };
+define(
+ 'tinymce.themes.inlite.Theme',
+ [
+ 'tinymce.core.ThemeManager',
+ 'tinymce.core.ui.Api',
+ 'tinymce.core.util.Delay',
+ 'tinymce.themes.inlite.alien.Arr',
+ 'tinymce.themes.inlite.alien.EditorSettings',
+ 'tinymce.themes.inlite.core.ElementMatcher',
+ 'tinymce.themes.inlite.core.Matcher',
+ 'tinymce.themes.inlite.core.PredicateId',
+ 'tinymce.themes.inlite.core.SelectionMatcher',
+ 'tinymce.themes.inlite.core.SkinLoader',
+ 'tinymce.themes.inlite.ui.Buttons',
+ 'tinymce.themes.inlite.ui.Panel'
+ ],
+ function (
+ ThemeManager, Api, Delay, Arr, EditorSettings, ElementMatcher, Matcher,
+ PredicateId, SelectionMatcher, SkinLoader, Buttons, Panel
+ ) {
+ var getSelectionElements = function (editor) {
+ var node = editor.selection.getNode();
+ var elms = editor.dom.getParents(node);
+ return elms;
+ };
- var createToolbar = function (editor, selector, id, items) {
- var selectorPredicate = function (elm) {
- return editor.dom.is(elm, selector);
- };
+ var createToolbar = function (editor, selector, id, items) {
+ var selectorPredicate = function (elm) {
+ return editor.dom.is(elm, selector);
+ };
- return {
- predicate: selectorPredicate,
- id: id,
- items: items
- };
- };
+ return {
+ predicate: selectorPredicate,
+ id: id,
+ items: items
+ };
+ };
- var getToolbars = function (editor) {
- var contextToolbars = editor.contextToolbars;
+ var getToolbars = function (editor) {
+ var contextToolbars = editor.contextToolbars;
- return Arr.flatten([
- contextToolbars ? contextToolbars : [],
- createToolbar(editor, 'img', 'image', 'alignleft aligncenter alignright')
- ]);
- };
+ return Arr.flatten([
+ contextToolbars ? contextToolbars : [],
+ createToolbar(editor, 'img', 'image', 'alignleft aligncenter alignright')
+ ]);
+ };
- var findMatchResult = function (editor, toolbars) {
- var result, elements, contextToolbarsPredicateIds;
+ var findMatchResult = function (editor, toolbars) {
+ var result, elements, contextToolbarsPredicateIds;
- elements = getSelectionElements(editor);
- contextToolbarsPredicateIds = PredicateId.fromContextToolbars(toolbars);
+ elements = getSelectionElements(editor);
+ contextToolbarsPredicateIds = PredicateId.fromContextToolbars(toolbars);
- result = Matcher.match(editor, [
- ElementMatcher.element(elements[0], contextToolbarsPredicateIds),
- SelectionMatcher.textSelection('text'),
- SelectionMatcher.emptyTextBlock(elements, 'insert'),
- ElementMatcher.parent(elements, contextToolbarsPredicateIds)
- ]);
+ result = Matcher.match(editor, [
+ ElementMatcher.element(elements[0], contextToolbarsPredicateIds),
+ SelectionMatcher.textSelection('text'),
+ SelectionMatcher.emptyTextBlock(elements, 'insert'),
+ ElementMatcher.parent(elements, contextToolbarsPredicateIds)
+ ]);
- return result && result.rect ? result : null;
- };
+ return result && result.rect ? result : null;
+ };
- var togglePanel = function (editor, panel) {
- var toggle = function () {
- var toolbars = getToolbars(editor);
- var result = findMatchResult(editor, toolbars);
+ var togglePanel = function (editor, panel) {
+ var toggle = function () {
+ var toolbars = getToolbars(editor);
+ var result = findMatchResult(editor, toolbars);
- if (result) {
- panel.show(editor, result.id, result.rect, toolbars);
- } else {
- panel.hide();
- }
- };
+ if (result) {
+ panel.show(editor, result.id, result.rect, toolbars);
+ } else {
+ panel.hide();
+ }
+ };
- return function () {
- if (!editor.removed) {
- toggle();
- }
- };
- };
+ return function () {
+ if (!editor.removed) {
+ toggle();
+ }
+ };
+ };
- var repositionPanel = function (editor, panel) {
- return function () {
- var toolbars = getToolbars(editor);
- var result = findMatchResult(editor, toolbars);
+ var repositionPanel = function (editor, panel) {
+ return function () {
+ var toolbars = getToolbars(editor);
+ var result = findMatchResult(editor, toolbars);
- if (result) {
- panel.reposition(editor, result.id, result.rect);
- }
- };
- };
+ if (result) {
+ panel.reposition(editor, result.id, result.rect);
+ }
+ };
+ };
- var ignoreWhenFormIsVisible = function (panel, f) {
- return function () {
- if (!panel.inForm()) {
- f();
- }
- };
- };
+ var ignoreWhenFormIsVisible = function (editor, panel, f) {
+ return function () {
+ if (!editor.removed && !panel.inForm()) {
+ f();
+ }
+ };
+ };
- var bindContextualToolbarsEvents = function (editor, panel) {
- var throttledTogglePanel = Delay.throttle(togglePanel(editor, panel), 0);
- var throttledTogglePanelWhenNotInForm = Delay.throttle(ignoreWhenFormIsVisible(panel, togglePanel(editor, panel)), 0);
+ var bindContextualToolbarsEvents = function (editor, panel) {
+ var throttledTogglePanel = Delay.throttle(togglePanel(editor, panel), 0);
+ var throttledTogglePanelWhenNotInForm = Delay.throttle(ignoreWhenFormIsVisible(editor, panel, togglePanel(editor, panel)), 0);
- editor.on('blur hide ObjectResizeStart', panel.hide);
- editor.on('click', throttledTogglePanel);
- editor.on('nodeChange mouseup', throttledTogglePanelWhenNotInForm);
- editor.on('ResizeEditor keyup', throttledTogglePanel);
- editor.on('ResizeWindow', repositionPanel(editor, panel));
- editor.on('remove', panel.remove);
+ editor.on('blur hide ObjectResizeStart', panel.hide);
+ editor.on('click', throttledTogglePanel);
+ editor.on('nodeChange mouseup', throttledTogglePanelWhenNotInForm);
+ editor.on('ResizeEditor keyup', throttledTogglePanel);
+ editor.on('ResizeWindow', repositionPanel(editor, panel));
+ editor.on('remove', panel.remove);
- editor.shortcuts.add('Alt+F10', '', panel.focus);
- };
+ editor.shortcuts.add('Alt+F10', '', panel.focus);
+ };
- var overrideLinkShortcut = function (editor, panel) {
- editor.shortcuts.remove('meta+k');
- editor.shortcuts.add('meta+k', '', function () {
- var toolbars = getToolbars(editor);
- var result = result = Matcher.match(editor, [
- SelectionMatcher.textSelection('quicklink')
- ]);
+ var overrideLinkShortcut = function (editor, panel) {
+ editor.shortcuts.remove('meta+k');
+ editor.shortcuts.add('meta+k', '', function () {
+ var toolbars = getToolbars(editor);
+ var result = result = Matcher.match(editor, [
+ SelectionMatcher.textSelection('quicklink')
+ ]);
- if (result) {
- panel.show(editor, result.id, result.rect, toolbars);
- }
- });
- };
+ if (result) {
+ panel.show(editor, result.id, result.rect, toolbars);
+ }
+ });
+ };
- var renderInlineUI = function (editor, panel) {
- SkinLoader.load(editor, function () {
- bindContextualToolbarsEvents(editor, panel);
- overrideLinkShortcut(editor, panel);
- });
+ var renderInlineUI = function (editor, panel) {
+ SkinLoader.load(editor, function () {
+ bindContextualToolbarsEvents(editor, panel);
+ overrideLinkShortcut(editor, panel);
+ });
- return {};
- };
+ return {};
+ };
- var fail = function (message) {
- throw new Error(message);
- };
+ var fail = function (message) {
+ throw new Error(message);
+ };
- ThemeManager.add('inlite', function (editor) {
- var panel = new Panel();
+ ThemeManager.add('inlite', function (editor) {
+ var panel = new Panel();
- Buttons.addToEditor(editor, panel);
+ Buttons.addToEditor(editor, panel);
- var renderUI = function () {
- return editor.inline ? renderInlineUI(editor, panel) : fail('inlite theme only supports inline mode.');
- };
+ var renderUI = function () {
+ return editor.inline ? renderInlineUI(editor, panel) : fail('inlite theme only supports inline mode.');
+ };
- return {
- renderUI: renderUI
- };
- });
+ return {
+ renderUI: renderUI
+ };
+ });
- return function() {};
-});
+ Api.appendTo(window.tinymce ? window.tinymce : {});
-dem('tinymce/inlite/Theme')();
+ return function () { };
+ }
+);
+
+dem('tinymce.themes.inlite.Theme')();
})();
diff --git a/src/wp-includes/js/tinymce/themes/inlite/theme.min.js b/src/wp-includes/js/tinymce/themes/inlite/theme.min.js
index 41c4486c30..25996d2558 100644
--- a/src/wp-includes/js/tinymce/themes/inlite/theme.min.js
+++ b/src/wp-includes/js/tinymce/themes/inlite/theme.min.js
@@ -1 +1 @@
-!function(){var e={},t=function(t){for(var n=e[t],i=n.deps,o=n.defn,a=i.length,s=new Array(a),l=0;l',i+="",r=0;r';
+ html += '';
- for (y = 0; y < rows; y++) {
- html += '
';
+ html += '';
+ html += '';
+ for (y = 0; y < rows; y++) {
+ html += ' ';
- for (x = 0; x < cols; x++) {
- html += ' ';
- }
+ html += '';
+ }
- html += '';
- html += ' ';
- }
+ for (x = 0; x < cols; x++) {
+ html += ' ';
+ }
- html += '
";i+=""}return i+="
";e+=""}return e+="",e+="_dlg.js lang pack file.
- *
- * @method requireLangPack
- */
- requireLangPack : function() {
- var self = this, url = self.getWindowArg('plugin_url') || self.getWindowArg('theme_url'), settings = self.editor.settings, lang;
-
- if (settings.language !== false) {
- lang = settings.language || "en";
- }
-
- if (url && lang && self.features.translate_i18n !== false && settings.language_load !== false) {
- url += '/langs/' + lang + '_dlg.js';
-
- if (!tinymce.ScriptLoader.isDone(url)) {
- document.write('');
- tinymce.ScriptLoader.markDone(url);
- }
- }
- },
-
- /**
- * Executes a color picker on the specified element id. When the user
- * then selects a color it will be set as the value of the specified element.
- *
- * @method pickColor
- * @param {DOMEvent} e DOM event object.
- * @param {string} element_id Element id to be filled with the color value from the picker.
- */
- pickColor : function(e, element_id) {
- var el = document.getElementById(element_id), colorPickerCallback = this.editor.settings.color_picker_callback;
- if (colorPickerCallback) {
- colorPickerCallback.call(
- this.editor,
- function (value) {
- el.value = value;
- try {
- el.onchange();
- } catch (ex) {
- // Try fire event, ignore errors
- }
- },
- el.value
- );
- }
- },
-
- /**
- * Opens a filebrowser/imagebrowser this will set the output value from
- * the browser as a value on the specified element.
- *
- * @method openBrowser
- * @param {string} element_id Id of the element to set value in.
- * @param {string} type Type of browser to open image/file/flash.
- * @param {string} option Option name to get the file_broswer_callback function name from.
- */
- openBrowser : function(element_id, type) {
- tinyMCEPopup.restoreSelection();
- this.editor.execCallback('file_browser_callback', element_id, document.getElementById(element_id).value, type, window);
- },
-
- /**
- * Creates a confirm dialog. Please don't use the blocking behavior of this
- * native version use the callback method instead then it can be extended.
- *
- * @method confirm
- * @param {String} t Title for the new confirm dialog.
- * @param {function} cb Callback function to be executed after the user has selected ok or cancel.
- * @param {Object} s Optional scope to execute the callback in.
- */
- confirm : function(t, cb, s) {
- this.editor.windowManager.confirm(t, cb, s, window);
- },
-
- /**
- * Creates a alert dialog. Please don't use the blocking behavior of this
- * native version use the callback method instead then it can be extended.
- *
- * @method alert
- * @param {String} tx Title for the new alert dialog.
- * @param {function} cb Callback function to be executed after the user has selected ok.
- * @param {Object} s Optional scope to execute the callback in.
- */
- alert : function(tx, cb, s) {
- this.editor.windowManager.alert(tx, cb, s, window);
- },
-
- /**
- * Closes the current window.
- *
- * @method close
- */
- close : function() {
- var t = this;
-
- // To avoid domain relaxing issue in Opera
- function close() {
- t.editor.windowManager.close(window);
- tinymce = tinyMCE = t.editor = t.params = t.dom = t.dom.doc = null; // Cleanup
- }
-
- if (tinymce.isOpera) {
- t.getWin().setTimeout(close, 0);
- } else {
- close();
- }
- },
-
- // Internal functions
-
- _restoreSelection : function() {
- var e = window.event.srcElement;
-
- if (e.nodeName == 'INPUT' && (e.type == 'submit' || e.type == 'button')) {
- tinyMCEPopup.restoreSelection();
- }
- },
-
-/* _restoreSelection : function() {
- var e = window.event.srcElement;
-
- // If user focus a non text input or textarea
- if ((e.nodeName != 'INPUT' && e.nodeName != 'TEXTAREA') || e.type != 'text')
- tinyMCEPopup.restoreSelection();
- },*/
-
- _onDOMLoaded : function() {
- var t = tinyMCEPopup, ti = document.title, h, nv;
-
- // Translate page
- if (t.features.translate_i18n !== false) {
- var map = {
- "update": "Ok",
- "insert": "Ok",
- "cancel": "Cancel",
- "not_set": "--",
- "class_name": "Class name",
- "browse": "Browse"
- };
-
- var langCode = (tinymce.settings ? tinymce.settings : t.editor.settings).language || 'en';
- for (var key in map) {
- tinymce.i18n.data[langCode + "." + key] = tinymce.i18n.translate(map[key]);
- }
-
- h = document.body.innerHTML;
-
- // Replace a=x with a="x" in IE
- if (tinymce.isIE) {
- h = h.replace(/ (value|title|alt)=([^"][^\s>]+)/gi, ' $1="$2"');
- }
-
- document.dir = t.editor.getParam('directionality','');
-
- if ((nv = t.editor.translate(h)) && nv != h) {
- document.body.innerHTML = nv;
- }
-
- if ((nv = t.editor.translate(ti)) && nv != ti) {
- document.title = ti = nv;
- }
- }
-
- if (!t.editor.getParam('browser_preferred_colors', false) || !t.isWindow) {
- t.dom.addClass(document.body, 'forceColors');
- }
-
- document.body.style.display = '';
-
- // Restore selection in IE when focus is placed on a non textarea or input element of the type text
- if (tinymce.Env.ie) {
- if (tinymce.Env.ie < 11) {
- document.attachEvent('onmouseup', tinyMCEPopup._restoreSelection);
-
- // Add base target element for it since it would fail with modal dialogs
- t.dom.add(t.dom.select('head')[0], 'base', {target: '_self'});
- } else {
- document.addEventListener('mouseup', tinyMCEPopup._restoreSelection, false);
- }
- }
-
- t.restoreSelection();
- t.resizeToInnerSize();
-
- // Set inline title
- if (!t.isWindow) {
- t.editor.windowManager.setTitle(window, ti);
- } else {
- window.focus();
- }
-
- if (!tinymce.isIE && !t.isWindow) {
- t.dom.bind(document, 'focus', function() {
- t.editor.windowManager.focus(t.id);
- });
- }
-
- // Patch for accessibility
- tinymce.each(t.dom.select('select'), function(e) {
- e.onkeydown = tinyMCEPopup._accessHandler;
- });
-
- // Call onInit
- // Init must be called before focus so the selection won't get lost by the focus call
- tinymce.each(t.listeners, function(o) {
- o.func.call(o.scope, t.editor);
- });
-
- // Move focus to window
- if (t.getWindowArg('mce_auto_focus', true)) {
- window.focus();
-
- // Focus element with mceFocus class
- tinymce.each(document.forms, function(f) {
- tinymce.each(f.elements, function(e) {
- if (t.dom.hasClass(e, 'mceFocus') && !e.disabled) {
- e.focus();
- return false; // Break loop
- }
- });
- });
- }
-
- document.onkeyup = tinyMCEPopup._closeWinKeyHandler;
-
- if ('textContent' in document) {
- t.uiWindow.getEl('head').firstChild.textContent = document.title;
- } else {
- t.uiWindow.getEl('head').firstChild.innerText = document.title;
- }
- },
-
- _accessHandler : function(e) {
- e = e || window.event;
-
- if (e.keyCode == 13 || e.keyCode == 32) {
- var elm = e.target || e.srcElement;
-
- if (elm.onchange) {
- elm.onchange();
- }
-
- return tinymce.dom.Event.cancel(e);
- }
- },
-
- _closeWinKeyHandler : function(e) {
- e = e || window.event;
-
- if (e.keyCode == 27) {
- tinyMCEPopup.close();
- }
- },
-
- _eventProxy: function(id) {
- return function(evt) {
- tinyMCEPopup.dom.events.callNativeHandler(id, evt);
- };
- }
+ /**
+ * Initializes the popup this will be called automatically.
+ *
+ * @method init
+ */
+ init: function () {
+ var self = this, parentWin, settings, uiWindow;
+
+ // Find window & API
+ parentWin = self.getWin();
+ tinymce = tinyMCE = parentWin.tinymce;
+ self.editor = tinymce.EditorManager.activeEditor;
+ self.params = self.editor.windowManager.getParams();
+
+ uiWindow = self.editor.windowManager.windows[self.editor.windowManager.windows.length - 1];
+ self.features = uiWindow.features;
+ self.uiWindow = uiWindow;
+
+ settings = self.editor.settings;
+
+ // Setup popup CSS path(s)
+ if (settings.popup_css !== false) {
+ if (settings.popup_css) {
+ settings.popup_css = self.editor.documentBaseURI.toAbsolute(settings.popup_css);
+ } else {
+ settings.popup_css = self.editor.baseURI.toAbsolute("plugins/compat3x/css/dialog.css");
+ }
+ }
+
+ if (settings.popup_css_add) {
+ settings.popup_css += ',' + self.editor.documentBaseURI.toAbsolute(settings.popup_css_add);
+ }
+
+ // Setup local DOM
+ self.dom = self.editor.windowManager.createInstance('tinymce.plugins.dom.DOMUtils', document, {
+ ownEvents: true,
+ proxy: tinyMCEPopup._eventProxy
+ });
+
+ self.dom.bind(window, 'ready', self._onDOMLoaded, self);
+
+ // Enables you to skip loading the default css
+ if (self.features.popup_css !== false) {
+ self.dom.loadCSS(self.features.popup_css || self.editor.settings.popup_css);
+ }
+
+ // Setup on init listeners
+ self.listeners = [];
+
+ /**
+ * Fires when the popup is initialized.
+ *
+ * @event onInit
+ * @param {tinymce.Editor} editor Editor instance.
+ * @example
+ * // Alerts the selected contents when the dialog is loaded
+ * tinyMCEPopup.onInit.add(function(ed) {
+ * alert(ed.selection.getContent());
+ * });
+ *
+ * // Executes the init method on page load in some object using the SomeObject scope
+ * tinyMCEPopup.onInit.add(SomeObject.init, SomeObject);
+ */
+ self.onInit = {
+ add: function (func, scope) {
+ self.listeners.push({ func: func, scope: scope });
+ }
+ };
+
+ self.isWindow = !self.getWindowArg('mce_inline');
+ self.id = self.getWindowArg('mce_window_id');
+ },
+
+ /**
+ * Returns the reference to the parent window that opened the dialog.
+ *
+ * @method getWin
+ * @return {Window} Reference to the parent window that opened the dialog.
+ */
+ getWin: function () {
+ // Added frameElement check to fix bug: #2817583
+ return (!window.frameElement && window.dialogArguments) || opener || parent || top;
+ },
+
+ /**
+ * Returns a window argument/parameter by name.
+ *
+ * @method getWindowArg
+ * @param {String} name Name of the window argument to retrieve.
+ * @param {String} defaultValue Optional default value to return.
+ * @return {String} Argument value or default value if it wasn't found.
+ */
+ getWindowArg: function (name, defaultValue) {
+ var value = this.params[name];
+
+ return tinymce.is(value) ? value : defaultValue;
+ },
+
+ /**
+ * Returns a editor parameter/config option value.
+ *
+ * @method getParam
+ * @param {String} name Name of the editor config option to retrieve.
+ * @param {String} defaultValue Optional default value to return.
+ * @return {String} Parameter value or default value if it wasn't found.
+ */
+ getParam: function (name, defaultValue) {
+ return this.editor.getParam(name, defaultValue);
+ },
+
+ /**
+ * Returns a language item by key.
+ *
+ * @method getLang
+ * @param {String} name Language item like mydialog.something.
+ * @param {String} defaultValue Optional default value to return.
+ * @return {String} Language value for the item like "my string" or the default value if it wasn't found.
+ */
+ getLang: function (name, defaultValue) {
+ return this.editor.getLang(name, defaultValue);
+ },
+
+ /**
+ * Executed a command on editor that opened the dialog/popup.
+ *
+ * @method execCommand
+ * @param {String} cmd Command to execute.
+ * @param {Boolean} ui Optional boolean value if the UI for the command should be presented or not.
+ * @param {Object} val Optional value to pass with the comman like an URL.
+ * @param {Object} a Optional arguments object.
+ */
+ execCommand: function (cmd, ui, val, args) {
+ args = args || {};
+ args.skip_focus = 1;
+
+ this.restoreSelection();
+ return this.editor.execCommand(cmd, ui, val, args);
+ },
+
+ /**
+ * Resizes the dialog to the inner size of the window. This is needed since various browsers
+ * have different border sizes on windows.
+ *
+ * @method resizeToInnerSize
+ */
+ resizeToInnerSize: function () {
+ /*var self = this;
+
+ // Detach it to workaround a Chrome specific bug
+ // https://sourceforge.net/tracker/?func=detail&atid=635682&aid=2926339&group_id=103281
+ setTimeout(function() {
+ var vp = self.dom.getViewPort(window);
+
+ self.editor.windowManager.resizeBy(
+ self.getWindowArg('mce_width') - vp.w,
+ self.getWindowArg('mce_height') - vp.h,
+ self.id || window
+ );
+ }, 10);*/
+ },
+
+ /**
+ * Will executed the specified string when the page has been loaded. This function
+ * was added for compatibility with the 2.x branch.
+ *
+ * @method executeOnLoad
+ * @param {String} evil String to evalutate on init.
+ */
+ executeOnLoad: function (evil) {
+ this.onInit.add(function () {
+ eval(evil);
+ });
+ },
+
+ /**
+ * Stores the current editor selection for later restoration. This can be useful since some browsers
+ * looses it's selection if a control element is selected/focused inside the dialogs.
+ *
+ * @method storeSelection
+ */
+ storeSelection: function () {
+ this.editor.windowManager.bookmark = tinyMCEPopup.editor.selection.getBookmark(1);
+ },
+
+ /**
+ * Restores any stored selection. This can be useful since some browsers
+ * looses it's selection if a control element is selected/focused inside the dialogs.
+ *
+ * @method restoreSelection
+ */
+ restoreSelection: function () {
+ var self = tinyMCEPopup;
+
+ if (!self.isWindow && tinymce.isIE) {
+ self.editor.selection.moveToBookmark(self.editor.windowManager.bookmark);
+ }
+ },
+
+ /**
+ * Loads a specific dialog language pack. If you pass in plugin_url as a argument
+ * when you open the window it will load the _dlg.js lang pack file.
+ *
+ * @method requireLangPack
+ */
+ requireLangPack: function () {
+ var self = this, url = self.getWindowArg('plugin_url') || self.getWindowArg('theme_url'), settings = self.editor.settings, lang;
+
+ if (settings.language !== false) {
+ lang = settings.language || "en";
+ }
+
+ if (url && lang && self.features.translate_i18n !== false && settings.language_load !== false) {
+ url += '/langs/' + lang + '_dlg.js';
+
+ if (!tinymce.ScriptLoader.isDone(url)) {
+ document.write('');
+ tinymce.ScriptLoader.markDone(url);
+ }
+ }
+ },
+
+ /**
+ * Executes a color picker on the specified element id. When the user
+ * then selects a color it will be set as the value of the specified element.
+ *
+ * @method pickColor
+ * @param {DOMEvent} e DOM event object.
+ * @param {string} element_id Element id to be filled with the color value from the picker.
+ */
+ pickColor: function (e, element_id) {
+ var el = document.getElementById(element_id), colorPickerCallback = this.editor.settings.color_picker_callback;
+ if (colorPickerCallback) {
+ colorPickerCallback.call(
+ this.editor,
+ function (value) {
+ el.value = value;
+ try {
+ el.onchange();
+ } catch (ex) {
+ // Try fire event, ignore errors
+ }
+ },
+ el.value
+ );
+ }
+ },
+
+ /**
+ * Opens a filebrowser/imagebrowser this will set the output value from
+ * the browser as a value on the specified element.
+ *
+ * @method openBrowser
+ * @param {string} element_id Id of the element to set value in.
+ * @param {string} type Type of browser to open image/file/flash.
+ * @param {string} option Option name to get the file_broswer_callback function name from.
+ */
+ openBrowser: function (element_id, type) {
+ tinyMCEPopup.restoreSelection();
+ this.editor.execCallback('file_browser_callback', element_id, document.getElementById(element_id).value, type, window);
+ },
+
+ /**
+ * Creates a confirm dialog. Please don't use the blocking behavior of this
+ * native version use the callback method instead then it can be extended.
+ *
+ * @method confirm
+ * @param {String} t Title for the new confirm dialog.
+ * @param {function} cb Callback function to be executed after the user has selected ok or cancel.
+ * @param {Object} s Optional scope to execute the callback in.
+ */
+ confirm: function (t, cb, s) {
+ this.editor.windowManager.confirm(t, cb, s, window);
+ },
+
+ /**
+ * Creates a alert dialog. Please don't use the blocking behavior of this
+ * native version use the callback method instead then it can be extended.
+ *
+ * @method alert
+ * @param {String} tx Title for the new alert dialog.
+ * @param {function} cb Callback function to be executed after the user has selected ok.
+ * @param {Object} s Optional scope to execute the callback in.
+ */
+ alert: function (tx, cb, s) {
+ this.editor.windowManager.alert(tx, cb, s, window);
+ },
+
+ /**
+ * Closes the current window.
+ *
+ * @method close
+ */
+ close: function () {
+ var t = this;
+
+ // To avoid domain relaxing issue in Opera
+ function close() {
+ t.editor.windowManager.close(window);
+ tinymce = tinyMCE = t.editor = t.params = t.dom = t.dom.doc = null; // Cleanup
+ }
+
+ if (tinymce.isOpera) {
+ t.getWin().setTimeout(close, 0);
+ } else {
+ close();
+ }
+ },
+
+ // Internal functions
+
+ _restoreSelection: function () {
+ var e = window.event.srcElement;
+
+ if (e.nodeName == 'INPUT' && (e.type == 'submit' || e.type == 'button')) {
+ tinyMCEPopup.restoreSelection();
+ }
+ },
+
+ /* _restoreSelection : function() {
+ var e = window.event.srcElement;
+
+ // If user focus a non text input or textarea
+ if ((e.nodeName != 'INPUT' && e.nodeName != 'TEXTAREA') || e.type != 'text')
+ tinyMCEPopup.restoreSelection();
+ },*/
+
+ _onDOMLoaded: function () {
+ var t = tinyMCEPopup, ti = document.title, h, nv;
+
+ // Translate page
+ if (t.features.translate_i18n !== false) {
+ var map = {
+ "update": "Ok",
+ "insert": "Ok",
+ "cancel": "Cancel",
+ "not_set": "--",
+ "class_name": "Class name",
+ "browse": "Browse"
+ };
+
+ var langCode = (tinymce.settings ? tinymce.settings : t.editor.settings).language || 'en';
+ for (var key in map) {
+ tinymce.i18n.data[langCode + "." + key] = tinymce.i18n.translate(map[key]);
+ }
+
+ h = document.body.innerHTML;
+
+ // Replace a=x with a="x" in IE
+ if (tinymce.isIE) {
+ h = h.replace(/ (value|title|alt)=([^"][^\s>]+)/gi, ' $1="$2"');
+ }
+
+ document.dir = t.editor.getParam('directionality', '');
+
+ if ((nv = t.editor.translate(h)) && nv != h) {
+ document.body.innerHTML = nv;
+ }
+
+ if ((nv = t.editor.translate(ti)) && nv != ti) {
+ document.title = ti = nv;
+ }
+ }
+
+ if (!t.editor.getParam('browser_preferred_colors', false) || !t.isWindow) {
+ t.dom.addClass(document.body, 'forceColors');
+ }
+
+ document.body.style.display = '';
+
+ // Restore selection in IE when focus is placed on a non textarea or input element of the type text
+ if (tinymce.Env.ie) {
+ if (tinymce.Env.ie < 11) {
+ document.attachEvent('onmouseup', tinyMCEPopup._restoreSelection);
+
+ // Add base target element for it since it would fail with modal dialogs
+ t.dom.add(t.dom.select('head')[0], 'base', { target: '_self' });
+ } else {
+ document.addEventListener('mouseup', tinyMCEPopup._restoreSelection, false);
+ }
+ }
+
+ t.restoreSelection();
+ t.resizeToInnerSize();
+
+ // Set inline title
+ if (!t.isWindow) {
+ t.editor.windowManager.setTitle(window, ti);
+ } else {
+ window.focus();
+ }
+
+ if (!tinymce.isIE && !t.isWindow) {
+ t.dom.bind(document, 'focus', function () {
+ t.editor.windowManager.focus(t.id);
+ });
+ }
+
+ // Patch for accessibility
+ tinymce.each(t.dom.select('select'), function (e) {
+ e.onkeydown = tinyMCEPopup._accessHandler;
+ });
+
+ // Call onInit
+ // Init must be called before focus so the selection won't get lost by the focus call
+ tinymce.each(t.listeners, function (o) {
+ o.func.call(o.scope, t.editor);
+ });
+
+ // Move focus to window
+ if (t.getWindowArg('mce_auto_focus', true)) {
+ window.focus();
+
+ // Focus element with mceFocus class
+ tinymce.each(document.forms, function (f) {
+ tinymce.each(f.elements, function (e) {
+ if (t.dom.hasClass(e, 'mceFocus') && !e.disabled) {
+ e.focus();
+ return false; // Break loop
+ }
+ });
+ });
+ }
+
+ document.onkeyup = tinyMCEPopup._closeWinKeyHandler;
+
+ if ('textContent' in document) {
+ t.uiWindow.getEl('head').firstChild.textContent = document.title;
+ } else {
+ t.uiWindow.getEl('head').firstChild.innerText = document.title;
+ }
+ },
+
+ _accessHandler: function (e) {
+ e = e || window.event;
+
+ if (e.keyCode == 13 || e.keyCode == 32) {
+ var elm = e.target || e.srcElement;
+
+ if (elm.onchange) {
+ elm.onchange();
+ }
+
+ return tinymce.dom.Event.cancel(e);
+ }
+ },
+
+ _closeWinKeyHandler: function (e) {
+ e = e || window.event;
+
+ if (e.keyCode == 27) {
+ tinyMCEPopup.close();
+ }
+ },
+
+ _eventProxy: function (id) {
+ return function (evt) {
+ tinyMCEPopup.dom.events.callNativeHandler(id, evt);
+ };
+ }
};
tinyMCEPopup.init();
-tinymce.util.Dispatcher = function(scope) {
- this.scope = scope || this;
- this.listeners = [];
+tinymce.util.Dispatcher = function (scope) {
+ this.scope = scope || this;
+ this.listeners = [];
- this.add = function(callback, scope) {
- this.listeners.push({cb : callback, scope : scope || this.scope});
+ this.add = function (callback, scope) {
+ this.listeners.push({ cb: callback, scope: scope || this.scope });
- return callback;
- };
+ return callback;
+ };
- this.addToTop = function(callback, scope) {
- var self = this, listener = {cb : callback, scope : scope || self.scope};
+ this.addToTop = function (callback, scope) {
+ var self = this, listener = { cb: callback, scope: scope || self.scope };
- // Create new listeners if addToTop is executed in a dispatch loop
- if (self.inDispatch) {
- self.listeners = [listener].concat(self.listeners);
- } else {
- self.listeners.unshift(listener);
- }
+ // Create new listeners if addToTop is executed in a dispatch loop
+ if (self.inDispatch) {
+ self.listeners = [listener].concat(self.listeners);
+ } else {
+ self.listeners.unshift(listener);
+ }
- return callback;
- };
+ return callback;
+ };
- this.remove = function(callback) {
- var listeners = this.listeners, output = null;
+ this.remove = function (callback) {
+ var listeners = this.listeners, output = null;
- tinymce.each(listeners, function(listener, i) {
- if (callback == listener.cb) {
- output = listener;
- listeners.splice(i, 1);
- return false;
- }
- });
+ tinymce.each(listeners, function (listener, i) {
+ if (callback == listener.cb) {
+ output = listener;
+ listeners.splice(i, 1);
+ return false;
+ }
+ });
- return output;
- };
+ return output;
+ };
- this.dispatch = function() {
- var self = this, returnValue, args = arguments, i, listeners = self.listeners, listener;
+ this.dispatch = function () {
+ var self = this, returnValue, args = arguments, i, listeners = self.listeners, listener;
- self.inDispatch = true;
+ self.inDispatch = true;
- // Needs to be a real loop since the listener count might change while looping
- // And this is also more efficient
- for (i = 0; i < listeners.length; i++) {
- listener = listeners[i];
- returnValue = listener.cb.apply(listener.scope, args.length > 0 ? args : [listener.scope]);
+ // Needs to be a real loop since the listener count might change while looping
+ // And this is also more efficient
+ for (i = 0; i < listeners.length; i++) {
+ listener = listeners[i];
+ returnValue = listener.cb.apply(listener.scope, args.length > 0 ? args : [listener.scope]);
- if (returnValue === false) {
- break;
- }
- }
+ if (returnValue === false) {
+ break;
+ }
+ }
- self.inDispatch = false;
+ self.inDispatch = false;
- return returnValue;
- };
+ return returnValue;
+ };
};
diff --git a/src/wp-includes/js/tinymce/tinymce.js b/src/wp-includes/js/tinymce/tinymce.js
index 8ef0e938b0..1986dd986c 100644
--- a/src/wp-includes/js/tinymce/tinymce.js
+++ b/src/wp-includes/js/tinymce/tinymce.js
@@ -1,111 +1,93 @@
-// 4.5.6 (2017-03-30)
+(function () {
-/**
- * Compiled inline version. (Library mode)
- */
+var defs = {}; // id -> {dependencies, definition, instance (possibly undefined)}
-/*jshint smarttabs:true, undef:true, latedef:true, curly:true, bitwise:true, camelcase:true */
-/*globals $code */
+// Used when there is no 'main' module.
+// The name is probably (hopefully) unique so minification removes for releases.
+var register_3795 = function (id) {
+ var module = dem(id);
+ var fragments = id.split('.');
+ var target = Function('return this;')();
+ for (var i = 0; i < fragments.length - 1; ++i) {
+ if (target[fragments[i]] === undefined)
+ target[fragments[i]] = {};
+ target = target[fragments[i]];
+ }
+ target[fragments[fragments.length - 1]] = module;
+};
-(function(exports, undefined) {
- "use strict";
+var instantiate = function (id) {
+ var actual = defs[id];
+ var dependencies = actual.deps;
+ var definition = actual.defn;
+ var len = dependencies.length;
+ var instances = new Array(len);
+ for (var i = 0; i < len; ++i)
+ instances[i] = dem(dependencies[i]);
+ var defResult = definition.apply(null, instances);
+ if (defResult === undefined)
+ throw 'module [' + id + '] returned undefined';
+ actual.instance = defResult;
+};
- var modules = {};
+var def = function (id, dependencies, definition) {
+ if (typeof id !== 'string')
+ throw 'module id must be a string';
+ else if (dependencies === undefined)
+ throw 'no dependencies for ' + id;
+ else if (definition === undefined)
+ throw 'no definition function for ' + id;
+ defs[id] = {
+ deps: dependencies,
+ defn: definition,
+ instance: undefined
+ };
+};
- function require(ids, callback) {
- var module, defs = [];
+var dem = function (id) {
+ var actual = defs[id];
+ if (actual === undefined)
+ throw 'module [' + id + '] was undefined';
+ else if (actual.instance === undefined)
+ instantiate(id);
+ return actual.instance;
+};
- 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];
- }
+var req = function (ids, callback) {
+ var len = ids.length;
+ var instances = new Array(len);
+ for (var i = 0; i < len; ++i)
+ instances.push(dem(ids[i]));
+ callback.apply(null, callback);
+};
- defs.push(module);
- }
+var ephox = {};
- 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';
- }
-
- if (dependencies === undefined) {
- throw 'invalid module definition, dependencies must be specified';
- }
-
- if (definition === undefined) {
- throw 'invalid module definition, definition function must be specified';
- }
-
- require(dependencies, function() {
- modules[id] = definition.apply(null, arguments);
- });
- }
-
- function defined(id) {
- return !!modules[id];
- }
-
- function resolve(id) {
- var target = exports;
- var fragments = id.split(/[.\/]/);
-
- for (var fi = 0; fi < fragments.length; ++fi) {
- if (!target[fragments[fi]]) {
- return;
- }
-
- target = target[fragments[fi]];
- }
-
- return target;
- }
-
- function expose(ids) {
- var i, target, id, fragments, privateModules;
-
- for (i = 0; i < ids.length; i++) {
- target = exports;
- id = ids[i];
- fragments = id.split(/[.\/]/);
-
- for (var fi = 0; fi < fragments.length - 1; ++fi) {
- if (target[fragments[fi]] === undefined) {
- target[fragments[fi]] = {};
- }
-
- target = target[fragments[fi]];
- }
-
- 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;
- }
- }
-
-// Included from: js/tinymce/classes/geom/Rect.js
+ephox.bolt = {
+ module: {
+ api: {
+ define: def,
+ require: req,
+ demand: dem
+ }
+ }
+};
+var define = def;
+var require = req;
+var demand = dem;
+// this helps with minificiation when using a lot of global references
+var defineGlobal = function (id, ref) {
+ define(id, [], function () { return ref; });
+};
+/*jsc
+["tinymce.core.api.Main","tinymce.core.api.Tinymce","tinymce.core.Register","tinymce.core.geom.Rect","tinymce.core.util.Promise","tinymce.core.util.Delay","tinymce.core.Env","tinymce.core.dom.EventUtils","tinymce.core.dom.Sizzle","tinymce.core.util.Tools","tinymce.core.dom.DomQuery","tinymce.core.html.Styles","tinymce.core.dom.TreeWalker","tinymce.core.html.Entities","tinymce.core.dom.DOMUtils","tinymce.core.dom.ScriptLoader","tinymce.core.AddOnManager","tinymce.core.dom.RangeUtils","tinymce.core.html.Node","tinymce.core.html.Schema","tinymce.core.html.SaxParser","tinymce.core.html.DomParser","tinymce.core.html.Writer","tinymce.core.html.Serializer","tinymce.core.dom.Serializer","tinymce.core.util.VK","tinymce.core.dom.ControlSelection","tinymce.core.dom.BookmarkManager","tinymce.core.dom.Selection","tinymce.core.Formatter","tinymce.core.UndoManager","tinymce.core.EditorCommands","tinymce.core.util.URI","tinymce.core.util.Class","tinymce.core.util.EventDispatcher","tinymce.core.util.Observable","tinymce.core.WindowManager","tinymce.core.NotificationManager","tinymce.core.EditorObservable","tinymce.core.Shortcuts","tinymce.core.Editor","tinymce.core.util.I18n","tinymce.core.FocusManager","tinymce.core.EditorManager","tinymce.core.util.XHR","tinymce.core.util.JSON","tinymce.core.util.JSONRequest","tinymce.core.util.JSONP","tinymce.core.util.LocalStorage","tinymce.core.api.Compat","tinymce.core.util.Color","tinymce.core.ui.Api","tinymce.core.util.Arr","tinymce.core.dom.Range","tinymce.core.dom.StyleSheetLoader","tinymce.core.dom.NodeType","tinymce.core.caret.CaretContainer","tinymce.core.text.Zwsp","tinymce.core.caret.CaretBookmark","tinymce.core.caret.CaretPosition","tinymce.core.dom.ScrollIntoView","tinymce.core.dom.TridentSelection","tinymce.core.dom.ElementUtils","tinymce.core.util.Fun","tinymce.core.fmt.Preview","tinymce.core.fmt.Hooks","tinymce.core.undo.Levels","tinymce.core.delete.DeleteCommands","tinymce.core.InsertContent","global!document","tinymce.core.ui.Window","tinymce.core.ui.MessageBox","tinymce.core.ui.Notification","tinymce.core.init.Render","tinymce.core.Mode","tinymce.core.ui.Sidebar","tinymce.core.util.Uuid","tinymce.core.ErrorReporter","tinymce.core.LegacyInput","tinymce.core.ui.Selector","tinymce.core.ui.Collection","tinymce.core.ui.ReflowQueue","tinymce.core.ui.Control","tinymce.core.ui.Factory","tinymce.core.ui.KeyboardNavigation","tinymce.core.ui.Container","tinymce.core.ui.DragHelper","tinymce.core.ui.Scrollable","tinymce.core.ui.Panel","tinymce.core.ui.Movable","tinymce.core.ui.Resizable","tinymce.core.ui.FloatPanel","tinymce.core.ui.Tooltip","tinymce.core.ui.Widget","tinymce.core.ui.Progress","tinymce.core.ui.Layout","tinymce.core.ui.AbsoluteLayout","tinymce.core.ui.Button","tinymce.core.ui.ButtonGroup","tinymce.core.ui.Checkbox","tinymce.core.ui.ComboBox","tinymce.core.ui.ColorBox","tinymce.core.ui.PanelButton","tinymce.core.ui.ColorButton","tinymce.core.ui.ColorPicker","tinymce.core.ui.Path","tinymce.core.ui.ElementPath","tinymce.core.ui.FormItem","tinymce.core.ui.Form","tinymce.core.ui.FieldSet","tinymce.core.ui.FilePicker","tinymce.core.ui.FitLayout","tinymce.core.ui.FlexLayout","tinymce.core.ui.FlowLayout","tinymce.core.ui.FormatControls","tinymce.core.ui.GridLayout","tinymce.core.ui.Iframe","tinymce.core.ui.InfoBox","tinymce.core.ui.Label","tinymce.core.ui.Toolbar","tinymce.core.ui.MenuBar","tinymce.core.ui.MenuButton","tinymce.core.ui.MenuItem","tinymce.core.ui.Throbber","tinymce.core.ui.Menu","tinymce.core.ui.ListBox","tinymce.core.ui.Radio","tinymce.core.ui.ResizeHandle","tinymce.core.ui.SelectBox","tinymce.core.ui.Slider","tinymce.core.ui.Spacer","tinymce.core.ui.SplitButton","tinymce.core.ui.StackLayout","tinymce.core.ui.TabPanel","tinymce.core.ui.TextBox","ephox.katamari.api.Arr","ephox.katamari.api.Fun","ephox.katamari.api.Future","ephox.katamari.api.Futures","ephox.katamari.api.Result","tinymce.core.caret.CaretCandidate","tinymce.core.geom.ClientRect","tinymce.core.text.ExtendingChar","tinymce.core.undo.Fragments","tinymce.core.delete.BlockBoundaryDelete","tinymce.core.delete.BlockRangeDelete","tinymce.core.delete.CefDelete","tinymce.core.delete.InlineBoundaryDelete","tinymce.core.caret.CaretWalker","tinymce.core.dom.RangeNormalizer","tinymce.core.InsertList","tinymce.core.data.ObservableObject","tinymce.core.ui.DomUtils","tinymce.core.ui.BoxUtils","tinymce.core.ui.ClassList","global!window","tinymce.core.init.Init","tinymce.core.PluginManager","tinymce.core.ThemeManager","tinymce.core.content.LinkTargets","tinymce.core.fmt.FontInfo","ephox.katamari.api.Option","global!Array","global!Error","global!String","ephox.katamari.api.LazyValue","ephox.katamari.async.Bounce","ephox.katamari.async.AsyncValues","tinymce.core.undo.Diff","tinymce.core.delete.BlockBoundary","tinymce.core.delete.MergeBlocks","ephox.katamari.api.Options","ephox.sugar.api.dom.Compare","ephox.sugar.api.node.Element","tinymce.core.delete.DeleteUtils","tinymce.core.caret.CaretUtils","tinymce.core.delete.CefDeleteAction","tinymce.core.delete.DeleteElement","tinymce.core.keyboard.BoundaryCaret","tinymce.core.keyboard.BoundaryLocation","tinymce.core.keyboard.BoundarySelection","tinymce.core.keyboard.InlineUtils","tinymce.core.caret.CaretFinder","tinymce.core.data.Binding","tinymce.core.init.InitContentBody","global!Object","global!setTimeout","ephox.katamari.api.Struct","ephox.sand.api.Node","ephox.sand.api.PlatformDetection","ephox.sugar.api.search.Selectors","global!console","ephox.sugar.api.node.Node","ephox.sugar.api.search.PredicateFind","ephox.sugar.api.search.Traverse","tinymce.core.dom.Empty","ephox.sugar.api.dom.Insert","ephox.sugar.api.dom.Remove","ephox.katamari.api.Adt","tinymce.core.text.Bidi","tinymce.core.caret.CaretContainerInline","tinymce.core.caret.CaretContainerRemove","tinymce.core.util.LazyEvaluator","ephox.katamari.api.Cell","tinymce.core.caret.CaretContainerInput","tinymce.core.EditorUpload","tinymce.core.ForceBlocks","tinymce.core.keyboard.KeyboardOverrides","tinymce.core.NodeChange","tinymce.core.SelectionOverrides","tinymce.core.util.Quirks","ephox.katamari.data.Immutable","ephox.katamari.data.MixedBag","ephox.sand.util.Global","ephox.katamari.api.Thunk","ephox.sand.core.PlatformDetection","global!navigator","ephox.sugar.api.node.NodeTypes","ephox.katamari.api.Type","ephox.sugar.api.node.Body","ephox.sugar.impl.ClosestOrAncestor","ephox.sugar.alien.Recurse","ephox.sugar.api.search.SelectorExists","ephox.sugar.api.dom.InsertAll","ephox.katamari.api.Obj","tinymce.core.file.Uploader","tinymce.core.file.ImageScanner","tinymce.core.file.BlobCache","tinymce.core.file.UploadStatus","tinymce.core.keyboard.ArrowKeys","tinymce.core.keyboard.DeleteBackspaceKeys","tinymce.core.keyboard.EnterKey","tinymce.core.keyboard.SpaceKey","tinymce.core.caret.FakeCaret","tinymce.core.caret.LineWalker","tinymce.core.caret.LineUtils","tinymce.core.DragDropOverrides","tinymce.core.dom.NodePath","ephox.katamari.util.BagUtils","ephox.katamari.api.Resolve","ephox.sand.core.Browser","ephox.sand.core.OperatingSystem","ephox.sand.detect.DeviceType","ephox.sand.detect.UaString","ephox.sand.info.PlatformInfo","ephox.sugar.api.search.SelectorFind","tinymce.core.file.Conversions","global!URL","tinymce.core.keyboard.MatchKeys","tinymce.core.keyboard.InsertSpace","tinymce.core.dom.Dimensions","tinymce.core.dom.MousePosition","ephox.katamari.api.Global","ephox.sand.detect.Version","ephox.katamari.api.Strings","ephox.katamari.api.Merger","global!Number","ephox.katamari.str.StrAppend","ephox.katamari.str.StringParts"]
+jsc*/
/**
* Rect.js
*
* Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
+ * Copyright (c) 1999-2017 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
@@ -116,213 +98,215 @@
*
* @class tinymce.geom.Rect
*/
-define("tinymce/geom/Rect", [
-], function() {
- "use strict";
+define(
+ 'tinymce.core.geom.Rect',
+ [
+ ],
+ function () {
+ "use strict";
- var min = Math.min, max = Math.max, round = Math.round;
+ var min = Math.min, max = Math.max, round = Math.round;
- /**
- * Returns the rect positioned based on the relative position name
- * to the target rect.
- *
- * @method relativePosition
- * @param {Rect} rect Source rect to modify into a new rect.
- * @param {Rect} targetRect Rect to move relative to based on the rel option.
- * @param {String} rel Relative position. For example: tr-bl.
- */
- function relativePosition(rect, targetRect, rel) {
- var x, y, w, h, targetW, targetH;
+ /**
+ * Returns the rect positioned based on the relative position name
+ * to the target rect.
+ *
+ * @method relativePosition
+ * @param {Rect} rect Source rect to modify into a new rect.
+ * @param {Rect} targetRect Rect to move relative to based on the rel option.
+ * @param {String} rel Relative position. For example: tr-bl.
+ */
+ function relativePosition(rect, targetRect, rel) {
+ var x, y, w, h, targetW, targetH;
- x = targetRect.x;
- y = targetRect.y;
- w = rect.w;
- h = rect.h;
- targetW = targetRect.w;
- targetH = targetRect.h;
+ x = targetRect.x;
+ y = targetRect.y;
+ w = rect.w;
+ h = rect.h;
+ targetW = targetRect.w;
+ targetH = targetRect.h;
- rel = (rel || '').split('');
+ rel = (rel || '').split('');
- if (rel[0] === 'b') {
- y += targetH;
- }
+ if (rel[0] === 'b') {
+ y += targetH;
+ }
- if (rel[1] === 'r') {
- x += targetW;
- }
+ if (rel[1] === 'r') {
+ x += targetW;
+ }
- if (rel[0] === 'c') {
- y += round(targetH / 2);
- }
+ if (rel[0] === 'c') {
+ y += round(targetH / 2);
+ }
- if (rel[1] === 'c') {
- x += round(targetW / 2);
- }
+ if (rel[1] === 'c') {
+ x += round(targetW / 2);
+ }
- if (rel[3] === 'b') {
- y -= h;
- }
+ if (rel[3] === 'b') {
+ y -= h;
+ }
- if (rel[4] === 'r') {
- x -= w;
- }
+ if (rel[4] === 'r') {
+ x -= w;
+ }
- if (rel[3] === 'c') {
- y -= round(h / 2);
- }
+ if (rel[3] === 'c') {
+ y -= round(h / 2);
+ }
- if (rel[4] === 'c') {
- x -= round(w / 2);
- }
+ if (rel[4] === 'c') {
+ x -= round(w / 2);
+ }
- return create(x, y, w, h);
- }
+ return create(x, y, w, h);
+ }
- /**
- * Tests various positions to get the most suitable one.
- *
- * @method findBestRelativePosition
- * @param {Rect} rect Rect to use as source.
- * @param {Rect} targetRect Rect to move relative to.
- * @param {Rect} constrainRect Rect to constrain within.
- * @param {Array} rels Array of relative positions to test against.
- */
- function findBestRelativePosition(rect, targetRect, constrainRect, rels) {
- var pos, i;
+ /**
+ * Tests various positions to get the most suitable one.
+ *
+ * @method findBestRelativePosition
+ * @param {Rect} rect Rect to use as source.
+ * @param {Rect} targetRect Rect to move relative to.
+ * @param {Rect} constrainRect Rect to constrain within.
+ * @param {Array} rels Array of relative positions to test against.
+ */
+ function findBestRelativePosition(rect, targetRect, constrainRect, rels) {
+ var pos, i;
- for (i = 0; i < rels.length; i++) {
- pos = relativePosition(rect, targetRect, rels[i]);
+ for (i = 0; i < rels.length; i++) {
+ pos = relativePosition(rect, targetRect, rels[i]);
- if (pos.x >= constrainRect.x && pos.x + pos.w <= constrainRect.w + constrainRect.x &&
- pos.y >= constrainRect.y && pos.y + pos.h <= constrainRect.h + constrainRect.y) {
- return rels[i];
- }
- }
+ if (pos.x >= constrainRect.x && pos.x + pos.w <= constrainRect.w + constrainRect.x &&
+ pos.y >= constrainRect.y && pos.y + pos.h <= constrainRect.h + constrainRect.y) {
+ return rels[i];
+ }
+ }
- return null;
- }
+ return null;
+ }
- /**
- * Inflates the rect in all directions.
- *
- * @method inflate
- * @param {Rect} rect Rect to expand.
- * @param {Number} w Relative width to expand by.
- * @param {Number} h Relative height to expand by.
- * @return {Rect} New expanded rect.
- */
- function inflate(rect, w, h) {
- return create(rect.x - w, rect.y - h, rect.w + w * 2, rect.h + h * 2);
- }
+ /**
+ * Inflates the rect in all directions.
+ *
+ * @method inflate
+ * @param {Rect} rect Rect to expand.
+ * @param {Number} w Relative width to expand by.
+ * @param {Number} h Relative height to expand by.
+ * @return {Rect} New expanded rect.
+ */
+ function inflate(rect, w, h) {
+ return create(rect.x - w, rect.y - h, rect.w + w * 2, rect.h + h * 2);
+ }
- /**
- * Returns the intersection of the specified rectangles.
- *
- * @method intersect
- * @param {Rect} rect The first rectangle to compare.
- * @param {Rect} cropRect The second rectangle to compare.
- * @return {Rect} The intersection of the two rectangles or null if they don't intersect.
- */
- function intersect(rect, cropRect) {
- var x1, y1, x2, y2;
+ /**
+ * Returns the intersection of the specified rectangles.
+ *
+ * @method intersect
+ * @param {Rect} rect The first rectangle to compare.
+ * @param {Rect} cropRect The second rectangle to compare.
+ * @return {Rect} The intersection of the two rectangles or null if they don't intersect.
+ */
+ function intersect(rect, cropRect) {
+ var x1, y1, x2, y2;
- x1 = max(rect.x, cropRect.x);
- y1 = max(rect.y, cropRect.y);
- x2 = min(rect.x + rect.w, cropRect.x + cropRect.w);
- y2 = min(rect.y + rect.h, cropRect.y + cropRect.h);
+ x1 = max(rect.x, cropRect.x);
+ y1 = max(rect.y, cropRect.y);
+ x2 = min(rect.x + rect.w, cropRect.x + cropRect.w);
+ y2 = min(rect.y + rect.h, cropRect.y + cropRect.h);
- if (x2 - x1 < 0 || y2 - y1 < 0) {
- return null;
- }
+ if (x2 - x1 < 0 || y2 - y1 < 0) {
+ return null;
+ }
- return create(x1, y1, x2 - x1, y2 - y1);
- }
+ return create(x1, y1, x2 - x1, y2 - y1);
+ }
- /**
- * Returns a rect clamped within the specified clamp rect. This forces the
- * rect to be inside the clamp rect.
- *
- * @method clamp
- * @param {Rect} rect Rectangle to force within clamp rect.
- * @param {Rect} clampRect Rectable to force within.
- * @param {Boolean} fixedSize True/false if size should be fixed.
- * @return {Rect} Clamped rect.
- */
- function clamp(rect, clampRect, fixedSize) {
- var underflowX1, underflowY1, overflowX2, overflowY2,
- x1, y1, x2, y2, cx2, cy2;
+ /**
+ * Returns a rect clamped within the specified clamp rect. This forces the
+ * rect to be inside the clamp rect.
+ *
+ * @method clamp
+ * @param {Rect} rect Rectangle to force within clamp rect.
+ * @param {Rect} clampRect Rectable to force within.
+ * @param {Boolean} fixedSize True/false if size should be fixed.
+ * @return {Rect} Clamped rect.
+ */
+ function clamp(rect, clampRect, fixedSize) {
+ var underflowX1, underflowY1, overflowX2, overflowY2,
+ x1, y1, x2, y2, cx2, cy2;
- x1 = rect.x;
- y1 = rect.y;
- x2 = rect.x + rect.w;
- y2 = rect.y + rect.h;
- cx2 = clampRect.x + clampRect.w;
- cy2 = clampRect.y + clampRect.h;
+ x1 = rect.x;
+ y1 = rect.y;
+ x2 = rect.x + rect.w;
+ y2 = rect.y + rect.h;
+ cx2 = clampRect.x + clampRect.w;
+ cy2 = clampRect.y + clampRect.h;
- underflowX1 = max(0, clampRect.x - x1);
- underflowY1 = max(0, clampRect.y - y1);
- overflowX2 = max(0, x2 - cx2);
- overflowY2 = max(0, y2 - cy2);
+ underflowX1 = max(0, clampRect.x - x1);
+ underflowY1 = max(0, clampRect.y - y1);
+ overflowX2 = max(0, x2 - cx2);
+ overflowY2 = max(0, y2 - cy2);
- x1 += underflowX1;
- y1 += underflowY1;
+ x1 += underflowX1;
+ y1 += underflowY1;
- if (fixedSize) {
- x2 += underflowX1;
- y2 += underflowY1;
- x1 -= overflowX2;
- y1 -= overflowY2;
- }
+ if (fixedSize) {
+ x2 += underflowX1;
+ y2 += underflowY1;
+ x1 -= overflowX2;
+ y1 -= overflowY2;
+ }
- x2 -= overflowX2;
- y2 -= overflowY2;
+ x2 -= overflowX2;
+ y2 -= overflowY2;
- return create(x1, y1, x2 - x1, y2 - y1);
- }
+ return create(x1, y1, x2 - x1, y2 - y1);
+ }
- /**
- * Creates a new rectangle object.
- *
- * @method create
- * @param {Number} x Rectangle x location.
- * @param {Number} y Rectangle y location.
- * @param {Number} w Rectangle width.
- * @param {Number} h Rectangle height.
- * @return {Rect} New rectangle object.
- */
- function create(x, y, w, h) {
- return {x: x, y: y, w: w, h: h};
- }
+ /**
+ * Creates a new rectangle object.
+ *
+ * @method create
+ * @param {Number} x Rectangle x location.
+ * @param {Number} y Rectangle y location.
+ * @param {Number} w Rectangle width.
+ * @param {Number} h Rectangle height.
+ * @return {Rect} New rectangle object.
+ */
+ function create(x, y, w, h) {
+ return { x: x, y: y, w: w, h: h };
+ }
- /**
- * Creates a new rectangle object form a clientRects object.
- *
- * @method fromClientRect
- * @param {ClientRect} clientRect DOM ClientRect object.
- * @return {Rect} New rectangle object.
- */
- function fromClientRect(clientRect) {
- return create(clientRect.left, clientRect.top, clientRect.width, clientRect.height);
- }
+ /**
+ * Creates a new rectangle object form a clientRects object.
+ *
+ * @method fromClientRect
+ * @param {ClientRect} clientRect DOM ClientRect object.
+ * @return {Rect} New rectangle object.
+ */
+ function fromClientRect(clientRect) {
+ return create(clientRect.left, clientRect.top, clientRect.width, clientRect.height);
+ }
- return {
- inflate: inflate,
- relativePosition: relativePosition,
- findBestRelativePosition: findBestRelativePosition,
- intersect: intersect,
- clamp: clamp,
- create: create,
- fromClientRect: fromClientRect
- };
-});
-
-// Included from: js/tinymce/classes/util/Promise.js
+ return {
+ inflate: inflate,
+ relativePosition: relativePosition,
+ findBestRelativePosition: findBestRelativePosition,
+ intersect: intersect,
+ clamp: clamp,
+ create: create,
+ fromClientRect: fromClientRect
+ };
+ }
+);
/**
* Promise.js
*
* Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
+ * Copyright (c) 1999-2017 Ephox Corp. All rights reserved
*
* Promise polyfill under MIT license: https://github.com/taylorhakes/promise-polyfill
*
@@ -336,196 +320,197 @@ define("tinymce/geom/Rect", [
/**
* Modifed to be a feature fill and wrapped as tinymce module.
*/
-define("tinymce/util/Promise", [], function() {
- if (window.Promise) {
- return window.Promise;
- }
+define(
+ 'tinymce.core.util.Promise',
+ [],
+ function () {
+ if (window.Promise) {
+ return window.Promise;
+ }
- // Use polyfill for setImmediate for performance gains
- var asap = Promise.immediateFn || (typeof setImmediate === 'function' && setImmediate) ||
- function(fn) { setTimeout(fn, 1); };
+ // Use polyfill for setImmediate for performance gains
+ var asap = Promise.immediateFn || (typeof setImmediate === 'function' && setImmediate) ||
+ function (fn) { setTimeout(fn, 1); };
- // Polyfill for Function.prototype.bind
- function bind(fn, thisArg) {
- return function() {
- fn.apply(thisArg, arguments);
- };
- }
+ // Polyfill for Function.prototype.bind
+ function bind(fn, thisArg) {
+ return function () {
+ fn.apply(thisArg, arguments);
+ };
+ }
- var isArray = Array.isArray || function(value) { return Object.prototype.toString.call(value) === "[object Array]"; };
+ var isArray = Array.isArray || function (value) { return Object.prototype.toString.call(value) === "[object Array]"; };
- function Promise(fn) {
- if (typeof this !== 'object') throw new TypeError('Promises must be constructed via new');
- if (typeof fn !== 'function') throw new TypeError('not a function');
- this._state = null;
- this._value = null;
- this._deferreds = [];
+ function Promise(fn) {
+ if (typeof this !== 'object') throw new TypeError('Promises must be constructed via new');
+ if (typeof fn !== 'function') throw new TypeError('not a function');
+ this._state = null;
+ this._value = null;
+ this._deferreds = [];
- doResolve(fn, bind(resolve, this), bind(reject, this));
- }
+ doResolve(fn, bind(resolve, this), bind(reject, this));
+ }
- function handle(deferred) {
- var me = this;
- if (this._state === null) {
- this._deferreds.push(deferred);
- return;
- }
- asap(function() {
- var cb = me._state ? deferred.onFulfilled : deferred.onRejected;
- if (cb === null) {
- (me._state ? deferred.resolve : deferred.reject)(me._value);
- return;
- }
- var ret;
- try {
- ret = cb(me._value);
- }
- catch (e) {
- deferred.reject(e);
- return;
- }
- deferred.resolve(ret);
- });
- }
+ function handle(deferred) {
+ var me = this;
+ if (this._state === null) {
+ this._deferreds.push(deferred);
+ return;
+ }
+ asap(function () {
+ var cb = me._state ? deferred.onFulfilled : deferred.onRejected;
+ if (cb === null) {
+ (me._state ? deferred.resolve : deferred.reject)(me._value);
+ return;
+ }
+ var ret;
+ try {
+ ret = cb(me._value);
+ }
+ catch (e) {
+ deferred.reject(e);
+ return;
+ }
+ deferred.resolve(ret);
+ });
+ }
- function resolve(newValue) {
- try { //Promise Resolution Procedure: https://github.com/promises-aplus/promises-spec#the-promise-resolution-procedure
- if (newValue === this) throw new TypeError('A promise cannot be resolved with itself.');
- if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) {
- var then = newValue.then;
- if (typeof then === 'function') {
- doResolve(bind(then, newValue), bind(resolve, this), bind(reject, this));
- return;
- }
- }
- this._state = true;
- this._value = newValue;
- finale.call(this);
- } catch (e) { reject.call(this, e); }
- }
+ function resolve(newValue) {
+ try { //Promise Resolution Procedure: https://github.com/promises-aplus/promises-spec#the-promise-resolution-procedure
+ if (newValue === this) throw new TypeError('A promise cannot be resolved with itself.');
+ if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) {
+ var then = newValue.then;
+ if (typeof then === 'function') {
+ doResolve(bind(then, newValue), bind(resolve, this), bind(reject, this));
+ return;
+ }
+ }
+ this._state = true;
+ this._value = newValue;
+ finale.call(this);
+ } catch (e) { reject.call(this, e); }
+ }
- function reject(newValue) {
- this._state = false;
- this._value = newValue;
- finale.call(this);
- }
+ function reject(newValue) {
+ this._state = false;
+ this._value = newValue;
+ finale.call(this);
+ }
- function finale() {
- for (var i = 0, len = this._deferreds.length; i < len; i++) {
- handle.call(this, this._deferreds[i]);
- }
- this._deferreds = null;
- }
+ function finale() {
+ for (var i = 0, len = this._deferreds.length; i < len; i++) {
+ handle.call(this, this._deferreds[i]);
+ }
+ this._deferreds = null;
+ }
- function Handler(onFulfilled, onRejected, resolve, reject){
- this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null;
- this.onRejected = typeof onRejected === 'function' ? onRejected : null;
- this.resolve = resolve;
- this.reject = reject;
- }
+ function Handler(onFulfilled, onRejected, resolve, reject) {
+ this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null;
+ this.onRejected = typeof onRejected === 'function' ? onRejected : null;
+ this.resolve = resolve;
+ this.reject = reject;
+ }
- /**
- * Take a potentially misbehaving resolver function and make sure
- * onFulfilled and onRejected are only called once.
- *
- * Makes no guarantees about asynchrony.
- */
- function doResolve(fn, onFulfilled, onRejected) {
- var done = false;
- try {
- fn(function (value) {
- if (done) return;
- done = true;
- onFulfilled(value);
- }, function (reason) {
- if (done) return;
- done = true;
- onRejected(reason);
- });
- } catch (ex) {
- if (done) return;
- done = true;
- onRejected(ex);
- }
- }
+ /**
+ * Take a potentially misbehaving resolver function and make sure
+ * onFulfilled and onRejected are only called once.
+ *
+ * Makes no guarantees about asynchrony.
+ */
+ function doResolve(fn, onFulfilled, onRejected) {
+ var done = false;
+ try {
+ fn(function (value) {
+ if (done) return;
+ done = true;
+ onFulfilled(value);
+ }, function (reason) {
+ if (done) return;
+ done = true;
+ onRejected(reason);
+ });
+ } catch (ex) {
+ if (done) return;
+ done = true;
+ onRejected(ex);
+ }
+ }
- Promise.prototype['catch'] = function (onRejected) {
- return this.then(null, onRejected);
- };
+ Promise.prototype['catch'] = function (onRejected) {
+ return this.then(null, onRejected);
+ };
- Promise.prototype.then = function(onFulfilled, onRejected) {
- var me = this;
- return new Promise(function(resolve, reject) {
- handle.call(me, new Handler(onFulfilled, onRejected, resolve, reject));
- });
- };
+ Promise.prototype.then = function (onFulfilled, onRejected) {
+ var me = this;
+ return new Promise(function (resolve, reject) {
+ handle.call(me, new Handler(onFulfilled, onRejected, resolve, reject));
+ });
+ };
- Promise.all = function () {
- var args = Array.prototype.slice.call(arguments.length === 1 && isArray(arguments[0]) ? arguments[0] : arguments);
+ Promise.all = function () {
+ var args = Array.prototype.slice.call(arguments.length === 1 && isArray(arguments[0]) ? arguments[0] : arguments);
- return new Promise(function (resolve, reject) {
- if (args.length === 0) return resolve([]);
- var remaining = args.length;
- function res(i, val) {
- try {
- if (val && (typeof val === 'object' || typeof val === 'function')) {
- var then = val.then;
- if (typeof then === 'function') {
- then.call(val, function (val) { res(i, val); }, reject);
- return;
- }
- }
- args[i] = val;
- if (--remaining === 0) {
- resolve(args);
- }
- } catch (ex) {
- reject(ex);
- }
- }
- for (var i = 0; i < args.length; i++) {
- res(i, args[i]);
- }
- });
- };
+ return new Promise(function (resolve, reject) {
+ if (args.length === 0) return resolve([]);
+ var remaining = args.length;
+ function res(i, val) {
+ try {
+ if (val && (typeof val === 'object' || typeof val === 'function')) {
+ var then = val.then;
+ if (typeof then === 'function') {
+ then.call(val, function (val) { res(i, val); }, reject);
+ return;
+ }
+ }
+ args[i] = val;
+ if (--remaining === 0) {
+ resolve(args);
+ }
+ } catch (ex) {
+ reject(ex);
+ }
+ }
+ for (var i = 0; i < args.length; i++) {
+ res(i, args[i]);
+ }
+ });
+ };
- Promise.resolve = function (value) {
- if (value && typeof value === 'object' && value.constructor === Promise) {
- return value;
- }
+ Promise.resolve = function (value) {
+ if (value && typeof value === 'object' && value.constructor === Promise) {
+ return value;
+ }
- return new Promise(function (resolve) {
- resolve(value);
- });
- };
+ return new Promise(function (resolve) {
+ resolve(value);
+ });
+ };
- Promise.reject = function (value) {
- return new Promise(function (resolve, reject) {
- reject(value);
- });
- };
+ Promise.reject = function (value) {
+ return new Promise(function (resolve, reject) {
+ reject(value);
+ });
+ };
- Promise.race = function (values) {
- return new Promise(function (resolve, reject) {
- for(var i = 0, len = values.length; i < len; i++) {
- values[i].then(resolve, reject);
- }
- });
- };
+ Promise.race = function (values) {
+ return new Promise(function (resolve, reject) {
+ for (var i = 0, len = values.length; i < len; i++) {
+ values[i].then(resolve, reject);
+ }
+ });
+ };
- return Promise;
-});
+ return Promise;
+ }
+);
/* jshint ignore:end */
/* eslint-enable */
-
-// Included from: js/tinymce/classes/util/Delay.js
-
/**
* Delay.js
*
* Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
+ * Copyright (c) 1999-2017 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
@@ -536,195 +521,197 @@ define("tinymce/util/Promise", [], function() {
*
* @class tinymce.util.Delay
*/
-define("tinymce/util/Delay", [
- "tinymce/util/Promise"
-], function(Promise) {
- var requestAnimationFramePromise;
+define(
+ 'tinymce.core.util.Delay',
+ [
+ "tinymce.core.util.Promise"
+ ],
+ function (Promise) {
+ var requestAnimationFramePromise;
- function requestAnimationFrame(callback, element) {
- var i, requestAnimationFrameFunc = window.requestAnimationFrame, vendors = ['ms', 'moz', 'webkit'];
+ function requestAnimationFrame(callback, element) {
+ var i, requestAnimationFrameFunc = window.requestAnimationFrame, vendors = ['ms', 'moz', 'webkit'];
- function featurefill(callback) {
- window.setTimeout(callback, 0);
- }
+ function featurefill(callback) {
+ window.setTimeout(callback, 0);
+ }
- for (i = 0; i < vendors.length && !requestAnimationFrameFunc; i++) {
- requestAnimationFrameFunc = window[vendors[i] + 'RequestAnimationFrame'];
- }
+ for (i = 0; i < vendors.length && !requestAnimationFrameFunc; i++) {
+ requestAnimationFrameFunc = window[vendors[i] + 'RequestAnimationFrame'];
+ }
- if (!requestAnimationFrameFunc) {
- requestAnimationFrameFunc = featurefill;
- }
+ if (!requestAnimationFrameFunc) {
+ requestAnimationFrameFunc = featurefill;
+ }
- requestAnimationFrameFunc(callback, element);
- }
+ requestAnimationFrameFunc(callback, element);
+ }
- function wrappedSetTimeout(callback, time) {
- if (typeof time != 'number') {
- time = 0;
- }
+ function wrappedSetTimeout(callback, time) {
+ if (typeof time != 'number') {
+ time = 0;
+ }
- return setTimeout(callback, time);
- }
+ return setTimeout(callback, time);
+ }
- function wrappedSetInterval(callback, time) {
- if (typeof time != 'number') {
- time = 1; // IE 8 needs it to be > 0
- }
+ function wrappedSetInterval(callback, time) {
+ if (typeof time != 'number') {
+ time = 1; // IE 8 needs it to be > 0
+ }
- return setInterval(callback, time);
- }
+ return setInterval(callback, time);
+ }
- function wrappedClearTimeout(id) {
- return clearTimeout(id);
- }
+ function wrappedClearTimeout(id) {
+ return clearTimeout(id);
+ }
- function wrappedClearInterval(id) {
- return clearInterval(id);
- }
+ function wrappedClearInterval(id) {
+ return clearInterval(id);
+ }
- function debounce(callback, time) {
- var timer, func;
+ function debounce(callback, time) {
+ var timer, func;
- func = function() {
- var args = arguments;
+ func = function () {
+ var args = arguments;
- clearTimeout(timer);
+ clearTimeout(timer);
- timer = wrappedSetTimeout(function() {
- callback.apply(this, args);
- }, time);
- };
+ timer = wrappedSetTimeout(function () {
+ callback.apply(this, args);
+ }, time);
+ };
- func.stop = function() {
- clearTimeout(timer);
- };
+ func.stop = function () {
+ clearTimeout(timer);
+ };
- return func;
- }
+ return func;
+ }
- return {
- /**
- * Requests an animation frame and fallbacks to a timeout on older browsers.
- *
- * @method requestAnimationFrame
- * @param {function} callback Callback to execute when a new frame is available.
- * @param {DOMElement} element Optional element to scope it to.
- */
- requestAnimationFrame: function(callback, element) {
- if (requestAnimationFramePromise) {
- requestAnimationFramePromise.then(callback);
- return;
- }
+ return {
+ /**
+ * Requests an animation frame and fallbacks to a timeout on older browsers.
+ *
+ * @method requestAnimationFrame
+ * @param {function} callback Callback to execute when a new frame is available.
+ * @param {DOMElement} element Optional element to scope it to.
+ */
+ requestAnimationFrame: function (callback, element) {
+ if (requestAnimationFramePromise) {
+ requestAnimationFramePromise.then(callback);
+ return;
+ }
- requestAnimationFramePromise = new Promise(function(resolve) {
- if (!element) {
- element = document.body;
- }
+ requestAnimationFramePromise = new Promise(function (resolve) {
+ if (!element) {
+ element = document.body;
+ }
- requestAnimationFrame(resolve, element);
- }).then(callback);
- },
+ requestAnimationFrame(resolve, element);
+ }).then(callback);
+ },
- /**
- * Sets a timer in ms and executes the specified callback when the timer runs out.
- *
- * @method setTimeout
- * @param {function} callback Callback to execute when timer runs out.
- * @param {Number} time Optional time to wait before the callback is executed, defaults to 0.
- * @return {Number} Timeout id number.
- */
- setTimeout: wrappedSetTimeout,
+ /**
+ * Sets a timer in ms and executes the specified callback when the timer runs out.
+ *
+ * @method setTimeout
+ * @param {function} callback Callback to execute when timer runs out.
+ * @param {Number} time Optional time to wait before the callback is executed, defaults to 0.
+ * @return {Number} Timeout id number.
+ */
+ setTimeout: wrappedSetTimeout,
- /**
- * Sets an interval timer in ms and executes the specified callback at every interval of that time.
- *
- * @method setInterval
- * @param {function} callback Callback to execute when interval time runs out.
- * @param {Number} time Optional time to wait before the callback is executed, defaults to 0.
- * @return {Number} Timeout id number.
- */
- setInterval: wrappedSetInterval,
+ /**
+ * Sets an interval timer in ms and executes the specified callback at every interval of that time.
+ *
+ * @method setInterval
+ * @param {function} callback Callback to execute when interval time runs out.
+ * @param {Number} time Optional time to wait before the callback is executed, defaults to 0.
+ * @return {Number} Timeout id number.
+ */
+ setInterval: wrappedSetInterval,
- /**
- * Sets an editor timeout it's similar to setTimeout except that it checks if the editor instance is
- * still alive when the callback gets executed.
- *
- * @method setEditorTimeout
- * @param {tinymce.Editor} editor Editor instance to check the removed state on.
- * @param {function} callback Callback to execute when timer runs out.
- * @param {Number} time Optional time to wait before the callback is executed, defaults to 0.
- * @return {Number} Timeout id number.
- */
- setEditorTimeout: function(editor, callback, time) {
- return wrappedSetTimeout(function() {
- if (!editor.removed) {
- callback();
- }
- }, time);
- },
+ /**
+ * Sets an editor timeout it's similar to setTimeout except that it checks if the editor instance is
+ * still alive when the callback gets executed.
+ *
+ * @method setEditorTimeout
+ * @param {tinymce.Editor} editor Editor instance to check the removed state on.
+ * @param {function} callback Callback to execute when timer runs out.
+ * @param {Number} time Optional time to wait before the callback is executed, defaults to 0.
+ * @return {Number} Timeout id number.
+ */
+ setEditorTimeout: function (editor, callback, time) {
+ return wrappedSetTimeout(function () {
+ if (!editor.removed) {
+ callback();
+ }
+ }, time);
+ },
- /**
- * Sets an interval timer it's similar to setInterval except that it checks if the editor instance is
- * still alive when the callback gets executed.
- *
- * @method setEditorInterval
- * @param {function} callback Callback to execute when interval time runs out.
- * @param {Number} time Optional time to wait before the callback is executed, defaults to 0.
- * @return {Number} Timeout id number.
- */
- setEditorInterval: function(editor, callback, time) {
- var timer;
+ /**
+ * Sets an interval timer it's similar to setInterval except that it checks if the editor instance is
+ * still alive when the callback gets executed.
+ *
+ * @method setEditorInterval
+ * @param {function} callback Callback to execute when interval time runs out.
+ * @param {Number} time Optional time to wait before the callback is executed, defaults to 0.
+ * @return {Number} Timeout id number.
+ */
+ setEditorInterval: function (editor, callback, time) {
+ var timer;
- timer = wrappedSetInterval(function() {
- if (!editor.removed) {
- callback();
- } else {
- clearInterval(timer);
- }
- }, time);
+ timer = wrappedSetInterval(function () {
+ if (!editor.removed) {
+ callback();
+ } else {
+ clearInterval(timer);
+ }
+ }, time);
- return timer;
- },
+ return timer;
+ },
- /**
- * Creates debounced callback function that only gets executed once within the specified time.
- *
- * @method debounce
- * @param {function} callback Callback to execute when timer finishes.
- * @param {Number} time Optional time to wait before the callback is executed, defaults to 0.
- * @return {Function} debounced function callback.
- */
- debounce: debounce,
+ /**
+ * Creates debounced callback function that only gets executed once within the specified time.
+ *
+ * @method debounce
+ * @param {function} callback Callback to execute when timer finishes.
+ * @param {Number} time Optional time to wait before the callback is executed, defaults to 0.
+ * @return {Function} debounced function callback.
+ */
+ debounce: debounce,
- // Throttle needs to be debounce due to backwards compatibility.
- throttle: debounce,
+ // Throttle needs to be debounce due to backwards compatibility.
+ throttle: debounce,
- /**
- * Clears an interval timer so it won't execute.
- *
- * @method clearInterval
- * @param {Number} Interval timer id number.
- */
- clearInterval: wrappedClearInterval,
+ /**
+ * Clears an interval timer so it won't execute.
+ *
+ * @method clearInterval
+ * @param {Number} Interval timer id number.
+ */
+ clearInterval: wrappedClearInterval,
- /**
- * Clears an timeout timer so it won't execute.
- *
- * @method clearTimeout
- * @param {Number} Timeout timer id number.
- */
- clearTimeout: wrappedClearTimeout
- };
-});
-
-// Included from: js/tinymce/classes/Env.js
+ /**
+ * Clears an timeout timer so it won't execute.
+ *
+ * @method clearTimeout
+ * @param {Number} Timeout timer id number.
+ */
+ clearTimeout: wrappedClearTimeout
+ };
+ }
+);
/**
* Env.js
*
* Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
+ * Copyright (c) 1999-2017 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
@@ -738,178 +725,181 @@ define("tinymce/util/Delay", [
* @class tinymce.Env
* @static
*/
-define("tinymce/Env", [], function() {
- var nav = navigator, userAgent = nav.userAgent;
- var opera, webkit, ie, ie11, ie12, gecko, mac, iDevice, android, fileApi, phone, tablet, windowsPhone;
+define(
+ 'tinymce.core.Env',
+ [
+ ],
+ function () {
+ var nav = navigator, userAgent = nav.userAgent;
+ var opera, webkit, ie, ie11, ie12, gecko, mac, iDevice, android, fileApi, phone, tablet, windowsPhone;
- function matchMediaQuery(query) {
- return "matchMedia" in window ? matchMedia(query).matches : false;
- }
+ function matchMediaQuery(query) {
+ return "matchMedia" in window ? matchMedia(query).matches : false;
+ }
- opera = window.opera && window.opera.buildNumber;
- android = /Android/.test(userAgent);
- webkit = /WebKit/.test(userAgent);
- ie = !webkit && !opera && (/MSIE/gi).test(userAgent) && (/Explorer/gi).test(nav.appName);
- ie = ie && /MSIE (\w+)\./.exec(userAgent)[1];
- ie11 = userAgent.indexOf('Trident/') != -1 && (userAgent.indexOf('rv:') != -1 || nav.appName.indexOf('Netscape') != -1) ? 11 : false;
- ie12 = (userAgent.indexOf('Edge/') != -1 && !ie && !ie11) ? 12 : false;
- ie = ie || ie11 || ie12;
- gecko = !webkit && !ie11 && /Gecko/.test(userAgent);
- mac = userAgent.indexOf('Mac') != -1;
- iDevice = /(iPad|iPhone)/.test(userAgent);
- fileApi = "FormData" in window && "FileReader" in window && "URL" in window && !!URL.createObjectURL;
- phone = matchMediaQuery("only screen and (max-device-width: 480px)") && (android || iDevice);
- tablet = matchMediaQuery("only screen and (min-width: 800px)") && (android || iDevice);
- windowsPhone = userAgent.indexOf('Windows Phone') != -1;
+ opera = window.opera && window.opera.buildNumber;
+ android = /Android/.test(userAgent);
+ webkit = /WebKit/.test(userAgent);
+ ie = !webkit && !opera && (/MSIE/gi).test(userAgent) && (/Explorer/gi).test(nav.appName);
+ ie = ie && /MSIE (\w+)\./.exec(userAgent)[1];
+ ie11 = userAgent.indexOf('Trident/') != -1 && (userAgent.indexOf('rv:') != -1 || nav.appName.indexOf('Netscape') != -1) ? 11 : false;
+ ie12 = (userAgent.indexOf('Edge/') != -1 && !ie && !ie11) ? 12 : false;
+ ie = ie || ie11 || ie12;
+ gecko = !webkit && !ie11 && /Gecko/.test(userAgent);
+ mac = userAgent.indexOf('Mac') != -1;
+ iDevice = /(iPad|iPhone)/.test(userAgent);
+ fileApi = "FormData" in window && "FileReader" in window && "URL" in window && !!URL.createObjectURL;
+ phone = matchMediaQuery("only screen and (max-device-width: 480px)") && (android || iDevice);
+ tablet = matchMediaQuery("only screen and (min-width: 800px)") && (android || iDevice);
+ windowsPhone = userAgent.indexOf('Windows Phone') != -1;
- if (ie12) {
- webkit = false;
- }
+ if (ie12) {
+ webkit = false;
+ }
- // Is a iPad/iPhone and not on iOS5 sniff the WebKit version since older iOS WebKit versions
- // says it has contentEditable support but there is no visible caret.
- var contentEditable = !iDevice || fileApi || userAgent.match(/AppleWebKit\/(\d*)/)[1] >= 534;
+ // Is a iPad/iPhone and not on iOS5 sniff the WebKit version since older iOS WebKit versions
+ // says it has contentEditable support but there is no visible caret.
+ var contentEditable = !iDevice || fileApi || userAgent.match(/AppleWebKit\/(\d*)/)[1] >= 534;
- return {
- /**
- * Constant that is true if the browser is Opera.
- *
- * @property opera
- * @type Boolean
- * @final
- */
- opera: opera,
+ return {
+ /**
+ * Constant that is true if the browser is Opera.
+ *
+ * @property opera
+ * @type Boolean
+ * @final
+ */
+ opera: opera,
- /**
- * Constant that is true if the browser is WebKit (Safari/Chrome).
- *
- * @property webKit
- * @type Boolean
- * @final
- */
- webkit: webkit,
+ /**
+ * Constant that is true if the browser is WebKit (Safari/Chrome).
+ *
+ * @property webKit
+ * @type Boolean
+ * @final
+ */
+ webkit: webkit,
- /**
- * Constant that is more than zero if the browser is IE.
- *
- * @property ie
- * @type Boolean
- * @final
- */
- ie: ie,
+ /**
+ * Constant that is more than zero if the browser is IE.
+ *
+ * @property ie
+ * @type Boolean
+ * @final
+ */
+ ie: ie,
- /**
- * Constant that is true if the browser is Gecko.
- *
- * @property gecko
- * @type Boolean
- * @final
- */
- gecko: gecko,
+ /**
+ * Constant that is true if the browser is Gecko.
+ *
+ * @property gecko
+ * @type Boolean
+ * @final
+ */
+ gecko: gecko,
- /**
- * Constant that is true if the os is Mac OS.
- *
- * @property mac
- * @type Boolean
- * @final
- */
- mac: mac,
+ /**
+ * Constant that is true if the os is Mac OS.
+ *
+ * @property mac
+ * @type Boolean
+ * @final
+ */
+ mac: mac,
- /**
- * Constant that is true if the os is iOS.
- *
- * @property iOS
- * @type Boolean
- * @final
- */
- iOS: iDevice,
+ /**
+ * Constant that is true if the os is iOS.
+ *
+ * @property iOS
+ * @type Boolean
+ * @final
+ */
+ iOS: iDevice,
- /**
- * Constant that is true if the os is android.
- *
- * @property android
- * @type Boolean
- * @final
- */
- android: android,
+ /**
+ * Constant that is true if the os is android.
+ *
+ * @property android
+ * @type Boolean
+ * @final
+ */
+ android: android,
- /**
- * Constant that is true if the browser supports editing.
- *
- * @property contentEditable
- * @type Boolean
- * @final
- */
- contentEditable: contentEditable,
+ /**
+ * Constant that is true if the browser supports editing.
+ *
+ * @property contentEditable
+ * @type Boolean
+ * @final
+ */
+ contentEditable: contentEditable,
- /**
- * Transparent image data url.
- *
- * @property transparentSrc
- * @type Boolean
- * @final
- */
- transparentSrc: "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7",
+ /**
+ * Transparent image data url.
+ *
+ * @property transparentSrc
+ * @type Boolean
+ * @final
+ */
+ transparentSrc: "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7",
- /**
- * Returns true/false if the browser can or can't place the caret after a inline block like an image.
- *
- * @property noCaretAfter
- * @type Boolean
- * @final
- */
- caretAfter: ie != 8,
+ /**
+ * Returns true/false if the browser can or can't place the caret after a inline block like an image.
+ *
+ * @property noCaretAfter
+ * @type Boolean
+ * @final
+ */
+ caretAfter: ie != 8,
- /**
- * Constant that is true if the browser supports native DOM Ranges. IE 9+.
- *
- * @property range
- * @type Boolean
- */
- range: window.getSelection && "Range" in window,
+ /**
+ * Constant that is true if the browser supports native DOM Ranges. IE 9+.
+ *
+ * @property range
+ * @type Boolean
+ */
+ range: window.getSelection && "Range" in window,
- /**
- * Returns the IE document mode for non IE browsers this will fake IE 10.
- *
- * @property documentMode
- * @type Number
- */
- documentMode: ie && !ie12 ? (document.documentMode || 7) : 10,
+ /**
+ * Returns the IE document mode for non IE browsers this will fake IE 10.
+ *
+ * @property documentMode
+ * @type Number
+ */
+ documentMode: ie && !ie12 ? (document.documentMode || 7) : 10,
- /**
- * Constant that is true if the browser has a modern file api.
- *
- * @property fileApi
- * @type Boolean
- */
- fileApi: fileApi,
+ /**
+ * Constant that is true if the browser has a modern file api.
+ *
+ * @property fileApi
+ * @type Boolean
+ */
+ fileApi: fileApi,
- /**
- * Constant that is true if the browser supports contentEditable=false regions.
- *
- * @property ceFalse
- * @type Boolean
- */
- ceFalse: (ie === false || ie > 8),
+ /**
+ * Constant that is true if the browser supports contentEditable=false regions.
+ *
+ * @property ceFalse
+ * @type Boolean
+ */
+ ceFalse: (ie === false || ie > 8),
- /**
- * Constant if CSP mode is possible or not. Meaning we can't use script urls for the iframe.
- */
- canHaveCSP: (ie === false || ie > 11),
+ /**
+ * Constant if CSP mode is possible or not. Meaning we can't use script urls for the iframe.
+ */
+ canHaveCSP: (ie === false || ie > 11),
- desktop: !phone && !tablet,
- windowsPhone: windowsPhone
- };
-});
-
-// Included from: js/tinymce/classes/dom/EventUtils.js
+ desktop: !phone && !tablet,
+ windowsPhone: windowsPhone
+ };
+ }
+);
/**
* EventUtils.js
*
* Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
+ * Copyright (c) 1999-2017 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
@@ -923,601 +913,612 @@ define("tinymce/Env", [], function() {
*
* @class tinymce.dom.EventUtils
*/
-define("tinymce/dom/EventUtils", [
- "tinymce/util/Delay",
- "tinymce/Env"
-], function(Delay, Env) {
- "use strict";
-
- var eventExpandoPrefix = "mce-data-";
- var mouseEventRe = /^(?:mouse|contextmenu)|click/;
- var deprecated = {
- keyLocation: 1, layerX: 1, layerY: 1, returnValue: 1,
- webkitMovementX: 1, webkitMovementY: 1, keyIdentifier: 1
- };
-
- /**
- * Binds a native event to a callback on the speified target.
- */
- function addEvent(target, name, callback, capture) {
- if (target.addEventListener) {
- target.addEventListener(name, callback, capture || false);
- } else if (target.attachEvent) {
- target.attachEvent('on' + name, callback);
- }
- }
-
- /**
- * Unbinds a native event callback on the specified target.
- */
- function removeEvent(target, name, callback, capture) {
- if (target.removeEventListener) {
- target.removeEventListener(name, callback, capture || false);
- } else if (target.detachEvent) {
- target.detachEvent('on' + name, callback);
- }
- }
-
- /**
- * Gets the event target based on shadow dom properties like path and deepPath.
- */
- function getTargetFromShadowDom(event, defaultTarget) {
- var path, target = defaultTarget;
-
- // When target element is inside Shadow DOM we need to take first element from path
- // otherwise we'll get Shadow Root parent, not actual target element
-
- // Normalize target for WebComponents v0 implementation (in Chrome)
- path = event.path;
- if (path && path.length > 0) {
- target = path[0];
- }
-
- // Normalize target for WebComponents v1 implementation (standard)
- if (event.deepPath) {
- path = event.deepPath();
- if (path && path.length > 0) {
- target = path[0];
- }
- }
-
- return target;
- }
-
- /**
- * Normalizes a native event object or just adds the event specific methods on a custom event.
- */
- function fix(originalEvent, data) {
- var name, event = data || {}, undef;
-
- // Dummy function that gets replaced on the delegation state functions
- function returnFalse() {
- return false;
- }
-
- // Dummy function that gets replaced on the delegation state functions
- function returnTrue() {
- return true;
- }
-
- // Copy all properties from the original event
- for (name in originalEvent) {
- // layerX/layerY is deprecated in Chrome and produces a warning
- if (!deprecated[name]) {
- event[name] = originalEvent[name];
- }
- }
-
- // Normalize target IE uses srcElement
- if (!event.target) {
- event.target = event.srcElement || document;
- }
-
- // Experimental shadow dom support
- if (Env.experimentalShadowDom) {
- event.target = getTargetFromShadowDom(originalEvent, event.target);
- }
-
- // Calculate pageX/Y if missing and clientX/Y available
- if (originalEvent && mouseEventRe.test(originalEvent.type) && originalEvent.pageX === undef && originalEvent.clientX !== undef) {
- var eventDoc = event.target.ownerDocument || document;
- var doc = eventDoc.documentElement;
- var body = eventDoc.body;
-
- event.pageX = originalEvent.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) -
- (doc && doc.clientLeft || body && body.clientLeft || 0);
-
- event.pageY = originalEvent.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) -
- (doc && doc.clientTop || body && body.clientTop || 0);
- }
-
- // Add preventDefault method
- event.preventDefault = function() {
- event.isDefaultPrevented = returnTrue;
-
- // Execute preventDefault on the original event object
- if (originalEvent) {
- if (originalEvent.preventDefault) {
- originalEvent.preventDefault();
- } else {
- originalEvent.returnValue = false; // IE
- }
- }
- };
-
- // Add stopPropagation
- event.stopPropagation = function() {
- event.isPropagationStopped = returnTrue;
-
- // Execute stopPropagation on the original event object
- if (originalEvent) {
- if (originalEvent.stopPropagation) {
- originalEvent.stopPropagation();
- } else {
- originalEvent.cancelBubble = true; // IE
- }
- }
- };
-
- // Add stopImmediatePropagation
- event.stopImmediatePropagation = function() {
- event.isImmediatePropagationStopped = returnTrue;
- event.stopPropagation();
- };
-
- // Add event delegation states
- if (!event.isDefaultPrevented) {
- event.isDefaultPrevented = returnFalse;
- event.isPropagationStopped = returnFalse;
- event.isImmediatePropagationStopped = returnFalse;
- }
-
- // Add missing metaKey for IE 8
- if (typeof event.metaKey == 'undefined') {
- event.metaKey = false;
- }
-
- return event;
- }
-
- /**
- * Bind a DOMContentLoaded event across browsers and executes the callback once the page DOM is initialized.
- * It will also set/check the domLoaded state of the event_utils instance so ready isn't called multiple times.
- */
- function bindOnReady(win, callback, eventUtils) {
- var doc = win.document, event = {type: 'ready'};
-
- if (eventUtils.domLoaded) {
- callback(event);
- return;
- }
-
- // Gets called when the DOM is ready
- function readyHandler() {
- if (!eventUtils.domLoaded) {
- eventUtils.domLoaded = true;
- callback(event);
- }
- }
-
- function waitForDomLoaded() {
- // Check complete or interactive state if there is a body
- // element on some iframes IE 8 will produce a null body
- if (doc.readyState === "complete" || (doc.readyState === "interactive" && doc.body)) {
- removeEvent(doc, "readystatechange", waitForDomLoaded);
- readyHandler();
- }
- }
-
- function tryScroll() {
- try {
- // If IE is used, use the trick by Diego Perini licensed under MIT by request to the author.
- // http://javascript.nwbox.com/IEContentLoaded/
- doc.documentElement.doScroll("left");
- } catch (ex) {
- Delay.setTimeout(tryScroll);
- return;
- }
-
- readyHandler();
- }
-
- // Use W3C method
- if (doc.addEventListener) {
- if (doc.readyState === "complete") {
- readyHandler();
- } else {
- addEvent(win, 'DOMContentLoaded', readyHandler);
- }
- } else {
- // Use IE method
- addEvent(doc, "readystatechange", waitForDomLoaded);
-
- // Wait until we can scroll, when we can the DOM is initialized
- if (doc.documentElement.doScroll && win.self === win.top) {
- tryScroll();
- }
- }
-
- // Fallback if any of the above methods should fail for some odd reason
- addEvent(win, 'load', readyHandler);
- }
-
- /**
- * This class enables you to bind/unbind native events to elements and normalize it's behavior across browsers.
- */
- function EventUtils() {
- var self = this, events = {}, count, expando, hasFocusIn, hasMouseEnterLeave, mouseEnterLeave;
-
- expando = eventExpandoPrefix + (+new Date()).toString(32);
- hasMouseEnterLeave = "onmouseenter" in document.documentElement;
- hasFocusIn = "onfocusin" in document.documentElement;
- mouseEnterLeave = {mouseenter: 'mouseover', mouseleave: 'mouseout'};
- count = 1;
-
- // State if the DOMContentLoaded was executed or not
- self.domLoaded = false;
- self.events = events;
-
- /**
- * Executes all event handler callbacks for a specific event.
- *
- * @private
- * @param {Event} evt Event object.
- * @param {String} id Expando id value to look for.
- */
- function executeHandlers(evt, id) {
- var callbackList, i, l, callback, container = events[id];
-
- callbackList = container && container[evt.type];
- if (callbackList) {
- for (i = 0, l = callbackList.length; i < l; i++) {
- callback = callbackList[i];
-
- // Check if callback exists might be removed if a unbind is called inside the callback
- if (callback && callback.func.call(callback.scope, evt) === false) {
- evt.preventDefault();
- }
-
- // Should we stop propagation to immediate listeners
- if (evt.isImmediatePropagationStopped()) {
- return;
- }
- }
- }
- }
-
- /**
- * Binds a callback to an event on the specified target.
- *
- * @method bind
- * @param {Object} target Target node/window or custom object.
- * @param {String} names Name of the event to bind.
- * @param {function} callback Callback function to execute when the event occurs.
- * @param {Object} scope Scope to call the callback function on, defaults to target.
- * @return {function} Callback function that got bound.
- */
- self.bind = function(target, names, callback, scope) {
- var id, callbackList, i, name, fakeName, nativeHandler, capture, win = window;
-
- // Native event handler function patches the event and executes the callbacks for the expando
- function defaultNativeHandler(evt) {
- executeHandlers(fix(evt || win.event), id);
- }
-
- // Don't bind to text nodes or comments
- if (!target || target.nodeType === 3 || target.nodeType === 8) {
- return;
- }
-
- // Create or get events id for the target
- if (!target[expando]) {
- id = count++;
- target[expando] = id;
- events[id] = {};
- } else {
- id = target[expando];
- }
-
- // Setup the specified scope or use the target as a default
- scope = scope || target;
-
- // Split names and bind each event, enables you to bind multiple events with one call
- names = names.split(' ');
- i = names.length;
- while (i--) {
- name = names[i];
- nativeHandler = defaultNativeHandler;
- fakeName = capture = false;
-
- // Use ready instead of DOMContentLoaded
- if (name === "DOMContentLoaded") {
- name = "ready";
- }
-
- // DOM is already ready
- if (self.domLoaded && name === "ready" && target.readyState == 'complete') {
- callback.call(scope, fix({type: name}));
- continue;
- }
-
- // Handle mouseenter/mouseleaver
- if (!hasMouseEnterLeave) {
- fakeName = mouseEnterLeave[name];
-
- if (fakeName) {
- nativeHandler = function(evt) {
- var current, related;
-
- current = evt.currentTarget;
- related = evt.relatedTarget;
-
- // Check if related is inside the current target if it's not then the event should
- // be ignored since it's a mouseover/mouseout inside the element
- if (related && current.contains) {
- // Use contains for performance
- related = current.contains(related);
- } else {
- while (related && related !== current) {
- related = related.parentNode;
- }
- }
-
- // Fire fake event
- if (!related) {
- evt = fix(evt || win.event);
- evt.type = evt.type === 'mouseout' ? 'mouseleave' : 'mouseenter';
- evt.target = current;
- executeHandlers(evt, id);
- }
- };
- }
- }
-
- // Fake bubbling of focusin/focusout
- if (!hasFocusIn && (name === "focusin" || name === "focusout")) {
- capture = true;
- fakeName = name === "focusin" ? "focus" : "blur";
- nativeHandler = function(evt) {
- evt = fix(evt || win.event);
- evt.type = evt.type === 'focus' ? 'focusin' : 'focusout';
- executeHandlers(evt, id);
- };
- }
-
- // Setup callback list and bind native event
- callbackList = events[id][name];
- if (!callbackList) {
- events[id][name] = callbackList = [{func: callback, scope: scope}];
- callbackList.fakeName = fakeName;
- callbackList.capture = capture;
- //callbackList.callback = callback;
-
- // Add the nativeHandler to the callback list so that we can later unbind it
- callbackList.nativeHandler = nativeHandler;
-
- // Check if the target has native events support
-
- if (name === "ready") {
- bindOnReady(target, nativeHandler, self);
- } else {
- addEvent(target, fakeName || name, nativeHandler, capture);
- }
- } else {
- if (name === "ready" && self.domLoaded) {
- callback({type: name});
- } else {
- // If it already has an native handler then just push the callback
- callbackList.push({func: callback, scope: scope});
- }
- }
- }
-
- target = callbackList = 0; // Clean memory for IE
-
- return callback;
- };
-
- /**
- * Unbinds the specified event by name, name and callback or all events on the target.
- *
- * @method unbind
- * @param {Object} target Target node/window or custom object.
- * @param {String} names Optional event name to unbind.
- * @param {function} callback Optional callback function to unbind.
- * @return {EventUtils} Event utils instance.
- */
- self.unbind = function(target, names, callback) {
- var id, callbackList, i, ci, name, eventMap;
-
- // Don't bind to text nodes or comments
- if (!target || target.nodeType === 3 || target.nodeType === 8) {
- return self;
- }
-
- // Unbind event or events if the target has the expando
- id = target[expando];
- if (id) {
- eventMap = events[id];
-
- // Specific callback
- if (names) {
- names = names.split(' ');
- i = names.length;
- while (i--) {
- name = names[i];
- callbackList = eventMap[name];
-
- // Unbind the event if it exists in the map
- if (callbackList) {
- // Remove specified callback
- if (callback) {
- ci = callbackList.length;
- while (ci--) {
- if (callbackList[ci].func === callback) {
- var nativeHandler = callbackList.nativeHandler;
- var fakeName = callbackList.fakeName, capture = callbackList.capture;
-
- // Clone callbackList since unbind inside a callback would otherwise break the handlers loop
- callbackList = callbackList.slice(0, ci).concat(callbackList.slice(ci + 1));
- callbackList.nativeHandler = nativeHandler;
- callbackList.fakeName = fakeName;
- callbackList.capture = capture;
-
- eventMap[name] = callbackList;
- }
- }
- }
-
- // Remove all callbacks if there isn't a specified callback or there is no callbacks left
- if (!callback || callbackList.length === 0) {
- delete eventMap[name];
- removeEvent(target, callbackList.fakeName || name, callbackList.nativeHandler, callbackList.capture);
- }
- }
- }
- } else {
- // All events for a specific element
- for (name in eventMap) {
- callbackList = eventMap[name];
- removeEvent(target, callbackList.fakeName || name, callbackList.nativeHandler, callbackList.capture);
- }
-
- eventMap = {};
- }
-
- // Check if object is empty, if it isn't then we won't remove the expando map
- for (name in eventMap) {
- return self;
- }
-
- // Delete event object
- delete events[id];
-
- // Remove expando from target
- try {
- // IE will fail here since it can't delete properties from window
- delete target[expando];
- } catch (ex) {
- // IE will set it to null
- target[expando] = null;
- }
- }
-
- return self;
- };
-
- /**
- * Fires the specified event on the specified target.
- *
- * @method fire
- * @param {Object} target Target node/window or custom object.
- * @param {String} name Event name to fire.
- * @param {Object} args Optional arguments to send to the observers.
- * @return {EventUtils} Event utils instance.
- */
- self.fire = function(target, name, args) {
- var id;
-
- // Don't bind to text nodes or comments
- if (!target || target.nodeType === 3 || target.nodeType === 8) {
- return self;
- }
-
- // Build event object by patching the args
- args = fix(null, args);
- args.type = name;
- args.target = target;
-
- do {
- // Found an expando that means there is listeners to execute
- id = target[expando];
- if (id) {
- executeHandlers(args, id);
- }
-
- // Walk up the DOM
- target = target.parentNode || target.ownerDocument || target.defaultView || target.parentWindow;
- } while (target && !args.isPropagationStopped());
-
- return self;
- };
-
- /**
- * Removes all bound event listeners for the specified target. This will also remove any bound
- * listeners to child nodes within that target.
- *
- * @method clean
- * @param {Object} target Target node/window object.
- * @return {EventUtils} Event utils instance.
- */
- self.clean = function(target) {
- var i, children, unbind = self.unbind;
-
- // Don't bind to text nodes or comments
- if (!target || target.nodeType === 3 || target.nodeType === 8) {
- return self;
- }
-
- // Unbind any element on the specified target
- if (target[expando]) {
- unbind(target);
- }
-
- // Target doesn't have getElementsByTagName it's probably a window object then use it's document to find the children
- if (!target.getElementsByTagName) {
- target = target.document;
- }
-
- // Remove events from each child element
- if (target && target.getElementsByTagName) {
- unbind(target);
-
- children = target.getElementsByTagName('*');
- i = children.length;
- while (i--) {
- target = children[i];
-
- if (target[expando]) {
- unbind(target);
- }
- }
- }
-
- return self;
- };
-
- /**
- * Destroys the event object. Call this on IE to remove memory leaks.
- */
- self.destroy = function() {
- events = {};
- };
-
- // Legacy function for canceling events
- self.cancel = function(e) {
- if (e) {
- e.preventDefault();
- e.stopImmediatePropagation();
- }
-
- return false;
- };
- }
-
- EventUtils.Event = new EventUtils();
- EventUtils.Event.bind(window, 'ready', function() {});
-
- return EventUtils;
-});
-
-// Included from: js/tinymce/classes/dom/Sizzle.js
+define(
+ 'tinymce.core.dom.EventUtils',
+ [
+ "tinymce.core.util.Delay",
+ "tinymce.core.Env"
+ ],
+ function (Delay, Env) {
+ "use strict";
+
+ var eventExpandoPrefix = "mce-data-";
+ var mouseEventRe = /^(?:mouse|contextmenu)|click/;
+ var deprecated = {
+ keyLocation: 1, layerX: 1, layerY: 1, returnValue: 1,
+ webkitMovementX: 1, webkitMovementY: 1, keyIdentifier: 1
+ };
+
+ // Checks if it is our own isDefaultPrevented function
+ var hasIsDefaultPrevented = function (event) {
+ return event.isDefaultPrevented === returnTrue || event.isDefaultPrevented === returnFalse;
+ };
+
+ // Dummy function that gets replaced on the delegation state functions
+ var returnFalse = function () {
+ return false;
+ };
+
+ // Dummy function that gets replaced on the delegation state functions
+ var returnTrue = function () {
+ return true;
+ };
+
+ /**
+ * Binds a native event to a callback on the speified target.
+ */
+ function addEvent(target, name, callback, capture) {
+ if (target.addEventListener) {
+ target.addEventListener(name, callback, capture || false);
+ } else if (target.attachEvent) {
+ target.attachEvent('on' + name, callback);
+ }
+ }
+
+ /**
+ * Unbinds a native event callback on the specified target.
+ */
+ function removeEvent(target, name, callback, capture) {
+ if (target.removeEventListener) {
+ target.removeEventListener(name, callback, capture || false);
+ } else if (target.detachEvent) {
+ target.detachEvent('on' + name, callback);
+ }
+ }
+
+ /**
+ * Gets the event target based on shadow dom properties like path and deepPath.
+ */
+ function getTargetFromShadowDom(event, defaultTarget) {
+ var path, target = defaultTarget;
+
+ // When target element is inside Shadow DOM we need to take first element from path
+ // otherwise we'll get Shadow Root parent, not actual target element
+
+ // Normalize target for WebComponents v0 implementation (in Chrome)
+ path = event.path;
+ if (path && path.length > 0) {
+ target = path[0];
+ }
+
+ // Normalize target for WebComponents v1 implementation (standard)
+ if (event.deepPath) {
+ path = event.deepPath();
+ if (path && path.length > 0) {
+ target = path[0];
+ }
+ }
+
+ return target;
+ }
+
+ /**
+ * Normalizes a native event object or just adds the event specific methods on a custom event.
+ */
+ function fix(originalEvent, data) {
+ var name, event = data || {}, undef;
+
+ // Copy all properties from the original event
+ for (name in originalEvent) {
+ // layerX/layerY is deprecated in Chrome and produces a warning
+ if (!deprecated[name]) {
+ event[name] = originalEvent[name];
+ }
+ }
+
+ // Normalize target IE uses srcElement
+ if (!event.target) {
+ event.target = event.srcElement || document;
+ }
+
+ // Experimental shadow dom support
+ if (Env.experimentalShadowDom) {
+ event.target = getTargetFromShadowDom(originalEvent, event.target);
+ }
+
+ // Calculate pageX/Y if missing and clientX/Y available
+ if (originalEvent && mouseEventRe.test(originalEvent.type) && originalEvent.pageX === undef && originalEvent.clientX !== undef) {
+ var eventDoc = event.target.ownerDocument || document;
+ var doc = eventDoc.documentElement;
+ var body = eventDoc.body;
+
+ event.pageX = originalEvent.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) -
+ (doc && doc.clientLeft || body && body.clientLeft || 0);
+
+ event.pageY = originalEvent.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) -
+ (doc && doc.clientTop || body && body.clientTop || 0);
+ }
+
+ // Add preventDefault method
+ event.preventDefault = function () {
+ event.isDefaultPrevented = returnTrue;
+
+ // Execute preventDefault on the original event object
+ if (originalEvent) {
+ if (originalEvent.preventDefault) {
+ originalEvent.preventDefault();
+ } else {
+ originalEvent.returnValue = false; // IE
+ }
+ }
+ };
+
+ // Add stopPropagation
+ event.stopPropagation = function () {
+ event.isPropagationStopped = returnTrue;
+
+ // Execute stopPropagation on the original event object
+ if (originalEvent) {
+ if (originalEvent.stopPropagation) {
+ originalEvent.stopPropagation();
+ } else {
+ originalEvent.cancelBubble = true; // IE
+ }
+ }
+ };
+
+ // Add stopImmediatePropagation
+ event.stopImmediatePropagation = function () {
+ event.isImmediatePropagationStopped = returnTrue;
+ event.stopPropagation();
+ };
+
+ // Add event delegation states
+ if (hasIsDefaultPrevented(event) === false) {
+ event.isDefaultPrevented = returnFalse;
+ event.isPropagationStopped = returnFalse;
+ event.isImmediatePropagationStopped = returnFalse;
+ }
+
+ // Add missing metaKey for IE 8
+ if (typeof event.metaKey == 'undefined') {
+ event.metaKey = false;
+ }
+
+ return event;
+ }
+
+ /**
+ * Bind a DOMContentLoaded event across browsers and executes the callback once the page DOM is initialized.
+ * It will also set/check the domLoaded state of the event_utils instance so ready isn't called multiple times.
+ */
+ function bindOnReady(win, callback, eventUtils) {
+ var doc = win.document, event = { type: 'ready' };
+
+ if (eventUtils.domLoaded) {
+ callback(event);
+ return;
+ }
+
+ function isDocReady() {
+ // Check complete or interactive state if there is a body
+ // element on some iframes IE 8 will produce a null body
+ return doc.readyState === "complete" || (doc.readyState === "interactive" && doc.body);
+ }
+
+ // Gets called when the DOM is ready
+ function readyHandler() {
+ if (!eventUtils.domLoaded) {
+ eventUtils.domLoaded = true;
+ callback(event);
+ }
+ }
+
+ function waitForDomLoaded() {
+ if (isDocReady()) {
+ removeEvent(doc, "readystatechange", waitForDomLoaded);
+ readyHandler();
+ }
+ }
+
+ function tryScroll() {
+ try {
+ // If IE is used, use the trick by Diego Perini licensed under MIT by request to the author.
+ // http://javascript.nwbox.com/IEContentLoaded/
+ doc.documentElement.doScroll("left");
+ } catch (ex) {
+ Delay.setTimeout(tryScroll);
+ return;
+ }
+
+ readyHandler();
+ }
+
+ // Use W3C method (exclude IE 9,10 - readyState "interactive" became valid only in IE 11)
+ if (doc.addEventListener && !(Env.ie && Env.ie < 11)) {
+ if (isDocReady()) {
+ readyHandler();
+ } else {
+ addEvent(win, 'DOMContentLoaded', readyHandler);
+ }
+ } else {
+ // Use IE method
+ addEvent(doc, "readystatechange", waitForDomLoaded);
+
+ // Wait until we can scroll, when we can the DOM is initialized
+ if (doc.documentElement.doScroll && win.self === win.top) {
+ tryScroll();
+ }
+ }
+
+ // Fallback if any of the above methods should fail for some odd reason
+ addEvent(win, 'load', readyHandler);
+ }
+
+ /**
+ * This class enables you to bind/unbind native events to elements and normalize it's behavior across browsers.
+ */
+ function EventUtils() {
+ var self = this, events = {}, count, expando, hasFocusIn, hasMouseEnterLeave, mouseEnterLeave;
+
+ expando = eventExpandoPrefix + (+new Date()).toString(32);
+ hasMouseEnterLeave = "onmouseenter" in document.documentElement;
+ hasFocusIn = "onfocusin" in document.documentElement;
+ mouseEnterLeave = { mouseenter: 'mouseover', mouseleave: 'mouseout' };
+ count = 1;
+
+ // State if the DOMContentLoaded was executed or not
+ self.domLoaded = false;
+ self.events = events;
+
+ /**
+ * Executes all event handler callbacks for a specific event.
+ *
+ * @private
+ * @param {Event} evt Event object.
+ * @param {String} id Expando id value to look for.
+ */
+ function executeHandlers(evt, id) {
+ var callbackList, i, l, callback, container = events[id];
+
+ callbackList = container && container[evt.type];
+ if (callbackList) {
+ for (i = 0, l = callbackList.length; i < l; i++) {
+ callback = callbackList[i];
+
+ // Check if callback exists might be removed if a unbind is called inside the callback
+ if (callback && callback.func.call(callback.scope, evt) === false) {
+ evt.preventDefault();
+ }
+
+ // Should we stop propagation to immediate listeners
+ if (evt.isImmediatePropagationStopped()) {
+ return;
+ }
+ }
+ }
+ }
+
+ /**
+ * Binds a callback to an event on the specified target.
+ *
+ * @method bind
+ * @param {Object} target Target node/window or custom object.
+ * @param {String} names Name of the event to bind.
+ * @param {function} callback Callback function to execute when the event occurs.
+ * @param {Object} scope Scope to call the callback function on, defaults to target.
+ * @return {function} Callback function that got bound.
+ */
+ self.bind = function (target, names, callback, scope) {
+ var id, callbackList, i, name, fakeName, nativeHandler, capture, win = window;
+
+ // Native event handler function patches the event and executes the callbacks for the expando
+ function defaultNativeHandler(evt) {
+ executeHandlers(fix(evt || win.event), id);
+ }
+
+ // Don't bind to text nodes or comments
+ if (!target || target.nodeType === 3 || target.nodeType === 8) {
+ return;
+ }
+
+ // Create or get events id for the target
+ if (!target[expando]) {
+ id = count++;
+ target[expando] = id;
+ events[id] = {};
+ } else {
+ id = target[expando];
+ }
+
+ // Setup the specified scope or use the target as a default
+ scope = scope || target;
+
+ // Split names and bind each event, enables you to bind multiple events with one call
+ names = names.split(' ');
+ i = names.length;
+ while (i--) {
+ name = names[i];
+ nativeHandler = defaultNativeHandler;
+ fakeName = capture = false;
+
+ // Use ready instead of DOMContentLoaded
+ if (name === "DOMContentLoaded") {
+ name = "ready";
+ }
+
+ // DOM is already ready
+ if (self.domLoaded && name === "ready" && target.readyState == 'complete') {
+ callback.call(scope, fix({ type: name }));
+ continue;
+ }
+
+ // Handle mouseenter/mouseleaver
+ if (!hasMouseEnterLeave) {
+ fakeName = mouseEnterLeave[name];
+
+ if (fakeName) {
+ nativeHandler = function (evt) {
+ var current, related;
+
+ current = evt.currentTarget;
+ related = evt.relatedTarget;
+
+ // Check if related is inside the current target if it's not then the event should
+ // be ignored since it's a mouseover/mouseout inside the element
+ if (related && current.contains) {
+ // Use contains for performance
+ related = current.contains(related);
+ } else {
+ while (related && related !== current) {
+ related = related.parentNode;
+ }
+ }
+
+ // Fire fake event
+ if (!related) {
+ evt = fix(evt || win.event);
+ evt.type = evt.type === 'mouseout' ? 'mouseleave' : 'mouseenter';
+ evt.target = current;
+ executeHandlers(evt, id);
+ }
+ };
+ }
+ }
+
+ // Fake bubbling of focusin/focusout
+ if (!hasFocusIn && (name === "focusin" || name === "focusout")) {
+ capture = true;
+ fakeName = name === "focusin" ? "focus" : "blur";
+ nativeHandler = function (evt) {
+ evt = fix(evt || win.event);
+ evt.type = evt.type === 'focus' ? 'focusin' : 'focusout';
+ executeHandlers(evt, id);
+ };
+ }
+
+ // Setup callback list and bind native event
+ callbackList = events[id][name];
+ if (!callbackList) {
+ events[id][name] = callbackList = [{ func: callback, scope: scope }];
+ callbackList.fakeName = fakeName;
+ callbackList.capture = capture;
+ //callbackList.callback = callback;
+
+ // Add the nativeHandler to the callback list so that we can later unbind it
+ callbackList.nativeHandler = nativeHandler;
+
+ // Check if the target has native events support
+
+ if (name === "ready") {
+ bindOnReady(target, nativeHandler, self);
+ } else {
+ addEvent(target, fakeName || name, nativeHandler, capture);
+ }
+ } else {
+ if (name === "ready" && self.domLoaded) {
+ callback({ type: name });
+ } else {
+ // If it already has an native handler then just push the callback
+ callbackList.push({ func: callback, scope: scope });
+ }
+ }
+ }
+
+ target = callbackList = 0; // Clean memory for IE
+
+ return callback;
+ };
+
+ /**
+ * Unbinds the specified event by name, name and callback or all events on the target.
+ *
+ * @method unbind
+ * @param {Object} target Target node/window or custom object.
+ * @param {String} names Optional event name to unbind.
+ * @param {function} callback Optional callback function to unbind.
+ * @return {EventUtils} Event utils instance.
+ */
+ self.unbind = function (target, names, callback) {
+ var id, callbackList, i, ci, name, eventMap;
+
+ // Don't bind to text nodes or comments
+ if (!target || target.nodeType === 3 || target.nodeType === 8) {
+ return self;
+ }
+
+ // Unbind event or events if the target has the expando
+ id = target[expando];
+ if (id) {
+ eventMap = events[id];
+
+ // Specific callback
+ if (names) {
+ names = names.split(' ');
+ i = names.length;
+ while (i--) {
+ name = names[i];
+ callbackList = eventMap[name];
+
+ // Unbind the event if it exists in the map
+ if (callbackList) {
+ // Remove specified callback
+ if (callback) {
+ ci = callbackList.length;
+ while (ci--) {
+ if (callbackList[ci].func === callback) {
+ var nativeHandler = callbackList.nativeHandler;
+ var fakeName = callbackList.fakeName, capture = callbackList.capture;
+
+ // Clone callbackList since unbind inside a callback would otherwise break the handlers loop
+ callbackList = callbackList.slice(0, ci).concat(callbackList.slice(ci + 1));
+ callbackList.nativeHandler = nativeHandler;
+ callbackList.fakeName = fakeName;
+ callbackList.capture = capture;
+
+ eventMap[name] = callbackList;
+ }
+ }
+ }
+
+ // Remove all callbacks if there isn't a specified callback or there is no callbacks left
+ if (!callback || callbackList.length === 0) {
+ delete eventMap[name];
+ removeEvent(target, callbackList.fakeName || name, callbackList.nativeHandler, callbackList.capture);
+ }
+ }
+ }
+ } else {
+ // All events for a specific element
+ for (name in eventMap) {
+ callbackList = eventMap[name];
+ removeEvent(target, callbackList.fakeName || name, callbackList.nativeHandler, callbackList.capture);
+ }
+
+ eventMap = {};
+ }
+
+ // Check if object is empty, if it isn't then we won't remove the expando map
+ for (name in eventMap) {
+ return self;
+ }
+
+ // Delete event object
+ delete events[id];
+
+ // Remove expando from target
+ try {
+ // IE will fail here since it can't delete properties from window
+ delete target[expando];
+ } catch (ex) {
+ // IE will set it to null
+ target[expando] = null;
+ }
+ }
+
+ return self;
+ };
+
+ /**
+ * Fires the specified event on the specified target.
+ *
+ * @method fire
+ * @param {Object} target Target node/window or custom object.
+ * @param {String} name Event name to fire.
+ * @param {Object} args Optional arguments to send to the observers.
+ * @return {EventUtils} Event utils instance.
+ */
+ self.fire = function (target, name, args) {
+ var id;
+
+ // Don't bind to text nodes or comments
+ if (!target || target.nodeType === 3 || target.nodeType === 8) {
+ return self;
+ }
+
+ // Build event object by patching the args
+ args = fix(null, args);
+ args.type = name;
+ args.target = target;
+
+ do {
+ // Found an expando that means there is listeners to execute
+ id = target[expando];
+ if (id) {
+ executeHandlers(args, id);
+ }
+
+ // Walk up the DOM
+ target = target.parentNode || target.ownerDocument || target.defaultView || target.parentWindow;
+ } while (target && !args.isPropagationStopped());
+
+ return self;
+ };
+
+ /**
+ * Removes all bound event listeners for the specified target. This will also remove any bound
+ * listeners to child nodes within that target.
+ *
+ * @method clean
+ * @param {Object} target Target node/window object.
+ * @return {EventUtils} Event utils instance.
+ */
+ self.clean = function (target) {
+ var i, children, unbind = self.unbind;
+
+ // Don't bind to text nodes or comments
+ if (!target || target.nodeType === 3 || target.nodeType === 8) {
+ return self;
+ }
+
+ // Unbind any element on the specified target
+ if (target[expando]) {
+ unbind(target);
+ }
+
+ // Target doesn't have getElementsByTagName it's probably a window object then use it's document to find the children
+ if (!target.getElementsByTagName) {
+ target = target.document;
+ }
+
+ // Remove events from each child element
+ if (target && target.getElementsByTagName) {
+ unbind(target);
+
+ children = target.getElementsByTagName('*');
+ i = children.length;
+ while (i--) {
+ target = children[i];
+
+ if (target[expando]) {
+ unbind(target);
+ }
+ }
+ }
+
+ return self;
+ };
+
+ /**
+ * Destroys the event object. Call this on IE to remove memory leaks.
+ */
+ self.destroy = function () {
+ events = {};
+ };
+
+ // Legacy function for canceling events
+ self.cancel = function (e) {
+ if (e) {
+ e.preventDefault();
+ e.stopImmediatePropagation();
+ }
+
+ return false;
+ };
+ }
+
+ EventUtils.Event = new EventUtils();
+ EventUtils.Event.bind(window, 'ready', function () { });
+
+ return EventUtils;
+ }
+);
/**
* Sizzle.js
*
* Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
+ * Copyright (c) 1999-2017 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
@@ -1538,2039 +1539,2043 @@ define("tinymce/dom/EventUtils", [
*
* Date: @DATE
*/
-define("tinymce/dom/Sizzle", [], function() {
-var i,
- support,
- Expr,
- getText,
- isXML,
- tokenize,
- compile,
- select,
- outermostContext,
- sortInput,
- hasDuplicate,
-
- // Local document vars
- setDocument,
- document,
- docElem,
- documentIsHTML,
- rbuggyQSA,
- rbuggyMatches,
- matches,
- contains,
-
- // Instance-specific data
- expando = "sizzle" + -(new Date()),
- preferredDoc = window.document,
- dirruns = 0,
- done = 0,
- classCache = createCache(),
- tokenCache = createCache(),
- compilerCache = createCache(),
- sortOrder = function( a, b ) {
- if ( a === b ) {
- hasDuplicate = true;
- }
- return 0;
- },
-
- // General-purpose constants
- strundefined = typeof undefined,
- MAX_NEGATIVE = 1 << 31,
-
- // Instance methods
- hasOwn = ({}).hasOwnProperty,
- arr = [],
- pop = arr.pop,
- push_native = arr.push,
- push = arr.push,
- slice = arr.slice,
- // Use a stripped-down indexOf if we can't use a native one
- indexOf = arr.indexOf || function( elem ) {
- var i = 0,
- len = this.length;
- for ( ; i < len; i++ ) {
- if ( this[i] === elem ) {
- return i;
- }
- }
- return -1;
- },
-
- booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",
-
- // Regular expressions
-
- // http://www.w3.org/TR/css3-selectors/#whitespace
- whitespace = "[\\x20\\t\\r\\n\\f]",
-
- // http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier
- identifier = "(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",
-
- // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors
- attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace +
- // Operator (capture 2)
- "*([*^$|!~]?=)" + whitespace +
- // "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]"
- "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace +
- "*\\]",
-
- pseudos = ":(" + identifier + ")(?:\\((" +
- // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments:
- // 1. quoted (capture 3; capture 4 or capture 5)
- "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" +
- // 2. simple (capture 6)
- "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" +
- // 3. anything else (capture 2)
- ".*" +
- ")\\)|)",
-
- // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter
- rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ),
-
- rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ),
- rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ),
-
- rattributeQuotes = new RegExp( "=" + whitespace + "*([^\\]'\"]*?)" + whitespace + "*\\]", "g" ),
-
- rpseudo = new RegExp( pseudos ),
- ridentifier = new RegExp( "^" + identifier + "$" ),
-
- matchExpr = {
- "ID": new RegExp( "^#(" + identifier + ")" ),
- "CLASS": new RegExp( "^\\.(" + identifier + ")" ),
- "TAG": new RegExp( "^(" + identifier + "|[*])" ),
- "ATTR": new RegExp( "^" + attributes ),
- "PSEUDO": new RegExp( "^" + pseudos ),
- "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace +
- "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace +
- "*(\\d+)|))" + whitespace + "*\\)|)", "i" ),
- "bool": new RegExp( "^(?:" + booleans + ")$", "i" ),
- // For use in libraries implementing .is()
- // We use this for POS matching in `select`
- "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" +
- whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" )
- },
-
- rinputs = /^(?:input|select|textarea|button)$/i,
- rheader = /^h\d$/i,
-
- rnative = /^[^{]+\{\s*\[native \w/,
-
- // Easily-parseable/retrievable ID or TAG or CLASS selectors
- rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,
-
- rsibling = /[+~]/,
- rescape = /'|\\/g,
-
- // CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters
- runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ),
- funescape = function( _, escaped, escapedWhitespace ) {
- var high = "0x" + escaped - 0x10000;
- // NaN means non-codepoint
- // Support: Firefox<24
- // Workaround erroneous numeric interpretation of +"0x"
- return high !== high || escapedWhitespace ?
- escaped :
- high < 0 ?
- // BMP codepoint
- String.fromCharCode( high + 0x10000 ) :
- // Supplemental Plane codepoint (surrogate pair)
- String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 );
- };
-
-// Optimize for push.apply( _, NodeList )
-try {
- push.apply(
- (arr = slice.call( preferredDoc.childNodes )),
- preferredDoc.childNodes
- );
- // Support: Android<4.0
- // Detect silently failing push.apply
- arr[ preferredDoc.childNodes.length ].nodeType;
-} catch ( e ) {
- push = { apply: arr.length ?
-
- // Leverage slice if possible
- function( target, els ) {
- push_native.apply( target, slice.call(els) );
- } :
-
- // Support: IE<9
- // Otherwise append directly
- function( target, els ) {
- var j = target.length,
- i = 0;
- // Can't trust NodeList.length
- while ( (target[j++] = els[i++]) ) {}
- target.length = j - 1;
- }
- };
-}
-
-function Sizzle( selector, context, results, seed ) {
- var match, elem, m, nodeType,
- // QSA vars
- i, groups, old, nid, newContext, newSelector;
-
- if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) {
- setDocument( context );
- }
-
- context = context || document;
- results = results || [];
-
- if ( !selector || typeof selector !== "string" ) {
- return results;
- }
-
- if ( (nodeType = context.nodeType) !== 1 && nodeType !== 9 ) {
- return [];
- }
-
- if ( documentIsHTML && !seed ) {
-
- // Shortcuts
- if ( (match = rquickExpr.exec( selector )) ) {
- // Speed-up: Sizzle("#ID")
- if ( (m = match[1]) ) {
- if ( nodeType === 9 ) {
- elem = context.getElementById( m );
- // Check parentNode to catch when Blackberry 4.6 returns
- // nodes that are no longer in the document (jQuery #6963)
- if ( elem && elem.parentNode ) {
- // Handle the case where IE, Opera, and Webkit return items
- // by name instead of ID
- if ( elem.id === m ) {
- results.push( elem );
- return results;
- }
- } else {
- return results;
- }
- } else {
- // Context is not a document
- if ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) &&
- contains( context, elem ) && elem.id === m ) {
- results.push( elem );
- return results;
- }
- }
-
- // Speed-up: Sizzle("TAG")
- } else if ( match[2] ) {
- push.apply( results, context.getElementsByTagName( selector ) );
- return results;
-
- // Speed-up: Sizzle(".CLASS")
- } else if ( (m = match[3]) && support.getElementsByClassName ) {
- push.apply( results, context.getElementsByClassName( m ) );
- return results;
- }
- }
-
- // QSA path
- if ( support.qsa && (!rbuggyQSA || !rbuggyQSA.test( selector )) ) {
- nid = old = expando;
- newContext = context;
- newSelector = nodeType === 9 && selector;
-
- // qSA works strangely on Element-rooted queries
- // We can work around this by specifying an extra ID on the root
- // and working up from there (Thanks to Andrew Dupont for the technique)
- // IE 8 doesn't work on object elements
- if ( nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {
- groups = tokenize( selector );
-
- if ( (old = context.getAttribute("id")) ) {
- nid = old.replace( rescape, "\\$&" );
- } else {
- context.setAttribute( "id", nid );
- }
- nid = "[id='" + nid + "'] ";
-
- i = groups.length;
- while ( i-- ) {
- groups[i] = nid + toSelector( groups[i] );
- }
- newContext = rsibling.test( selector ) && testContext( context.parentNode ) || context;
- newSelector = groups.join(",");
- }
-
- if ( newSelector ) {
- try {
- push.apply( results,
- newContext.querySelectorAll( newSelector )
- );
- return results;
- } catch(qsaError) {
- } finally {
- if ( !old ) {
- context.removeAttribute("id");
- }
- }
- }
- }
- }
-
- // All others
- return select( selector.replace( rtrim, "$1" ), context, results, seed );
-}
-
-/**
- * Create key-value caches of limited size
- * @returns {Function(string, Object)} Returns the Object data after storing it on itself with
- * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength)
- * deleting the oldest entry
- */
-function createCache() {
- var keys = [];
-
- function cache( key, value ) {
- // Use (key + " ") to avoid collision with native prototype properties (see Issue #157)
- if ( keys.push( key + " " ) > Expr.cacheLength ) {
- // Only keep the most recent entries
- delete cache[ keys.shift() ];
- }
- return (cache[ key + " " ] = value);
- }
- return cache;
-}
-
-/**
- * Mark a function for special use by Sizzle
- * @param {Function} fn The function to mark
- */
-function markFunction( fn ) {
- fn[ expando ] = true;
- return fn;
-}
-
-/**
- * Support testing using an element
- * @param {Function} fn Passed the created div and expects a boolean result
- */
-function assert( fn ) {
- var div = document.createElement("div");
-
- try {
- return !!fn( div );
- } catch (e) {
- return false;
- } finally {
- // Remove from its parent by default
- if ( div.parentNode ) {
- div.parentNode.removeChild( div );
- }
- // release memory in IE
- div = null;
- }
-}
-
-/**
- * Adds the same handler for all of the specified attrs
- * @param {String} attrs Pipe-separated list of attributes
- * @param {Function} handler The method that will be applied
- */
-function addHandle( attrs, handler ) {
- var arr = attrs.split("|"),
- i = attrs.length;
-
- while ( i-- ) {
- Expr.attrHandle[ arr[i] ] = handler;
- }
-}
-
-/**
- * Checks document order of two siblings
- * @param {Element} a
- * @param {Element} b
- * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b
- */
-function siblingCheck( a, b ) {
- var cur = b && a,
- diff = cur && a.nodeType === 1 && b.nodeType === 1 &&
- ( ~b.sourceIndex || MAX_NEGATIVE ) -
- ( ~a.sourceIndex || MAX_NEGATIVE );
-
- // Use IE sourceIndex if available on both nodes
- if ( diff ) {
- return diff;
- }
-
- // Check if b follows a
- if ( cur ) {
- while ( (cur = cur.nextSibling) ) {
- if ( cur === b ) {
- return -1;
- }
- }
- }
-
- return a ? 1 : -1;
-}
-
-/**
- * Returns a function to use in pseudos for input types
- * @param {String} type
- */
-function createInputPseudo( type ) {
- return function( elem ) {
- var name = elem.nodeName.toLowerCase();
- return name === "input" && elem.type === type;
- };
-}
-
-/**
- * Returns a function to use in pseudos for buttons
- * @param {String} type
- */
-function createButtonPseudo( type ) {
- return function( elem ) {
- var name = elem.nodeName.toLowerCase();
- return (name === "input" || name === "button") && elem.type === type;
- };
-}
-
-/**
- * Returns a function to use in pseudos for positionals
- * @param {Function} fn
- */
-function createPositionalPseudo( fn ) {
- return markFunction(function( argument ) {
- argument = +argument;
- return markFunction(function( seed, matches ) {
- var j,
- matchIndexes = fn( [], seed.length, argument ),
- i = matchIndexes.length;
-
- // Match elements found at the specified indexes
- while ( i-- ) {
- if ( seed[ (j = matchIndexes[i]) ] ) {
- seed[j] = !(matches[j] = seed[j]);
- }
- }
- });
- });
-}
-
-/**
- * Checks a node for validity as a Sizzle context
- * @param {Element|Object=} context
- * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value
- */
-function testContext( context ) {
- return context && typeof context.getElementsByTagName !== strundefined && context;
-}
-
-// Expose support vars for convenience
-support = Sizzle.support = {};
-
-/**
- * Detects XML nodes
- * @param {Element|Object} elem An element or a document
- * @returns {Boolean} True iff elem is a non-HTML XML node
- */
-isXML = Sizzle.isXML = function( elem ) {
- // documentElement is verified for cases where it doesn't yet exist
- // (such as loading iframes in IE - #4833)
- var documentElement = elem && (elem.ownerDocument || elem).documentElement;
- return documentElement ? documentElement.nodeName !== "HTML" : false;
-};
-
-/**
- * Sets document-related variables once based on the current document
- * @param {Element|Object} [doc] An element or document object to use to set the document
- * @returns {Object} Returns the current document
- */
-setDocument = Sizzle.setDocument = function( node ) {
- var hasCompare,
- doc = node ? node.ownerDocument || node : preferredDoc,
- parent = doc.defaultView;
-
- function getTop(win) {
- // Edge throws a lovely Object expected if you try to get top on a detached reference see #2642
- try {
- return win.top;
- } catch (ex) {
- // Ignore
- }
-
- return null;
- }
-
- // If no document and documentElement is available, return
- if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) {
- return document;
- }
-
- // Set our document
- document = doc;
- docElem = doc.documentElement;
-
- // Support tests
- documentIsHTML = !isXML( doc );
-
- // Support: IE>8
- // If iframe document is assigned to "document" variable and if iframe has been reloaded,
- // IE will throw "permission denied" error when accessing "document" variable, see jQuery #13936
- // IE6-8 do not support the defaultView property so parent will be undefined
- if ( parent && parent !== getTop(parent) ) {
- // IE11 does not have attachEvent, so all must suffer
- if ( parent.addEventListener ) {
- parent.addEventListener( "unload", function() {
- setDocument();
- }, false );
- } else if ( parent.attachEvent ) {
- parent.attachEvent( "onunload", function() {
- setDocument();
- });
- }
- }
-
- /* Attributes
- ---------------------------------------------------------------------- */
-
- // Support: IE<8
- // Verify that getAttribute really returns attributes and not properties (excepting IE8 booleans)
- support.attributes = assert(function( div ) {
- div.className = "i";
- return !div.getAttribute("className");
- });
-
- /* getElement(s)By*
- ---------------------------------------------------------------------- */
-
- // Check if getElementsByTagName("*") returns only elements
- support.getElementsByTagName = assert(function( div ) {
- div.appendChild( doc.createComment("") );
- return !div.getElementsByTagName("*").length;
- });
-
- // Support: IE<9
- support.getElementsByClassName = rnative.test( doc.getElementsByClassName );
-
- // Support: IE<10
- // Check if getElementById returns elements by name
- // The broken getElementById methods don't pick up programatically-set names,
- // so use a roundabout getElementsByName test
- support.getById = assert(function( div ) {
- docElem.appendChild( div ).id = expando;
- return !doc.getElementsByName || !doc.getElementsByName( expando ).length;
- });
-
- // ID find and filter
- if ( support.getById ) {
- Expr.find["ID"] = function( id, context ) {
- if ( typeof context.getElementById !== strundefined && documentIsHTML ) {
- var m = context.getElementById( id );
- // Check parentNode to catch when Blackberry 4.6 returns
- // nodes that are no longer in the document #6963
- return m && m.parentNode ? [ m ] : [];
- }
- };
- Expr.filter["ID"] = function( id ) {
- var attrId = id.replace( runescape, funescape );
- return function( elem ) {
- return elem.getAttribute("id") === attrId;
- };
- };
- } else {
- // Support: IE6/7
- // getElementById is not reliable as a find shortcut
- delete Expr.find["ID"];
-
- Expr.filter["ID"] = function( id ) {
- var attrId = id.replace( runescape, funescape );
- return function( elem ) {
- var node = typeof elem.getAttributeNode !== strundefined && elem.getAttributeNode("id");
- return node && node.value === attrId;
- };
- };
- }
-
- // Tag
- Expr.find["TAG"] = support.getElementsByTagName ?
- function( tag, context ) {
- if ( typeof context.getElementsByTagName !== strundefined ) {
- return context.getElementsByTagName( tag );
- }
- } :
- function( tag, context ) {
- var elem,
- tmp = [],
- i = 0,
- results = context.getElementsByTagName( tag );
-
- // Filter out possible comments
- if ( tag === "*" ) {
- while ( (elem = results[i++]) ) {
- if ( elem.nodeType === 1 ) {
- tmp.push( elem );
- }
- }
-
- return tmp;
- }
- return results;
- };
-
- // Class
- Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) {
- if ( documentIsHTML ) {
- return context.getElementsByClassName( className );
- }
- };
-
- /* QSA/matchesSelector
- ---------------------------------------------------------------------- */
-
- // QSA and matchesSelector support
-
- // matchesSelector(:active) reports false when true (IE9/Opera 11.5)
- rbuggyMatches = [];
-
- // qSa(:focus) reports false when true (Chrome 21)
- // We allow this because of a bug in IE8/9 that throws an error
- // whenever `document.activeElement` is accessed on an iframe
- // So, we allow :focus to pass through QSA all the time to avoid the IE error
- // See http://bugs.jquery.com/ticket/13378
- rbuggyQSA = [];
-
- if ( (support.qsa = rnative.test( doc.querySelectorAll )) ) {
- // Build QSA regex
- // Regex strategy adopted from Diego Perini
- assert(function( div ) {
- // Select is set to empty string on purpose
- // This is to test IE's treatment of not explicitly
- // setting a boolean content attribute,
- // since its presence should be enough
- // http://bugs.jquery.com/ticket/12359
- div.innerHTML = "";
-
- // Support: IE8, Opera 11-12.16
- // Nothing should be selected when empty strings follow ^= or $= or *=
- // The test attribute must be unknown in Opera but "safe" for WinRT
- // http://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section
- if ( div.querySelectorAll("[msallowcapture^='']").length ) {
- rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" );
- }
-
- // Support: IE8
- // Boolean attributes and "value" are not treated correctly
- if ( !div.querySelectorAll("[selected]").length ) {
- rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" );
- }
-
- // Webkit/Opera - :checked should return selected option elements
- // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
- // IE8 throws error here and will not see later tests
- if ( !div.querySelectorAll(":checked").length ) {
- rbuggyQSA.push(":checked");
- }
- });
-
- assert(function( div ) {
- // Support: Windows 8 Native Apps
- // The type and name attributes are restricted during .innerHTML assignment
- var input = doc.createElement("input");
- input.setAttribute( "type", "hidden" );
- div.appendChild( input ).setAttribute( "name", "D" );
-
- // Support: IE8
- // Enforce case-sensitivity of name attribute
- if ( div.querySelectorAll("[name=d]").length ) {
- rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" );
- }
-
- // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled)
- // IE8 throws error here and will not see later tests
- if ( !div.querySelectorAll(":enabled").length ) {
- rbuggyQSA.push( ":enabled", ":disabled" );
- }
-
- // Opera 10-11 does not throw on post-comma invalid pseudos
- div.querySelectorAll("*,:x");
- rbuggyQSA.push(",.*:");
- });
- }
-
- if ( (support.matchesSelector = rnative.test( (matches = docElem.matches ||
- docElem.webkitMatchesSelector ||
- docElem.mozMatchesSelector ||
- docElem.oMatchesSelector ||
- docElem.msMatchesSelector) )) ) {
-
- assert(function( div ) {
- // Check to see if it's possible to do matchesSelector
- // on a disconnected node (IE 9)
- support.disconnectedMatch = matches.call( div, "div" );
-
- // This should fail with an exception
- // Gecko does not error, returns false instead
- matches.call( div, "[s!='']:x" );
- rbuggyMatches.push( "!=", pseudos );
- });
- }
-
- rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") );
- rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") );
-
- /* Contains
- ---------------------------------------------------------------------- */
- hasCompare = rnative.test( docElem.compareDocumentPosition );
-
- // Element contains another
- // Purposefully does not implement inclusive descendent
- // As in, an element does not contain itself
- contains = hasCompare || rnative.test( docElem.contains ) ?
- function( a, b ) {
- var adown = a.nodeType === 9 ? a.documentElement : a,
- bup = b && b.parentNode;
- return a === bup || !!( bup && bup.nodeType === 1 && (
- adown.contains ?
- adown.contains( bup ) :
- a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16
- ));
- } :
- function( a, b ) {
- if ( b ) {
- while ( (b = b.parentNode) ) {
- if ( b === a ) {
- return true;
- }
- }
- }
- return false;
- };
-
- /* Sorting
- ---------------------------------------------------------------------- */
-
- // Document order sorting
- sortOrder = hasCompare ?
- function( a, b ) {
-
- // Flag for duplicate removal
- if ( a === b ) {
- hasDuplicate = true;
- return 0;
- }
-
- // Sort on method existence if only one input has compareDocumentPosition
- var compare = !a.compareDocumentPosition - !b.compareDocumentPosition;
- if ( compare ) {
- return compare;
- }
-
- // Calculate position if both inputs belong to the same document
- compare = ( a.ownerDocument || a ) === ( b.ownerDocument || b ) ?
- a.compareDocumentPosition( b ) :
-
- // Otherwise we know they are disconnected
- 1;
-
- // Disconnected nodes
- if ( compare & 1 ||
- (!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) {
-
- // Choose the first element that is related to our preferred document
- if ( a === doc || a.ownerDocument === preferredDoc && contains(preferredDoc, a) ) {
- return -1;
- }
- if ( b === doc || b.ownerDocument === preferredDoc && contains(preferredDoc, b) ) {
- return 1;
- }
-
- // Maintain original order
- return sortInput ?
- ( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) :
- 0;
- }
-
- return compare & 4 ? -1 : 1;
- } :
- function( a, b ) {
- // Exit early if the nodes are identical
- if ( a === b ) {
- hasDuplicate = true;
- return 0;
- }
-
- var cur,
- i = 0,
- aup = a.parentNode,
- bup = b.parentNode,
- ap = [ a ],
- bp = [ b ];
-
- // Parentless nodes are either documents or disconnected
- if ( !aup || !bup ) {
- return a === doc ? -1 :
- b === doc ? 1 :
- aup ? -1 :
- bup ? 1 :
- sortInput ?
- ( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) :
- 0;
-
- // If the nodes are siblings, we can do a quick check
- } else if ( aup === bup ) {
- return siblingCheck( a, b );
- }
-
- // Otherwise we need full lists of their ancestors for comparison
- cur = a;
- while ( (cur = cur.parentNode) ) {
- ap.unshift( cur );
- }
- cur = b;
- while ( (cur = cur.parentNode) ) {
- bp.unshift( cur );
- }
-
- // Walk down the tree looking for a discrepancy
- while ( ap[i] === bp[i] ) {
- i++;
- }
-
- return i ?
- // Do a sibling check if the nodes have a common ancestor
- siblingCheck( ap[i], bp[i] ) :
-
- // Otherwise nodes in our document sort first
- ap[i] === preferredDoc ? -1 :
- bp[i] === preferredDoc ? 1 :
- 0;
- };
-
- return doc;
-};
-
-Sizzle.matches = function( expr, elements ) {
- return Sizzle( expr, null, null, elements );
-};
-
-Sizzle.matchesSelector = function( elem, expr ) {
- // Set document vars if needed
- if ( ( elem.ownerDocument || elem ) !== document ) {
- setDocument( elem );
- }
-
- // Make sure that attribute selectors are quoted
- expr = expr.replace( rattributeQuotes, "='$1']" );
-
- if ( support.matchesSelector && documentIsHTML &&
- ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) &&
- ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) {
-
- try {
- var ret = matches.call( elem, expr );
-
- // IE 9's matchesSelector returns false on disconnected nodes
- if ( ret || support.disconnectedMatch ||
- // As well, disconnected nodes are said to be in a document
- // fragment in IE 9
- elem.document && elem.document.nodeType !== 11 ) {
- return ret;
- }
- } catch(e) {}
- }
-
- return Sizzle( expr, document, null, [ elem ] ).length > 0;
-};
-
-Sizzle.contains = function( context, elem ) {
- // Set document vars if needed
- if ( ( context.ownerDocument || context ) !== document ) {
- setDocument( context );
- }
- return contains( context, elem );
-};
-
-Sizzle.attr = function( elem, name ) {
- // Set document vars if needed
- if ( ( elem.ownerDocument || elem ) !== document ) {
- setDocument( elem );
- }
-
- var fn = Expr.attrHandle[ name.toLowerCase() ],
- // Don't get fooled by Object.prototype properties (jQuery #13807)
- val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ?
- fn( elem, name, !documentIsHTML ) :
- undefined;
-
- return val !== undefined ?
- val :
- support.attributes || !documentIsHTML ?
- elem.getAttribute( name ) :
- (val = elem.getAttributeNode(name)) && val.specified ?
- val.value :
- null;
-};
-
-Sizzle.error = function( msg ) {
- throw new Error( "Syntax error, unrecognized expression: " + msg );
-};
-
-/**
- * Document sorting and removing duplicates
- * @param {ArrayLike} results
- */
-Sizzle.uniqueSort = function( results ) {
- var elem,
- duplicates = [],
- j = 0,
- i = 0;
-
- // Unless we *know* we can detect duplicates, assume their presence
- hasDuplicate = !support.detectDuplicates;
- sortInput = !support.sortStable && results.slice( 0 );
- results.sort( sortOrder );
-
- if ( hasDuplicate ) {
- while ( (elem = results[i++]) ) {
- if ( elem === results[ i ] ) {
- j = duplicates.push( i );
- }
- }
- while ( j-- ) {
- results.splice( duplicates[ j ], 1 );
- }
- }
-
- // Clear input after sorting to release objects
- // See https://github.com/jquery/sizzle/pull/225
- sortInput = null;
-
- return results;
-};
-
-/**
- * Utility function for retrieving the text value of an array of DOM nodes
- * @param {Array|Element} elem
- */
-getText = Sizzle.getText = function( elem ) {
- var node,
- ret = "",
- i = 0,
- nodeType = elem.nodeType;
-
- if ( !nodeType ) {
- // If no nodeType, this is expected to be an array
- while ( (node = elem[i++]) ) {
- // Do not traverse comment nodes
- ret += getText( node );
- }
- } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {
- // Use textContent for elements
- // innerText usage removed for consistency of new lines (jQuery #11153)
- if ( typeof elem.textContent === "string" ) {
- return elem.textContent;
- } else {
- // Traverse its children
- for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
- ret += getText( elem );
- }
- }
- } else if ( nodeType === 3 || nodeType === 4 ) {
- return elem.nodeValue;
- }
- // Do not include comment or processing instruction nodes
-
- return ret;
-};
-
-Expr = Sizzle.selectors = {
-
- // Can be adjusted by the user
- cacheLength: 50,
-
- createPseudo: markFunction,
-
- match: matchExpr,
-
- attrHandle: {},
-
- find: {},
-
- relative: {
- ">": { dir: "parentNode", first: true },
- " ": { dir: "parentNode" },
- "+": { dir: "previousSibling", first: true },
- "~": { dir: "previousSibling" }
- },
-
- preFilter: {
- "ATTR": function( match ) {
- match[1] = match[1].replace( runescape, funescape );
-
- // Move the given value to match[3] whether quoted or unquoted
- match[3] = ( match[3] || match[4] || match[5] || "" ).replace( runescape, funescape );
-
- if ( match[2] === "~=" ) {
- match[3] = " " + match[3] + " ";
- }
-
- return match.slice( 0, 4 );
- },
-
- "CHILD": function( match ) {
- /* matches from matchExpr["CHILD"]
- 1 type (only|nth|...)
- 2 what (child|of-type)
- 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...)
- 4 xn-component of xn+y argument ([+-]?\d*n|)
- 5 sign of xn-component
- 6 x of xn-component
- 7 sign of y-component
- 8 y of y-component
- */
- match[1] = match[1].toLowerCase();
-
- if ( match[1].slice( 0, 3 ) === "nth" ) {
- // nth-* requires argument
- if ( !match[3] ) {
- Sizzle.error( match[0] );
- }
-
- // numeric x and y parameters for Expr.filter.CHILD
- // remember that false/true cast respectively to 0/1
- match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) );
- match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" );
-
- // other types prohibit arguments
- } else if ( match[3] ) {
- Sizzle.error( match[0] );
- }
-
- return match;
- },
-
- "PSEUDO": function( match ) {
- var excess,
- unquoted = !match[6] && match[2];
-
- if ( matchExpr["CHILD"].test( match[0] ) ) {
- return null;
- }
-
- // Accept quoted arguments as-is
- if ( match[3] ) {
- match[2] = match[4] || match[5] || "";
-
- // Strip excess characters from unquoted arguments
- } else if ( unquoted && rpseudo.test( unquoted ) &&
- // Get excess from tokenize (recursively)
- (excess = tokenize( unquoted, true )) &&
- // advance to the next closing parenthesis
- (excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) {
-
- // excess is a negative index
- match[0] = match[0].slice( 0, excess );
- match[2] = unquoted.slice( 0, excess );
- }
-
- // Return only captures needed by the pseudo filter method (type and argument)
- return match.slice( 0, 3 );
- }
- },
-
- filter: {
-
- "TAG": function( nodeNameSelector ) {
- var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase();
- return nodeNameSelector === "*" ?
- function() { return true; } :
- function( elem ) {
- return elem.nodeName && elem.nodeName.toLowerCase() === nodeName;
- };
- },
-
- "CLASS": function( className ) {
- var pattern = classCache[ className + " " ];
-
- return pattern ||
- (pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) &&
- classCache( className, function( elem ) {
- return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== strundefined && elem.getAttribute("class") || "" );
- });
- },
-
- "ATTR": function( name, operator, check ) {
- return function( elem ) {
- var result = Sizzle.attr( elem, name );
-
- if ( result == null ) {
- return operator === "!=";
- }
- if ( !operator ) {
- return true;
- }
-
- result += "";
-
- return operator === "=" ? result === check :
- operator === "!=" ? result !== check :
- operator === "^=" ? check && result.indexOf( check ) === 0 :
- operator === "*=" ? check && result.indexOf( check ) > -1 :
- operator === "$=" ? check && result.slice( -check.length ) === check :
- operator === "~=" ? ( " " + result + " " ).indexOf( check ) > -1 :
- operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" :
- false;
- };
- },
-
- "CHILD": function( type, what, argument, first, last ) {
- var simple = type.slice( 0, 3 ) !== "nth",
- forward = type.slice( -4 ) !== "last",
- ofType = what === "of-type";
-
- return first === 1 && last === 0 ?
-
- // Shortcut for :nth-*(n)
- function( elem ) {
- return !!elem.parentNode;
- } :
-
- function( elem, context, xml ) {
- var cache, outerCache, node, diff, nodeIndex, start,
- dir = simple !== forward ? "nextSibling" : "previousSibling",
- parent = elem.parentNode,
- name = ofType && elem.nodeName.toLowerCase(),
- useCache = !xml && !ofType;
-
- if ( parent ) {
-
- // :(first|last|only)-(child|of-type)
- if ( simple ) {
- while ( dir ) {
- node = elem;
- while ( (node = node[ dir ]) ) {
- if ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) {
- return false;
- }
- }
- // Reverse direction for :only-* (if we haven't yet done so)
- start = dir = type === "only" && !start && "nextSibling";
- }
- return true;
- }
-
- start = [ forward ? parent.firstChild : parent.lastChild ];
-
- // non-xml :nth-child(...) stores cache data on `parent`
- if ( forward && useCache ) {
- // Seek `elem` from a previously-cached index
- outerCache = parent[ expando ] || (parent[ expando ] = {});
- cache = outerCache[ type ] || [];
- nodeIndex = cache[0] === dirruns && cache[1];
- diff = cache[0] === dirruns && cache[2];
- node = nodeIndex && parent.childNodes[ nodeIndex ];
-
- while ( (node = ++nodeIndex && node && node[ dir ] ||
-
- // Fallback to seeking `elem` from the start
- (diff = nodeIndex = 0) || start.pop()) ) {
-
- // When found, cache indexes on `parent` and break
- if ( node.nodeType === 1 && ++diff && node === elem ) {
- outerCache[ type ] = [ dirruns, nodeIndex, diff ];
- break;
- }
- }
-
- // Use previously-cached element index if available
- } else if ( useCache && (cache = (elem[ expando ] || (elem[ expando ] = {}))[ type ]) && cache[0] === dirruns ) {
- diff = cache[1];
-
- // xml :nth-child(...) or :nth-last-child(...) or :nth(-last)?-of-type(...)
- } else {
- // Use the same loop as above to seek `elem` from the start
- while ( (node = ++nodeIndex && node && node[ dir ] ||
- (diff = nodeIndex = 0) || start.pop()) ) {
-
- if ( ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) && ++diff ) {
- // Cache the index of each encountered element
- if ( useCache ) {
- (node[ expando ] || (node[ expando ] = {}))[ type ] = [ dirruns, diff ];
- }
-
- if ( node === elem ) {
- break;
- }
- }
- }
- }
-
- // Incorporate the offset, then check against cycle size
- diff -= last;
- return diff === first || ( diff % first === 0 && diff / first >= 0 );
- }
- };
- },
-
- "PSEUDO": function( pseudo, argument ) {
- // pseudo-class names are case-insensitive
- // http://www.w3.org/TR/selectors/#pseudo-classes
- // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters
- // Remember that setFilters inherits from pseudos
- var args,
- fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] ||
- Sizzle.error( "unsupported pseudo: " + pseudo );
-
- // The user may use createPseudo to indicate that
- // arguments are needed to create the filter function
- // just as Sizzle does
- if ( fn[ expando ] ) {
- return fn( argument );
- }
-
- // But maintain support for old signatures
- if ( fn.length > 1 ) {
- args = [ pseudo, pseudo, "", argument ];
- return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ?
- markFunction(function( seed, matches ) {
- var idx,
- matched = fn( seed, argument ),
- i = matched.length;
- while ( i-- ) {
- idx = indexOf.call( seed, matched[i] );
- seed[ idx ] = !( matches[ idx ] = matched[i] );
- }
- }) :
- function( elem ) {
- return fn( elem, 0, args );
- };
- }
-
- return fn;
- }
- },
-
- pseudos: {
- // Potentially complex pseudos
- "not": markFunction(function( selector ) {
- // Trim the selector passed to compile
- // to avoid treating leading and trailing
- // spaces as combinators
- var input = [],
- results = [],
- matcher = compile( selector.replace( rtrim, "$1" ) );
-
- return matcher[ expando ] ?
- markFunction(function( seed, matches, context, xml ) {
- var elem,
- unmatched = matcher( seed, null, xml, [] ),
- i = seed.length;
-
- // Match elements unmatched by `matcher`
- while ( i-- ) {
- if ( (elem = unmatched[i]) ) {
- seed[i] = !(matches[i] = elem);
- }
- }
- }) :
- function( elem, context, xml ) {
- input[0] = elem;
- matcher( input, null, xml, results );
- return !results.pop();
- };
- }),
-
- "has": markFunction(function( selector ) {
- return function( elem ) {
- return Sizzle( selector, elem ).length > 0;
- };
- }),
-
- "contains": markFunction(function( text ) {
- text = text.replace( runescape, funescape );
- return function( elem ) {
- return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1;
- };
- }),
-
- // "Whether an element is represented by a :lang() selector
- // is based solely on the element's language value
- // being equal to the identifier C,
- // or beginning with the identifier C immediately followed by "-".
- // The matching of C against the element's language value is performed case-insensitively.
- // The identifier C does not have to be a valid language name."
- // http://www.w3.org/TR/selectors/#lang-pseudo
- "lang": markFunction( function( lang ) {
- // lang value must be a valid identifier
- if ( !ridentifier.test(lang || "") ) {
- Sizzle.error( "unsupported lang: " + lang );
- }
- lang = lang.replace( runescape, funescape ).toLowerCase();
- return function( elem ) {
- var elemLang;
- do {
- if ( (elemLang = documentIsHTML ?
- elem.lang :
- elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) {
-
- elemLang = elemLang.toLowerCase();
- return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0;
- }
- } while ( (elem = elem.parentNode) && elem.nodeType === 1 );
- return false;
- };
- }),
-
- // Miscellaneous
- "target": function( elem ) {
- var hash = window.location && window.location.hash;
- return hash && hash.slice( 1 ) === elem.id;
- },
-
- "root": function( elem ) {
- return elem === docElem;
- },
-
- "focus": function( elem ) {
- return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex);
- },
-
- // Boolean properties
- "enabled": function( elem ) {
- return elem.disabled === false;
- },
-
- "disabled": function( elem ) {
- return elem.disabled === true;
- },
-
- "checked": function( elem ) {
- // In CSS3, :checked should return both checked and selected elements
- // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
- var nodeName = elem.nodeName.toLowerCase();
- return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected);
- },
-
- "selected": function( elem ) {
- // Accessing this property makes selected-by-default
- // options in Safari work properly
- if ( elem.parentNode ) {
- elem.parentNode.selectedIndex;
- }
-
- return elem.selected === true;
- },
-
- // Contents
- "empty": function( elem ) {
- // http://www.w3.org/TR/selectors/#empty-pseudo
- // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5),
- // but not by others (comment: 8; processing instruction: 7; etc.)
- // nodeType < 6 works because attributes (2) do not appear as children
- for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
- if ( elem.nodeType < 6 ) {
- return false;
- }
- }
- return true;
- },
-
- "parent": function( elem ) {
- return !Expr.pseudos["empty"]( elem );
- },
-
- // Element/input types
- "header": function( elem ) {
- return rheader.test( elem.nodeName );
- },
-
- "input": function( elem ) {
- return rinputs.test( elem.nodeName );
- },
-
- "button": function( elem ) {
- var name = elem.nodeName.toLowerCase();
- return name === "input" && elem.type === "button" || name === "button";
- },
-
- "text": function( elem ) {
- var attr;
- return elem.nodeName.toLowerCase() === "input" &&
- elem.type === "text" &&
-
- // Support: IE<8
- // New HTML5 attribute values (e.g., "search") appear with elem.type === "text"
- ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === "text" );
- },
-
- // Position-in-collection
- "first": createPositionalPseudo(function() {
- return [ 0 ];
- }),
-
- "last": createPositionalPseudo(function( matchIndexes, length ) {
- return [ length - 1 ];
- }),
-
- "eq": createPositionalPseudo(function( matchIndexes, length, argument ) {
- return [ argument < 0 ? argument + length : argument ];
- }),
-
- "even": createPositionalPseudo(function( matchIndexes, length ) {
- var i = 0;
- for ( ; i < length; i += 2 ) {
- matchIndexes.push( i );
- }
- return matchIndexes;
- }),
-
- "odd": createPositionalPseudo(function( matchIndexes, length ) {
- var i = 1;
- for ( ; i < length; i += 2 ) {
- matchIndexes.push( i );
- }
- return matchIndexes;
- }),
-
- "lt": createPositionalPseudo(function( matchIndexes, length, argument ) {
- var i = argument < 0 ? argument + length : argument;
- for ( ; --i >= 0; ) {
- matchIndexes.push( i );
- }
- return matchIndexes;
- }),
-
- "gt": createPositionalPseudo(function( matchIndexes, length, argument ) {
- var i = argument < 0 ? argument + length : argument;
- for ( ; ++i < length; ) {
- matchIndexes.push( i );
- }
- return matchIndexes;
- })
- }
-};
-
-Expr.pseudos["nth"] = Expr.pseudos["eq"];
-
-// Add button/input type pseudos
-for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) {
- Expr.pseudos[ i ] = createInputPseudo( i );
-}
-for ( i in { submit: true, reset: true } ) {
- Expr.pseudos[ i ] = createButtonPseudo( i );
-}
-
-// Easy API for creating new setFilters
-function setFilters() {}
-setFilters.prototype = Expr.filters = Expr.pseudos;
-Expr.setFilters = new setFilters();
-
-tokenize = Sizzle.tokenize = function( selector, parseOnly ) {
- var matched, match, tokens, type,
- soFar, groups, preFilters,
- cached = tokenCache[ selector + " " ];
-
- if ( cached ) {
- return parseOnly ? 0 : cached.slice( 0 );
- }
-
- soFar = selector;
- groups = [];
- preFilters = Expr.preFilter;
-
- while ( soFar ) {
-
- // Comma and first run
- if ( !matched || (match = rcomma.exec( soFar )) ) {
- if ( match ) {
- // Don't consume trailing commas as valid
- soFar = soFar.slice( match[0].length ) || soFar;
- }
- groups.push( (tokens = []) );
- }
-
- matched = false;
-
- // Combinators
- if ( (match = rcombinators.exec( soFar )) ) {
- matched = match.shift();
- tokens.push({
- value: matched,
- // Cast descendant combinators to space
- type: match[0].replace( rtrim, " " )
- });
- soFar = soFar.slice( matched.length );
- }
-
- // Filters
- for ( type in Expr.filter ) {
- if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] ||
- (match = preFilters[ type ]( match ))) ) {
- matched = match.shift();
- tokens.push({
- value: matched,
- type: type,
- matches: match
- });
- soFar = soFar.slice( matched.length );
- }
- }
-
- if ( !matched ) {
- break;
- }
- }
-
- // Return the length of the invalid excess
- // if we're just parsing
- // Otherwise, throw an error or return tokens
- return parseOnly ?
- soFar.length :
- soFar ?
- Sizzle.error( selector ) :
- // Cache the tokens
- tokenCache( selector, groups ).slice( 0 );
-};
-
-function toSelector( tokens ) {
- var i = 0,
- len = tokens.length,
- selector = "";
- for ( ; i < len; i++ ) {
- selector += tokens[i].value;
- }
- return selector;
-}
-
-function addCombinator( matcher, combinator, base ) {
- var dir = combinator.dir,
- checkNonElements = base && dir === "parentNode",
- doneName = done++;
-
- return combinator.first ?
- // Check against closest ancestor/preceding element
- function( elem, context, xml ) {
- while ( (elem = elem[ dir ]) ) {
- if ( elem.nodeType === 1 || checkNonElements ) {
- return matcher( elem, context, xml );
- }
- }
- } :
-
- // Check against all ancestor/preceding elements
- function( elem, context, xml ) {
- var oldCache, outerCache,
- newCache = [ dirruns, doneName ];
-
- // We can't set arbitrary data on XML nodes, so they don't benefit from dir caching
- if ( xml ) {
- while ( (elem = elem[ dir ]) ) {
- if ( elem.nodeType === 1 || checkNonElements ) {
- if ( matcher( elem, context, xml ) ) {
- return true;
- }
- }
- }
- } else {
- while ( (elem = elem[ dir ]) ) {
- if ( elem.nodeType === 1 || checkNonElements ) {
- outerCache = elem[ expando ] || (elem[ expando ] = {});
- if ( (oldCache = outerCache[ dir ]) &&
- oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) {
-
- // Assign to newCache so results back-propagate to previous elements
- return (newCache[ 2 ] = oldCache[ 2 ]);
- } else {
- // Reuse newcache so results back-propagate to previous elements
- outerCache[ dir ] = newCache;
-
- // A match means we're done; a fail means we have to keep checking
- if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) {
- return true;
- }
- }
- }
- }
- }
- };
-}
-
-function elementMatcher( matchers ) {
- return matchers.length > 1 ?
- function( elem, context, xml ) {
- var i = matchers.length;
- while ( i-- ) {
- if ( !matchers[i]( elem, context, xml ) ) {
- return false;
- }
- }
- return true;
- } :
- matchers[0];
-}
-
-function multipleContexts( selector, contexts, results ) {
- var i = 0,
- len = contexts.length;
- for ( ; i < len; i++ ) {
- Sizzle( selector, contexts[i], results );
- }
- return results;
-}
-
-function condense( unmatched, map, filter, context, xml ) {
- var elem,
- newUnmatched = [],
- i = 0,
- len = unmatched.length,
- mapped = map != null;
-
- for ( ; i < len; i++ ) {
- if ( (elem = unmatched[i]) ) {
- if ( !filter || filter( elem, context, xml ) ) {
- newUnmatched.push( elem );
- if ( mapped ) {
- map.push( i );
- }
- }
- }
- }
-
- return newUnmatched;
-}
-
-function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) {
- if ( postFilter && !postFilter[ expando ] ) {
- postFilter = setMatcher( postFilter );
- }
- if ( postFinder && !postFinder[ expando ] ) {
- postFinder = setMatcher( postFinder, postSelector );
- }
- return markFunction(function( seed, results, context, xml ) {
- var temp, i, elem,
- preMap = [],
- postMap = [],
- preexisting = results.length,
-
- // Get initial elements from seed or context
- elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ),
-
- // Prefilter to get matcher input, preserving a map for seed-results synchronization
- matcherIn = preFilter && ( seed || !selector ) ?
- condense( elems, preMap, preFilter, context, xml ) :
- elems,
-
- matcherOut = matcher ?
- // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results,
- postFinder || ( seed ? preFilter : preexisting || postFilter ) ?
-
- // ...intermediate processing is necessary
- [] :
-
- // ...otherwise use results directly
- results :
- matcherIn;
-
- // Find primary matches
- if ( matcher ) {
- matcher( matcherIn, matcherOut, context, xml );
- }
-
- // Apply postFilter
- if ( postFilter ) {
- temp = condense( matcherOut, postMap );
- postFilter( temp, [], context, xml );
-
- // Un-match failing elements by moving them back to matcherIn
- i = temp.length;
- while ( i-- ) {
- if ( (elem = temp[i]) ) {
- matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem);
- }
- }
- }
-
- if ( seed ) {
- if ( postFinder || preFilter ) {
- if ( postFinder ) {
- // Get the final matcherOut by condensing this intermediate into postFinder contexts
- temp = [];
- i = matcherOut.length;
- while ( i-- ) {
- if ( (elem = matcherOut[i]) ) {
- // Restore matcherIn since elem is not yet a final match
- temp.push( (matcherIn[i] = elem) );
- }
- }
- postFinder( null, (matcherOut = []), temp, xml );
- }
-
- // Move matched elements from seed to results to keep them synchronized
- i = matcherOut.length;
- while ( i-- ) {
- if ( (elem = matcherOut[i]) &&
- (temp = postFinder ? indexOf.call( seed, elem ) : preMap[i]) > -1 ) {
-
- seed[temp] = !(results[temp] = elem);
- }
- }
- }
-
- // Add elements to results, through postFinder if defined
- } else {
- matcherOut = condense(
- matcherOut === results ?
- matcherOut.splice( preexisting, matcherOut.length ) :
- matcherOut
- );
- if ( postFinder ) {
- postFinder( null, results, matcherOut, xml );
- } else {
- push.apply( results, matcherOut );
- }
- }
- });
-}
-
-function matcherFromTokens( tokens ) {
- var checkContext, matcher, j,
- len = tokens.length,
- leadingRelative = Expr.relative[ tokens[0].type ],
- implicitRelative = leadingRelative || Expr.relative[" "],
- i = leadingRelative ? 1 : 0,
-
- // The foundational matcher ensures that elements are reachable from top-level context(s)
- matchContext = addCombinator( function( elem ) {
- return elem === checkContext;
- }, implicitRelative, true ),
- matchAnyContext = addCombinator( function( elem ) {
- return indexOf.call( checkContext, elem ) > -1;
- }, implicitRelative, true ),
- matchers = [ function( elem, context, xml ) {
- return ( !leadingRelative && ( xml || context !== outermostContext ) ) || (
- (checkContext = context).nodeType ?
- matchContext( elem, context, xml ) :
- matchAnyContext( elem, context, xml ) );
- } ];
-
- for ( ; i < len; i++ ) {
- if ( (matcher = Expr.relative[ tokens[i].type ]) ) {
- matchers = [ addCombinator(elementMatcher( matchers ), matcher) ];
- } else {
- matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches );
-
- // Return special upon seeing a positional matcher
- if ( matcher[ expando ] ) {
- // Find the next relative operator (if any) for proper handling
- j = ++i;
- for ( ; j < len; j++ ) {
- if ( Expr.relative[ tokens[j].type ] ) {
- break;
- }
- }
- return setMatcher(
- i > 1 && elementMatcher( matchers ),
- i > 1 && toSelector(
- // If the preceding token was a descendant combinator, insert an implicit any-element `*`
- tokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" })
- ).replace( rtrim, "$1" ),
- matcher,
- i < j && matcherFromTokens( tokens.slice( i, j ) ),
- j < len && matcherFromTokens( (tokens = tokens.slice( j )) ),
- j < len && toSelector( tokens )
- );
- }
- matchers.push( matcher );
- }
- }
-
- return elementMatcher( matchers );
-}
-
-function matcherFromGroupMatchers( elementMatchers, setMatchers ) {
- var bySet = setMatchers.length > 0,
- byElement = elementMatchers.length > 0,
- superMatcher = function( seed, context, xml, results, outermost ) {
- var elem, j, matcher,
- matchedCount = 0,
- i = "0",
- unmatched = seed && [],
- setMatched = [],
- contextBackup = outermostContext,
- // We must always have either seed elements or outermost context
- elems = seed || byElement && Expr.find["TAG"]( "*", outermost ),
- // Use integer dirruns iff this is the outermost matcher
- dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1),
- len = elems.length;
-
- if ( outermost ) {
- outermostContext = context !== document && context;
- }
-
- // Add elements passing elementMatchers directly to results
- // Keep `i` a string if there are no elements so `matchedCount` will be "00" below
- // Support: IE<9, Safari
- // Tolerate NodeList properties (IE: "length"; Safari:
' + html;
- target.removeChild(target.firstChild);
- } catch (ex) {
- // IE sometimes produces an unknown runtime error on innerHTML if it's a div inside a p
- $('').html('
' + html).contents().slice(1).appendTo(target);
- }
-
- return html;
- });
- } else {
- elm.html(html);
- }
- },
-
- /**
- * Returns the outer HTML of an element.
- *
- * @method getOuterHTML
- * @param {String/Element} elm Element ID or element object to get outer HTML from.
- * @return {String} Outer HTML string.
- * @example
- * tinymce.DOM.getOuterHTML(editorElement);
- * tinymce.activeEditor.getOuterHTML(tinymce.activeEditor.getBody());
- */
- getOuterHTML: function(elm) {
- elm = this.get(elm);
-
- // Older FF doesn't have outerHTML 3.6 is still used by some orgaizations
- return elm.nodeType == 1 && "outerHTML" in elm ? elm.outerHTML : $('').append($(elm).clone()).html();
- },
-
- /**
- * Sets the specified outer HTML on an element or elements.
- *
- * @method setOuterHTML
- * @param {Element/String/Array} elm DOM element, element id string or array of elements/ids to set outer HTML on.
- * @param {Object} html HTML code to set as outer value for the element.
- * @example
- * // Sets the outer HTML of all paragraphs in the active editor
- * tinymce.activeEditor.dom.setOuterHTML(tinymce.activeEditor.dom.select('p'), '
- name = node.nodeName.toLowerCase();
- if (elements && elements[name]) {
- // Ignore single BR elements in blocks like
' + html;
+ target.removeChild(target.firstChild);
+ } catch (ex) {
+ // IE sometimes produces an unknown runtime error on innerHTML if it's a div inside a p
+ DomQuery('').html('
' + html).contents().slice(1).appendTo(target);
+ }
+
+ return html;
+ });
+ } else {
+ elm.html(html);
+ }
+ },
+
+ /**
+ * Returns the outer HTML of an element.
+ *
+ * @method getOuterHTML
+ * @param {String/Element} elm Element ID or element object to get outer HTML from.
+ * @return {String} Outer HTML string.
+ * @example
+ * tinymce.DOM.getOuterHTML(editorElement);
+ * tinymce.activeEditor.getOuterHTML(tinymce.activeEditor.getBody());
+ */
+ getOuterHTML: function (elm) {
+ elm = this.get(elm);
+
+ // Older FF doesn't have outerHTML 3.6 is still used by some orgaizations
+ return elm.nodeType == 1 && "outerHTML" in elm ? elm.outerHTML : DomQuery('').append(DomQuery(elm).clone()).html();
+ },
+
+ /**
+ * Sets the specified outer HTML on an element or elements.
+ *
+ * @method setOuterHTML
+ * @param {Element/String/Array} elm DOM element, element id string or array of elements/ids to set outer HTML on.
+ * @param {Object} html HTML code to set as outer value for the element.
+ * @example
+ * // Sets the outer HTML of all paragraphs in the active editor
+ * tinymce.activeEditor.dom.setOuterHTML(tinymce.activeEditor.dom.select('p'), '
+ name = node.nodeName.toLowerCase();
+ if (elements && elements[name]) {
+ // Ignore single BR elements in blocks like
|]
- if (!collapsed && container === body.lastChild && container.nodeName === 'TABLE') {
- return;
- }
-
- if (hasContentEditableFalseParent(container) || isCaretContainer(container)) {
- return;
- }
-
- // Don't walk into elements that doesn't have any child nodes like a IMG
- if (container.hasChildNodes() && !/TABLE/.test(container.nodeName)) {
- // Walk the DOM to find a text node to place the caret at or a BR
- node = container;
- walker = new TreeWalker(container, body);
-
- do {
- if (isContentEditableFalse(node) || isCaretContainer(node)) {
- normalized = false;
- break;
- }
-
- // Found a text node use that position
- if (node.nodeType === 3 && node.nodeValue.length > 0) {
- offset = directionLeft ? 0 : node.nodeValue.length;
- container = node;
- normalized = true;
- break;
- }
-
- // Found a BR/IMG element that we can place the caret before
- if (nonEmptyElementsMap[node.nodeName.toLowerCase()] && !isTableCell(node)) {
- offset = dom.nodeIndex(node);
- container = node.parentNode;
-
- // Put caret after image when moving the end point
- if (node.nodeName == "IMG" && !directionLeft) {
- offset++;
- }
-
- normalized = true;
- break;
- }
- } while ((node = (directionLeft ? walker.next() : walker.prev())));
- }
- }
- }
-
- // Lean the caret to the left if possible
- if (collapsed) {
- // So this: x|x
- // Becomes: x|x
- // Seems that only gecko has issues with this
- if (container.nodeType === 3 && offset === 0) {
- findTextNodeRelative(true);
- }
-
- // Lean left into empty inline elements when the caret is before a BR
- // So this: |
- // Becomes: |
- // Seems that only gecko has issues with this.
- // Special edge case for
|]
+ if (!collapsed && container === body.lastChild && container.nodeName === 'TABLE') {
+ return;
+ }
+
+ if (hasContentEditableFalseParent(container) || isCaretContainer(container)) {
+ return;
+ }
+
+ // Don't walk into elements that doesn't have any child nodes like a IMG
+ if (container.hasChildNodes() && !/TABLE/.test(container.nodeName)) {
+ // Walk the DOM to find a text node to place the caret at or a BR
+ node = container;
+ walker = new TreeWalker(container, body);
+
+ do {
+ if (isContentEditableFalse(node) || isCaretContainer(node)) {
+ normalized = false;
+ break;
+ }
+
+ // Found a text node use that position
+ if (node.nodeType === 3 && node.nodeValue.length > 0) {
+ offset = directionLeft ? 0 : node.nodeValue.length;
+ container = node;
+ normalized = true;
+ break;
+ }
+
+ // Found a BR/IMG element that we can place the caret before
+ if (nonEmptyElementsMap[node.nodeName.toLowerCase()] && !isTableCell(node)) {
+ offset = dom.nodeIndex(node);
+ container = node.parentNode;
+
+ // Put caret after image when moving the end point
+ if (node.nodeName == "IMG" && !directionLeft) {
+ offset++;
+ }
+
+ normalized = true;
+ break;
+ }
+ } while ((node = (directionLeft ? walker.next() : walker.prev())));
+ }
+ }
+ }
+
+ // Lean the caret to the left if possible
+ if (collapsed) {
+ // So this: x|x
+ // Becomes: x|x
+ // Seems that only gecko has issues with this
+ if (container.nodeType === 3 && offset === 0) {
+ findTextNodeRelative(true);
+ }
+
+ // Lean left into empty inline elements when the caret is before a BR
+ // So this: |
+ // Becomes: |
+ // Seems that only gecko has issues with this.
+ // Special edge case for
- if (elements[node.name]) {
- return false;
- }
-
- // Keep bookmark nodes and name attribute like
- i = node.attributes.length;
- while (i--) {
- name = node.attributes[i].name;
- if (name === "name" || name.indexOf('data-mce-bookmark') === 0) {
- return false;
- }
- }
- }
-
- // Keep comments
- if (node.type === 8) {
- return false;
- }
-
- // Keep non whitespace text nodes
- if (node.type === 3 && !whiteSpaceRegExp.test(node.value)) {
- return false;
- }
-
- // Keep whitespace preserve elements
- if (node.type === 3 && node.parent && whitespace[node.parent.name] && whiteSpaceRegExp.test(node.value)) {
- return false;
- }
- } while ((node = walk(node, self)));
- }
-
- return true;
- },
-
- /**
- * Walks to the next or previous node and returns that node or null if it wasn't found.
- *
- * @method walk
- * @param {Boolean} prev Optional previous node state defaults to false.
- * @return {tinymce.html.Node} Node that is next to or previous of the current node.
- */
- walk: function(prev) {
- return walk(this, null, prev);
- }
- };
-
- /**
- * Creates a node of a specific type.
- *
- * @static
- * @method create
- * @param {String} name Name of the node type to create for example "b" or "#text".
- * @param {Object} attrs Name/value collection of attributes that will be applied to elements.
- */
- Node.create = function(name, attrs) {
- var node, attrName;
-
- // Create node
- node = new Node(name, typeLookup[name] || 1);
-
- // Add attributes if needed
- if (attrs) {
- for (attrName in attrs) {
- node.attr(attrName, attrs[attrName]);
- }
- }
-
- return node;
- };
-
- return Node;
-});
-
-// Included from: js/tinymce/classes/html/Schema.js
-
-/**
- * Schema.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
- */
-
-/**
- * Schema validator class.
- *
- * @class tinymce.html.Schema
- * @example
- * if (tinymce.activeEditor.schema.isValidChild('p', 'span'))
- * alert('span is valid child of p.');
- *
- * if (tinymce.activeEditor.schema.getElementRule('p'))
- * alert('P is a valid element.');
- *
- * @class tinymce.html.Schema
- * @version 3.4
- */
-define("tinymce/html/Schema", [
- "tinymce/util/Tools"
-], function(Tools) {
- var mapCache = {}, dummyObj = {};
- var makeMap = Tools.makeMap, each = Tools.each, extend = Tools.extend, explode = Tools.explode, inArray = Tools.inArray;
-
- function split(items, delim) {
- items = Tools.trim(items);
- return items ? items.split(delim || ' ') : [];
- }
-
- /**
- * Builds a schema lookup table
- *
- * @private
- * @param {String} type html4, html5 or html5-strict schema type.
- * @return {Object} Schema lookup table.
- */
- function compileSchema(type) {
- var schema = {}, globalAttributes, blockContent;
- var phrasingContent, flowContent, html4BlockContent, html4PhrasingContent;
-
- function add(name, attributes, children) {
- var ni, attributesOrder, element;
-
- function arrayToMap(array, obj) {
- var map = {}, i, l;
-
- for (i = 0, l = array.length; i < l; i++) {
- map[array[i]] = obj || {};
- }
-
- return map;
- }
-
- children = children || [];
- attributes = attributes || "";
-
- if (typeof children === "string") {
- children = split(children);
- }
-
- name = split(name);
- ni = name.length;
- while (ni--) {
- attributesOrder = split([globalAttributes, attributes].join(' '));
-
- element = {
- attributes: arrayToMap(attributesOrder),
- attributesOrder: attributesOrder,
- children: arrayToMap(children, dummyObj)
- };
-
- schema[name[ni]] = element;
- }
- }
-
- function addAttrs(name, attributes) {
- var ni, schemaItem, i, l;
-
- name = split(name);
- ni = name.length;
- attributes = split(attributes);
- while (ni--) {
- schemaItem = schema[name[ni]];
- for (i = 0, l = attributes.length; i < l; i++) {
- schemaItem.attributes[attributes[i]] = {};
- schemaItem.attributesOrder.push(attributes[i]);
- }
- }
- }
-
- // Use cached schema
- if (mapCache[type]) {
- return mapCache[type];
- }
-
- // Attributes present on all elements
- globalAttributes = "id accesskey class dir lang style tabindex title";
-
- // Event attributes can be opt-in/opt-out
- /*eventAttributes = split("onabort onblur oncancel oncanplay oncanplaythrough onchange onclick onclose oncontextmenu oncuechange " +
- "ondblclick ondrag ondragend ondragenter ondragleave ondragover ondragstart ondrop ondurationchange onemptied onended " +
- "onerror onfocus oninput oninvalid onkeydown onkeypress onkeyup onload onloadeddata onloadedmetadata onloadstart " +
- "onmousedown onmousemove onmouseout onmouseover onmouseup onmousewheel onpause onplay onplaying onprogress onratechange " +
- "onreset onscroll onseeked onseeking onseeking onselect onshow onstalled onsubmit onsuspend ontimeupdate onvolumechange " +
- "onwaiting"
- );*/
-
- // Block content elements
- blockContent =
- "address blockquote div dl fieldset form h1 h2 h3 h4 h5 h6 hr menu ol p pre table ul";
-
- // Phrasing content elements from the HTML5 spec (inline)
- phrasingContent =
- "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"
- ;
-
- // Add HTML5 items to globalAttributes, blockContent, phrasingContent
- if (type != "html4") {
- globalAttributes += " contenteditable contextmenu draggable dropzone " +
- "hidden spellcheck translate";
- blockContent += " article aside details dialog figure header footer hgroup section nav";
- phrasingContent += " audio canvas command datalist mark meter output picture " +
- "progress time wbr video ruby bdi keygen";
- }
-
- // Add HTML4 elements unless it's html5-strict
- if (type != "html5-strict") {
- globalAttributes += " xml:lang";
-
- html4PhrasingContent = "acronym applet basefont big font strike tt";
- phrasingContent = [phrasingContent, html4PhrasingContent].join(' ');
-
- each(split(html4PhrasingContent), function(name) {
- add(name, "", phrasingContent);
- });
-
- html4BlockContent = "center dir isindex noframes";
- blockContent = [blockContent, html4BlockContent].join(' ');
-
- // Flow content elements from the HTML5 spec (block+inline)
- flowContent = [blockContent, phrasingContent].join(' ');
-
- each(split(html4BlockContent), function(name) {
- add(name, "", flowContent);
- });
- }
-
- // Flow content elements from the HTML5 spec (block+inline)
- flowContent = flowContent || [blockContent, phrasingContent].join(" ");
-
- // HTML4 base schema TODO: Move HTML5 specific attributes to HTML5 specific if statement
- // Schema items
+ if (elements[node.name]) {
+ return false;
+ }
+
+ // Keep bookmark nodes and name attribute like
+ i = node.attributes.length;
+ while (i--) {
+ name = node.attributes[i].name;
+ if (name === "name" || name.indexOf('data-mce-bookmark') === 0) {
+ return false;
+ }
+ }
+ }
+
+ // Keep comments
+ if (node.type === 8) {
+ return false;
+ }
+
+ // Keep non whitespace text nodes
+ if (node.type === 3 && !whiteSpaceRegExp.test(node.value)) {
+ return false;
+ }
+
+ // Keep whitespace preserve elements
+ if (node.type === 3 && node.parent && whitespace[node.parent.name] && whiteSpaceRegExp.test(node.value)) {
+ return false;
+ }
+ } while ((node = walk(node, self)));
+ }
+
+ return true;
+ },
+
+ /**
+ * Walks to the next or previous node and returns that node or null if it wasn't found.
+ *
+ * @method walk
+ * @param {Boolean} prev Optional previous node state defaults to false.
+ * @return {tinymce.html.Node} Node that is next to or previous of the current node.
+ */
+ walk: function (prev) {
+ return walk(this, null, prev);
+ }
+ };
+
+ /**
+ * Creates a node of a specific type.
+ *
+ * @static
+ * @method create
+ * @param {String} name Name of the node type to create for example "b" or "#text".
+ * @param {Object} attrs Name/value collection of attributes that will be applied to elements.
+ */
+ Node.create = function (name, attrs) {
+ var node, attrName;
+
+ // Create node
+ node = new Node(name, typeLookup[name] || 1);
+
+ // Add attributes if needed
+ if (attrs) {
+ for (attrName in attrs) {
+ node.attr(attrName, attrs[attrName]);
+ }
+ }
+
+ return node;
+ };
+
+ return Node;
+ }
+);
/**
* SaxParser.js
*
* Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
+ * Copyright (c) 1999-2017 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
@@ -12736,436 +13644,449 @@ define("tinymce/html/Schema", [
* @class tinymce.html.SaxParser
* @version 3.4
*/
-define("tinymce/html/SaxParser", [
- "tinymce/html/Schema",
- "tinymce/html/Entities",
- "tinymce/util/Tools"
-], function(Schema, Entities, Tools) {
- var each = Tools.each;
-
- /**
- * Returns the index of the end tag for a specific start tag. This can be
- * used to skip all children of a parent element from being processed.
- *
- * @private
- * @method findEndTag
- * @param {tinymce.html.Schema} schema Schema instance to use to match short ended elements.
- * @param {String} html HTML string to find the end tag in.
- * @param {Number} startIndex Indext to start searching at should be after the start tag.
- * @return {Number} Index of the end tag.
- */
- function findEndTag(schema, html, startIndex) {
- var count = 1, index, matches, tokenRegExp, shortEndedElements;
-
- shortEndedElements = schema.getShortEndedElements();
- tokenRegExp = /<([!?\/])?([A-Za-z0-9\-_\:\.]+)((?:\s+[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*|\/|\s+)>/g;
- tokenRegExp.lastIndex = index = startIndex;
-
- while ((matches = tokenRegExp.exec(html))) {
- index = tokenRegExp.lastIndex;
-
- if (matches[1] === '/') { // End element
- count--;
- } else if (!matches[1]) { // Start element
- if (matches[2] in shortEndedElements) {
- continue;
- }
-
- count++;
- }
-
- if (count === 0) {
- break;
- }
- }
-
- return index;
- }
-
- /**
- * Constructs a new SaxParser instance.
- *
- * @constructor
- * @method SaxParser
- * @param {Object} settings Name/value collection of settings. comment, cdata, text, start and end are callbacks.
- * @param {tinymce.html.Schema} schema HTML Schema class to use when parsing.
- */
- function SaxParser(settings, schema) {
- var self = this;
-
- function noop() {}
-
- settings = settings || {};
- self.schema = schema = schema || new Schema();
-
- if (settings.fix_self_closing !== false) {
- settings.fix_self_closing = true;
- }
-
- // Add handler functions from settings and setup default handlers
- each('comment cdata text start end pi doctype'.split(' '), function(name) {
- if (name) {
- self[name] = settings[name] || noop;
- }
- });
-
- /**
- * Parses the specified HTML string and executes the callbacks for each item it finds.
- *
- * @example
- * new SaxParser({...}).parse('text');
- * @method parse
- * @param {String} html Html string to sax parse.
- */
- self.parse = function(html) {
- var self = this, matches, index = 0, value, endRegExp, stack = [], attrList, i, text, name;
- var isInternalElement, removeInternalElements, shortEndedElements, fillAttrsMap, isShortEnded;
- var validate, elementRule, isValidElement, attr, attribsValue, validAttributesMap, validAttributePatterns;
- var attributesRequired, attributesDefault, attributesForced;
- var anyAttributesRequired, selfClosing, tokenRegExp, attrRegExp, specialElements, attrValue, idCount = 0;
- var decode = Entities.decode, fixSelfClosing, filteredUrlAttrs = Tools.makeMap('src,href,data,background,formaction,poster');
- var scriptUriRegExp = /((java|vb)script|mhtml):/i, dataUriRegExp = /^data:/i;
-
- function processEndTag(name) {
- var pos, i;
-
- // Find position of parent of the same type
- pos = stack.length;
- while (pos--) {
- if (stack[pos].name === name) {
- break;
- }
- }
-
- // Found parent
- if (pos >= 0) {
- // Close all the open elements
- for (i = stack.length - 1; i >= pos; i--) {
- name = stack[i];
-
- if (name.valid) {
- self.end(name.name);
- }
- }
-
- // Remove the open elements from the stack
- stack.length = pos;
- }
- }
-
- function parseAttribute(match, name, value, val2, val3) {
- var attrRule, i, trimRegExp = /[\s\u0000-\u001F]+/g;
-
- name = name.toLowerCase();
- value = name in fillAttrsMap ? name : decode(value || val2 || val3 || ''); // Handle boolean attribute than value attribute
-
- // Validate name and value pass through all data- attributes
- if (validate && !isInternalElement && name.indexOf('data-') !== 0) {
- attrRule = validAttributesMap[name];
-
- // Find rule by pattern matching
- if (!attrRule && validAttributePatterns) {
- i = validAttributePatterns.length;
- while (i--) {
- attrRule = validAttributePatterns[i];
- if (attrRule.pattern.test(name)) {
- break;
- }
- }
-
- // No rule matched
- if (i === -1) {
- attrRule = null;
- }
- }
-
- // No attribute rule found
- if (!attrRule) {
- return;
- }
-
- // Validate value
- if (attrRule.validValues && !(value in attrRule.validValues)) {
- return;
- }
- }
-
- // Block any javascript: urls or non image data uris
- if (filteredUrlAttrs[name] && !settings.allow_script_urls) {
- var uri = value.replace(trimRegExp, '');
-
- try {
- // Might throw malformed URI sequence
- uri = decodeURIComponent(uri);
- } catch (ex) {
- // Fallback to non UTF-8 decoder
- uri = unescape(uri);
- }
-
- if (scriptUriRegExp.test(uri)) {
- return;
- }
-
- if (!settings.allow_html_data_urls && dataUriRegExp.test(uri) && !/^data:image\//i.test(uri)) {
- return;
- }
- }
-
- // Add attribute to list and map
- attrList.map[name] = value;
- attrList.push({
- name: name,
- value: value
- });
- }
-
- // Precompile RegExps and map objects
- tokenRegExp = new RegExp('<(?:' +
- '(?:!--([\\w\\W]*?)-->)|' + // Comment
- '(?:!\\[CDATA\\[([\\w\\W]*?)\\]\\]>)|' + // CDATA
- '(?:!DOCTYPE([\\w\\W]*?)>)|' + // DOCTYPE
- '(?:\\?([^\\s\\/<>]+) ?([\\w\\W]*?)[?/]>)|' + // PI
- '(?:\\/([^>]+)>)|' + // End element
- '(?:([A-Za-z0-9\\-_\\:\\.]+)((?:\\s+[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*|\\/|\\s+)>)' + // Start element
- ')', 'g');
-
- attrRegExp = /([\w:\-]+)(?:\s*=\s*(?:(?:\"((?:[^\"])*)\")|(?:\'((?:[^\'])*)\')|([^>\s]+)))?/g;
-
- // Setup lookup tables for empty elements and boolean attributes
- shortEndedElements = schema.getShortEndedElements();
- selfClosing = settings.self_closing_elements || schema.getSelfClosingElements();
- fillAttrsMap = schema.getBoolAttrs();
- validate = settings.validate;
- removeInternalElements = settings.remove_internals;
- fixSelfClosing = settings.fix_self_closing;
- specialElements = schema.getSpecialElements();
-
- while ((matches = tokenRegExp.exec(html))) {
- // Text
- if (index < matches.index) {
- self.text(decode(html.substr(index, matches.index - index)));
- }
-
- if ((value = matches[6])) { // End element
- value = value.toLowerCase();
-
- // IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements
- if (value.charAt(0) === ':') {
- value = value.substr(1);
- }
-
- processEndTag(value);
- } else if ((value = matches[7])) { // Start element
- value = value.toLowerCase();
-
- // IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements
- if (value.charAt(0) === ':') {
- value = value.substr(1);
- }
-
- isShortEnded = value in shortEndedElements;
-
- // Is self closing tag for example an
or
- if (!empty) {
- node = newNode;
- }
-
- // Check if we are inside a whitespace preserved element
- if (!isInWhiteSpacePreservedElement && whiteSpaceElements[name]) {
- isInWhiteSpacePreservedElement = true;
- }
- }
- },
-
- end: function(name) {
- var textNode, elementRule, text, sibling, tempNode;
-
- elementRule = validate ? schema.getElementRule(name) : {};
- if (elementRule) {
- if (blockElements[name]) {
- if (!isInWhiteSpacePreservedElement) {
- // Trim whitespace of the first node in a block
- textNode = node.firstChild;
- if (textNode && textNode.type === 3) {
- text = textNode.value.replace(startWhiteSpaceRegExp, '');
-
- // Any characters left after trim or should we remove it
- if (text.length > 0) {
- textNode.value = text;
- textNode = textNode.next;
- } else {
- sibling = textNode.next;
- textNode.remove();
- textNode = sibling;
-
- // Remove any pure whitespace siblings
- while (textNode && textNode.type === 3) {
- text = textNode.value;
- sibling = textNode.next;
-
- if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) {
- textNode.remove();
- textNode = sibling;
- }
-
- textNode = sibling;
- }
- }
- }
-
- // Trim whitespace of the last node in a block
- textNode = node.lastChild;
- if (textNode && textNode.type === 3) {
- text = textNode.value.replace(endWhiteSpaceRegExp, '');
-
- // Any characters left after trim or should we remove it
- if (text.length > 0) {
- textNode.value = text;
- textNode = textNode.prev;
- } else {
- sibling = textNode.prev;
- textNode.remove();
- textNode = sibling;
-
- // Remove any pure whitespace siblings
- while (textNode && textNode.type === 3) {
- text = textNode.value;
- sibling = textNode.prev;
-
- if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) {
- textNode.remove();
- textNode = sibling;
- }
-
- textNode = sibling;
- }
- }
- }
- }
-
- // Trim start white space
- // Removed due to: #5424
- /*textNode = node.prev;
- if (textNode && textNode.type === 3) {
- text = textNode.value.replace(startWhiteSpaceRegExp, '');
-
- if (text.length > 0)
- textNode.value = text;
- else
- textNode.remove();
- }*/
- }
-
- // Check if we exited a whitespace preserved element
- if (isInWhiteSpacePreservedElement && whiteSpaceElements[name]) {
- isInWhiteSpacePreservedElement = false;
- }
-
- // Handle empty nodes
- if (elementRule.removeEmpty || elementRule.paddEmpty) {
- if (node.isEmpty(nonEmptyElements, whiteSpaceElements)) {
- if (elementRule.paddEmpty) {
- paddEmptyNode(settings, node);
- } else {
- // Leave nodes that have a name like
- if (!node.attributes.map.name && !node.attributes.map.id) {
- tempNode = node.parent;
-
- if (blockElements[node.name]) {
- node.empty().remove();
- } else {
- node.unwrap();
- }
-
- node = tempNode;
- return;
- }
- }
- }
- }
-
- node = node.parent;
- }
- }
- }, schema);
-
- rootNode = node = new Node(args.context || settings.root_name, 11);
-
- parser.parse(html);
-
- // Fix invalid children or report invalid children in a contextual parsing
- if (validate && invalidChildren.length) {
- if (!args.context) {
- fixInvalidChildren(invalidChildren);
- } else {
- args.invalid = true;
- }
- }
-
- // Wrap nodes in the root into block elements if the root is body
- if (rootBlockName && (rootNode.name == 'body' || args.isRootContent)) {
- addRootBlocks();
- }
-
- // Run filters only when the contents is valid
- if (!args.invalid) {
- // Run node filters
- for (name in matchedNodes) {
- list = nodeFilters[name];
- nodes = matchedNodes[name];
-
- // Remove already removed children
- fi = nodes.length;
- while (fi--) {
- if (!nodes[fi].parent) {
- nodes.splice(fi, 1);
- }
- }
-
- for (i = 0, l = list.length; i < l; i++) {
- list[i](nodes, name, args);
- }
- }
-
- // Run attribute filters
- for (i = 0, l = attributeFilters.length; i < l; i++) {
- list = attributeFilters[i];
-
- if (list.name in matchedAttributes) {
- nodes = matchedAttributes[list.name];
-
- // Remove already removed children
- fi = nodes.length;
- while (fi--) {
- if (!nodes[fi].parent) {
- nodes.splice(fi, 1);
- }
- }
-
- for (fi = 0, fl = list.callbacks.length; fi < fl; fi++) {
- list.callbacks[fi](nodes, list.name, args);
- }
- }
- }
- }
-
- return rootNode;
- };
-
- // Remove
at end of block elements Gecko and WebKit injects BR elements to
- // make it possible to place the caret inside empty blocks. This logic tries to remove
- // these elements and keep br elements that where intended to be there intact
- if (settings.remove_trailing_brs) {
- self.addNodeFilter('br', function(nodes) {
- var i, l = nodes.length, node, blockElements = extend({}, schema.getBlockElements());
- var nonEmptyElements = schema.getNonEmptyElements(), parent, lastParent, prev, prevName;
- var whiteSpaceElements = schema.getNonEmptyElements();
- var elementRule, textNode;
-
- // Remove brs from body element as well
- blockElements.body = 1;
-
- // Must loop forwards since it will otherwise remove all brs in
structure then don't remove anything
- if (prevName === 'br') {
- node = null;
- break;
- }
- }
-
- prev = prev.prev;
- }
-
- if (node) {
- node.remove();
-
- // Is the parent to be considered empty after we removed the BR
- if (parent.isEmpty(nonEmptyElements, whiteSpaceElements)) {
- elementRule = schema.getElementRule(parent.name);
-
- // Remove or padd the element depending on schema rule
- if (elementRule) {
- if (elementRule.removeEmpty) {
- parent.remove();
- } else if (elementRule.paddEmpty) {
- paddEmptyNode(settings, parent);
- }
- }
- }
- }
- } else {
- // Replaces BR elements inside inline elements like
or
+ if (!empty) {
+ node = newNode;
+ }
+
+ // Check if we are inside a whitespace preserved element
+ if (!isInWhiteSpacePreservedElement && whiteSpaceElements[name]) {
+ isInWhiteSpacePreservedElement = true;
+ }
+ }
+ },
+
+ end: function (name) {
+ var textNode, elementRule, text, sibling, tempNode;
+
+ elementRule = validate ? schema.getElementRule(name) : {};
+ if (elementRule) {
+ if (blockElements[name]) {
+ if (!isInWhiteSpacePreservedElement) {
+ // Trim whitespace of the first node in a block
+ textNode = node.firstChild;
+ if (textNode && textNode.type === 3) {
+ text = textNode.value.replace(startWhiteSpaceRegExp, '');
+
+ // Any characters left after trim or should we remove it
+ if (text.length > 0) {
+ textNode.value = text;
+ textNode = textNode.next;
+ } else {
+ sibling = textNode.next;
+ textNode.remove();
+ textNode = sibling;
+
+ // Remove any pure whitespace siblings
+ while (textNode && textNode.type === 3) {
+ text = textNode.value;
+ sibling = textNode.next;
+
+ if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) {
+ textNode.remove();
+ textNode = sibling;
+ }
+
+ textNode = sibling;
+ }
+ }
+ }
+
+ // Trim whitespace of the last node in a block
+ textNode = node.lastChild;
+ if (textNode && textNode.type === 3) {
+ text = textNode.value.replace(endWhiteSpaceRegExp, '');
+
+ // Any characters left after trim or should we remove it
+ if (text.length > 0) {
+ textNode.value = text;
+ textNode = textNode.prev;
+ } else {
+ sibling = textNode.prev;
+ textNode.remove();
+ textNode = sibling;
+
+ // Remove any pure whitespace siblings
+ while (textNode && textNode.type === 3) {
+ text = textNode.value;
+ sibling = textNode.prev;
+
+ if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) {
+ textNode.remove();
+ textNode = sibling;
+ }
+
+ textNode = sibling;
+ }
+ }
+ }
+ }
+
+ // Trim start white space
+ // Removed due to: #5424
+ /*textNode = node.prev;
+ if (textNode && textNode.type === 3) {
+ text = textNode.value.replace(startWhiteSpaceRegExp, '');
+
+ if (text.length > 0)
+ textNode.value = text;
+ else
+ textNode.remove();
+ }*/
+ }
+
+ // Check if we exited a whitespace preserved element
+ if (isInWhiteSpacePreservedElement && whiteSpaceElements[name]) {
+ isInWhiteSpacePreservedElement = false;
+ }
+
+ // Handle empty nodes
+ if (elementRule.removeEmpty || elementRule.paddEmpty) {
+ if (node.isEmpty(nonEmptyElements, whiteSpaceElements)) {
+ if (elementRule.paddEmpty) {
+ paddEmptyNode(settings, node);
+ } else {
+ // Leave nodes that have a name like
+ if (!node.attributes.map.name && !node.attributes.map.id) {
+ tempNode = node.parent;
+
+ if (blockElements[node.name]) {
+ node.empty().remove();
+ } else {
+ node.unwrap();
+ }
+
+ node = tempNode;
+ return;
+ }
+ }
+ }
+ }
+
+ node = node.parent;
+ }
+ }
+ }, schema);
+
+ rootNode = node = new Node(args.context || settings.root_name, 11);
+
+ parser.parse(html);
+
+ // Fix invalid children or report invalid children in a contextual parsing
+ if (validate && invalidChildren.length) {
+ if (!args.context) {
+ fixInvalidChildren(invalidChildren);
+ } else {
+ args.invalid = true;
+ }
+ }
+
+ // Wrap nodes in the root into block elements if the root is body
+ if (rootBlockName && (rootNode.name == 'body' || args.isRootContent)) {
+ addRootBlocks();
+ }
+
+ // Run filters only when the contents is valid
+ if (!args.invalid) {
+ // Run node filters
+ for (name in matchedNodes) {
+ list = nodeFilters[name];
+ nodes = matchedNodes[name];
+
+ // Remove already removed children
+ fi = nodes.length;
+ while (fi--) {
+ if (!nodes[fi].parent) {
+ nodes.splice(fi, 1);
+ }
+ }
+
+ for (i = 0, l = list.length; i < l; i++) {
+ list[i](nodes, name, args);
+ }
+ }
+
+ // Run attribute filters
+ for (i = 0, l = attributeFilters.length; i < l; i++) {
+ list = attributeFilters[i];
+
+ if (list.name in matchedAttributes) {
+ nodes = matchedAttributes[list.name];
+
+ // Remove already removed children
+ fi = nodes.length;
+ while (fi--) {
+ if (!nodes[fi].parent) {
+ nodes.splice(fi, 1);
+ }
+ }
+
+ for (fi = 0, fl = list.callbacks.length; fi < fl; fi++) {
+ list.callbacks[fi](nodes, list.name, args);
+ }
+ }
+ }
+ }
+
+ return rootNode;
+ };
+
+ // Remove
at end of block elements Gecko and WebKit injects BR elements to
+ // make it possible to place the caret inside empty blocks. This logic tries to remove
+ // these elements and keep br elements that where intended to be there intact
+ if (settings.remove_trailing_brs) {
+ self.addNodeFilter('br', function (nodes) {
+ var i, l = nodes.length, node, blockElements = extend({}, schema.getBlockElements());
+ var nonEmptyElements = schema.getNonEmptyElements(), parent, lastParent, prev, prevName;
+ var whiteSpaceElements = schema.getNonEmptyElements();
+ var elementRule, textNode;
+
+ // Remove brs from body element as well
+ blockElements.body = 1;
+
+ // Must loop forwards since it will otherwise remove all brs in
structure then don't remove anything
+ if (prevName === 'br') {
+ node = null;
+ break;
+ }
+ }
+
+ prev = prev.prev;
+ }
+
+ if (node) {
+ node.remove();
+
+ // Is the parent to be considered empty after we removed the BR
+ if (parent.isEmpty(nonEmptyElements, whiteSpaceElements)) {
+ elementRule = schema.getElementRule(parent.name);
+
+ // Remove or padd the element depending on schema rule
+ if (elementRule) {
+ if (elementRule.removeEmpty) {
+ parent.remove();
+ } else if (elementRule.paddEmpty) {
+ paddEmptyNode(settings, parent);
+ }
+ }
+ }
+ }
+ } else {
+ // Replaces BR elements inside inline elements like
.
- */
- start: function(name, attrs, empty) {
- var i, l, attr, value;
+ return {
+ /**
+ * Writes the a start element such as
.
+ */
+ start: function (name, attrs, empty) {
+ var i, l, attr, value;
- if (indent && indentBefore[name] && html.length > 0) {
- value = html[html.length - 1];
+ if (indent && indentBefore[name] && html.length > 0) {
+ value = html[html.length - 1];
- if (value.length > 0 && value !== '\n') {
- html.push('\n');
- }
- }
+ if (value.length > 0 && value !== '\n') {
+ html.push('\n');
+ }
+ }
- html.push('<', name);
+ html.push('<', name);
- if (attrs) {
- for (i = 0, l = attrs.length; i < l; i++) {
- attr = attrs[i];
- html.push(' ', attr.name, '="', encode(attr.value, true), '"');
- }
- }
+ if (attrs) {
+ for (i = 0, l = attrs.length; i < l; i++) {
+ attr = attrs[i];
+ html.push(' ', attr.name, '="', encode(attr.value, true), '"');
+ }
+ }
- if (!empty || htmlOutput) {
- html[html.length] = '>';
- } else {
- html[html.length] = ' />';
- }
+ if (!empty || htmlOutput) {
+ html[html.length] = '>';
+ } else {
+ html[html.length] = ' />';
+ }
- if (empty && indent && indentAfter[name] && html.length > 0) {
- value = html[html.length - 1];
+ if (empty && indent && indentAfter[name] && html.length > 0) {
+ value = html[html.length - 1];
- if (value.length > 0 && value !== '\n') {
- html.push('\n');
- }
- }
- },
+ if (value.length > 0 && value !== '\n') {
+ html.push('\n');
+ }
+ }
+ },
- /**
- * Writes the a end element such as
text
')); - * @method serialize - * @param {tinymce.html.Node} node Node instance to serialize. - * @return {String} String with HTML based on DOM tree. - */ - self.serialize = function(node) { - var handlers, validate; + /** + * Serializes the specified node into a string. + * + * @example + * new tinymce.html.Serializer().serialize(new tinymce.html.DomParser().parse('text
')); + * @method serialize + * @param {tinymce.html.Node} node Node instance to serialize. + * @return {String} String with HTML based on DOM tree. + */ + self.serialize = function (node) { + var handlers, validate; - validate = settings.validate; + validate = settings.validate; - handlers = { - // #text - 3: function(node) { - writer.text(node.value, node.raw); - }, + handlers = { + // #text + 3: function (node) { + writer.text(node.value, node.raw); + }, - // #comment - 8: function(node) { - writer.comment(node.value); - }, + // #comment + 8: function (node) { + writer.comment(node.value); + }, - // Processing instruction - 7: function(node) { - writer.pi(node.name, node.value); - }, + // Processing instruction + 7: function (node) { + writer.pi(node.name, node.value); + }, - // Doctype - 10: function(node) { - writer.doctype(node.value); - }, + // Doctype + 10: function (node) { + writer.doctype(node.value); + }, - // CDATA - 4: function(node) { - writer.cdata(node.value); - }, + // CDATA + 4: function (node) { + writer.cdata(node.value); + }, - // Document fragment - 11: function(node) { - if ((node = node.firstChild)) { - do { - walk(node); - } while ((node = node.next)); - } - } - }; + // Document fragment + 11: function (node) { + if ((node = node.firstChild)) { + do { + walk(node); + } while ((node = node.next)); + } + } + }; - writer.reset(); + writer.reset(); - function walk(node) { - var handler = handlers[node.type], name, isEmpty, attrs, attrName, attrValue, sortedAttrs, i, l, elementRule; + function walk(node) { + var handler = handlers[node.type], name, isEmpty, attrs, attrName, attrValue, sortedAttrs, i, l, elementRule; - if (!handler) { - name = node.name; - isEmpty = node.shortEnded; - attrs = node.attributes; + if (!handler) { + name = node.name; + isEmpty = node.shortEnded; + attrs = node.attributes; - // Sort attributes - if (validate && attrs && attrs.length > 1) { - sortedAttrs = []; - sortedAttrs.map = {}; + // Sort attributes + if (validate && attrs && attrs.length > 1) { + sortedAttrs = []; + sortedAttrs.map = {}; - elementRule = schema.getElementRule(node.name); - if (elementRule) { - for (i = 0, l = elementRule.attributesOrder.length; i < l; i++) { - attrName = elementRule.attributesOrder[i]; + elementRule = schema.getElementRule(node.name); + if (elementRule) { + for (i = 0, l = elementRule.attributesOrder.length; i < l; i++) { + attrName = elementRule.attributesOrder[i]; - if (attrName in attrs.map) { - attrValue = attrs.map[attrName]; - sortedAttrs.map[attrName] = attrValue; - sortedAttrs.push({name: attrName, value: attrValue}); - } - } + if (attrName in attrs.map) { + attrValue = attrs.map[attrName]; + sortedAttrs.map[attrName] = attrValue; + sortedAttrs.push({ name: attrName, value: attrValue }); + } + } - for (i = 0, l = attrs.length; i < l; i++) { - attrName = attrs[i].name; + for (i = 0, l = attrs.length; i < l; i++) { + attrName = attrs[i].name; - if (!(attrName in sortedAttrs.map)) { - attrValue = attrs.map[attrName]; - sortedAttrs.map[attrName] = attrValue; - sortedAttrs.push({name: attrName, value: attrValue}); - } - } + if (!(attrName in sortedAttrs.map)) { + attrValue = attrs.map[attrName]; + sortedAttrs.map[attrName] = attrValue; + sortedAttrs.push({ name: attrName, value: attrValue }); + } + } - attrs = sortedAttrs; - } - } + attrs = sortedAttrs; + } + } - writer.start(node.name, attrs, isEmpty); + writer.start(node.name, attrs, isEmpty); - if (!isEmpty) { - if ((node = node.firstChild)) { - do { - walk(node); - } while ((node = node.next)); - } + if (!isEmpty) { + if ((node = node.firstChild)) { + do { + walk(node); + } while ((node = node.next)); + } - writer.end(name); - } - } else { - handler(node); - } - } + writer.end(name); + } + } else { + handler(node); + } + } - // Serialize element and treat all non elements as fragments - if (node.type == 1 && !settings.inner) { - walk(node); - } else { - handlers[11](node); - } + // Serialize element and treat all non elements as fragments + if (node.type == 1 && !settings.inner) { + walk(node); + } else { + handlers[11](node); + } - return writer.getContent(); - }; - }; -}); - -// Included from: js/tinymce/classes/dom/Serializer.js + return writer.getContent(); + }; + }; + } +); /** * Serializer.js * * Released under LGPL License. - * Copyright (c) 1999-2015 Ephox Corp. All rights reserved + * Copyright (c) 1999-2017 Ephox Corp. All rights reserved * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing @@ -14432,1002 +15358,493 @@ define("tinymce/html/Serializer", [ * * @class tinymce.dom.Serializer */ -define("tinymce/dom/Serializer", [ - "tinymce/dom/DOMUtils", - "tinymce/html/DomParser", - "tinymce/html/SaxParser", - "tinymce/html/Entities", - "tinymce/html/Serializer", - "tinymce/html/Node", - "tinymce/html/Schema", - "tinymce/Env", - "tinymce/util/Tools", - "tinymce/text/Zwsp" -], function(DOMUtils, DomParser, SaxParser, Entities, Serializer, Node, Schema, Env, Tools, Zwsp) { - var each = Tools.each, trim = Tools.trim; - var DOM = DOMUtils.DOM; - - /** - * IE 11 has a fantastic bug where it will produce two trailing BR elements to iframe bodies when - * the iframe is hidden by display: none on a parent container. The DOM is actually out of sync - * with innerHTML in this case. It's like IE adds shadow DOM BR elements that appears on innerHTML - * but not as the lastChild of the body. So this fix simply removes the last two - * BR elements at the end of the document. - * - * Example of what happens: text becomes text|
would become this:|
- sibling = startContainer.previousSibling; - if (sibling && !sibling.hasChildNodes() && dom.isBlock(sibling)) { - sibling.innerHTML = ''; - } else { - sibling = null; - } - - startContainer.innerHTML = ''; - ieRng.moveToElementText(startContainer.lastChild); - ieRng.select(); - dom.doc.selection.clear(); - startContainer.innerHTML = ''; - - if (sibling) { - sibling.innerHTML = ''; - } - return; - } - - startOffset = dom.nodeIndex(startContainer); - startContainer = startContainer.parentNode; - } - - if (startOffset == endOffset - 1) { - try { - ctrlElm = startContainer.childNodes[startOffset]; - ctrlRng = body.createControlRange(); - ctrlRng.addElement(ctrlElm); - ctrlRng.select(); - - // Check if the range produced is on the correct element and is a control range - // On IE 8 it will select the parent contentEditable container if you select an inner element see: #5398 - nativeRng = selection.getRng(); - if (nativeRng.item && ctrlElm === nativeRng.item(0)) { - return; - } - } catch (ex) { - // Ignore - } - } - } - - // Set start/end point of selection - setEndPoint(true); - setEndPoint(); - - // Select the new range and scroll it into view - ieRng.select(); - }; - - // Expose range method - this.getRangeAt = getRange; - } - - return Selection; -}); - -// Included from: js/tinymce/classes/util/VK.js +define( + 'tinymce.core.dom.Serializer', + [ + "tinymce.core.dom.DOMUtils", + "tinymce.core.html.DomParser", + "tinymce.core.html.SaxParser", + "tinymce.core.html.Entities", + "tinymce.core.html.Serializer", + "tinymce.core.html.Node", + "tinymce.core.html.Schema", + "tinymce.core.Env", + "tinymce.core.util.Tools", + "tinymce.core.text.Zwsp" + ], + function (DOMUtils, DomParser, SaxParser, Entities, Serializer, Node, Schema, Env, Tools, Zwsp) { + var each = Tools.each, trim = Tools.trim; + var DOM = DOMUtils.DOM; + + /** + * IE 11 has a fantastic bug where it will produce two trailing BR elements to iframe bodies when + * the iframe is hidden by display: none on a parent container. The DOM is actually out of sync + * with innerHTML in this case. It's like IE adds shadow DOM BR elements that appears on innerHTML + * but not as the lastChild of the body. So this fix simply removes the last two + * BR elements at the end of the document. + * + * Example of what happens: text becomes text]
- rng.moveToElementText(rng2.parentElement()); - if (rng.compareEndPoints('StartToEnd', rng2) === 0) { - rng2.move('character', -1); - } - - rng2.pasteHTML('' + chr + ''); - } - } catch (ex) { - // IE might throw unspecified error so lets ignore it - return null; - } - } else { - // Control selection - element = rng.item(0); - name = element.nodeName; - - return {name: name, index: findIndex(name, element)}; - } - } else { - element = selection.getNode(); - name = element.nodeName; - if (name == 'IMG') { - return {name: name, index: findIndex(name, element)}; - } - - // W3C method - rng2 = normalizeTableCellSelection(rng.cloneRange()); - - // Insert end marker - if (!collapsed) { - rng2.collapse(false); - rng2.insertNode(dom.create('span', {'data-mce-type': "bookmark", id: id + '_end', style: styles}, chr)); - } - - rng = normalizeTableCellSelection(rng); - rng.collapse(true); - rng.insertNode(dom.create('span', {'data-mce-type': "bookmark", id: id + '_start', style: styles}, chr)); - } - - selection.moveToBookmark({id: id, keep: 1}); - - return {id: id}; - }; - - /** - * Restores the selection to the specified bookmark. - * - * @method moveToBookmark - * @param {Object} bookmark Bookmark to restore selection from. - * @return {Boolean} true/false if it was successful or not. - * @example - * // Stores a bookmark of the current selection - * var bm = tinymce.activeEditor.selection.getBookmark(); - * - * tinymce.activeEditor.setContent(tinymce.activeEditor.getContent() + 'Some new content'); - * - * // Restore the selection bookmark - * tinymce.activeEditor.selection.moveToBookmark(bm); - */ - this.moveToBookmark = function(bookmark) { - var rng, root, startContainer, endContainer, startOffset, endOffset; - - function setEndPoint(start) { - var point = bookmark[start ? 'start' : 'end'], i, node, offset, children; - - if (point) { - offset = point[0]; - - // Find container node - for (node = root, i = point.length - 1; i >= 1; i--) { - children = node.childNodes; - - if (point[i] > children.length - 1) { - return; - } - - node = children[point[i]]; - } - - // Move text offset to best suitable location - if (node.nodeType === 3) { - offset = Math.min(point[0], node.nodeValue.length); - } - - // Move element offset to best suitable location - if (node.nodeType === 1) { - offset = Math.min(point[0], node.childNodes.length); - } - - // Set offset within container node - if (start) { - rng.setStart(node, offset); - } else { - rng.setEnd(node, offset); - } - } - - return true; - } - - function restoreEndPoint(suffix) { - var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep; - - if (marker) { - node = marker.parentNode; - - if (suffix == 'start') { - if (!keep) { - idx = dom.nodeIndex(marker); - } else { - node = marker.firstChild; - idx = 1; - } - - startContainer = endContainer = node; - startOffset = endOffset = idx; - } else { - if (!keep) { - idx = dom.nodeIndex(marker); - } else { - node = marker.firstChild; - idx = 1; - } - - endContainer = node; - endOffset = idx; - } - - if (!keep) { - prev = marker.previousSibling; - next = marker.nextSibling; - - // Remove all marker text nodes - Tools.each(Tools.grep(marker.childNodes), function(node) { - if (node.nodeType == 3) { - node.nodeValue = node.nodeValue.replace(/\uFEFF/g, ''); - } - }); - - // Remove marker but keep children if for example contents where inserted into the marker - // Also remove duplicated instances of the marker for example by a - // split operation or by WebKit auto split on paste feature - while ((marker = dom.get(bookmark.id + '_' + suffix))) { - dom.remove(marker, 1); - } - - // If siblings are text nodes then merge them unless it's Opera since it some how removes the node - // and we are sniffing since adding a lot of detection code for a browser with 3% of the market - // isn't worth the effort. Sorry, Opera but it's just a fact - if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3 && !Env.opera) { - idx = prev.nodeValue.length; - prev.appendData(next.nodeValue); - dom.remove(next); - - if (suffix == 'start') { - startContainer = endContainer = prev; - startOffset = endOffset = idx; - } else { - endContainer = prev; - endOffset = idx; - } - } - } - } - } - - function addBogus(node) { - // Adds a bogus BR element for empty block elements - if (dom.isBlock(node) && !node.innerHTML && !Env.ie) { - node.innerHTML = ']
+ rng.moveToElementText(rng2.parentElement()); + if (rng.compareEndPoints('StartToEnd', rng2) === 0) { + rng2.move('character', -1); + } + + rng2.pasteHTML('' + chr + ''); + } + } catch (ex) { + // IE might throw unspecified error so lets ignore it + return null; + } + } else { + // Control selection + element = rng.item(0); + name = element.nodeName; + + return { name: name, index: findIndex(name, element) }; + } + } else { + element = selection.getNode(); + name = element.nodeName; + if (name == 'IMG') { + return { name: name, index: findIndex(name, element) }; + } + + // W3C method + rng2 = normalizeTableCellSelection(rng.cloneRange()); + + // Insert end marker + if (!collapsed) { + rng2.collapse(false); + rng2.insertNode(dom.create('span', { 'data-mce-type': "bookmark", id: id + '_end', style: styles }, chr)); + } + + rng = normalizeTableCellSelection(rng); + rng.collapse(true); + rng.insertNode(dom.create('span', { 'data-mce-type': "bookmark", id: id + '_start', style: styles }, chr)); + } + + selection.moveToBookmark({ id: id, keep: 1 }); + + return { id: id }; + }; + + /** + * Restores the selection to the specified bookmark. + * + * @method moveToBookmark + * @param {Object} bookmark Bookmark to restore selection from. + * @return {Boolean} true/false if it was successful or not. + * @example + * // Stores a bookmark of the current selection + * var bm = tinymce.activeEditor.selection.getBookmark(); + * + * tinymce.activeEditor.setContent(tinymce.activeEditor.getContent() + 'Some new content'); + * + * // Restore the selection bookmark + * tinymce.activeEditor.selection.moveToBookmark(bm); + */ + this.moveToBookmark = function (bookmark) { + var rng, root, startContainer, endContainer, startOffset, endOffset; + + function setEndPoint(start) { + var point = bookmark[start ? 'start' : 'end'], i, node, offset, children; + + if (point) { + offset = point[0]; + + // Find container node + for (node = root, i = point.length - 1; i >= 1; i--) { + children = node.childNodes; + + if (point[i] > children.length - 1) { + return; + } + + node = children[point[i]]; + } + + // Move text offset to best suitable location + if (node.nodeType === 3) { + offset = Math.min(point[0], node.nodeValue.length); + } + + // Move element offset to best suitable location + if (node.nodeType === 1) { + offset = Math.min(point[0], node.childNodes.length); + } + + // Set offset within container node + if (start) { + rng.setStart(node, offset); + } else { + rng.setEnd(node, offset); + } + } + + return true; + } + + function restoreEndPoint(suffix) { + var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep; + + if (marker) { + node = marker.parentNode; + + if (suffix == 'start') { + if (!keep) { + idx = dom.nodeIndex(marker); + } else { + node = marker.firstChild; + idx = 1; + } + + startContainer = endContainer = node; + startOffset = endOffset = idx; + } else { + if (!keep) { + idx = dom.nodeIndex(marker); + } else { + node = marker.firstChild; + idx = 1; + } + + endContainer = node; + endOffset = idx; + } + + if (!keep) { + prev = marker.previousSibling; + next = marker.nextSibling; + + // Remove all marker text nodes + Tools.each(Tools.grep(marker.childNodes), function (node) { + if (node.nodeType == 3) { + node.nodeValue = node.nodeValue.replace(/\uFEFF/g, ''); + } + }); + + // Remove marker but keep children if for example contents where inserted into the marker + // Also remove duplicated instances of the marker for example by a + // split operation or by WebKit auto split on paste feature + while ((marker = dom.get(bookmark.id + '_' + suffix))) { + dom.remove(marker, 1); + } + + // If siblings are text nodes then merge them unless it's Opera since it some how removes the node + // and we are sniffing since adding a lot of detection code for a browser with 3% of the market + // isn't worth the effort. Sorry, Opera but it's just a fact + if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3 && !Env.opera) { + idx = prev.nodeValue.length; + prev.appendData(next.nodeValue); + dom.remove(next); + + if (suffix == 'start') { + startContainer = endContainer = prev; + startOffset = endOffset = idx; + } else { + endContainer = prev; + endOffset = idx; + } + } + } + } + } + + function addBogus(node) { + // Adds a bogus BR element for empty block elements + if (dom.isBlock(node) && !node.innerHTML && !Env.ie) { + node.innerHTML = '|
would become this:|
+ sibling = startContainer.previousSibling; + if (sibling && !sibling.hasChildNodes() && dom.isBlock(sibling)) { + sibling.innerHTML = ''; + } else { + sibling = null; + } + + startContainer.innerHTML = ''; + ieRng.moveToElementText(startContainer.lastChild); + ieRng.select(); + dom.doc.selection.clear(); + startContainer.innerHTML = ''; + + if (sibling) { + sibling.innerHTML = ''; + } + return; + } + + startOffset = dom.nodeIndex(startContainer); + startContainer = startContainer.parentNode; + } + + if (startOffset == endOffset - 1) { + try { + ctrlElm = startContainer.childNodes[startOffset]; + ctrlRng = body.createControlRange(); + ctrlRng.addElement(ctrlElm); + ctrlRng.select(); + + // Check if the range produced is on the correct element and is a control range + // On IE 8 it will select the parent contentEditable container if you select an inner element see: #5398 + nativeRng = selection.getRng(); + if (nativeRng.item && ctrlElm === nativeRng.item(0)) { + return; + } + } catch (ex) { + // Ignore + } + } + } + + // Set start/end point of selection + setEndPoint(true); + setEndPoint(); + + // Select the new range and scroll it into view + ieRng.select(); + }; + + // Expose range method + this.getRangeAt = getRange; + } + + return Selection; + } +); /** * Selection.js * * Released under LGPL License. - * Copyright (c) 1999-2015 Ephox Corp. All rights reserved + * Copyright (c) 1999-2017 Ephox Corp. All rights reserved * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing @@ -17638,1032 +18681,993 @@ define("tinymce/dom/BookmarkManager", [ * // Getting the currently selected node for the active editor * alert(tinymce.activeEditor.selection.getNode().nodeName); */ -define("tinymce/dom/Selection", [ - "tinymce/dom/TreeWalker", - "tinymce/dom/TridentSelection", - "tinymce/dom/ControlSelection", - "tinymce/dom/RangeUtils", - "tinymce/dom/BookmarkManager", - "tinymce/dom/NodeType", - "tinymce/Env", - "tinymce/util/Tools", - "tinymce/caret/CaretPosition" -], function(TreeWalker, TridentSelection, ControlSelection, RangeUtils, BookmarkManager, NodeType, Env, Tools, CaretPosition) { - var each = Tools.each, trim = Tools.trim; - var isIE = Env.ie; - - /** - * Constructs a new selection instance. - * - * @constructor - * @method Selection - * @param {tinymce.dom.DOMUtils} dom DOMUtils object reference. - * @param {Window} win Window to bind the selection object to. - * @param {tinymce.Editor} editor Editor instance of the selection. - * @param {tinymce.dom.Serializer} serializer DOM serialization class to use for getContent. - */ - function Selection(dom, win, serializer, editor) { - var self = this; - - self.dom = dom; - self.win = win; - self.serializer = serializer; - self.editor = editor; - self.bookmarkManager = new BookmarkManager(self); - self.controlSelection = new ControlSelection(self, editor); - - // No W3C Range support - if (!self.win.getSelection) { - self.tridentSel = new TridentSelection(self); - } - } - - Selection.prototype = { - /** - * Move the selection cursor range to the specified node and offset. - * If there is no node specified it will move it to the first suitable location within the body. - * - * @method setCursorLocation - * @param {Node} node Optional node to put the cursor in. - * @param {Number} offset Optional offset from the start of the node to put the cursor at. - */ - setCursorLocation: function(node, offset) { - var self = this, rng = self.dom.createRng(); - - if (!node) { - self._moveEndPoint(rng, self.editor.getBody(), true); - self.setRng(rng); - } else { - rng.setStart(node, offset); - rng.setEnd(node, offset); - self.setRng(rng); - self.collapse(false); - } - }, - - /** - * Returns the selected contents using the DOM serializer passed in to this class. - * - * @method getContent - * @param {Object} args Optional settings class with for example output format text or html. - * @return {String} Selected contents in for example HTML format. - * @example - * // Alerts the currently selected contents - * alert(tinymce.activeEditor.selection.getContent()); - * - * // Alerts the currently selected contents as plain text - * alert(tinymce.activeEditor.selection.getContent({format: 'text'})); - */ - getContent: function(args) { - var self = this, rng = self.getRng(), tmpElm = self.dom.create("body"); - var se = self.getSel(), whiteSpaceBefore, whiteSpaceAfter, fragment; - - args = args || {}; - whiteSpaceBefore = whiteSpaceAfter = ''; - args.get = true; - args.format = args.format || 'html'; - args.selection = true; - self.editor.fire('BeforeGetContent', args); - - if (args.format == 'text') { - return self.isCollapsed() ? '' : (rng.text || (se.toString ? se.toString() : '')); - } - - if (rng.cloneContents) { - fragment = rng.cloneContents(); - - if (fragment) { - tmpElm.appendChild(fragment); - } - } else if (rng.item !== undefined || rng.htmlText !== undefined) { - // IE will produce invalid markup if elements are present that - // it doesn't understand like custom elements or HTML5 elements. - // Adding a BR in front of the contents and then remoiving it seems to fix it though. - tmpElm.innerHTML = '[a
]b
->[a]
b
- function adjustSelectionToVisibleSelection() { - function findSelectionEnd(start, end) { - var walker = new TreeWalker(end); - for (node = walker.prev2(); node; node = walker.prev2()) { - if (node.nodeType == 3 && node.data.length > 0) { - return node; - } - - if (node.childNodes.length > 1 || node == start || node.tagName == 'BR') { - return node; - } - } - } - - // Adjust selection so that a end container with a end offset of zero is not included in the selection - // as this isn't visible to the user. - var rng = ed.selection.getRng(); - var start = rng.startContainer; - var end = rng.endContainer; - - if (start != end && rng.endOffset === 0) { - var newEnd = findSelectionEnd(start, end); - var endOffset = newEnd.nodeType == 3 ? newEnd.data.length : newEnd.childNodes.length; - - rng.setEnd(newEnd, endOffset); - } - - return rng; - } - - function applyRngStyle(rng, bookmark, node_specific) { - var newWrappers = [], wrapName, wrapElm, contentEditable = true; - - // Setup wrapper element - wrapName = format.inline || format.block; - wrapElm = dom.create(wrapName); - setElementFormat(wrapElm); - - rangeUtils.walk(rng, function(nodes) { - var currentWrapElm; - - /** - * Process a list of nodes wrap them. - */ - function process(node) { - var nodeName, parentName, hasContentEditableState, lastContentEditable; - - lastContentEditable = contentEditable; - nodeName = node.nodeName.toLowerCase(); - parentName = node.parentNode.nodeName.toLowerCase(); - - // Node has a contentEditable value - if (node.nodeType === 1 && getContentEditable(node)) { - lastContentEditable = contentEditable; - contentEditable = getContentEditable(node) === "true"; - hasContentEditableState = true; // We don't want to wrap the container only it's children - } - - // Stop wrapping on br elements - if (isEq(nodeName, 'br')) { - currentWrapElm = 0; - - // Remove any br elements when we wrap things - if (format.block) { - dom.remove(node); - } - - return; - } - - // If node is wrapper type - if (format.wrapper && matchNode(node, name, vars)) { - currentWrapElm = 0; - return; - } - - // Can we rename the block - // TODO: Break this if up, too complex - if (contentEditable && !hasContentEditableState && format.block && - !format.wrapper && isTextBlock(nodeName) && isValid(parentName, wrapName)) { - node = dom.rename(node, wrapName); - setElementFormat(node); - newWrappers.push(node); - currentWrapElm = 0; - return; - } - - // Handle selector patterns - if (format.selector) { - var found = applyNodeStyle(formatList, node); - - // Continue processing if a selector match wasn't found and a inline element is defined - if (!format.inline || found) { - currentWrapElm = 0; - return; - } - } - - // Is it valid to wrap this item - // TODO: Break this if up, too complex - if (contentEditable && !hasContentEditableState && isValid(wrapName, nodeName) && isValid(parentName, wrapName) && - !(!node_specific && node.nodeType === 3 && - node.nodeValue.length === 1 && - node.nodeValue.charCodeAt(0) === 65279) && - !isCaretNode(node) && - (!format.inline || !isBlock(node))) { - // Start wrapping - if (!currentWrapElm) { - // Wrap the node - currentWrapElm = dom.clone(wrapElm, FALSE); - node.parentNode.insertBefore(currentWrapElm, node); - newWrappers.push(currentWrapElm); - } - - currentWrapElm.appendChild(node); - } else { - // Start a new wrapper for possible children - currentWrapElm = 0; - - each(grep(node.childNodes), process); - - if (hasContentEditableState) { - contentEditable = lastContentEditable; // Restore last contentEditable state from stack - } - - // End the last wrapper - currentWrapElm = 0; - } - } - - // Process siblings from range - each(nodes, process); - }); - - // Apply formats to links as well to get the color of the underline to change as well - if (format.links === true) { - each(newWrappers, function(node) { - function process(node) { - if (node.nodeName === 'A') { - setElementFormat(node, format); - } - - each(grep(node.childNodes), process); - } - - process(node); - }); - } - - // Cleanup - each(newWrappers, function(node) { - var childCount; - - function getChildCount(node) { - var count = 0; - - each(node.childNodes, function(node) { - if (!isWhiteSpaceNode(node) && !isBookmarkNode(node)) { - count++; - } - }); - - return count; - } - - function getChildElementNode(root) { - var child = false; - each(root.childNodes, function(node) { - if (isElementNode(node)) { - child = node; - return false; // break loop - } - }); - return child; - } - - function matchNestedWrapper(node, filter) { - do { - if (getChildCount(node) !== 1) { - break; - } - - node = getChildElementNode(node); - if (!node) { - break; - } else if (filter(node)) { - return node; - } - } while (node); - - return null; - } - - function mergeStyles(node) { - var child, clone; - - child = getChildElementNode(node); - - // If child was found and of the same type as the current node - if (child && !isBookmarkNode(child) && matchName(child, format)) { - clone = dom.clone(child, FALSE); - setElementFormat(clone); - - dom.replace(clone, node, TRUE); - dom.remove(child, 1); - } - - return clone || node; - } - - childCount = getChildCount(node); - - // Remove empty nodes but only if there is multiple wrappers and they are not block - // elements so never remove single since that would remove the - // current empty block element where the caret is at - if ((newWrappers.length > 1 || !isBlock(node)) && childCount === 0) { - dom.remove(node, 1); - return; - } - - if (format.inline || format.wrapper) { - // Merges the current node with it's children of similar type to reduce the number of elements - if (!format.exact && childCount === 1) { - node = mergeStyles(node); - } - - // Remove/merge children - each(formatList, function(format) { - // Merge all children of similar type will move styles from child to parent - // this: text - // will become: text - each(dom.select(format.inline, node), function(child) { - if (isBookmarkNode(child)) { - return; - } - - removeFormat(format, vars, child, format.exact ? child : null); - }); - }); - - // Remove format if direct parent already has the same format - if (matchNode(node.parentNode, name, vars)) { - if (removeFormat(format, vars, node)) { - node = 0; - } - } - - // Remove format if any ancestor already has the same format - if (format.merge_with_parents) { - dom.getParent(node.parentNode, function(parent) { - if (matchNode(parent, name, vars)) { - if (removeFormat(format, vars, node)) { - node = 0; - } - return TRUE; - } - }); - } - - // fontSize defines the line height for the whole branch of nested style wrappers, - // therefore it should be set on the outermost wrapper - if (!isBlock(node) && !getStyle(node, 'fontSize')) { - var styleNode = matchNestedWrapper(node, hasStyle('fontSize')); - if (styleNode) { - apply('fontsize', {value: getStyle(styleNode, 'fontSize')}, node); - } - } - - // Merge next and previous siblings if they are similar texttext becomes texttext - if (node && format.merge_siblings !== false) { - node = mergeSiblings(getNonWhiteSpaceSibling(node), node); - node = mergeSiblings(node, getNonWhiteSpaceSibling(node, TRUE)); - } - } - }); - } - - if (getContentEditable(selection.getNode()) === "false") { - node = selection.getNode(); - for (var i = 0, l = formatList.length; i < l; i++) { - if (formatList[i].ceFalseOverride && dom.is(node, formatList[i].selector)) { - setElementFormat(node, formatList[i]); - return; - } - } - - return; - } - - if (format) { - if (node) { - if (node.nodeType) { - if (!applyNodeStyle(formatList, node)) { - rng = dom.createRng(); - rng.setStartBefore(node); - rng.setEndAfter(node); - applyRngStyle(expandRng(rng, formatList), null, true); - } - } else { - applyRngStyle(node, null, true); - } - } else { - if (!isCollapsed || !format.inline || dom.select('td[data-mce-selected],th[data-mce-selected]').length) { - // Obtain selection node before selection is unselected by applyRngStyle() - var curSelNode = ed.selection.getNode(); - - // If the formats have a default block and we can't find a parent block then - // start wrapping it with a DIV this is for forced_root_blocks: false - // It's kind of a hack but people should be using the default block type P since all desktop editors work that way - if (!forcedRootBlock && formatList[0].defaultBlock && !dom.getParent(curSelNode, dom.isBlock)) { - apply(formatList[0].defaultBlock); - } - - // Apply formatting to selection - ed.selection.setRng(adjustSelectionToVisibleSelection()); - bookmark = selection.getBookmark(); - applyRngStyle(expandRng(selection.getRng(TRUE), formatList), bookmark); - - if (format.styles) { - // Colored nodes should be underlined so that the color of the underline matches the text color. - if (format.styles.color || format.styles.textDecoration) { - walk(curSelNode, processUnderlineAndColor, 'childNodes'); - processUnderlineAndColor(curSelNode); - } - - // nodes with font-size should have their own background color as well to fit the line-height (see TINY-882) - if (format.styles.backgroundColor) { - processChildElements(curSelNode, - hasStyle('fontSize'), - applyStyle('backgroundColor', replaceVars(format.styles.backgroundColor, vars)) - ); - } - } - - selection.moveToBookmark(bookmark); - moveStart(selection.getRng(TRUE)); - ed.nodeChanged(); - } else { - performCaretAction('apply', name, vars); - } - } - - Hooks.postProcess(name, ed); - } - } - - /** - * Removes the specified format from the current selection or specified node. - * - * @method remove - * @param {String} name Name of format to remove. - * @param {Object} vars Optional list of variables to replace within format before removing it. - * @param {Node/Range} node Optional node or DOM range to remove the format from defaults to current selection. - */ - function remove(name, vars, node, similar) { - var formatList = get(name), format = formatList[0], bookmark, rng, contentEditable = true; - - // Merges the styles for each node - function process(node) { - var children, i, l, lastContentEditable, hasContentEditableState; - - // Node has a contentEditable value - if (node.nodeType === 1 && getContentEditable(node)) { - lastContentEditable = contentEditable; - contentEditable = getContentEditable(node) === "true"; - hasContentEditableState = true; // We don't want to wrap the container only it's children - } - - // Grab the children first since the nodelist might be changed - children = grep(node.childNodes); - - // Process current node - if (contentEditable && !hasContentEditableState) { - for (i = 0, l = formatList.length; i < l; i++) { - if (removeFormat(formatList[i], vars, node, node)) { - break; - } - } - } - - // Process the children - if (format.deep) { - if (children.length) { - for (i = 0, l = children.length; i < l; i++) { - process(children[i]); - } - - if (hasContentEditableState) { - contentEditable = lastContentEditable; // Restore last contentEditable state from stack - } - } - } - } - - function findFormatRoot(container) { - var formatRoot; - - // Find format root - each(getParents(container.parentNode).reverse(), function(parent) { - var format; - - // Find format root element - if (!formatRoot && parent.id != '_start' && parent.id != '_end') { - // Is the node matching the format we are looking for - format = matchNode(parent, name, vars, similar); - if (format && format.split !== false) { - formatRoot = parent; - } - } - }); - - return formatRoot; - } - - function wrapAndSplit(formatRoot, container, target, split) { - var parent, clone, lastClone, firstClone, i, formatRootParent; - - // Format root found then clone formats and split it - if (formatRoot) { - formatRootParent = formatRoot.parentNode; - - for (parent = container.parentNode; parent && parent != formatRootParent; parent = parent.parentNode) { - clone = dom.clone(parent, FALSE); - - for (i = 0; i < formatList.length; i++) { - if (removeFormat(formatList[i], vars, clone, clone)) { - clone = 0; - break; - } - } - - // Build wrapper node - if (clone) { - if (lastClone) { - clone.appendChild(lastClone); - } - - if (!firstClone) { - firstClone = clone; - } - - lastClone = clone; - } - } - - // Never split block elements if the format is mixed - if (split && (!format.mixed || !isBlock(formatRoot))) { - container = dom.split(formatRoot, container); - } - - // Wrap container in cloned formats - if (lastClone) { - target.parentNode.insertBefore(lastClone, target); - firstClone.appendChild(target); - } - } - - return container; - } - - function splitToFormatRoot(container) { - return wrapAndSplit(findFormatRoot(container), container, container, true); - } - - function unwrap(start) { - var node = dom.get(start ? '_start' : '_end'), - out = node[start ? 'firstChild' : 'lastChild']; - - // If the end is placed within the start the result will be removed - // So this checks if the out node is a bookmark node if it is it - // checks for another more suitable node - if (isBookmarkNode(out)) { - out = out[start ? 'firstChild' : 'lastChild']; - } - - // Since dom.remove removes empty text nodes then we need to try to find a better node - if (out.nodeType == 3 && out.data.length === 0) { - out = start ? node.previousSibling || node.nextSibling : node.nextSibling || node.previousSibling; - } - - dom.remove(node, true); - - return out; - } - - function removeRngStyle(rng) { - var startContainer, endContainer; - var commonAncestorContainer = rng.commonAncestorContainer; - - rng = expandRng(rng, formatList, TRUE); - - if (format.split) { - startContainer = getContainer(rng, TRUE); - endContainer = getContainer(rng); - - if (startContainer != endContainer) { - // WebKit will render the table incorrectly if we wrap a TH or TD in a SPAN - // so let's see if we can use the first child instead - // This will happen if you triple click a table cell and use remove formatting - if (/^(TR|TH|TD)$/.test(startContainer.nodeName) && startContainer.firstChild) { - if (startContainer.nodeName == "TR") { - startContainer = startContainer.firstChild.firstChild || startContainer; - } else { - startContainer = startContainer.firstChild || startContainer; - } - } - - // Try to adjust endContainer as well if cells on the same row were selected - bug #6410 - if (commonAncestorContainer && - /^T(HEAD|BODY|FOOT|R)$/.test(commonAncestorContainer.nodeName) && - isTableCell(endContainer) && endContainer.firstChild) { - endContainer = endContainer.firstChild || endContainer; - } - - if (dom.isChildOf(startContainer, endContainer) && !isBlock(endContainer) && - !isTableCell(startContainer) && !isTableCell(endContainer)) { - startContainer = wrap(startContainer, 'span', {id: '_start', 'data-mce-type': 'bookmark'}); - splitToFormatRoot(startContainer); - startContainer = unwrap(TRUE); - return; - } - - // Wrap start/end nodes in span element since these might be cloned/moved - startContainer = wrap(startContainer, 'span', {id: '_start', 'data-mce-type': 'bookmark'}); - endContainer = wrap(endContainer, 'span', {id: '_end', 'data-mce-type': 'bookmark'}); - - // Split start/end - splitToFormatRoot(startContainer); - splitToFormatRoot(endContainer); - - // Unwrap start/end to get real elements again - startContainer = unwrap(TRUE); - endContainer = unwrap(); - } else { - startContainer = endContainer = splitToFormatRoot(startContainer); - } - - // Update range positions since they might have changed after the split operations - rng.startContainer = startContainer.parentNode ? startContainer.parentNode : startContainer; - rng.startOffset = nodeIndex(startContainer); - rng.endContainer = endContainer.parentNode ? endContainer.parentNode : endContainer; - rng.endOffset = nodeIndex(endContainer) + 1; - } - - // Remove items between start/end - rangeUtils.walk(rng, function(nodes) { - each(nodes, function(node) { - process(node); - - // Remove parent span if it only contains text-decoration: underline, yet a parent node is also underlined. - if (node.nodeType === 1 && ed.dom.getStyle(node, 'text-decoration') === 'underline' && - node.parentNode && getTextDecoration(node.parentNode) === 'underline') { - removeFormat({ - 'deep': false, - 'exact': true, - 'inline': 'span', - 'styles': { - 'textDecoration': 'underline' - } - }, null, node); - } - }); - }); - } - - // Handle node - if (node) { - if (node.nodeType) { - rng = dom.createRng(); - rng.setStartBefore(node); - rng.setEndAfter(node); - removeRngStyle(rng); - } else { - removeRngStyle(node); - } - - return; - } - - if (getContentEditable(selection.getNode()) === "false") { - node = selection.getNode(); - for (var i = 0, l = formatList.length; i < l; i++) { - if (formatList[i].ceFalseOverride) { - if (removeFormat(formatList[i], vars, node, node)) { - break; - } - } - } - - return; - } - - if (!selection.isCollapsed() || !format.inline || dom.select('td[data-mce-selected],th[data-mce-selected]').length) { - bookmark = selection.getBookmark(); - removeRngStyle(selection.getRng(TRUE)); - selection.moveToBookmark(bookmark); - - // Check if start element still has formatting then we are at: "text|text" - // and need to move the start into the next text node - if (format.inline && match(name, vars, selection.getStart())) { - moveStart(selection.getRng(true)); - } - - ed.nodeChanged(); - } else { - performCaretAction('remove', name, vars, similar); - } - } - - /** - * Toggles the specified format on/off. - * - * @method toggle - * @param {String} name Name of format to apply/remove. - * @param {Object} vars Optional list of variables to replace within format before applying/removing it. - * @param {Node} node Optional node to apply the format to or remove from. Defaults to current selection. - */ - function toggle(name, vars, node) { - var fmt = get(name); - - if (match(name, vars, node) && (!('toggle' in fmt[0]) || fmt[0].toggle)) { - remove(name, vars, node); - } else { - apply(name, vars, node); - } - } - - /** - * Return true/false if the specified node has the specified format. - * - * @method matchNode - * @param {Node} node Node to check the format on. - * @param {String} name Format name to check. - * @param {Object} vars Optional list of variables to replace before checking it. - * @param {Boolean} similar Match format that has similar properties. - * @return {Object} Returns the format object it matches or undefined if it doesn't match. - */ - function matchNode(node, name, vars, similar) { - var formatList = get(name), format, i, classes; - - function matchItems(node, format, item_name) { - var key, value, items = format[item_name], i; - - // Custom match - if (format.onmatch) { - return format.onmatch(node, format, item_name); - } - - // Check all items - if (items) { - // Non indexed object - if (items.length === undef) { - for (key in items) { - if (items.hasOwnProperty(key)) { - if (item_name === 'attributes') { - value = dom.getAttrib(node, key); - } else { - value = getStyle(node, key); - } - - if (similar && !value && !format.exact) { - return; - } - - if ((!similar || format.exact) && !isEq(value, normalizeStyleValue(replaceVars(items[key], vars), key))) { - return; - } - } - } - } else { - // Only one match needed for indexed arrays - for (i = 0; i < items.length; i++) { - if (item_name === 'attributes' ? dom.getAttrib(node, items[i]) : getStyle(node, items[i])) { - return format; - } - } - } - } - - return format; - } - - if (formatList && node) { - // Check each format in list - for (i = 0; i < formatList.length; i++) { - format = formatList[i]; - - // Name name, attributes, styles and classes - if (matchName(node, format) && matchItems(node, format, 'attributes') && matchItems(node, format, 'styles')) { - // Match classes - if ((classes = format.classes)) { - for (i = 0; i < classes.length; i++) { - if (!dom.hasClass(node, classes[i])) { - return; - } - } - } - - return format; - } - } - } - } - - /** - * Matches the current selection or specified node against the specified format name. - * - * @method match - * @param {String} name Name of format to match. - * @param {Object} vars Optional list of variables to replace before checking it. - * @param {Node} node Optional node to check. - * @return {boolean} true/false if the specified selection/node matches the format. - */ - function match(name, vars, node) { - var startNode; - - function matchParents(node) { - var root = dom.getRoot(); - - if (node === root) { - return false; - } - - // Find first node with similar format settings - node = dom.getParent(node, function(node) { - if (matchesUnInheritedFormatSelector(node, name)) { - return true; - } - - return node.parentNode === root || !!matchNode(node, name, vars, true); - }); - - // Do an exact check on the similar format element - return matchNode(node, name, vars); - } - - // Check specified node - if (node) { - return matchParents(node); - } - - // Check selected node - node = selection.getNode(); - if (matchParents(node)) { - return TRUE; - } - - // Check start node if it's different - startNode = selection.getStart(); - if (startNode != node) { - if (matchParents(startNode)) { - return TRUE; - } - } - - return FALSE; - } - - /** - * Matches the current selection against the array of formats and returns a new array with matching formats. - * - * @method matchAll - * @param {Array} names Name of format to match. - * @param {Object} vars Optional list of variables to replace before checking it. - * @return {Array} Array with matched formats. - */ - function matchAll(names, vars) { - var startElement, matchedFormatNames = [], checkedMap = {}; - - // Check start of selection for formats - startElement = selection.getStart(); - dom.getParent(startElement, function(node) { - var i, name; - - for (i = 0; i < names.length; i++) { - name = names[i]; - - if (!checkedMap[name] && matchNode(node, name, vars)) { - checkedMap[name] = true; - matchedFormatNames.push(name); - } - } - }, dom.getRoot()); - - return matchedFormatNames; - } - - /** - * Returns true/false if the specified format can be applied to the current selection or not. It - * will currently only check the state for selector formats, it returns true on all other format types. - * - * @method canApply - * @param {String} name Name of format to check. - * @return {boolean} true/false if the specified format can be applied to the current selection/node. - */ - function canApply(name) { - var formatList = get(name), startNode, parents, i, x, selector; - - if (formatList) { - startNode = selection.getStart(); - parents = getParents(startNode); - - for (x = formatList.length - 1; x >= 0; x--) { - selector = formatList[x].selector; - - // Format is not selector based then always return TRUE - // Is it has a defaultBlock then it's likely it can be applied for example align on a non block element line - if (!selector || formatList[x].defaultBlock) { - return TRUE; - } - - for (i = parents.length - 1; i >= 0; i--) { - if (dom.is(parents[i], selector)) { - return TRUE; - } - } - } - } - - return FALSE; - } - - /** - * Executes the specified callback when the current selection matches the formats or not. - * - * @method formatChanged - * @param {String} formats Comma separated list of formats to check for. - * @param {function} callback Callback with state and args when the format is changed/toggled on/off. - * @param {Boolean} similar True/false state if the match should handle similar or exact formats. - */ - function formatChanged(formats, callback, similar) { - var currentFormats; - - // Setup format node change logic - if (!formatChangeData) { - formatChangeData = {}; - currentFormats = {}; - - ed.on('NodeChange', function(e) { - var parents = getParents(e.element), matchedFormats = {}; - - // Ignore bogus nodes like the tag created by moveStart() - parents = Tools.grep(parents, function(node) { - return node.nodeType == 1 && !node.getAttribute('data-mce-bogus'); - }); - - // Check for new formats - each(formatChangeData, function(callbacks, format) { - each(parents, function(node) { - if (matchNode(node, format, {}, callbacks.similar)) { - if (!currentFormats[format]) { - // Execute callbacks - each(callbacks, function(callback) { - callback(true, {node: node, format: format, parents: parents}); - }); - - currentFormats[format] = callbacks; - } - - matchedFormats[format] = callbacks; - return false; - } - - if (matchesUnInheritedFormatSelector(node, format)) { - return false; - } - }); - }); - - // Check if current formats still match - each(currentFormats, function(callbacks, format) { - if (!matchedFormats[format]) { - delete currentFormats[format]; - - each(callbacks, function(callback) { - callback(false, {node: e.element, format: format, parents: parents}); - }); - } - }); - }); - } - - // Add format listeners - each(formats.split(','), function(format) { - if (!formatChangeData[format]) { - formatChangeData[format] = []; - formatChangeData[format].similar = similar; - } - - formatChangeData[format].push(callback); - }); - - return this; - } - - /** - * Returns a preview css text for the specified format. - * - * @method getCssText - * @param {String/Object} format Format to generate preview css text for. - * @return {String} Css text for the specified format. - * @example - * var cssText1 = editor.formatter.getCssText('bold'); - * var cssText2 = editor.formatter.getCssText({inline: 'b'}); - */ - function getCssText(format) { - return Preview.getCssText(ed, format); - } - - // Expose to public - extend(this, { - get: get, - register: register, - unregister: unregister, - apply: apply, - remove: remove, - toggle: toggle, - match: match, - matchAll: matchAll, - matchNode: matchNode, - canApply: canApply, - formatChanged: formatChanged, - getCssText: getCssText - }); - - // Initialize - defaultFormats(); - addKeyboardShortcuts(); - ed.on('BeforeGetContent', function(e) { - if (markCaretContainersBogus && e.format != 'raw') { - markCaretContainersBogus(); - } - }); - ed.on('mouseup keydown', function(e) { - if (disableCaretContainer) { - disableCaretContainer(e); - } - }); - - // Private functions - - /** - * Checks if the specified nodes name matches the format inline/block or selector. - * - * @private - * @param {Node} node Node to match against the specified format. - * @param {Object} format Format object o match with. - * @return {boolean} true/false if the format matches. - */ - function matchName(node, format) { - // Check for inline match - if (isEq(node, format.inline)) { - return TRUE; - } - - // Check for block match - if (isEq(node, format.block)) { - return TRUE; - } - - // Check for selector match - if (format.selector) { - return node.nodeType == 1 && dom.is(node, format.selector); - } - } - - /** - * Compares two string/nodes regardless of their case. - * - * @private - * @param {String/Node} str1 Node or string to compare. - * @param {String/Node} str2 Node or string to compare. - * @return {boolean} True/false if they match. - */ - function isEq(str1, str2) { - str1 = str1 || ''; - str2 = str2 || ''; - - str1 = '' + (str1.nodeName || str1); - str2 = '' + (str2.nodeName || str2); - - return str1.toLowerCase() == str2.toLowerCase(); - } - - function processChildElements(node, filter, process) { - each(node.childNodes, function(node) { - if (isElementNode(node)) { - if (filter(node)) { - process(node); - } - if (node.hasChildNodes()) { - processChildElements(node, filter, process); - } - } - }); - } - - function isElementNode(node) { - return node.nodeType == 1 && !isBookmarkNode(node) && !isWhiteSpaceNode(node) && !isCaretNode(node); - } - - function hasStyle(name) { - return Fun.curry(function(name, node) { - return !!(node && getStyle(node, name)); - }, name); - } - - function applyStyle(name, value) { - return Fun.curry(function(name, value, node) { - dom.setStyle(node, name, value); - }, name, value); - } - - /** - * Returns the style by name on the specified node. This method modifies the style - * contents to make it more easy to match. This will resolve a few browser issues. - * - * @private - * @param {Node} node to get style from. - * @param {String} name Style name to get. - * @return {String} Style item value. - */ - function getStyle(node, name) { - return normalizeStyleValue(dom.getStyle(node, name), name); - } - - /** - * Normalize style value by name. This method modifies the style contents - * to make it more easy to match. This will resolve a few browser issues. - * - * @private - * @param {String} value Value to get style from. - * @param {String} name Style name to get. - * @return {String} Style item value. - */ - function normalizeStyleValue(value, name) { - // Force the format to hex - if (name == 'color' || name == 'backgroundColor') { - value = dom.toHex(value); - } - - // Opera will return bold as 700 - if (name == 'fontWeight' && value == 700) { - value = 'bold'; - } - - // Normalize fontFamily so "'Font name', Font" becomes: "Font name,Font" - if (name == 'fontFamily') { - value = value.replace(/[\'\"]/g, '').replace(/,\s+/g, ','); - } - - return '' + value; - } - - /** - * Replaces variables in the value. The variable format is %var. - * - * @private - * @param {String} value Value to replace variables in. - * @param {Object} vars Name/value array with variables to replace. - * @return {String} New value with replaced variables. - */ - function replaceVars(value, vars) { - if (typeof value != "string") { - value = value(vars); - } else if (vars) { - value = value.replace(/%(\w+)/g, function(str, name) { - return vars[name] || str; - }); - } - - return value; - } - - function isWhiteSpaceNode(node) { - return node && node.nodeType === 3 && /^([\t \r\n]+|)$/.test(node.nodeValue); - } - - function wrap(node, name, attrs) { - var wrapper = dom.create(name, attrs); - - node.parentNode.insertBefore(wrapper, node); - wrapper.appendChild(node); - - return wrapper; - } - - /** - * Expands the specified range like object to depending on format. - * - * For example on block formats it will move the start/end position - * to the beginning of the current block. - * - * @private - * @param {Object} rng Range like object. - * @param {Array} format Array with formats to expand by. - * @param {Boolean} remove - * @return {Object} Expanded range like object. - */ - function expandRng(rng, format, remove) { - var lastIdx, leaf, endPoint, - startContainer = rng.startContainer, - startOffset = rng.startOffset, - endContainer = rng.endContainer, - endOffset = rng.endOffset; - - // This function walks up the tree if there is no siblings before/after the node - function findParentContainer(start) { - var container, parent, sibling, siblingName, root; - - container = parent = start ? startContainer : endContainer; - siblingName = start ? 'previousSibling' : 'nextSibling'; - root = dom.getRoot(); - - function isBogusBr(node) { - return node.nodeName == "BR" && node.getAttribute('data-mce-bogus') && !node.nextSibling; - } - - // If it's a text node and the offset is inside the text - if (container.nodeType == 3 && !isWhiteSpaceNode(container)) { - if (start ? startOffset > 0 : endOffset < container.nodeValue.length) { - return container; - } - } - - /*eslint no-constant-condition:0 */ - while (true) { - // Stop expanding on block elements - if (!format[0].block_expand && isBlock(parent)) { - return parent; - } - - // Walk left/right - for (sibling = parent[siblingName]; sibling; sibling = sibling[siblingName]) { - if (!isBookmarkNode(sibling) && !isWhiteSpaceNode(sibling) && !isBogusBr(sibling)) { - return parent; - } - } - - // Check if we can move up are we at root level or body level - if (parent == root || parent.parentNode == root) { - container = parent; - break; - } - - parent = parent.parentNode; - } - - return container; - } - - // This function walks down the tree to find the leaf at the selection. - // The offset is also returned as if node initially a leaf, the offset may be in the middle of the text node. - function findLeaf(node, offset) { - if (offset === undef) { - offset = node.nodeType === 3 ? node.length : node.childNodes.length; - } - - while (node && node.hasChildNodes()) { - node = node.childNodes[offset]; - if (node) { - offset = node.nodeType === 3 ? node.length : node.childNodes.length; - } - } - return {node: node, offset: offset}; - } - - // If index based start position then resolve it - if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) { - lastIdx = startContainer.childNodes.length - 1; - startContainer = startContainer.childNodes[startOffset > lastIdx ? lastIdx : startOffset]; - - if (startContainer.nodeType == 3) { - startOffset = 0; - } - } - - // If index based end position then resolve it - if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) { - lastIdx = endContainer.childNodes.length - 1; - endContainer = endContainer.childNodes[endOffset > lastIdx ? lastIdx : endOffset - 1]; - - if (endContainer.nodeType == 3) { - endOffset = endContainer.nodeValue.length; - } - } - - // Expands the node to the closes contentEditable false element if it exists - function findParentContentEditable(node) { - var parent = node; - - while (parent) { - if (parent.nodeType === 1 && getContentEditable(parent)) { - return getContentEditable(parent) === "false" ? parent : node; - } - - parent = parent.parentNode; - } - - return node; - } - - function findWordEndPoint(container, offset, start) { - var walker, node, pos, lastTextNode; - - function findSpace(node, offset) { - var pos, pos2, str = node.nodeValue; - - if (typeof offset == "undefined") { - offset = start ? str.length : 0; - } - - if (start) { - pos = str.lastIndexOf(' ', offset); - pos2 = str.lastIndexOf('\u00a0', offset); - pos = pos > pos2 ? pos : pos2; - - // Include the space on remove to avoid tag soup - if (pos !== -1 && !remove) { - pos++; - } - } else { - pos = str.indexOf(' ', offset); - pos2 = str.indexOf('\u00a0', offset); - pos = pos !== -1 && (pos2 === -1 || pos < pos2) ? pos : pos2; - } - - return pos; - } - - if (container.nodeType === 3) { - pos = findSpace(container, offset); - - if (pos !== -1) { - return {container: container, offset: pos}; - } - - lastTextNode = container; - } - - // Walk the nodes inside the block - walker = new TreeWalker(container, dom.getParent(container, isBlock) || ed.getBody()); - while ((node = walker[start ? 'prev' : 'next']())) { - if (node.nodeType === 3) { - lastTextNode = node; - pos = findSpace(node); - - if (pos !== -1) { - return {container: node, offset: pos}; - } - } else if (isBlock(node)) { - break; - } - } - - if (lastTextNode) { - if (start) { - offset = 0; - } else { - offset = lastTextNode.length; - } - - return {container: lastTextNode, offset: offset}; - } - } - - function findSelectorEndPoint(container, sibling_name) { - var parents, i, y, curFormat; - - if (container.nodeType == 3 && container.nodeValue.length === 0 && container[sibling_name]) { - container = container[sibling_name]; - } - - parents = getParents(container); - for (i = 0; i < parents.length; i++) { - for (y = 0; y < format.length; y++) { - curFormat = format[y]; - - // If collapsed state is set then skip formats that doesn't match that - if ("collapsed" in curFormat && curFormat.collapsed !== rng.collapsed) { - continue; - } - - if (dom.is(parents[i], curFormat.selector)) { - return parents[i]; - } - } - } - - return container; - } - - function findBlockEndPoint(container, sibling_name) { - var node, root = dom.getRoot(); - - // Expand to block of similar type - if (!format[0].wrapper) { - node = dom.getParent(container, format[0].block, root); - } - - // Expand to first wrappable block element or any block element - if (!node) { - node = dom.getParent(container.nodeType == 3 ? container.parentNode : container, function(node) { - // Fixes #6183 where it would expand to editable parent element in inline mode - return node != root && isTextBlock(node); - }); - } - - // Exclude inner lists from wrapping - if (node && format[0].wrapper) { - node = getParents(node, 'ul,ol').reverse()[0] || node; - } - - // Didn't find a block element look for first/last wrappable element - if (!node) { - node = container; - - while (node[sibling_name] && !isBlock(node[sibling_name])) { - node = node[sibling_name]; - - // Break on BR but include it will be removed later on - // we can't remove it now since we need to check if it can be wrapped - if (isEq(node, 'br')) { - break; - } - } - } - - return node || container; - } - - // Expand to closest contentEditable element - startContainer = findParentContentEditable(startContainer); - endContainer = findParentContentEditable(endContainer); - - // Exclude bookmark nodes if possible - if (isBookmarkNode(startContainer.parentNode) || isBookmarkNode(startContainer)) { - startContainer = isBookmarkNode(startContainer) ? startContainer : startContainer.parentNode; - startContainer = startContainer.nextSibling || startContainer; - - if (startContainer.nodeType == 3) { - startOffset = 0; - } - } - - if (isBookmarkNode(endContainer.parentNode) || isBookmarkNode(endContainer)) { - endContainer = isBookmarkNode(endContainer) ? endContainer : endContainer.parentNode; - endContainer = endContainer.previousSibling || endContainer; - - if (endContainer.nodeType == 3) { - endOffset = endContainer.length; - } - } - - if (format[0].inline) { - if (rng.collapsed) { - // Expand left to closest word boundary - endPoint = findWordEndPoint(startContainer, startOffset, true); - if (endPoint) { - startContainer = endPoint.container; - startOffset = endPoint.offset; - } - - // Expand right to closest word boundary - endPoint = findWordEndPoint(endContainer, endOffset); - if (endPoint) { - endContainer = endPoint.container; - endOffset = endPoint.offset; - } - } - - // Avoid applying formatting to a trailing space. - leaf = findLeaf(endContainer, endOffset); - if (leaf.node) { - while (leaf.node && leaf.offset === 0 && leaf.node.previousSibling) { - leaf = findLeaf(leaf.node.previousSibling); - } - - if (leaf.node && leaf.offset > 0 && leaf.node.nodeType === 3 && - leaf.node.nodeValue.charAt(leaf.offset - 1) === ' ') { - - if (leaf.offset > 1) { - endContainer = leaf.node; - endContainer.splitText(leaf.offset - 1); - } - } - } - } - - // Move start/end point up the tree if the leaves are sharp and if we are in different containers - // Example * becomes !: !*texttext*
! - // This will reduce the number of wrapper elements that needs to be created - // Move start point up the tree - if (format[0].inline || format[0].block_expand) { - if (!format[0].inline || (startContainer.nodeType != 3 || startOffset === 0)) { - startContainer = findParentContainer(true); - } - - if (!format[0].inline || (endContainer.nodeType != 3 || endOffset === endContainer.nodeValue.length)) { - endContainer = findParentContainer(); - } - } - - // Expand start/end container to matching selector - if (format[0].selector && format[0].expand !== FALSE && !format[0].inline) { - // Find new startContainer/endContainer if there is better one - startContainer = findSelectorEndPoint(startContainer, 'previousSibling'); - endContainer = findSelectorEndPoint(endContainer, 'nextSibling'); - } - - // Expand start/end container to matching block element or text node - if (format[0].block || format[0].selector) { - // Find new startContainer/endContainer if there is better one - startContainer = findBlockEndPoint(startContainer, 'previousSibling'); - endContainer = findBlockEndPoint(endContainer, 'nextSibling'); - - // Non block element then try to expand up the leaf - if (format[0].block) { - if (!isBlock(startContainer)) { - startContainer = findParentContainer(true); - } - - if (!isBlock(endContainer)) { - endContainer = findParentContainer(); - } - } - } - - // Setup index for startContainer - if (startContainer.nodeType == 1) { - startOffset = nodeIndex(startContainer); - startContainer = startContainer.parentNode; - } - - // Setup index for endContainer - if (endContainer.nodeType == 1) { - endOffset = nodeIndex(endContainer) + 1; - endContainer = endContainer.parentNode; - } - - // Return new range like object - return { - startContainer: startContainer, - startOffset: startOffset, - endContainer: endContainer, - endOffset: endOffset - }; - } - - function isColorFormatAndAnchor(node, format) { - return format.links && node.tagName == 'A'; - } - - /** - * Removes the specified format for the specified node. It will also remove the node if it doesn't have - * any attributes if the format specifies it to do so. - * - * @private - * @param {Object} format Format object with items to remove from node. - * @param {Object} vars Name/value object with variables to apply to format. - * @param {Node} node Node to remove the format styles on. - * @param {Node} compare_node Optional compare node, if specified the styles will be compared to that node. - * @return {Boolean} True/false if the node was removed or not. - */ - function removeFormat(format, vars, node, compare_node) { - var i, attrs, stylesModified; - - // Check if node matches format - if (!matchName(node, format) && !isColorFormatAndAnchor(node, format)) { - return FALSE; - } - - // Should we compare with format attribs and styles - if (format.remove != 'all') { - // Remove styles - each(format.styles, function(value, name) { - value = normalizeStyleValue(replaceVars(value, vars), name); - - // Indexed array - if (typeof name === 'number') { - name = value; - compare_node = 0; - } - - if (format.remove_similar || (!compare_node || isEq(getStyle(compare_node, name), value))) { - dom.setStyle(node, name, ''); - } - - stylesModified = 1; - }); - - // Remove style attribute if it's empty - if (stylesModified && dom.getAttrib(node, 'style') === '') { - node.removeAttribute('style'); - node.removeAttribute('data-mce-style'); - } - - // Remove attributes - each(format.attributes, function(value, name) { - var valueOut; - - value = replaceVars(value, vars); - - // Indexed array - if (typeof name === 'number') { - name = value; - compare_node = 0; - } - - if (!compare_node || isEq(dom.getAttrib(compare_node, name), value)) { - // Keep internal classes - if (name == 'class') { - value = dom.getAttrib(node, name); - if (value) { - // Build new class value where everything is removed except the internal prefixed classes - valueOut = ''; - each(value.split(/\s+/), function(cls) { - if (/mce\-\w+/.test(cls)) { - valueOut += (valueOut ? ' ' : '') + cls; - } - }); - - // We got some internal classes left - if (valueOut) { - dom.setAttrib(node, name, valueOut); - return; - } - } - } - - // IE6 has a bug where the attribute doesn't get removed correctly - if (name == "class") { - node.removeAttribute('className'); - } - - // Remove mce prefixed attributes - if (MCE_ATTR_RE.test(name)) { - node.removeAttribute('data-mce-' + name); - } - - node.removeAttribute(name); - } - }); - - // Remove classes - each(format.classes, function(value) { - value = replaceVars(value, vars); - - if (!compare_node || dom.hasClass(compare_node, value)) { - dom.removeClass(node, value); - } - }); - - // Check for non internal attributes - attrs = dom.getAttribs(node); - for (i = 0; i < attrs.length; i++) { - var attrName = attrs[i].nodeName; - if (attrName.indexOf('_') !== 0 && attrName.indexOf('data-') !== 0) { - return FALSE; - } - } - } - - // Remove the inline child if it's empty for example or - if (format.remove != 'none') { - removeNode(node, format); - return TRUE; - } - } - - /** - * Removes the node and wrap it's children in paragraphs before doing so or - * appends BR elements to the beginning/end of the block element if forcedRootBlocks is disabled. - * - * If the div in the node below gets removed: - * text|
- formatNode.parentNode.replaceChild(caretContainer, formatNode); - } else { - // Insert caret container after the formatted node - dom.insertAfter(caretContainer, formatNode); - } - - // Move selection to text node - selection.setCursorLocation(node, 1); - - // If the formatNode is empty, we can remove it safely. - if (dom.isEmpty(formatNode)) { - dom.remove(formatNode); - } - } - } - - // Checks if the parent caret container node isn't empty if that is the case it - // will remove the bogus state on all children that isn't empty - function unmarkBogusCaretParents() { - var caretContainer; - - caretContainer = getParentCaretContainer(selection.getStart()); - if (caretContainer && !dom.isEmpty(caretContainer)) { - walk(caretContainer, function(node) { - if (node.nodeType == 1 && node.id !== caretContainerId && !dom.isEmpty(node)) { - dom.setAttrib(node, 'data-mce-bogus', null); - } - }, 'childNodes'); - } - } - - // Only bind the caret events once - if (!ed._hasCaretEvents) { - // Mark current caret container elements as bogus when getting the contents so we don't end up with empty elements - markCaretContainersBogus = function() { - var nodes = [], i; - - if (isCaretContainerEmpty(getParentCaretContainer(selection.getStart()), nodes)) { - // Mark children - i = nodes.length; - while (i--) { - dom.setAttrib(nodes[i], 'data-mce-bogus', '1'); - } - } - }; - - disableCaretContainer = function(e) { - var keyCode = e.keyCode; - - removeCaretContainer(); - - // Remove caret container if it's empty - if (keyCode == 8 && selection.isCollapsed() && selection.getStart().innerHTML == INVISIBLE_CHAR) { - removeCaretContainer(getParentCaretContainer(selection.getStart())); - } - - // Remove caret container on keydown and it's left/right arrow keys - if (keyCode == 37 || keyCode == 39) { - removeCaretContainer(getParentCaretContainer(selection.getStart())); - } - - unmarkBogusCaretParents(); - }; - - // Remove bogus state if they got filled by contents using editor.selection.setContent - ed.on('SetContent', function(e) { - if (e.selection) { - unmarkBogusCaretParents(); - } - }); - ed._hasCaretEvents = true; - } - - // Do apply or remove caret format - if (type == "apply") { - applyCaretFormat(); - } else { - removeCaretFormat(); - } - } - - /** - * Moves the start to the first suitable text node. - */ - function moveStart(rng) { - var container = rng.startContainer, - offset = rng.startOffset, isAtEndOfText, - walker, node, nodes, tmpNode; - - if (rng.startContainer == rng.endContainer) { - if (isInlineBlock(rng.startContainer.childNodes[rng.startOffset])) { - return; - } - } - - // Convert text node into index if possible - if (container.nodeType == 3 && offset >= container.nodeValue.length) { - // Get the parent container location and walk from there - offset = nodeIndex(container); - container = container.parentNode; - isAtEndOfText = true; - } - - // Move startContainer/startOffset in to a suitable node - if (container.nodeType == 1) { - nodes = container.childNodes; - container = nodes[Math.min(offset, nodes.length - 1)]; - walker = new TreeWalker(container, dom.getParent(container, dom.isBlock)); - - // If offset is at end of the parent node walk to the next one - if (offset > nodes.length - 1 || isAtEndOfText) { - walker.next(); - } - - for (node = walker.current(); node; node = walker.next()) { - if (node.nodeType == 3 && !isWhiteSpaceNode(node)) { - // IE has a "neat" feature where it moves the start node into the closest element - // we can avoid this by inserting an element before it and then remove it after we set the selection - tmpNode = dom.create('a', {'data-mce-bogus': 'all'}, INVISIBLE_CHAR); - node.parentNode.insertBefore(tmpNode, node); - - // Set selection and remove tmpNode - rng.setStart(node, 0); - selection.setRng(rng); - dom.remove(tmpNode); - - return; - } - } - } - } - }; -}); - -// Included from: js/tinymce/classes/undo/Diff.js +define( + 'tinymce.core.Formatter', + [ + "tinymce.core.dom.TreeWalker", + "tinymce.core.dom.RangeUtils", + "tinymce.core.dom.BookmarkManager", + "tinymce.core.dom.ElementUtils", + "tinymce.core.dom.NodeType", + "tinymce.core.util.Fun", + "tinymce.core.util.Tools", + "tinymce.core.fmt.Preview", + "tinymce.core.fmt.Hooks" + ], + function (TreeWalker, RangeUtils, BookmarkManager, ElementUtils, NodeType, Fun, Tools, Preview, Hooks) { + /** + * Constructs a new formatter instance. + * + * @constructor Formatter + * @param {tinymce.Editor} ed Editor instance to construct the formatter engine to. + */ + return function (ed) { + var formats = {}, + dom = ed.dom, + selection = ed.selection, + rangeUtils = new RangeUtils(dom), + isValid = ed.schema.isValidChild, + isBlock = dom.isBlock, + forcedRootBlock = ed.settings.forced_root_block, + nodeIndex = dom.nodeIndex, + INVISIBLE_CHAR = '\uFEFF', + MCE_ATTR_RE = /^(src|href|style)$/, + FALSE = false, + TRUE = true, + formatChangeData, + undef, + getContentEditable = dom.getContentEditable, + disableCaretContainer, + markCaretContainersBogus, + isBookmarkNode = BookmarkManager.isBookmarkNode; + + var each = Tools.each, + grep = Tools.grep, + walk = Tools.walk, + extend = Tools.extend; + + function isTextBlock(name) { + if (name.nodeType) { + name = name.nodeName; + } + + return !!ed.schema.getTextBlockElements()[name.toLowerCase()]; + } + + function isTableCell(node) { + return /^(TH|TD)$/.test(node.nodeName); + } + + function isInlineBlock(node) { + return node && /^(IMG)$/.test(node.nodeName); + } + + function getParents(node, selector) { + return dom.getParents(node, selector, dom.getRoot()); + } + + function isCaretNode(node) { + return node.nodeType === 1 && node.id === '_mce_caret'; + } + + function defaultFormats() { + register({ + valigntop: [ + { selector: 'td,th', styles: { 'verticalAlign': 'top' } } + ], + + valignmiddle: [ + { selector: 'td,th', styles: { 'verticalAlign': 'middle' } } + ], + + valignbottom: [ + { selector: 'td,th', styles: { 'verticalAlign': 'bottom' } } + ], + + alignleft: [ + { + selector: 'figure.image', + collapsed: false, + classes: 'align-left', + ceFalseOverride: true, + preview: 'font-family font-size' + }, + { + selector: 'figure,p,h1,h2,h3,h4,h5,h6,td,th,tr,div,ul,ol,li', + styles: { + textAlign: 'left' + }, + inherit: false, + preview: false, + defaultBlock: 'div' + }, + { selector: 'img,table', collapsed: false, styles: { 'float': 'left' }, preview: 'font-family font-size' } + ], + + aligncenter: [ + { + selector: 'figure,p,h1,h2,h3,h4,h5,h6,td,th,tr,div,ul,ol,li', + styles: { + textAlign: 'center' + }, + inherit: false, + preview: false, + defaultBlock: 'div' + }, + { + selector: 'figure.image', + collapsed: false, + classes: 'align-center', + ceFalseOverride: true, + preview: 'font-family font-size' + }, + { + selector: 'img', + collapsed: false, + styles: { + display: 'block', + marginLeft: 'auto', + marginRight: 'auto' + }, + preview: false + }, + { + selector: 'table', + collapsed: false, + styles: { + marginLeft: 'auto', + marginRight: 'auto' + }, + preview: 'font-family font-size' + } + ], + + alignright: [ + { + selector: 'figure.image', + collapsed: false, + classes: 'align-right', + ceFalseOverride: true, + preview: 'font-family font-size' + }, + { + selector: 'figure,p,h1,h2,h3,h4,h5,h6,td,th,tr,div,ul,ol,li', + styles: { + textAlign: 'right' + }, + inherit: false, + preview: 'font-family font-size', + defaultBlock: 'div' + }, + { + selector: 'img,table', + collapsed: false, + styles: { + 'float': 'right' + }, + preview: 'font-family font-size' + } + ], + + alignjustify: [ + { + selector: 'figure,p,h1,h2,h3,h4,h5,h6,td,th,tr,div,ul,ol,li', + styles: { + textAlign: 'justify' + }, + inherit: false, + defaultBlock: 'div', + preview: 'font-family font-size' + } + ], + + bold: [ + { inline: 'strong', remove: 'all' }, + { inline: 'span', styles: { fontWeight: 'bold' } }, + { inline: 'b', remove: 'all' } + ], + + italic: [ + { inline: 'em', remove: 'all' }, + { inline: 'span', styles: { fontStyle: 'italic' } }, + { inline: 'i', remove: 'all' } + ], + + underline: [ + { inline: 'span', styles: { textDecoration: 'underline' }, exact: true }, + { inline: 'u', remove: 'all' } + ], + + strikethrough: [ + { inline: 'span', styles: { textDecoration: 'line-through' }, exact: true }, + { inline: 'strike', remove: 'all' } + ], + + forecolor: { inline: 'span', styles: { color: '%value' }, links: true, remove_similar: true }, + hilitecolor: { inline: 'span', styles: { backgroundColor: '%value' }, links: true, remove_similar: true }, + fontname: { inline: 'span', styles: { fontFamily: '%value' } }, + fontsize: { inline: 'span', styles: { fontSize: '%value' } }, + fontsize_class: { inline: 'span', attributes: { 'class': '%value' } }, + blockquote: { block: 'blockquote', wrapper: 1, remove: 'all' }, + subscript: { inline: 'sub' }, + superscript: { inline: 'sup' }, + code: { inline: 'code' }, + + link: { + inline: 'a', selector: 'a', remove: 'all', split: true, deep: true, + onmatch: function () { + return true; + }, + + onformat: function (elm, fmt, vars) { + each(vars, function (value, key) { + dom.setAttrib(elm, key, value); + }); + } + }, + + removeformat: [ + { + selector: 'b,strong,em,i,font,u,strike,sub,sup,dfn,code,samp,kbd,var,cite,mark,q,del,ins', + remove: 'all', + split: true, + expand: false, + block_expand: true, + deep: true + }, + { selector: 'span', attributes: ['style', 'class'], remove: 'empty', split: true, expand: false, deep: true }, + { selector: '*', attributes: ['style', 'class'], split: false, expand: false, deep: true } + ] + }); + + // Register default block formats + each('p h1 h2 h3 h4 h5 h6 div address pre div dt dd samp'.split(/\s/), function (name) { + register(name, { block: name, remove: 'all' }); + }); + + // Register user defined formats + register(ed.settings.formats); + } + + function addKeyboardShortcuts() { + // Add some inline shortcuts + ed.addShortcut('meta+b', 'bold_desc', 'Bold'); + ed.addShortcut('meta+i', 'italic_desc', 'Italic'); + ed.addShortcut('meta+u', 'underline_desc', 'Underline'); + + // BlockFormat shortcuts keys + for (var i = 1; i <= 6; i++) { + ed.addShortcut('access+' + i, '', ['FormatBlock', false, 'h' + i]); + } + + ed.addShortcut('access+7', '', ['FormatBlock', false, 'p']); + ed.addShortcut('access+8', '', ['FormatBlock', false, 'div']); + ed.addShortcut('access+9', '', ['FormatBlock', false, 'address']); + } + + // Public functions + + /** + * Returns the format by name or all formats if no name is specified. + * + * @method get + * @param {String} name Optional name to retrieve by. + * @return {Array/Object} Array/Object with all registered formats or a specific format. + */ + function get(name) { + return name ? formats[name] : formats; + } + + /** + * Registers a specific format by name. + * + * @method register + * @param {Object/String} name Name of the format for example "bold". + * @param {Object/Array} format Optional format object or array of format variants + * can only be omitted if the first arg is an object. + */ + function register(name, format) { + if (name) { + if (typeof name !== 'string') { + each(name, function (format, name) { + register(name, format); + }); + } else { + // Force format into array and add it to internal collection + format = format.length ? format : [format]; + + each(format, function (format) { + // Set deep to false by default on selector formats this to avoid removing + // alignment on images inside paragraphs when alignment is changed on paragraphs + if (format.deep === undef) { + format.deep = !format.selector; + } + + // Default to true + if (format.split === undef) { + format.split = !format.selector || format.inline; + } + + // Default to true + if (format.remove === undef && format.selector && !format.inline) { + format.remove = 'none'; + } + + // Mark format as a mixed format inline + block level + if (format.selector && format.inline) { + format.mixed = true; + format.block_expand = true; + } + + // Split classes if needed + if (typeof format.classes === 'string') { + format.classes = format.classes.split(/\s+/); + } + }); + + formats[name] = format; + } + } + } + + /** + * Unregister a specific format by name. + * + * @method unregister + * @param {String} name Name of the format for example "bold". + */ + function unregister(name) { + if (name && formats[name]) { + delete formats[name]; + } + + return formats; + } + + function matchesUnInheritedFormatSelector(node, name) { + var formatList = get(name); + + if (formatList) { + for (var i = 0; i < formatList.length; i++) { + if (formatList[i].inherit === false && dom.is(node, formatList[i].selector)) { + return true; + } + } + } + + return false; + } + + function getTextDecoration(node) { + var decoration; + + ed.dom.getParent(node, function (n) { + decoration = ed.dom.getStyle(n, 'text-decoration'); + return decoration && decoration !== 'none'; + }); + + return decoration; + } + + function processUnderlineAndColor(node) { + var textDecoration; + if (node.nodeType === 1 && node.parentNode && node.parentNode.nodeType === 1) { + textDecoration = getTextDecoration(node.parentNode); + if (ed.dom.getStyle(node, 'color') && textDecoration) { + ed.dom.setStyle(node, 'text-decoration', textDecoration); + } else if (ed.dom.getStyle(node, 'text-decoration') === textDecoration) { + ed.dom.setStyle(node, 'text-decoration', null); + } + } + } + + /** + * Applies the specified format to the current selection or specified node. + * + * @method apply + * @param {String} name Name of format to apply. + * @param {Object} vars Optional list of variables to replace within format before applying it. + * @param {Node} node Optional node to apply the format to defaults to current selection. + */ + function apply(name, vars, node) { + var formatList = get(name), format = formatList[0], bookmark, rng, isCollapsed = !node && selection.isCollapsed(); + + function setElementFormat(elm, fmt) { + fmt = fmt || format; + + if (elm) { + if (fmt.onformat) { + fmt.onformat(elm, fmt, vars, node); + } + + each(fmt.styles, function (value, name) { + dom.setStyle(elm, name, replaceVars(value, vars)); + }); + + // Needed for the WebKit span spam bug + // TODO: Remove this once WebKit/Blink fixes this + if (fmt.styles) { + var styleVal = dom.getAttrib(elm, 'style'); + + if (styleVal) { + elm.setAttribute('data-mce-style', styleVal); + } + } + + each(fmt.attributes, function (value, name) { + dom.setAttrib(elm, name, replaceVars(value, vars)); + }); + + each(fmt.classes, function (value) { + value = replaceVars(value, vars); + + if (!dom.hasClass(elm, value)) { + dom.addClass(elm, value); + } + }); + } + } + + function applyNodeStyle(formatList, node) { + var found = false; + + if (!format.selector) { + return false; + } + + // Look for matching formats + each(formatList, function (format) { + // Check collapsed state if it exists + if ('collapsed' in format && format.collapsed !== isCollapsed) { + return; + } + + if (dom.is(node, format.selector) && !isCaretNode(node)) { + setElementFormat(node, format); + found = true; + return false; + } + }); + + return found; + } + + // This converts:[a
]b
->[a]
b
+ function adjustSelectionToVisibleSelection() { + function findSelectionEnd(start, end) { + var walker = new TreeWalker(end); + for (node = walker.prev2(); node; node = walker.prev2()) { + if (node.nodeType == 3 && node.data.length > 0) { + return node; + } + + if (node.childNodes.length > 1 || node == start || node.tagName == 'BR') { + return node; + } + } + } + + // Adjust selection so that a end container with a end offset of zero is not included in the selection + // as this isn't visible to the user. + var rng = ed.selection.getRng(); + var start = rng.startContainer; + var end = rng.endContainer; + + if (start != end && rng.endOffset === 0) { + var newEnd = findSelectionEnd(start, end); + var endOffset = newEnd.nodeType == 3 ? newEnd.data.length : newEnd.childNodes.length; + + rng.setEnd(newEnd, endOffset); + } + + return rng; + } + + function applyRngStyle(rng, bookmark, nodeSpecific) { + var newWrappers = [], wrapName, wrapElm, contentEditable = true; + + // Setup wrapper element + wrapName = format.inline || format.block; + wrapElm = dom.create(wrapName); + setElementFormat(wrapElm); + + rangeUtils.walk(rng, function (nodes) { + var currentWrapElm; + + /** + * Process a list of nodes wrap them. + */ + function process(node) { + var nodeName, parentName, hasContentEditableState, lastContentEditable; + + lastContentEditable = contentEditable; + nodeName = node.nodeName.toLowerCase(); + parentName = node.parentNode.nodeName.toLowerCase(); + + // Node has a contentEditable value + if (node.nodeType === 1 && getContentEditable(node)) { + lastContentEditable = contentEditable; + contentEditable = getContentEditable(node) === "true"; + hasContentEditableState = true; // We don't want to wrap the container only it's children + } + + // Stop wrapping on br elements + if (isEq(nodeName, 'br')) { + currentWrapElm = 0; + + // Remove any br elements when we wrap things + if (format.block) { + dom.remove(node); + } + + return; + } + + // If node is wrapper type + if (format.wrapper && matchNode(node, name, vars)) { + currentWrapElm = 0; + return; + } + + // Can we rename the block + // TODO: Break this if up, too complex + if (contentEditable && !hasContentEditableState && format.block && + !format.wrapper && isTextBlock(nodeName) && isValid(parentName, wrapName)) { + node = dom.rename(node, wrapName); + setElementFormat(node); + newWrappers.push(node); + currentWrapElm = 0; + return; + } + + // Handle selector patterns + if (format.selector) { + var found = applyNodeStyle(formatList, node); + + // Continue processing if a selector match wasn't found and a inline element is defined + if (!format.inline || found) { + currentWrapElm = 0; + return; + } + } + + // Is it valid to wrap this item + // TODO: Break this if up, too complex + if (contentEditable && !hasContentEditableState && isValid(wrapName, nodeName) && isValid(parentName, wrapName) && + !(!nodeSpecific && node.nodeType === 3 && + node.nodeValue.length === 1 && + node.nodeValue.charCodeAt(0) === 65279) && + !isCaretNode(node) && + (!format.inline || !isBlock(node))) { + // Start wrapping + if (!currentWrapElm) { + // Wrap the node + currentWrapElm = dom.clone(wrapElm, FALSE); + node.parentNode.insertBefore(currentWrapElm, node); + newWrappers.push(currentWrapElm); + } + + currentWrapElm.appendChild(node); + } else { + // Start a new wrapper for possible children + currentWrapElm = 0; + + each(grep(node.childNodes), process); + + if (hasContentEditableState) { + contentEditable = lastContentEditable; // Restore last contentEditable state from stack + } + + // End the last wrapper + currentWrapElm = 0; + } + } + + // Process siblings from range + each(nodes, process); + }); + + // Apply formats to links as well to get the color of the underline to change as well + if (format.links === true) { + each(newWrappers, function (node) { + function process(node) { + if (node.nodeName === 'A') { + setElementFormat(node, format); + } + + each(grep(node.childNodes), process); + } + + process(node); + }); + } + + // Cleanup + each(newWrappers, function (node) { + var childCount; + + function getChildCount(node) { + var count = 0; + + each(node.childNodes, function (node) { + if (!isWhiteSpaceNode(node) && !isBookmarkNode(node)) { + count++; + } + }); + + return count; + } + + function getChildElementNode(root) { + var child = false; + each(root.childNodes, function (node) { + if (isElementNode(node)) { + child = node; + return false; // break loop + } + }); + return child; + } + + function matchNestedWrapper(node, filter) { + do { + if (getChildCount(node) !== 1) { + break; + } + + node = getChildElementNode(node); + if (!node) { + break; + } else if (filter(node)) { + return node; + } + } while (node); + + return null; + } + + function mergeStyles(node) { + var child, clone; + + child = getChildElementNode(node); + + // If child was found and of the same type as the current node + if (child && !isBookmarkNode(child) && matchName(child, format)) { + clone = dom.clone(child, FALSE); + setElementFormat(clone); + + dom.replace(clone, node, TRUE); + dom.remove(child, 1); + } + + return clone || node; + } + + childCount = getChildCount(node); + + // Remove empty nodes but only if there is multiple wrappers and they are not block + // elements so never remove single since that would remove the + // current empty block element where the caret is at + if ((newWrappers.length > 1 || !isBlock(node)) && childCount === 0) { + dom.remove(node, 1); + return; + } + + if (format.inline || format.wrapper) { + // Merges the current node with it's children of similar type to reduce the number of elements + if (!format.exact && childCount === 1) { + node = mergeStyles(node); + } + + // Remove/merge children + each(formatList, function (format) { + // Merge all children of similar type will move styles from child to parent + // this: text + // will become: text + each(dom.select(format.inline, node), function (child) { + if (!isElementNode(child)) { + return; + } + + removeFormat(format, vars, child, format.exact ? child : null); + }); + }); + + // Remove format if direct parent already has the same format + if (matchNode(node.parentNode, name, vars)) { + if (removeFormat(format, vars, node)) { + node = 0; + } + } + + // Remove format if any ancestor already has the same format + if (format.merge_with_parents) { + dom.getParent(node.parentNode, function (parent) { + if (matchNode(parent, name, vars)) { + if (removeFormat(format, vars, node)) { + node = 0; + } + return TRUE; + } + }); + } + + // fontSize defines the line height for the whole branch of nested style wrappers, + // therefore it should be set on the outermost wrapper + if (node && !isBlock(node) && !getStyle(node, 'fontSize')) { + var styleNode = matchNestedWrapper(node, hasStyle('fontSize')); + if (styleNode) { + apply('fontsize', { value: getStyle(styleNode, 'fontSize') }, node); + } + } + + // Merge next and previous siblings if they are similar texttext becomes texttext + if (node && format.merge_siblings !== false) { + node = mergeSiblings(getNonWhiteSpaceSibling(node), node); + node = mergeSiblings(node, getNonWhiteSpaceSibling(node, TRUE)); + } + } + }); + } + + if (getContentEditable(selection.getNode()) === "false") { + node = selection.getNode(); + for (var i = 0, l = formatList.length; i < l; i++) { + if (formatList[i].ceFalseOverride && dom.is(node, formatList[i].selector)) { + setElementFormat(node, formatList[i]); + return; + } + } + + return; + } + + if (format) { + if (node) { + if (node.nodeType) { + if (!applyNodeStyle(formatList, node)) { + rng = dom.createRng(); + rng.setStartBefore(node); + rng.setEndAfter(node); + applyRngStyle(expandRng(rng, formatList), null, true); + } + } else { + applyRngStyle(node, null, true); + } + } else { + if (!isCollapsed || !format.inline || dom.select('td[data-mce-selected],th[data-mce-selected]').length) { + // Obtain selection node before selection is unselected by applyRngStyle() + var curSelNode = ed.selection.getNode(); + + // If the formats have a default block and we can't find a parent block then + // start wrapping it with a DIV this is for forced_root_blocks: false + // It's kind of a hack but people should be using the default block type P since all desktop editors work that way + if (!forcedRootBlock && formatList[0].defaultBlock && !dom.getParent(curSelNode, dom.isBlock)) { + apply(formatList[0].defaultBlock); + } + + // Apply formatting to selection + ed.selection.setRng(adjustSelectionToVisibleSelection()); + bookmark = selection.getBookmark(); + applyRngStyle(expandRng(selection.getRng(TRUE), formatList), bookmark); + + if (format.styles) { + // Colored nodes should be underlined so that the color of the underline matches the text color. + if (format.styles.color || format.styles.textDecoration) { + walk(curSelNode, processUnderlineAndColor, 'childNodes'); + processUnderlineAndColor(curSelNode); + } + + // nodes with font-size should have their own background color as well to fit the line-height (see TINY-882) + if (format.styles.backgroundColor) { + processChildElements(curSelNode, + hasStyle('fontSize'), + applyStyle('backgroundColor', replaceVars(format.styles.backgroundColor, vars)) + ); + } + } + + selection.moveToBookmark(bookmark); + moveStart(selection.getRng(TRUE)); + ed.nodeChanged(); + } else { + performCaretAction('apply', name, vars); + } + } + + Hooks.postProcess(name, ed); + } + } + + /** + * Removes the specified format from the current selection or specified node. + * + * @method remove + * @param {String} name Name of format to remove. + * @param {Object} vars Optional list of variables to replace within format before removing it. + * @param {Node/Range} node Optional node or DOM range to remove the format from defaults to current selection. + */ + function remove(name, vars, node, similar) { + var formatList = get(name), format = formatList[0], bookmark, rng, contentEditable = true; + + // Merges the styles for each node + function process(node) { + var children, i, l, lastContentEditable, hasContentEditableState; + + // Node has a contentEditable value + if (node.nodeType === 1 && getContentEditable(node)) { + lastContentEditable = contentEditable; + contentEditable = getContentEditable(node) === "true"; + hasContentEditableState = true; // We don't want to wrap the container only it's children + } + + // Grab the children first since the nodelist might be changed + children = grep(node.childNodes); + + // Process current node + if (contentEditable && !hasContentEditableState) { + for (i = 0, l = formatList.length; i < l; i++) { + if (removeFormat(formatList[i], vars, node, node)) { + break; + } + } + } + + // Process the children + if (format.deep) { + if (children.length) { + for (i = 0, l = children.length; i < l; i++) { + process(children[i]); + } + + if (hasContentEditableState) { + contentEditable = lastContentEditable; // Restore last contentEditable state from stack + } + } + } + } + + function findFormatRoot(container) { + var formatRoot; + + // Find format root + each(getParents(container.parentNode).reverse(), function (parent) { + var format; + + // Find format root element + if (!formatRoot && parent.id != '_start' && parent.id != '_end') { + // Is the node matching the format we are looking for + format = matchNode(parent, name, vars, similar); + if (format && format.split !== false) { + formatRoot = parent; + } + } + }); + + return formatRoot; + } + + function wrapAndSplit(formatRoot, container, target, split) { + var parent, clone, lastClone, firstClone, i, formatRootParent; + + // Format root found then clone formats and split it + if (formatRoot) { + formatRootParent = formatRoot.parentNode; + + for (parent = container.parentNode; parent && parent != formatRootParent; parent = parent.parentNode) { + clone = dom.clone(parent, FALSE); + + for (i = 0; i < formatList.length; i++) { + if (removeFormat(formatList[i], vars, clone, clone)) { + clone = 0; + break; + } + } + + // Build wrapper node + if (clone) { + if (lastClone) { + clone.appendChild(lastClone); + } + + if (!firstClone) { + firstClone = clone; + } + + lastClone = clone; + } + } + + // Never split block elements if the format is mixed + if (split && (!format.mixed || !isBlock(formatRoot))) { + container = dom.split(formatRoot, container); + } + + // Wrap container in cloned formats + if (lastClone) { + target.parentNode.insertBefore(lastClone, target); + firstClone.appendChild(target); + } + } + + return container; + } + + function splitToFormatRoot(container) { + return wrapAndSplit(findFormatRoot(container), container, container, true); + } + + function unwrap(start) { + var node = dom.get(start ? '_start' : '_end'), + out = node[start ? 'firstChild' : 'lastChild']; + + // If the end is placed within the start the result will be removed + // So this checks if the out node is a bookmark node if it is it + // checks for another more suitable node + if (isBookmarkNode(out)) { + out = out[start ? 'firstChild' : 'lastChild']; + } + + // Since dom.remove removes empty text nodes then we need to try to find a better node + if (out.nodeType == 3 && out.data.length === 0) { + out = start ? node.previousSibling || node.nextSibling : node.nextSibling || node.previousSibling; + } + + dom.remove(node, true); + + return out; + } + + function removeRngStyle(rng) { + var startContainer, endContainer; + var commonAncestorContainer = rng.commonAncestorContainer; + + rng = expandRng(rng, formatList, TRUE); + + if (format.split) { + startContainer = getContainer(rng, TRUE); + endContainer = getContainer(rng); + + if (startContainer != endContainer) { + // WebKit will render the table incorrectly if we wrap a TH or TD in a SPAN + // so let's see if we can use the first child instead + // This will happen if you triple click a table cell and use remove formatting + if (/^(TR|TH|TD)$/.test(startContainer.nodeName) && startContainer.firstChild) { + if (startContainer.nodeName == "TR") { + startContainer = startContainer.firstChild.firstChild || startContainer; + } else { + startContainer = startContainer.firstChild || startContainer; + } + } + + // Try to adjust endContainer as well if cells on the same row were selected - bug #6410 + if (commonAncestorContainer && + /^T(HEAD|BODY|FOOT|R)$/.test(commonAncestorContainer.nodeName) && + isTableCell(endContainer) && endContainer.firstChild) { + endContainer = endContainer.firstChild || endContainer; + } + + if (dom.isChildOf(startContainer, endContainer) && !isBlock(endContainer) && + !isTableCell(startContainer) && !isTableCell(endContainer)) { + startContainer = wrap(startContainer, 'span', { id: '_start', 'data-mce-type': 'bookmark' }); + splitToFormatRoot(startContainer); + startContainer = unwrap(TRUE); + return; + } + + // Wrap start/end nodes in span element since these might be cloned/moved + startContainer = wrap(startContainer, 'span', { id: '_start', 'data-mce-type': 'bookmark' }); + endContainer = wrap(endContainer, 'span', { id: '_end', 'data-mce-type': 'bookmark' }); + + // Split start/end + splitToFormatRoot(startContainer); + splitToFormatRoot(endContainer); + + // Unwrap start/end to get real elements again + startContainer = unwrap(TRUE); + endContainer = unwrap(); + } else { + startContainer = endContainer = splitToFormatRoot(startContainer); + } + + // Update range positions since they might have changed after the split operations + rng.startContainer = startContainer.parentNode ? startContainer.parentNode : startContainer; + rng.startOffset = nodeIndex(startContainer); + rng.endContainer = endContainer.parentNode ? endContainer.parentNode : endContainer; + rng.endOffset = nodeIndex(endContainer) + 1; + } + + // Remove items between start/end + rangeUtils.walk(rng, function (nodes) { + each(nodes, function (node) { + process(node); + + // Remove parent span if it only contains text-decoration: underline, yet a parent node is also underlined. + if (node.nodeType === 1 && ed.dom.getStyle(node, 'text-decoration') === 'underline' && + node.parentNode && getTextDecoration(node.parentNode) === 'underline') { + removeFormat({ + 'deep': false, + 'exact': true, + 'inline': 'span', + 'styles': { + 'textDecoration': 'underline' + } + }, null, node); + } + }); + }); + } + + // Handle node + if (node) { + if (node.nodeType) { + rng = dom.createRng(); + rng.setStartBefore(node); + rng.setEndAfter(node); + removeRngStyle(rng); + } else { + removeRngStyle(node); + } + + return; + } + + if (getContentEditable(selection.getNode()) === "false") { + node = selection.getNode(); + for (var i = 0, l = formatList.length; i < l; i++) { + if (formatList[i].ceFalseOverride) { + if (removeFormat(formatList[i], vars, node, node)) { + break; + } + } + } + + return; + } + + if (!selection.isCollapsed() || !format.inline || dom.select('td[data-mce-selected],th[data-mce-selected]').length) { + bookmark = selection.getBookmark(); + removeRngStyle(selection.getRng(TRUE)); + selection.moveToBookmark(bookmark); + + // Check if start element still has formatting then we are at: "text|text" + // and need to move the start into the next text node + if (format.inline && match(name, vars, selection.getStart())) { + moveStart(selection.getRng(true)); + } + + ed.nodeChanged(); + } else { + performCaretAction('remove', name, vars, similar); + } + } + + /** + * Toggles the specified format on/off. + * + * @method toggle + * @param {String} name Name of format to apply/remove. + * @param {Object} vars Optional list of variables to replace within format before applying/removing it. + * @param {Node} node Optional node to apply the format to or remove from. Defaults to current selection. + */ + function toggle(name, vars, node) { + var fmt = get(name); + + if (match(name, vars, node) && (!('toggle' in fmt[0]) || fmt[0].toggle)) { + remove(name, vars, node); + } else { + apply(name, vars, node); + } + } + + /** + * Return true/false if the specified node has the specified format. + * + * @method matchNode + * @param {Node} node Node to check the format on. + * @param {String} name Format name to check. + * @param {Object} vars Optional list of variables to replace before checking it. + * @param {Boolean} similar Match format that has similar properties. + * @return {Object} Returns the format object it matches or undefined if it doesn't match. + */ + function matchNode(node, name, vars, similar) { + var formatList = get(name), format, i, classes; + + function matchItems(node, format, itemName) { + var key, value, items = format[itemName], i; + + // Custom match + if (format.onmatch) { + return format.onmatch(node, format, itemName); + } + + // Check all items + if (items) { + // Non indexed object + if (items.length === undef) { + for (key in items) { + if (items.hasOwnProperty(key)) { + if (itemName === 'attributes') { + value = dom.getAttrib(node, key); + } else { + value = getStyle(node, key); + } + + if (similar && !value && !format.exact) { + return; + } + + if ((!similar || format.exact) && !isEq(value, normalizeStyleValue(replaceVars(items[key], vars), key))) { + return; + } + } + } + } else { + // Only one match needed for indexed arrays + for (i = 0; i < items.length; i++) { + if (itemName === 'attributes' ? dom.getAttrib(node, items[i]) : getStyle(node, items[i])) { + return format; + } + } + } + } + + return format; + } + + if (formatList && node) { + // Check each format in list + for (i = 0; i < formatList.length; i++) { + format = formatList[i]; + + // Name name, attributes, styles and classes + if (matchName(node, format) && matchItems(node, format, 'attributes') && matchItems(node, format, 'styles')) { + // Match classes + if ((classes = format.classes)) { + for (i = 0; i < classes.length; i++) { + if (!dom.hasClass(node, classes[i])) { + return; + } + } + } + + return format; + } + } + } + } + + /** + * Matches the current selection or specified node against the specified format name. + * + * @method match + * @param {String} name Name of format to match. + * @param {Object} vars Optional list of variables to replace before checking it. + * @param {Node} node Optional node to check. + * @return {boolean} true/false if the specified selection/node matches the format. + */ + function match(name, vars, node) { + var startNode; + + function matchParents(node) { + var root = dom.getRoot(); + + if (node === root) { + return false; + } + + // Find first node with similar format settings + node = dom.getParent(node, function (node) { + if (matchesUnInheritedFormatSelector(node, name)) { + return true; + } + + return node.parentNode === root || !!matchNode(node, name, vars, true); + }); + + // Do an exact check on the similar format element + return matchNode(node, name, vars); + } + + // Check specified node + if (node) { + return matchParents(node); + } + + // Check selected node + node = selection.getNode(); + if (matchParents(node)) { + return TRUE; + } + + // Check start node if it's different + startNode = selection.getStart(); + if (startNode != node) { + if (matchParents(startNode)) { + return TRUE; + } + } + + return FALSE; + } + + /** + * Matches the current selection against the array of formats and returns a new array with matching formats. + * + * @method matchAll + * @param {Array} names Name of format to match. + * @param {Object} vars Optional list of variables to replace before checking it. + * @return {Array} Array with matched formats. + */ + function matchAll(names, vars) { + var startElement, matchedFormatNames = [], checkedMap = {}; + + // Check start of selection for formats + startElement = selection.getStart(); + dom.getParent(startElement, function (node) { + var i, name; + + for (i = 0; i < names.length; i++) { + name = names[i]; + + if (!checkedMap[name] && matchNode(node, name, vars)) { + checkedMap[name] = true; + matchedFormatNames.push(name); + } + } + }, dom.getRoot()); + + return matchedFormatNames; + } + + /** + * Returns true/false if the specified format can be applied to the current selection or not. It + * will currently only check the state for selector formats, it returns true on all other format types. + * + * @method canApply + * @param {String} name Name of format to check. + * @return {boolean} true/false if the specified format can be applied to the current selection/node. + */ + function canApply(name) { + var formatList = get(name), startNode, parents, i, x, selector; + + if (formatList) { + startNode = selection.getStart(); + parents = getParents(startNode); + + for (x = formatList.length - 1; x >= 0; x--) { + selector = formatList[x].selector; + + // Format is not selector based then always return TRUE + // Is it has a defaultBlock then it's likely it can be applied for example align on a non block element line + if (!selector || formatList[x].defaultBlock) { + return TRUE; + } + + for (i = parents.length - 1; i >= 0; i--) { + if (dom.is(parents[i], selector)) { + return TRUE; + } + } + } + } + + return FALSE; + } + + /** + * Executes the specified callback when the current selection matches the formats or not. + * + * @method formatChanged + * @param {String} formats Comma separated list of formats to check for. + * @param {function} callback Callback with state and args when the format is changed/toggled on/off. + * @param {Boolean} similar True/false state if the match should handle similar or exact formats. + */ + function formatChanged(formats, callback, similar) { + var currentFormats; + + // Setup format node change logic + if (!formatChangeData) { + formatChangeData = {}; + currentFormats = {}; + + ed.on('NodeChange', function (e) { + var parents = getParents(e.element), matchedFormats = {}; + + // Ignore bogus nodes like the tag created by moveStart() + parents = Tools.grep(parents, function (node) { + return node.nodeType == 1 && !node.getAttribute('data-mce-bogus'); + }); + + // Check for new formats + each(formatChangeData, function (callbacks, format) { + each(parents, function (node) { + if (matchNode(node, format, {}, callbacks.similar)) { + if (!currentFormats[format]) { + // Execute callbacks + each(callbacks, function (callback) { + callback(true, { node: node, format: format, parents: parents }); + }); + + currentFormats[format] = callbacks; + } + + matchedFormats[format] = callbacks; + return false; + } + + if (matchesUnInheritedFormatSelector(node, format)) { + return false; + } + }); + }); + + // Check if current formats still match + each(currentFormats, function (callbacks, format) { + if (!matchedFormats[format]) { + delete currentFormats[format]; + + each(callbacks, function (callback) { + callback(false, { node: e.element, format: format, parents: parents }); + }); + } + }); + }); + } + + // Add format listeners + each(formats.split(','), function (format) { + if (!formatChangeData[format]) { + formatChangeData[format] = []; + formatChangeData[format].similar = similar; + } + + formatChangeData[format].push(callback); + }); + + return this; + } + + /** + * Returns a preview css text for the specified format. + * + * @method getCssText + * @param {String/Object} format Format to generate preview css text for. + * @return {String} Css text for the specified format. + * @example + * var cssText1 = editor.formatter.getCssText('bold'); + * var cssText2 = editor.formatter.getCssText({inline: 'b'}); + */ + function getCssText(format) { + return Preview.getCssText(ed, format); + } + + // Expose to public + extend(this, { + get: get, + register: register, + unregister: unregister, + apply: apply, + remove: remove, + toggle: toggle, + match: match, + matchAll: matchAll, + matchNode: matchNode, + canApply: canApply, + formatChanged: formatChanged, + getCssText: getCssText + }); + + // Initialize + defaultFormats(); + addKeyboardShortcuts(); + ed.on('BeforeGetContent', function (e) { + if (markCaretContainersBogus && e.format != 'raw') { + markCaretContainersBogus(); + } + }); + ed.on('mouseup keydown', function (e) { + if (disableCaretContainer) { + disableCaretContainer(e); + } + }); + + // Private functions + + /** + * Checks if the specified nodes name matches the format inline/block or selector. + * + * @private + * @param {Node} node Node to match against the specified format. + * @param {Object} format Format object o match with. + * @return {boolean} true/false if the format matches. + */ + function matchName(node, format) { + // Check for inline match + if (isEq(node, format.inline)) { + return TRUE; + } + + // Check for block match + if (isEq(node, format.block)) { + return TRUE; + } + + // Check for selector match + if (format.selector) { + return node.nodeType == 1 && dom.is(node, format.selector); + } + } + + /** + * Compares two string/nodes regardless of their case. + * + * @private + * @param {String/Node} str1 Node or string to compare. + * @param {String/Node} str2 Node or string to compare. + * @return {boolean} True/false if they match. + */ + function isEq(str1, str2) { + str1 = str1 || ''; + str2 = str2 || ''; + + str1 = '' + (str1.nodeName || str1); + str2 = '' + (str2.nodeName || str2); + + return str1.toLowerCase() == str2.toLowerCase(); + } + + function processChildElements(node, filter, process) { + each(node.childNodes, function (node) { + if (isElementNode(node)) { + if (filter(node)) { + process(node); + } + if (node.hasChildNodes()) { + processChildElements(node, filter, process); + } + } + }); + } + + function isElementNode(node) { + return node && node.nodeType === 1 && !isBookmarkNode(node) && !isCaretNode(node) && !NodeType.isBogus(node); + } + + function hasStyle(name) { + return Fun.curry(function (name, node) { + return !!(node && getStyle(node, name)); + }, name); + } + + function applyStyle(name, value) { + return Fun.curry(function (name, value, node) { + dom.setStyle(node, name, value); + }, name, value); + } + + /** + * Returns the style by name on the specified node. This method modifies the style + * contents to make it more easy to match. This will resolve a few browser issues. + * + * @private + * @param {Node} node to get style from. + * @param {String} name Style name to get. + * @return {String} Style item value. + */ + function getStyle(node, name) { + return normalizeStyleValue(dom.getStyle(node, name), name); + } + + /** + * Normalize style value by name. This method modifies the style contents + * to make it more easy to match. This will resolve a few browser issues. + * + * @private + * @param {String} value Value to get style from. + * @param {String} name Style name to get. + * @return {String} Style item value. + */ + function normalizeStyleValue(value, name) { + // Force the format to hex + if (name == 'color' || name == 'backgroundColor') { + value = dom.toHex(value); + } + + // Opera will return bold as 700 + if (name == 'fontWeight' && value == 700) { + value = 'bold'; + } + + // Normalize fontFamily so "'Font name', Font" becomes: "Font name,Font" + if (name == 'fontFamily') { + value = value.replace(/[\'\"]/g, '').replace(/,\s+/g, ','); + } + + return '' + value; + } + + /** + * Replaces variables in the value. The variable format is %var. + * + * @private + * @param {String} value Value to replace variables in. + * @param {Object} vars Name/value array with variables to replace. + * @return {String} New value with replaced variables. + */ + function replaceVars(value, vars) { + if (typeof value != "string") { + value = value(vars); + } else if (vars) { + value = value.replace(/%(\w+)/g, function (str, name) { + return vars[name] || str; + }); + } + + return value; + } + + function isWhiteSpaceNode(node) { + return node && node.nodeType === 3 && /^([\t \r\n]+|)$/.test(node.nodeValue); + } + + function wrap(node, name, attrs) { + var wrapper = dom.create(name, attrs); + + node.parentNode.insertBefore(wrapper, node); + wrapper.appendChild(node); + + return wrapper; + } + + /** + * Expands the specified range like object to depending on format. + * + * For example on block formats it will move the start/end position + * to the beginning of the current block. + * + * @private + * @param {Object} rng Range like object. + * @param {Array} format Array with formats to expand by. + * @param {Boolean} remove + * @return {Object} Expanded range like object. + */ + function expandRng(rng, format, remove) { + var lastIdx, leaf, endPoint, + startContainer = rng.startContainer, + startOffset = rng.startOffset, + endContainer = rng.endContainer, + endOffset = rng.endOffset; + + // This function walks up the tree if there is no siblings before/after the node + function findParentContainer(start) { + var container, parent, sibling, siblingName, root; + + container = parent = start ? startContainer : endContainer; + siblingName = start ? 'previousSibling' : 'nextSibling'; + root = dom.getRoot(); + + function isBogusBr(node) { + return node.nodeName == "BR" && node.getAttribute('data-mce-bogus') && !node.nextSibling; + } + + // If it's a text node and the offset is inside the text + if (container.nodeType == 3 && !isWhiteSpaceNode(container)) { + if (start ? startOffset > 0 : endOffset < container.nodeValue.length) { + return container; + } + } + + /*eslint no-constant-condition:0 */ + while (true) { + // Stop expanding on block elements + if (!format[0].block_expand && isBlock(parent)) { + return parent; + } + + // Walk left/right + for (sibling = parent[siblingName]; sibling; sibling = sibling[siblingName]) { + if (!isBookmarkNode(sibling) && !isWhiteSpaceNode(sibling) && !isBogusBr(sibling)) { + return parent; + } + } + + // Check if we can move up are we at root level or body level + if (parent == root || parent.parentNode == root) { + container = parent; + break; + } + + parent = parent.parentNode; + } + + return container; + } + + // This function walks down the tree to find the leaf at the selection. + // The offset is also returned as if node initially a leaf, the offset may be in the middle of the text node. + function findLeaf(node, offset) { + if (offset === undef) { + offset = node.nodeType === 3 ? node.length : node.childNodes.length; + } + + while (node && node.hasChildNodes()) { + node = node.childNodes[offset]; + if (node) { + offset = node.nodeType === 3 ? node.length : node.childNodes.length; + } + } + return { node: node, offset: offset }; + } + + // If index based start position then resolve it + if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) { + lastIdx = startContainer.childNodes.length - 1; + startContainer = startContainer.childNodes[startOffset > lastIdx ? lastIdx : startOffset]; + + if (startContainer.nodeType == 3) { + startOffset = 0; + } + } + + // If index based end position then resolve it + if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) { + lastIdx = endContainer.childNodes.length - 1; + endContainer = endContainer.childNodes[endOffset > lastIdx ? lastIdx : endOffset - 1]; + + if (endContainer.nodeType == 3) { + endOffset = endContainer.nodeValue.length; + } + } + + // Expands the node to the closes contentEditable false element if it exists + function findParentContentEditable(node) { + var parent = node; + + while (parent) { + if (parent.nodeType === 1 && getContentEditable(parent)) { + return getContentEditable(parent) === "false" ? parent : node; + } + + parent = parent.parentNode; + } + + return node; + } + + function findWordEndPoint(container, offset, start) { + var walker, node, pos, lastTextNode; + + function findSpace(node, offset) { + var pos, pos2, str = node.nodeValue; + + if (typeof offset == "undefined") { + offset = start ? str.length : 0; + } + + if (start) { + pos = str.lastIndexOf(' ', offset); + pos2 = str.lastIndexOf('\u00a0', offset); + pos = pos > pos2 ? pos : pos2; + + // Include the space on remove to avoid tag soup + if (pos !== -1 && !remove) { + pos++; + } + } else { + pos = str.indexOf(' ', offset); + pos2 = str.indexOf('\u00a0', offset); + pos = pos !== -1 && (pos2 === -1 || pos < pos2) ? pos : pos2; + } + + return pos; + } + + if (container.nodeType === 3) { + pos = findSpace(container, offset); + + if (pos !== -1) { + return { container: container, offset: pos }; + } + + lastTextNode = container; + } + + // Walk the nodes inside the block + walker = new TreeWalker(container, dom.getParent(container, isBlock) || ed.getBody()); + while ((node = walker[start ? 'prev' : 'next']())) { + if (node.nodeType === 3) { + lastTextNode = node; + pos = findSpace(node); + + if (pos !== -1) { + return { container: node, offset: pos }; + } + } else if (isBlock(node)) { + break; + } + } + + if (lastTextNode) { + if (start) { + offset = 0; + } else { + offset = lastTextNode.length; + } + + return { container: lastTextNode, offset: offset }; + } + } + + function findSelectorEndPoint(container, siblingName) { + var parents, i, y, curFormat; + + if (container.nodeType == 3 && container.nodeValue.length === 0 && container[siblingName]) { + container = container[siblingName]; + } + + parents = getParents(container); + for (i = 0; i < parents.length; i++) { + for (y = 0; y < format.length; y++) { + curFormat = format[y]; + + // If collapsed state is set then skip formats that doesn't match that + if ("collapsed" in curFormat && curFormat.collapsed !== rng.collapsed) { + continue; + } + + if (dom.is(parents[i], curFormat.selector)) { + return parents[i]; + } + } + } + + return container; + } + + function findBlockEndPoint(container, siblingName) { + var node, root = dom.getRoot(); + + // Expand to block of similar type + if (!format[0].wrapper) { + node = dom.getParent(container, format[0].block, root); + } + + // Expand to first wrappable block element or any block element + if (!node) { + node = dom.getParent(container.nodeType == 3 ? container.parentNode : container, function (node) { + // Fixes #6183 where it would expand to editable parent element in inline mode + return node != root && isTextBlock(node); + }); + } + + // Exclude inner lists from wrapping + if (node && format[0].wrapper) { + node = getParents(node, 'ul,ol').reverse()[0] || node; + } + + // Didn't find a block element look for first/last wrappable element + if (!node) { + node = container; + + while (node[siblingName] && !isBlock(node[siblingName])) { + node = node[siblingName]; + + // Break on BR but include it will be removed later on + // we can't remove it now since we need to check if it can be wrapped + if (isEq(node, 'br')) { + break; + } + } + } + + return node || container; + } + + // Expand to closest contentEditable element + startContainer = findParentContentEditable(startContainer); + endContainer = findParentContentEditable(endContainer); + + // Exclude bookmark nodes if possible + if (isBookmarkNode(startContainer.parentNode) || isBookmarkNode(startContainer)) { + startContainer = isBookmarkNode(startContainer) ? startContainer : startContainer.parentNode; + startContainer = startContainer.nextSibling || startContainer; + + if (startContainer.nodeType == 3) { + startOffset = 0; + } + } + + if (isBookmarkNode(endContainer.parentNode) || isBookmarkNode(endContainer)) { + endContainer = isBookmarkNode(endContainer) ? endContainer : endContainer.parentNode; + endContainer = endContainer.previousSibling || endContainer; + + if (endContainer.nodeType == 3) { + endOffset = endContainer.length; + } + } + + if (format[0].inline) { + if (rng.collapsed) { + // Expand left to closest word boundary + endPoint = findWordEndPoint(startContainer, startOffset, true); + if (endPoint) { + startContainer = endPoint.container; + startOffset = endPoint.offset; + } + + // Expand right to closest word boundary + endPoint = findWordEndPoint(endContainer, endOffset); + if (endPoint) { + endContainer = endPoint.container; + endOffset = endPoint.offset; + } + } + + // Avoid applying formatting to a trailing space. + leaf = findLeaf(endContainer, endOffset); + if (leaf.node) { + while (leaf.node && leaf.offset === 0 && leaf.node.previousSibling) { + leaf = findLeaf(leaf.node.previousSibling); + } + + if (leaf.node && leaf.offset > 0 && leaf.node.nodeType === 3 && + leaf.node.nodeValue.charAt(leaf.offset - 1) === ' ') { + + if (leaf.offset > 1) { + endContainer = leaf.node; + endContainer.splitText(leaf.offset - 1); + } + } + } + } + + // Move start/end point up the tree if the leaves are sharp and if we are in different containers + // Example * becomes !: !*texttext*
! + // This will reduce the number of wrapper elements that needs to be created + // Move start point up the tree + if (format[0].inline || format[0].block_expand) { + if (!format[0].inline || (startContainer.nodeType != 3 || startOffset === 0)) { + startContainer = findParentContainer(true); + } + + if (!format[0].inline || (endContainer.nodeType != 3 || endOffset === endContainer.nodeValue.length)) { + endContainer = findParentContainer(); + } + } + + // Expand start/end container to matching selector + if (format[0].selector && format[0].expand !== FALSE && !format[0].inline) { + // Find new startContainer/endContainer if there is better one + startContainer = findSelectorEndPoint(startContainer, 'previousSibling'); + endContainer = findSelectorEndPoint(endContainer, 'nextSibling'); + } + + // Expand start/end container to matching block element or text node + if (format[0].block || format[0].selector) { + // Find new startContainer/endContainer if there is better one + startContainer = findBlockEndPoint(startContainer, 'previousSibling'); + endContainer = findBlockEndPoint(endContainer, 'nextSibling'); + + // Non block element then try to expand up the leaf + if (format[0].block) { + if (!isBlock(startContainer)) { + startContainer = findParentContainer(true); + } + + if (!isBlock(endContainer)) { + endContainer = findParentContainer(); + } + } + } + + // Setup index for startContainer + if (startContainer.nodeType == 1) { + startOffset = nodeIndex(startContainer); + startContainer = startContainer.parentNode; + } + + // Setup index for endContainer + if (endContainer.nodeType == 1) { + endOffset = nodeIndex(endContainer) + 1; + endContainer = endContainer.parentNode; + } + + // Return new range like object + return { + startContainer: startContainer, + startOffset: startOffset, + endContainer: endContainer, + endOffset: endOffset + }; + } + + function isColorFormatAndAnchor(node, format) { + return format.links && node.tagName == 'A'; + } + + /** + * Removes the specified format for the specified node. It will also remove the node if it doesn't have + * any attributes if the format specifies it to do so. + * + * @private + * @param {Object} format Format object with items to remove from node. + * @param {Object} vars Name/value object with variables to apply to format. + * @param {Node} node Node to remove the format styles on. + * @param {Node} compareNode Optional compare node, if specified the styles will be compared to that node. + * @return {Boolean} True/false if the node was removed or not. + */ + function removeFormat(format, vars, node, compareNode) { + var i, attrs, stylesModified; + + // Check if node matches format + if (!matchName(node, format) && !isColorFormatAndAnchor(node, format)) { + return FALSE; + } + + // Should we compare with format attribs and styles + if (format.remove != 'all') { + // Remove styles + each(format.styles, function (value, name) { + value = normalizeStyleValue(replaceVars(value, vars), name); + + // Indexed array + if (typeof name === 'number') { + name = value; + compareNode = 0; + } + + if (format.remove_similar || (!compareNode || isEq(getStyle(compareNode, name), value))) { + dom.setStyle(node, name, ''); + } + + stylesModified = 1; + }); + + // Remove style attribute if it's empty + if (stylesModified && dom.getAttrib(node, 'style') === '') { + node.removeAttribute('style'); + node.removeAttribute('data-mce-style'); + } + + // Remove attributes + each(format.attributes, function (value, name) { + var valueOut; + + value = replaceVars(value, vars); + + // Indexed array + if (typeof name === 'number') { + name = value; + compareNode = 0; + } + + if (!compareNode || isEq(dom.getAttrib(compareNode, name), value)) { + // Keep internal classes + if (name == 'class') { + value = dom.getAttrib(node, name); + if (value) { + // Build new class value where everything is removed except the internal prefixed classes + valueOut = ''; + each(value.split(/\s+/), function (cls) { + if (/mce\-\w+/.test(cls)) { + valueOut += (valueOut ? ' ' : '') + cls; + } + }); + + // We got some internal classes left + if (valueOut) { + dom.setAttrib(node, name, valueOut); + return; + } + } + } + + // IE6 has a bug where the attribute doesn't get removed correctly + if (name == "class") { + node.removeAttribute('className'); + } + + // Remove mce prefixed attributes + if (MCE_ATTR_RE.test(name)) { + node.removeAttribute('data-mce-' + name); + } + + node.removeAttribute(name); + } + }); + + // Remove classes + each(format.classes, function (value) { + value = replaceVars(value, vars); + + if (!compareNode || dom.hasClass(compareNode, value)) { + dom.removeClass(node, value); + } + }); + + // Check for non internal attributes + attrs = dom.getAttribs(node); + for (i = 0; i < attrs.length; i++) { + var attrName = attrs[i].nodeName; + if (attrName.indexOf('_') !== 0 && attrName.indexOf('data-') !== 0) { + return FALSE; + } + } + } + + // Remove the inline child if it's empty for example or + if (format.remove != 'none') { + removeNode(node, format); + return TRUE; + } + } + + /** + * Removes the node and wrap it's children in paragraphs before doing so or + * appends BR elements to the beginning/end of the block element if forcedRootBlocks is disabled. + * + * If the div in the node below gets removed: + * text|
+ formatNode.parentNode.replaceChild(caretContainer, formatNode); + } else { + // Insert caret container after the formatted node + dom.insertAfter(caretContainer, formatNode); + } + + // Move selection to text node + selection.setCursorLocation(node, 1); + + // If the formatNode is empty, we can remove it safely. + if (dom.isEmpty(formatNode)) { + dom.remove(formatNode); + } + } + } + + // Checks if the parent caret container node isn't empty if that is the case it + // will remove the bogus state on all children that isn't empty + function unmarkBogusCaretParents() { + var caretContainer; + + caretContainer = getParentCaretContainer(selection.getStart()); + if (caretContainer && !dom.isEmpty(caretContainer)) { + walk(caretContainer, function (node) { + if (node.nodeType == 1 && node.id !== caretContainerId && !dom.isEmpty(node)) { + dom.setAttrib(node, 'data-mce-bogus', null); + } + }, 'childNodes'); + } + } + + // Only bind the caret events once + if (!ed._hasCaretEvents) { + // Mark current caret container elements as bogus when getting the contents so we don't end up with empty elements + markCaretContainersBogus = function () { + var nodes = [], i; + + if (isCaretContainerEmpty(getParentCaretContainer(selection.getStart()), nodes)) { + // Mark children + i = nodes.length; + while (i--) { + dom.setAttrib(nodes[i], 'data-mce-bogus', '1'); + } + } + }; + + disableCaretContainer = function (e) { + var keyCode = e.keyCode; + + removeCaretContainer(); + + // Remove caret container if it's empty + if (keyCode == 8 && selection.isCollapsed() && selection.getStart().innerHTML == INVISIBLE_CHAR) { + removeCaretContainer(getParentCaretContainer(selection.getStart())); + } + + // Remove caret container on keydown and it's left/right arrow keys + if (keyCode == 37 || keyCode == 39) { + removeCaretContainer(getParentCaretContainer(selection.getStart())); + } + + unmarkBogusCaretParents(); + }; + + // Remove bogus state if they got filled by contents using editor.selection.setContent + ed.on('SetContent', function (e) { + if (e.selection) { + unmarkBogusCaretParents(); + } + }); + ed._hasCaretEvents = true; + } + + // Do apply or remove caret format + if (type == "apply") { + applyCaretFormat(); + } else { + removeCaretFormat(); + } + } + + /** + * Moves the start to the first suitable text node. + */ + function moveStart(rng) { + var container = rng.startContainer, + offset = rng.startOffset, isAtEndOfText, + walker, node, nodes, tmpNode; + + if (rng.startContainer == rng.endContainer) { + if (isInlineBlock(rng.startContainer.childNodes[rng.startOffset])) { + return; + } + } + + // Convert text node into index if possible + if (container.nodeType == 3 && offset >= container.nodeValue.length) { + // Get the parent container location and walk from there + offset = nodeIndex(container); + container = container.parentNode; + isAtEndOfText = true; + } + + // Move startContainer/startOffset in to a suitable node + if (container.nodeType == 1) { + nodes = container.childNodes; + container = nodes[Math.min(offset, nodes.length - 1)]; + walker = new TreeWalker(container, dom.getParent(container, dom.isBlock)); + + // If offset is at end of the parent node walk to the next one + if (offset > nodes.length - 1 || isAtEndOfText) { + walker.next(); + } + + for (node = walker.current(); node; node = walker.next()) { + if (node.nodeType == 3 && !isWhiteSpaceNode(node)) { + // IE has a "neat" feature where it moves the start node into the closest element + // we can avoid this by inserting an element before it and then remove it after we set the selection + tmpNode = dom.create('a', { 'data-mce-bogus': 'all' }, INVISIBLE_CHAR); + node.parentNode.insertBefore(tmpNode, node); + + // Set selection and remove tmpNode + rng.setStart(node, 0); + selection.setRng(rng); + dom.remove(tmpNode); + + return; + } + } + } + } + }; + } +); /** * Diff.js * * Released under LGPL License. - * Copyright (c) 1999-2016 Ephox Corp. All rights reserved + * Copyright (c) 1999-2017 Ephox Corp. All rights reserved * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing @@ -21783,150 +22797,151 @@ define("tinymce/Formatter", [ * @class tinymce.undo.Diff * @private */ -define("tinymce/undo/Diff", [ -], function () { - var KEEP = 0, INSERT = 1, DELETE = 2; +define( + 'tinymce.core.undo.Diff', + [ + ], + function () { + var KEEP = 0, INSERT = 1, DELETE = 2; - var diff = function (left, right) { - var size = left.length + right.length + 2; - var vDown = new Array(size); - var vUp = new Array(size); + var diff = function (left, right) { + var size = left.length + right.length + 2; + var vDown = new Array(size); + var vUp = new Array(size); - var snake = function (start, end, diag) { - return { - start: start, - end: end, - diag: diag - }; - }; + var snake = function (start, end, diag) { + return { + start: start, + end: end, + diag: diag + }; + }; - var buildScript = function (start1, end1, start2, end2, script) { - var middle = getMiddleSnake(start1, end1, start2, end2); + var buildScript = function (start1, end1, start2, end2, script) { + var middle = getMiddleSnake(start1, end1, start2, end2); - if (middle === null || middle.start === end1 && middle.diag === end1 - end2 || - middle.end === start1 && middle.diag === start1 - start2) { - var i = start1; - var j = start2; - while (i < end1 || j < end2) { - if (i < end1 && j < end2 && left[i] === right[j]) { - script.push([KEEP, left[i]]); - ++i; - ++j; - } else { - if (end1 - start1 > end2 - start2) { - script.push([DELETE, left[i]]); - ++i; - } else { - script.push([INSERT, right[j]]); - ++j; - } - } - } - } else { - buildScript(start1, middle.start, start2, middle.start - middle.diag, script); - for (var i2 = middle.start; i2 < middle.end; ++i2) { - script.push([KEEP, left[i2]]); - } - buildScript(middle.end, end1, middle.end - middle.diag, end2, script); - } - }; + if (middle === null || middle.start === end1 && middle.diag === end1 - end2 || + middle.end === start1 && middle.diag === start1 - start2) { + var i = start1; + var j = start2; + while (i < end1 || j < end2) { + if (i < end1 && j < end2 && left[i] === right[j]) { + script.push([KEEP, left[i]]); + ++i; + ++j; + } else { + if (end1 - start1 > end2 - start2) { + script.push([DELETE, left[i]]); + ++i; + } else { + script.push([INSERT, right[j]]); + ++j; + } + } + } + } else { + buildScript(start1, middle.start, start2, middle.start - middle.diag, script); + for (var i2 = middle.start; i2 < middle.end; ++i2) { + script.push([KEEP, left[i2]]); + } + buildScript(middle.end, end1, middle.end - middle.diag, end2, script); + } + }; - var buildSnake = function (start, diag, end1, end2) { - var end = start; - while (end - diag < end2 && end < end1 && left[end] === right[end - diag]) { - ++end; - } - return snake(start, end, diag); - }; + var buildSnake = function (start, diag, end1, end2) { + var end = start; + while (end - diag < end2 && end < end1 && left[end] === right[end - diag]) { + ++end; + } + return snake(start, end, diag); + }; - var getMiddleSnake = function (start1, end1, start2, end2) { - // Myers Algorithm - // Initialisations - var m = end1 - start1; - var n = end2 - start2; - if (m === 0 || n === 0) { - return null; - } + var getMiddleSnake = function (start1, end1, start2, end2) { + // Myers Algorithm + // Initialisations + var m = end1 - start1; + var n = end2 - start2; + if (m === 0 || n === 0) { + return null; + } - var delta = m - n; - var sum = n + m; - var offset = (sum % 2 === 0 ? sum : sum + 1) / 2; - vDown[1 + offset] = start1; - vUp[1 + offset] = end1 + 1; + var delta = m - n; + var sum = n + m; + var offset = (sum % 2 === 0 ? sum : sum + 1) / 2; + vDown[1 + offset] = start1; + vUp[1 + offset] = end1 + 1; - for (var d = 0; d <= offset; ++d) { - // Down - for (var k = -d; k <= d; k += 2) { - // First step + for (var d = 0; d <= offset; ++d) { + // Down + for (var k = -d; k <= d; k += 2) { + // First step - var i = k + offset; - if (k === -d || k != d && vDown[i - 1] < vDown[i + 1]) { - vDown[i] = vDown[i + 1]; - } else { - vDown[i] = vDown[i - 1] + 1; - } + var i = k + offset; + if (k === -d || k != d && vDown[i - 1] < vDown[i + 1]) { + vDown[i] = vDown[i + 1]; + } else { + vDown[i] = vDown[i - 1] + 1; + } - var x = vDown[i]; - var y = x - start1 + start2 - k; + var x = vDown[i]; + var y = x - start1 + start2 - k; - while (x < end1 && y < end2 && left[x] === right[y]) { - vDown[i] = ++x; - ++y; - } - // Second step - if (delta % 2 != 0 && delta - d <= k && k <= delta + d) { - if (vUp[i - delta] <= vDown[i]) { - return buildSnake(vUp[i - delta], k + start1 - start2, end1, end2); - } - } - } + while (x < end1 && y < end2 && left[x] === right[y]) { + vDown[i] = ++x; + ++y; + } + // Second step + if (delta % 2 != 0 && delta - d <= k && k <= delta + d) { + if (vUp[i - delta] <= vDown[i]) { + return buildSnake(vUp[i - delta], k + start1 - start2, end1, end2); + } + } + } - // Up - for (k = delta - d; k <= delta + d; k += 2) { - // First step - i = k + offset - delta; - if (k === delta - d || k != delta + d && vUp[i + 1] <= vUp[i - 1]) { - vUp[i] = vUp[i + 1] - 1; - } else { - vUp[i] = vUp[i - 1]; - } + // Up + for (k = delta - d; k <= delta + d; k += 2) { + // First step + i = k + offset - delta; + if (k === delta - d || k != delta + d && vUp[i + 1] <= vUp[i - 1]) { + vUp[i] = vUp[i + 1] - 1; + } else { + vUp[i] = vUp[i - 1]; + } - x = vUp[i] - 1; - y = x - start1 + start2 - k; - while (x >= start1 && y >= start2 && left[x] === right[y]) { - vUp[i] = x--; - y--; - } - // Second step - if (delta % 2 === 0 && -d <= k && k <= d) { - if (vUp[i] <= vDown[i + delta]) { - return buildSnake(vUp[i], k + start1 - start2, end1, end2); - } - } - } - } - }; + x = vUp[i] - 1; + y = x - start1 + start2 - k; + while (x >= start1 && y >= start2 && left[x] === right[y]) { + vUp[i] = x--; + y--; + } + // Second step + if (delta % 2 === 0 && -d <= k && k <= d) { + if (vUp[i] <= vDown[i + delta]) { + return buildSnake(vUp[i], k + start1 - start2, end1, end2); + } + } + } + } + }; - var script = []; - buildScript(0, left.length, 0, right.length, script); - return script; - }; - - return { - KEEP: KEEP, - DELETE: DELETE, - INSERT: INSERT, - diff: diff - }; -}); - -// Included from: js/tinymce/classes/undo/Fragments.js + var script = []; + buildScript(0, left.length, 0, right.length, script); + return script; + }; + return { + KEEP: KEEP, + DELETE: DELETE, + INSERT: INSERT, + diff: diff + }; + } +); /** * Fragments.js * * Released under LGPL License. - * Copyright (c) 1999-2016 Ephox Corp. All rights reserved + * Copyright (c) 1999-2017 Ephox Corp. All rights reserved * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing @@ -21938,94 +22953,95 @@ define("tinymce/undo/Diff", [ * @class tinymce.undo.Fragments * @private */ -define("tinymce/undo/Fragments", [ - "tinymce/util/Arr", - "tinymce/html/Entities", - "tinymce/undo/Diff" -], function (Arr, Entities, Diff) { - var getOuterHtml = function (elm) { - if (elm.nodeType === 1) { - return elm.outerHTML; - } else if (elm.nodeType === 3) { - return Entities.encodeRaw(elm.data, false); - } else if (elm.nodeType === 8) { - return ''; - } +define( + 'tinymce.core.undo.Fragments', + [ + "tinymce.core.util.Arr", + "tinymce.core.html.Entities", + "tinymce.core.undo.Diff" + ], + function (Arr, Entities, Diff) { + var getOuterHtml = function (elm) { + if (elm.nodeType === 1) { + return elm.outerHTML; + } else if (elm.nodeType === 3) { + return Entities.encodeRaw(elm.data, false); + } else if (elm.nodeType === 8) { + return ''; + } - return ''; - }; + return ''; + }; - var createFragment = function(html) { - var frag, node, container; + var createFragment = function (html) { + var frag, node, container; - container = document.createElement("div"); - frag = document.createDocumentFragment(); + container = document.createElement("div"); + frag = document.createDocumentFragment(); - if (html) { - container.innerHTML = html; - } + if (html) { + container.innerHTML = html; + } - while ((node = container.firstChild)) { - frag.appendChild(node); - } + while ((node = container.firstChild)) { + frag.appendChild(node); + } - return frag; - }; + return frag; + }; - var insertAt = function (elm, html, index) { - var fragment = createFragment(html); - if (elm.hasChildNodes() && index < elm.childNodes.length) { - var target = elm.childNodes[index]; - target.parentNode.insertBefore(fragment, target); - } else { - elm.appendChild(fragment); - } - }; + var insertAt = function (elm, html, index) { + var fragment = createFragment(html); + if (elm.hasChildNodes() && index < elm.childNodes.length) { + var target = elm.childNodes[index]; + target.parentNode.insertBefore(fragment, target); + } else { + elm.appendChild(fragment); + } + }; - var removeAt = function (elm, index) { - if (elm.hasChildNodes() && index < elm.childNodes.length) { - var target = elm.childNodes[index]; - target.parentNode.removeChild(target); - } - }; + var removeAt = function (elm, index) { + if (elm.hasChildNodes() && index < elm.childNodes.length) { + var target = elm.childNodes[index]; + target.parentNode.removeChild(target); + } + }; - var applyDiff = function (diff, elm) { - var index = 0; - Arr.each(diff, function (action) { - if (action[0] === Diff.KEEP) { - index++; - } else if (action[0] === Diff.INSERT) { - insertAt(elm, action[1], index); - index++; - } else if (action[0] === Diff.DELETE) { - removeAt(elm, index); - } - }); - }; + var applyDiff = function (diff, elm) { + var index = 0; + Arr.each(diff, function (action) { + if (action[0] === Diff.KEEP) { + index++; + } else if (action[0] === Diff.INSERT) { + insertAt(elm, action[1], index); + index++; + } else if (action[0] === Diff.DELETE) { + removeAt(elm, index); + } + }); + }; - var read = function (elm) { - return Arr.map(elm.childNodes, getOuterHtml); - }; + var read = function (elm) { + return Arr.map(elm.childNodes, getOuterHtml); + }; - var write = function (fragments, elm) { - var currentFragments = Arr.map(elm.childNodes, getOuterHtml); - applyDiff(Diff.diff(currentFragments, fragments), elm); - return elm; - }; - - return { - read: read, - write: write - }; -}); - -// Included from: js/tinymce/classes/undo/Levels.js + var write = function (fragments, elm) { + var currentFragments = Arr.map(elm.childNodes, getOuterHtml); + applyDiff(Diff.diff(currentFragments, fragments), elm); + return elm; + }; + return { + read: read, + write: write + }; + } +); /** * Levels.js * * Released under LGPL License. - * Copyright (c) 1999-2016 Ephox Corp. All rights reserved + * Copyright (c) 1999-2017 Ephox Corp. All rights reserved * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing @@ -22037,80 +23053,81 @@ define("tinymce/undo/Fragments", [ * @class tinymce.undo.Levels * @private */ -define("tinymce/undo/Levels", [ - "tinymce/util/Arr", - "tinymce/undo/Fragments" -], function (Arr, Fragments) { - var hasIframes = function (html) { - return html.indexOf('') !== -1; - }; +define( + 'tinymce.core.undo.Levels', + [ + "tinymce.core.util.Arr", + "tinymce.core.undo.Fragments" + ], + function (Arr, Fragments) { + var hasIframes = function (html) { + return html.indexOf('') !== -1; + }; - var createFragmentedLevel = function (fragments) { - return { - type: 'fragmented', - fragments: fragments, - content: '', - bookmark: null, - beforeBookmark: null - }; - }; + var createFragmentedLevel = function (fragments) { + return { + type: 'fragmented', + fragments: fragments, + content: '', + bookmark: null, + beforeBookmark: null + }; + }; - var createCompleteLevel = function (content) { - return { - type: 'complete', - fragments: null, - content: content, - bookmark: null, - beforeBookmark: null - }; - }; + var createCompleteLevel = function (content) { + return { + type: 'complete', + fragments: null, + content: content, + bookmark: null, + beforeBookmark: null + }; + }; - var createFromEditor = function (editor) { - var fragments, content, trimmedFragments; + var createFromEditor = function (editor) { + var fragments, content, trimmedFragments; - fragments = Fragments.read(editor.getBody()); - trimmedFragments = Arr.map(fragments, function (html) { - return editor.serializer.trimContent(html); - }); - content = trimmedFragments.join(''); + fragments = Fragments.read(editor.getBody()); + trimmedFragments = Arr.map(fragments, function (html) { + return editor.serializer.trimContent(html); + }); + content = trimmedFragments.join(''); - return hasIframes(content) ? createFragmentedLevel(trimmedFragments) : createCompleteLevel(content); - }; + return hasIframes(content) ? createFragmentedLevel(trimmedFragments) : createCompleteLevel(content); + }; - var applyToEditor = function (editor, level, before) { - if (level.type === 'fragmented') { - Fragments.write(level.fragments, editor.getBody()); - } else { - editor.setContent(level.content, {format: 'raw'}); - } + var applyToEditor = function (editor, level, before) { + if (level.type === 'fragmented') { + Fragments.write(level.fragments, editor.getBody()); + } else { + editor.setContent(level.content, { format: 'raw' }); + } - editor.selection.moveToBookmark(before ? level.beforeBookmark : level.bookmark); - }; + editor.selection.moveToBookmark(before ? level.beforeBookmark : level.bookmark); + }; - var getLevelContent = function (level) { - return level.type === 'fragmented' ? level.fragments.join('') : level.content; - }; + var getLevelContent = function (level) { + return level.type === 'fragmented' ? level.fragments.join('') : level.content; + }; - var isEq = function (level1, level2) { - return getLevelContent(level1) === getLevelContent(level2); - }; - - return { - createFragmentedLevel: createFragmentedLevel, - createCompleteLevel: createCompleteLevel, - createFromEditor: createFromEditor, - applyToEditor: applyToEditor, - isEq: isEq - }; -}); - -// Included from: js/tinymce/classes/UndoManager.js + var isEq = function (level1, level2) { + return getLevelContent(level1) === getLevelContent(level2); + }; + return { + createFragmentedLevel: createFragmentedLevel, + createCompleteLevel: createCompleteLevel, + createFromEditor: createFromEditor, + applyToEditor: applyToEditor, + isEq: isEq + }; + } +); /** * UndoManager.js * * Released under LGPL License. - * Copyright (c) 1999-2015 Ephox Corp. All rights reserved + * Copyright (c) 1999-2017 Ephox Corp. All rights reserved * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing @@ -22121,1204 +23138,2101 @@ define("tinymce/undo/Levels", [ * * @class tinymce.UndoManager */ -define("tinymce/UndoManager", [ - "tinymce/util/VK", - "tinymce/util/Tools", - "tinymce/undo/Levels" -], function(VK, Tools, Levels) { - return function(editor) { - var self = this, index = 0, data = [], beforeBookmark, isFirstTypedCharacter, locks = 0; - - function setDirty(state) { - editor.setDirty(state); - } - - function addNonTypingUndoLevel(e) { - self.typing = false; - self.add({}, e); - } - - function endTyping() { - if (self.typing) { - self.typing = false; - self.add(); - } - } - - // Add initial undo level when the editor is initialized - editor.on('init', function() { - self.add(); - }); - - // Get position before an execCommand is processed - editor.on('BeforeExecCommand', function(e) { - var cmd = e.command; - - if (cmd !== 'Undo' && cmd !== 'Redo' && cmd !== 'mceRepaint') { - endTyping(); - self.beforeChange(); - } - }); - - // Add undo level after an execCommand call was made - editor.on('ExecCommand', function(e) { - var cmd = e.command; - - if (cmd !== 'Undo' && cmd !== 'Redo' && cmd !== 'mceRepaint') { - addNonTypingUndoLevel(e); - } - }); - - editor.on('ObjectResizeStart Cut', function() { - self.beforeChange(); - }); - - editor.on('SaveContent ObjectResized blur', addNonTypingUndoLevel); - editor.on('DragEnd', addNonTypingUndoLevel); - - editor.on('KeyUp', function(e) { - var keyCode = e.keyCode; - - // If key is prevented then don't add undo level - // This would happen on keyboard shortcuts for example - if (e.isDefaultPrevented()) { - return; - } - - if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode === 45 || e.ctrlKey) { - addNonTypingUndoLevel(); - editor.nodeChanged(); - } - - if (keyCode === 46 || keyCode === 8) { - editor.nodeChanged(); - } - - // Fire a TypingUndo event on the first character entered - if (isFirstTypedCharacter && self.typing) { - // Make it dirty if the content was changed after typing the first character - if (!editor.isDirty()) { - setDirty(data[0] && !Levels.isEq(Levels.createFromEditor(editor), data[0])); - - // Fire initial change event - if (editor.isDirty()) { - editor.fire('change', {level: data[0], lastLevel: null}); - } - } - - editor.fire('TypingUndo'); - isFirstTypedCharacter = false; - editor.nodeChanged(); - } - }); - - editor.on('KeyDown', function(e) { - var keyCode = e.keyCode; - - // If key is prevented then don't add undo level - // This would happen on keyboard shortcuts for example - if (e.isDefaultPrevented()) { - return; - } - - // Is character position keys left,right,up,down,home,end,pgdown,pgup,enter - if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode === 45) { - if (self.typing) { - addNonTypingUndoLevel(e); - } - - return; - } - - // If key isn't Ctrl+Alt/AltGr - var modKey = (e.ctrlKey && !e.altKey) || e.metaKey; - if ((keyCode < 16 || keyCode > 20) && keyCode !== 224 && keyCode !== 91 && !self.typing && !modKey) { - self.beforeChange(); - self.typing = true; - self.add({}, e); - isFirstTypedCharacter = true; - } - }); - - editor.on('MouseDown', function(e) { - if (self.typing) { - addNonTypingUndoLevel(e); - } - }); - - // Add keyboard shortcuts for undo/redo keys - editor.addShortcut('meta+z', '', 'Undo'); - editor.addShortcut('meta+y,meta+shift+z', '', 'Redo'); - - editor.on('AddUndo Undo Redo ClearUndos', function(e) { - if (!e.isDefaultPrevented()) { - editor.nodeChanged(); - } - }); - - /*eslint consistent-this:0 */ - self = { - // Explode for debugging reasons - data: data, - - /** - * State if the user is currently typing or not. This will add a typing operation into one undo - * level instead of one new level for each keystroke. - * - * @field {Boolean} typing - */ - typing: false, - - /** - * Stores away a bookmark to be used when performing an undo action so that the selection is before - * the change has been made. - * - * @method beforeChange - */ - beforeChange: function() { - if (!locks) { - beforeBookmark = editor.selection.getBookmark(2, true); - } - }, - - /** - * Adds a new undo level/snapshot to the undo list. - * - * @method add - * @param {Object} level Optional undo level object to add. - * @param {DOMEvent} event Optional event responsible for the creation of the undo level. - * @return {Object} Undo level that got added or null it a level wasn't needed. - */ - add: function(level, event) { - var i, settings = editor.settings, lastLevel, currentLevel; - - currentLevel = Levels.createFromEditor(editor); - level = level || {}; - level = Tools.extend(level, currentLevel); - - if (locks || editor.removed) { - return null; - } - - lastLevel = data[index]; - if (editor.fire('BeforeAddUndo', {level: level, lastLevel: lastLevel, originalEvent: event}).isDefaultPrevented()) { - return null; - } - - // Add undo level if needed - if (lastLevel && Levels.isEq(lastLevel, level)) { - return null; - } - - // Set before bookmark on previous level - if (data[index]) { - data[index].beforeBookmark = beforeBookmark; - } - - // Time to compress - if (settings.custom_undo_redo_levels) { - if (data.length > settings.custom_undo_redo_levels) { - for (i = 0; i < data.length - 1; i++) { - data[i] = data[i + 1]; - } - - data.length--; - index = data.length; - } - } - - // Get a non intrusive normalized bookmark - level.bookmark = editor.selection.getBookmark(2, true); - - // Crop array if needed - if (index < data.length - 1) { - data.length = index + 1; - } - - data.push(level); - index = data.length - 1; - - var args = {level: level, lastLevel: lastLevel, originalEvent: event}; - - editor.fire('AddUndo', args); - - if (index > 0) { - setDirty(true); - editor.fire('change', args); - } - - return level; - }, - - /** - * Undoes the last action. - * - * @method undo - * @return {Object} Undo level or null if no undo was performed. - */ - undo: function() { - var level; - - if (self.typing) { - self.add(); - self.typing = false; - } - - if (index > 0) { - level = data[--index]; - Levels.applyToEditor(editor, level, true); - setDirty(true); - editor.fire('undo', {level: level}); - } - - return level; - }, - - /** - * Redoes the last action. - * - * @method redo - * @return {Object} Redo level or null if no redo was performed. - */ - redo: function() { - var level; - - if (index < data.length - 1) { - level = data[++index]; - Levels.applyToEditor(editor, level, false); - setDirty(true); - editor.fire('redo', {level: level}); - } - - return level; - }, - - /** - * Removes all undo levels. - * - * @method clear - */ - clear: function() { - data = []; - index = 0; - self.typing = false; - self.data = data; - editor.fire('ClearUndos'); - }, - - /** - * Returns true/false if the undo manager has any undo levels. - * - * @method hasUndo - * @return {Boolean} true/false if the undo manager has any undo levels. - */ - hasUndo: function() { - // Has undo levels or typing and content isn't the same as the initial level - return index > 0 || (self.typing && data[0] && !Levels.isEq(Levels.createFromEditor(editor), data[0])); - }, - - /** - * Returns true/false if the undo manager has any redo levels. - * - * @method hasRedo - * @return {Boolean} true/false if the undo manager has any redo levels. - */ - hasRedo: function() { - return index < data.length - 1 && !self.typing; - }, - - /** - * Executes the specified mutator function as an undo transaction. The selection - * before the modification will be stored to the undo stack and if the DOM changes - * it will add a new undo level. Any methods within the translation that adds undo levels will - * be ignored. So a translation can include calls to execCommand or editor.insertContent. - * - * @method transact - * @param {function} callback Function that gets executed and has dom manipulation logic in it. - * @return {Object} Undo level that got added or null it a level wasn't needed. - */ - transact: function(callback) { - endTyping(); - self.beforeChange(); - - try { - locks++; - callback(); - } finally { - locks--; - } - - return self.add(); - }, - - /** - * Adds an extra "hidden" undo level by first applying the first mutation and store that to the undo stack - * then roll back that change and do the second mutation on top of the stack. This will produce an extra - * undo level that the user doesn't see until they undo. - * - * @method extra - * @param {function} callback1 Function that does mutation but gets stored as a "hidden" extra undo level. - * @param {function} callback2 Function that does mutation but gets displayed to the user. - */ - extra: function (callback1, callback2) { - var lastLevel, bookmark; - - if (self.transact(callback1)) { - bookmark = data[index].bookmark; - lastLevel = data[index - 1]; - Levels.applyToEditor(editor, lastLevel, true); - - if (self.transact(callback2)) { - data[index - 1].beforeBookmark = bookmark; - } - } - } - }; - - return self; - }; -}); - -// Included from: js/tinymce/classes/EnterKey.js - -/** - * EnterKey.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 - */ - -/** - * Contains logic for handling the enter key to split/generate block elements. - * - * @private - * @class tinymce.EnterKey - */ -define("tinymce/EnterKey", [ - "tinymce/dom/TreeWalker", - "tinymce/dom/RangeUtils", - "tinymce/caret/CaretContainer", - "tinymce/Env" -], function(TreeWalker, RangeUtils, CaretContainer, Env) { - var isIE = Env.ie && Env.ie < 11; - - return function(editor) { - var dom = editor.dom, selection = editor.selection, settings = editor.settings; - var undoManager = editor.undoManager, schema = editor.schema, nonEmptyElementsMap = schema.getNonEmptyElements(), - moveCaretBeforeOnEnterElementsMap = schema.getMoveCaretBeforeOnEnterElements(); - - function handleEnterKey(evt) { - var rng, tmpRng, editableRoot, container, offset, parentBlock, documentMode, shiftKey, - newBlock, fragment, containerBlock, parentBlockName, containerBlockName, newBlockName, isAfterLastNodeInContainer; - - // Returns true if the block can be split into two blocks or not - function canSplitBlock(node) { - return node && - dom.isBlock(node) && - !/^(TD|TH|CAPTION|FORM)$/.test(node.nodeName) && - !/^(fixed|absolute)/i.test(node.style.position) && - dom.getContentEditable(node) !== "true"; - } - - function isTableCell(node) { - return node && /^(TD|TH|CAPTION)$/.test(node.nodeName); - } - - // Renders empty block on IE - function renderBlockOnIE(block) { - var oldRng; - - if (dom.isBlock(block)) { - oldRng = selection.getRng(); - block.appendChild(dom.create('span', null, '\u00a0')); - selection.select(block); - block.lastChild.outerHTML = ''; - selection.setRng(oldRng); - } - } - - // Remove the first empty inline element of the block so this:x
becomes this:x
- function trimInlineElementsOnLeftSideOfBlock(block) { - var node = block, firstChilds = [], i; - - if (!node) { - return; - } - - // Find inner most first child ex:*
- while ((node = node.firstChild)) { - if (dom.isBlock(node)) { - return; - } - - if (node.nodeType == 1 && !nonEmptyElementsMap[node.nodeName.toLowerCase()]) { - firstChilds.push(node); - } - } - - i = firstChilds.length; - while (i--) { - node = firstChilds[i]; - if (!node.hasChildNodes() || (node.firstChild == node.lastChild && node.firstChild.nodeValue === '')) { - dom.remove(node); - } else { - // Remove see #5381 - if (node.nodeName == "A" && (node.innerText || node.textContent) === ' ') { - dom.remove(node); - } - } - } - } - - // Moves the caret to a suitable position within the root for example in the first non - // pure whitespace text node or before an image - function moveToCaretPosition(root) { - var walker, node, rng, lastNode = root, tempElm; - function firstNonWhiteSpaceNodeSibling(node) { - while (node) { - if (node.nodeType == 1 || (node.nodeType == 3 && node.data && /[\r\n\s]/.test(node.data))) { - return node; - } - - node = node.nextSibling; - } - } - - if (!root) { - return; - } - - // Old IE versions doesn't properly render blocks with br elements in them - // For exampletext|
text|text2
a |
- if (dom.isEmpty(newBlock)) { - dom.remove(newBlock); - insertNewBlockAfter(); - } else { - moveToCaretPosition(newBlock); - } - } - - dom.setAttrib(newBlock, 'id', ''); // Remove ID since it needs to be document unique - - // Allow custom handling of new blocks - editor.fire('NewBlock', {newBlock: newBlock}); - - undoManager.typing = false; - undoManager.add(); - } - - editor.on('keydown', function(evt) { - if (evt.keyCode == 13) { - if (handleEnterKey(evt) !== false) { - evt.preventDefault(); - } - } - }); - }; -}); - -// Included from: js/tinymce/classes/ForceBlocks.js - -/** - * ForceBlocks.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 - */ - -/** - * Makes sure that everything gets wrapped in paragraphs. - * - * @private - * @class tinymce.ForceBlocks - */ -define("tinymce/ForceBlocks", [], function() { - return function(editor) { - var settings = editor.settings, dom = editor.dom, selection = editor.selection; - var schema = editor.schema, blockElements = schema.getBlockElements(); - - function addRootBlocks() { - var node = selection.getStart(), rootNode = editor.getBody(), rng; - var startContainer, startOffset, endContainer, endOffset, rootBlockNode; - var tempNode, offset = -0xFFFFFF, wrapped, restoreSelection; - var tmpRng, rootNodeName, forcedRootBlock; - - forcedRootBlock = settings.forced_root_block; - - if (!node || node.nodeType !== 1 || !forcedRootBlock) { - return; - } - - // Check if node is wrapped in block - while (node && node != rootNode) { - if (blockElements[node.nodeName]) { - return; - } - - node = node.parentNode; - } - - // Get current selection - rng = selection.getRng(); - if (rng.setStart) { - startContainer = rng.startContainer; - startOffset = rng.startOffset; - endContainer = rng.endContainer; - endOffset = rng.endOffset; - - try { - restoreSelection = editor.getDoc().activeElement === rootNode; - } catch (ex) { - // IE throws unspecified error here sometimes - } - } else { - // Force control range into text range - if (rng.item) { - node = rng.item(0); - rng = editor.getDoc().body.createTextRange(); - rng.moveToElementText(node); - } - - restoreSelection = rng.parentElement().ownerDocument === editor.getDoc(); - tmpRng = rng.duplicate(); - tmpRng.collapse(true); - startOffset = tmpRng.move('character', offset) * -1; - - if (!tmpRng.collapsed) { - tmpRng = rng.duplicate(); - tmpRng.collapse(false); - endOffset = (tmpRng.move('character', offset) * -1) - startOffset; - } - } - - // Wrap non block elements and text nodes - node = rootNode.firstChild; - rootNodeName = rootNode.nodeName.toLowerCase(); - while (node) { - // TODO: Break this up, too complex - if (((node.nodeType === 3 || (node.nodeType == 1 && !blockElements[node.nodeName]))) && - schema.isValidChild(rootNodeName, forcedRootBlock.toLowerCase())) { - // Remove empty text nodes - if (node.nodeType === 3 && node.nodeValue.length === 0) { - tempNode = node; - node = node.nextSibling; - dom.remove(tempNode); - continue; - } - - if (!rootBlockNode) { - rootBlockNode = dom.create(forcedRootBlock, editor.settings.forced_root_block_attrs); - node.parentNode.insertBefore(rootBlockNode, node); - wrapped = true; - } - - tempNode = node; - node = node.nextSibling; - rootBlockNode.appendChild(tempNode); - } else { - rootBlockNode = null; - node = node.nextSibling; - } - } - - if (wrapped && restoreSelection) { - if (rng.setStart) { - rng.setStart(startContainer, startOffset); - rng.setEnd(endContainer, endOffset); - selection.setRng(rng); - } else { - // Only select if the previous selection was inside the document to prevent auto focus in quirks mode - try { - rng = editor.getDoc().body.createTextRange(); - rng.moveToElementText(rootNode); - rng.collapse(true); - rng.moveStart('character', startOffset); - - if (endOffset > 0) { - rng.moveEnd('character', endOffset); - } - - rng.select(); - } catch (ex) { - // Ignore - } - } - - editor.nodeChanged(); - } - } - - // Force root blocks - if (settings.forced_root_block) { - editor.on('NodeChange', addRootBlocks); - } - }; -}); - -// Included from: js/tinymce/classes/caret/CaretUtils.js +define( + 'tinymce.core.UndoManager', + [ + "tinymce.core.util.VK", + "tinymce.core.util.Tools", + "tinymce.core.undo.Levels" + ], + function (VK, Tools, Levels) { + return function (editor) { + var self = this, index = 0, data = [], beforeBookmark, isFirstTypedCharacter, locks = 0; + + var isUnlocked = function () { + return locks === 0; + }; + + var setTyping = function (typing) { + if (isUnlocked()) { + self.typing = typing; + } + }; + + function setDirty(state) { + editor.setDirty(state); + } + + function addNonTypingUndoLevel(e) { + setTyping(false); + self.add({}, e); + } + + function endTyping() { + if (self.typing) { + setTyping(false); + self.add(); + } + } + + // Add initial undo level when the editor is initialized + editor.on('init', function () { + self.add(); + }); + + // Get position before an execCommand is processed + editor.on('BeforeExecCommand', function (e) { + var cmd = e.command; + + if (cmd !== 'Undo' && cmd !== 'Redo' && cmd !== 'mceRepaint') { + endTyping(); + self.beforeChange(); + } + }); + + // Add undo level after an execCommand call was made + editor.on('ExecCommand', function (e) { + var cmd = e.command; + + if (cmd !== 'Undo' && cmd !== 'Redo' && cmd !== 'mceRepaint') { + addNonTypingUndoLevel(e); + } + }); + + editor.on('ObjectResizeStart Cut', function () { + self.beforeChange(); + }); + + editor.on('SaveContent ObjectResized blur', addNonTypingUndoLevel); + editor.on('DragEnd', addNonTypingUndoLevel); + + editor.on('KeyUp', function (e) { + var keyCode = e.keyCode; + + // If key is prevented then don't add undo level + // This would happen on keyboard shortcuts for example + if (e.isDefaultPrevented()) { + return; + } + + if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode === 45 || e.ctrlKey) { + addNonTypingUndoLevel(); + editor.nodeChanged(); + } + + if (keyCode === 46 || keyCode === 8) { + editor.nodeChanged(); + } + + // Fire a TypingUndo/Change event on the first character entered + if (isFirstTypedCharacter && self.typing && Levels.isEq(Levels.createFromEditor(editor), data[0]) === false) { + if (editor.isDirty() === false) { + setDirty(true); + editor.fire('change', { level: data[0], lastLevel: null }); + } + + editor.fire('TypingUndo'); + isFirstTypedCharacter = false; + editor.nodeChanged(); + } + }); + + editor.on('KeyDown', function (e) { + var keyCode = e.keyCode; + + // If key is prevented then don't add undo level + // This would happen on keyboard shortcuts for example + if (e.isDefaultPrevented()) { + return; + } + + // Is character position keys left,right,up,down,home,end,pgdown,pgup,enter + if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode === 45) { + if (self.typing) { + addNonTypingUndoLevel(e); + } + + return; + } + + // If key isn't Ctrl+Alt/AltGr + var modKey = (e.ctrlKey && !e.altKey) || e.metaKey; + if ((keyCode < 16 || keyCode > 20) && keyCode !== 224 && keyCode !== 91 && !self.typing && !modKey) { + self.beforeChange(); + setTyping(true); + self.add({}, e); + isFirstTypedCharacter = true; + } + }); + + editor.on('MouseDown', function (e) { + if (self.typing) { + addNonTypingUndoLevel(e); + } + }); + + // Add keyboard shortcuts for undo/redo keys + editor.addShortcut('meta+z', '', 'Undo'); + editor.addShortcut('meta+y,meta+shift+z', '', 'Redo'); + + editor.on('AddUndo Undo Redo ClearUndos', function (e) { + if (!e.isDefaultPrevented()) { + editor.nodeChanged(); + } + }); + + /*eslint consistent-this:0 */ + self = { + // Explode for debugging reasons + data: data, + + /** + * State if the user is currently typing or not. This will add a typing operation into one undo + * level instead of one new level for each keystroke. + * + * @field {Boolean} typing + */ + typing: false, + + /** + * Stores away a bookmark to be used when performing an undo action so that the selection is before + * the change has been made. + * + * @method beforeChange + */ + beforeChange: function () { + if (isUnlocked()) { + beforeBookmark = editor.selection.getBookmark(2, true); + } + }, + + /** + * Adds a new undo level/snapshot to the undo list. + * + * @method add + * @param {Object} level Optional undo level object to add. + * @param {DOMEvent} event Optional event responsible for the creation of the undo level. + * @return {Object} Undo level that got added or null it a level wasn't needed. + */ + add: function (level, event) { + var i, settings = editor.settings, lastLevel, currentLevel; + + currentLevel = Levels.createFromEditor(editor); + level = level || {}; + level = Tools.extend(level, currentLevel); + + if (isUnlocked() === false || editor.removed) { + return null; + } + + lastLevel = data[index]; + if (editor.fire('BeforeAddUndo', { level: level, lastLevel: lastLevel, originalEvent: event }).isDefaultPrevented()) { + return null; + } + + // Add undo level if needed + if (lastLevel && Levels.isEq(lastLevel, level)) { + return null; + } + + // Set before bookmark on previous level + if (data[index]) { + data[index].beforeBookmark = beforeBookmark; + } + + // Time to compress + if (settings.custom_undo_redo_levels) { + if (data.length > settings.custom_undo_redo_levels) { + for (i = 0; i < data.length - 1; i++) { + data[i] = data[i + 1]; + } + + data.length--; + index = data.length; + } + } + + // Get a non intrusive normalized bookmark + level.bookmark = editor.selection.getBookmark(2, true); + + // Crop array if needed + if (index < data.length - 1) { + data.length = index + 1; + } + + data.push(level); + index = data.length - 1; + + var args = { level: level, lastLevel: lastLevel, originalEvent: event }; + + editor.fire('AddUndo', args); + + if (index > 0) { + setDirty(true); + editor.fire('change', args); + } + + return level; + }, + + /** + * Undoes the last action. + * + * @method undo + * @return {Object} Undo level or null if no undo was performed. + */ + undo: function () { + var level; + + if (self.typing) { + self.add(); + self.typing = false; + setTyping(false); + } + + if (index > 0) { + level = data[--index]; + Levels.applyToEditor(editor, level, true); + setDirty(true); + editor.fire('undo', { level: level }); + } + + return level; + }, + + /** + * Redoes the last action. + * + * @method redo + * @return {Object} Redo level or null if no redo was performed. + */ + redo: function () { + var level; + + if (index < data.length - 1) { + level = data[++index]; + Levels.applyToEditor(editor, level, false); + setDirty(true); + editor.fire('redo', { level: level }); + } + + return level; + }, + + /** + * Removes all undo levels. + * + * @method clear + */ + clear: function () { + data = []; + index = 0; + self.typing = false; + self.data = data; + editor.fire('ClearUndos'); + }, + + /** + * Returns true/false if the undo manager has any undo levels. + * + * @method hasUndo + * @return {Boolean} true/false if the undo manager has any undo levels. + */ + hasUndo: function () { + // Has undo levels or typing and content isn't the same as the initial level + return index > 0 || (self.typing && data[0] && !Levels.isEq(Levels.createFromEditor(editor), data[0])); + }, + + /** + * Returns true/false if the undo manager has any redo levels. + * + * @method hasRedo + * @return {Boolean} true/false if the undo manager has any redo levels. + */ + hasRedo: function () { + return index < data.length - 1 && !self.typing; + }, + + /** + * Executes the specified mutator function as an undo transaction. The selection + * before the modification will be stored to the undo stack and if the DOM changes + * it will add a new undo level. Any logic within the translation that adds undo levels will + * be ignored. So a translation can include calls to execCommand or editor.insertContent. + * + * @method transact + * @param {function} callback Function that gets executed and has dom manipulation logic in it. + * @return {Object} Undo level that got added or null it a level wasn't needed. + */ + transact: function (callback) { + endTyping(); + self.beforeChange(); + self.ignore(callback); + return self.add(); + }, + + /** + * Executes the specified mutator function as an undo transaction. But without adding an undo level. + * Any logic within the translation that adds undo levels will be ignored. So a translation can + * include calls to execCommand or editor.insertContent. + * + * @method ignore + * @param {function} callback Function that gets executed and has dom manipulation logic in it. + * @return {Object} Undo level that got added or null it a level wasn't needed. + */ + ignore: function (callback) { + try { + locks++; + callback(); + } finally { + locks--; + } + }, + + /** + * Adds an extra "hidden" undo level by first applying the first mutation and store that to the undo stack + * then roll back that change and do the second mutation on top of the stack. This will produce an extra + * undo level that the user doesn't see until they undo. + * + * @method extra + * @param {function} callback1 Function that does mutation but gets stored as a "hidden" extra undo level. + * @param {function} callback2 Function that does mutation but gets displayed to the user. + */ + extra: function (callback1, callback2) { + var lastLevel, bookmark; + + if (self.transact(callback1)) { + bookmark = data[index].bookmark; + lastLevel = data[index - 1]; + Levels.applyToEditor(editor, lastLevel, true); + + if (self.transact(callback2)) { + data[index - 1].beforeBookmark = bookmark; + } + } + } + }; + + return self; + }; + } +); + +define( + 'ephox.katamari.api.Options', + + [ + 'ephox.katamari.api.Option' + ], + + function (Option) { + /** cat :: [Option a] -> [a] */ + var cat = function (arr) { + var r = []; + var push = function (x) { + r.push(x); + }; + for (var i = 0; i < arr.length; i++) { + arr[i].each(push); + } + return r; + }; + + /** findMap :: ([a], (a, Int -> Option b)) -> Option b */ + var findMap = function (arr, f) { + for (var i = 0; i < arr.length; i++) { + var r = f(arr[i], i); + if (r.isSome()) { + return r; + } + } + return Option.none(); + }; + + /** + * if all elements in arr are 'some', their inner values are passed as arguments to f + * f must have arity arr.length + */ + var liftN = function(arr, f) { + var r = []; + for (var i = 0; i < arr.length; i++) { + var x = arr[i]; + if (x.isSome()) { + r.push(x.getOrDie()); + } else { + return Option.none(); + } + } + return Option.some(f.apply(null, r)); + }; + + return { + cat: cat, + findMap: findMap, + liftN: liftN + }; + } +); + +define( + 'ephox.katamari.data.Immutable', + + [ + 'ephox.katamari.api.Arr', + 'ephox.katamari.api.Fun', + 'global!Array', + 'global!Error' + ], + + function (Arr, Fun, Array, Error) { + return function () { + var fields = arguments; + return function(/* values */) { + // Don't use array slice(arguments), makes the whole function unoptimisable on Chrome + var values = new Array(arguments.length); + for (var i = 0; i < values.length; i++) values[i] = arguments[i]; + + if (fields.length !== values.length) + throw new Error('Wrong number of arguments to struct. Expected "[' + fields.length + ']", got ' + values.length + ' arguments'); + + var struct = {}; + Arr.each(fields, function (name, i) { + struct[name] = Fun.constant(values[i]); + }); + return struct; + }; + }; + } +); + +define( + 'ephox.katamari.api.Obj', + + [ + 'ephox.katamari.api.Option', + 'global!Object' + ], + + function (Option, Object) { + // There are many variations of Object iteration that are faster than the 'for-in' style: + // http://jsperf.com/object-keys-iteration/107 + // + // Use the native keys if it is available (IE9+), otherwise fall back to manually filtering + var keys = (function () { + var fastKeys = Object.keys; + + // This technically means that 'each' and 'find' on IE8 iterate through the object twice. + // This code doesn't run on IE8 much, so it's an acceptable tradeoff. + // If it becomes a problem we can always duplicate the feature detection inside each and find as well. + var slowKeys = function (o) { + var r = []; + for (var i in o) { + if (o.hasOwnProperty(i)) { + r.push(i); + } + } + return r; + }; + + return fastKeys === undefined ? slowKeys : fastKeys; + })(); + + + var each = function (obj, f) { + var props = keys(obj); + for (var k = 0, len = props.length; k < len; k++) { + var i = props[k]; + var x = obj[i]; + f(x, i, obj); + } + }; + + /** objectMap :: (JsObj(k, v), (v, k, JsObj(k, v) -> x)) -> JsObj(k, x) */ + var objectMap = function (obj, f) { + return tupleMap(obj, function (x, i, obj) { + return { + k: i, + v: f(x, i, obj) + }; + }); + }; + + /** tupleMap :: (JsObj(k, v), (v, k, JsObj(k, v) -> { k: x, v: y })) -> JsObj(x, y) */ + var tupleMap = function (obj, f) { + var r = {}; + each(obj, function (x, i) { + var tuple = f(x, i, obj); + r[tuple.k] = tuple.v; + }); + return r; + }; + + /** bifilter :: (JsObj(k, v), (v, k -> Bool)) -> { t: JsObj(k, v), f: JsObj(k, v) } */ + var bifilter = function (obj, pred) { + var t = {}; + var f = {}; + each(obj, function(x, i) { + var branch = pred(x, i) ? t : f; + branch[i] = x; + }); + return { + t: t, + f: f + }; + }; + + /** mapToArray :: (JsObj(k, v), (v, k -> a)) -> [a] */ + var mapToArray = function (obj, f) { + var r = []; + each(obj, function(value, name) { + r.push(f(value, name)); + }); + return r; + }; + + /** find :: (JsObj(k, v), (v, k, JsObj(k, v) -> Bool)) -> Option v */ + var find = function (obj, pred) { + var props = keys(obj); + for (var k = 0, len = props.length; k < len; k++) { + var i = props[k]; + var x = obj[i]; + if (pred(x, i, obj)) { + return Option.some(x); + } + } + return Option.none(); + }; + + /** values :: JsObj(k, v) -> [v] */ + var values = function (obj) { + return mapToArray(obj, function (v) { + return v; + }); + }; + + var size = function (obj) { + return values(obj).length; + }; + + return { + bifilter: bifilter, + each: each, + map: objectMap, + mapToArray: mapToArray, + tupleMap: tupleMap, + find: find, + keys: keys, + values: values, + size: size + }; + } +); +define( + 'ephox.katamari.api.Type', + + [ + 'global!Array', + 'global!String' + ], + + function (Array, String) { + var typeOf = function(x) { + if (x === null) return 'null'; + var t = typeof x; + if (t === 'object' && Array.prototype.isPrototypeOf(x)) return 'array'; + if (t === 'object' && String.prototype.isPrototypeOf(x)) return 'string'; + return t; + }; + + var isType = function (type) { + return function (value) { + return typeOf(value) === type; + }; + }; + + return { + isString: isType('string'), + isObject: isType('object'), + isArray: isType('array'), + isNull: isType('null'), + isBoolean: isType('boolean'), + isUndefined: isType('undefined'), + isFunction: isType('function'), + isNumber: isType('number') + }; + } +); + + +define( + 'ephox.katamari.util.BagUtils', + + [ + 'ephox.katamari.api.Arr', + 'ephox.katamari.api.Type', + 'global!Error' + ], + + function (Arr, Type, Error) { + var sort = function (arr) { + return arr.slice(0).sort(); + }; + + var reqMessage = function (required, keys) { + throw new Error('All required keys (' + sort(required).join(', ') + ') were not specified. Specified keys were: ' + sort(keys).join(', ') + '.'); + }; + + var unsuppMessage = function (unsupported) { + throw new Error('Unsupported keys for object: ' + sort(unsupported).join(', ')); + }; + + var validateStrArr = function (label, array) { + if (!Type.isArray(array)) throw new Error('The ' + label + ' fields must be an array. Was: ' + array + '.'); + Arr.each(array, function (a) { + if (!Type.isString(a)) throw new Error('The value ' + a + ' in the ' + label + ' fields was not a string.'); + }); + }; + + var invalidTypeMessage = function (incorrect, type) { + throw new Error('All values need to be of type: ' + type + '. Keys (' + sort(incorrect).join(', ') + ') were not.'); + }; + + var checkDupes = function (everything) { + var sorted = sort(everything); + var dupe = Arr.find(sorted, function (s, i) { + return i < sorted.length -1 && s === sorted[i + 1]; + }); + + dupe.each(function (d) { + throw new Error('The field: ' + d + ' occurs more than once in the combined fields: [' + sorted.join(', ') + '].'); + }); + }; + + return { + sort: sort, + reqMessage: reqMessage, + unsuppMessage: unsuppMessage, + validateStrArr: validateStrArr, + invalidTypeMessage: invalidTypeMessage, + checkDupes: checkDupes + }; + } +); +define( + 'ephox.katamari.data.MixedBag', + + [ + 'ephox.katamari.api.Arr', + 'ephox.katamari.api.Fun', + 'ephox.katamari.api.Obj', + 'ephox.katamari.api.Option', + 'ephox.katamari.util.BagUtils', + 'global!Error', + 'global!Object' + ], + + function (Arr, Fun, Obj, Option, BagUtils, Error, Object) { + + return function (required, optional) { + var everything = required.concat(optional); + if (everything.length === 0) throw new Error('You must specify at least one required or optional field.'); + + BagUtils.validateStrArr('required', required); + BagUtils.validateStrArr('optional', optional); + + BagUtils.checkDupes(everything); + + return function (obj) { + var keys = Obj.keys(obj); + + // Ensure all required keys are present. + var allReqd = Arr.forall(required, function (req) { + return Arr.contains(keys, req); + }); + + if (! allReqd) BagUtils.reqMessage(required, keys); + + var unsupported = Arr.filter(keys, function (key) { + return !Arr.contains(everything, key); + }); + + if (unsupported.length > 0) BagUtils.unsuppMessage(unsupported); + + var r = {}; + Arr.each(required, function (req) { + r[req] = Fun.constant(obj[req]); + }); + + Arr.each(optional, function (opt) { + r[opt] = Fun.constant(Object.prototype.hasOwnProperty.call(obj, opt) ? Option.some(obj[opt]): Option.none()); + }); + + return r; + }; + }; + } +); +define( + 'ephox.katamari.api.Struct', + + [ + 'ephox.katamari.data.Immutable', + 'ephox.katamari.data.MixedBag' + ], + + function (Immutable, MixedBag) { + return { + immutable: Immutable, + immutableBag: MixedBag + }; + } +); + +define( + 'ephox.katamari.api.Global', + + [ + ], + + function () { + return Function('return this;')(); + } +); + + +define( + 'ephox.katamari.api.Resolve', + + [ + 'ephox.katamari.api.Global' + ], + + function (Global) { + /** path :: ([String], JsObj?) -> JsObj */ + var path = function (parts, scope) { + var o = scope !== undefined ? scope : Global; + for (var i = 0; i < parts.length && o !== undefined && o !== null; ++i) + o = o[parts[i]]; + return o; + }; + + /** resolve :: (String, JsObj?) -> JsObj */ + var resolve = function (p, scope) { + var parts = p.split('.'); + return path(parts, scope); + }; + + /** step :: (JsObj, String) -> JsObj */ + var step = function (o, part) { + if (o[part] === undefined || o[part] === null) + o[part] = {}; + return o[part]; + }; + + /** forge :: ([String], JsObj?) -> JsObj */ + var forge = function (parts, target) { + var o = target !== undefined ? target : Global; + for (var i = 0; i < parts.length; ++i) + o = step(o, parts[i]); + return o; + }; + + /** namespace :: (String, JsObj?) -> JsObj */ + var namespace = function (name, target) { + var parts = name.split('.'); + return forge(parts, target); + }; + + return { + path: path, + resolve: resolve, + forge: forge, + namespace: namespace + }; + } +); + + +define( + 'ephox.sand.util.Global', + + [ + 'ephox.katamari.api.Resolve' + ], + + function (Resolve) { + var unsafe = function (name, scope) { + return Resolve.resolve(name, scope); + }; + + var getOrDie = function (name, scope) { + var actual = unsafe(name, scope); + + if (actual === undefined) throw name + ' not available on this browser'; + return actual; + }; + + return { + getOrDie: getOrDie + }; + } +); +define( + 'ephox.sand.api.Node', + + [ + 'ephox.sand.util.Global' + ], + + function (Global) { + /* + * MDN says (yes) for IE, but it's undefined on IE8 + */ + var node = function () { + var f = Global.getOrDie('Node'); + return f; + }; + + /* + * Most of numerosity doesn't alter the methods on the object. + * We're making an exception for Node, because bitwise and is so easy to get wrong. + * + * Might be nice to ADT this at some point instead of having individual methods. + */ + + var compareDocumentPosition = function (a, b, match) { + // Returns: 0 if e1 and e2 are the same node, or a bitmask comparing the positions + // of nodes e1 and e2 in their documents. See the URL below for bitmask interpretation + // https://developer.mozilla.org/en-US/docs/Web/API/Node/compareDocumentPosition + return (a.compareDocumentPosition(b) & match) !== 0; + }; + + var documentPositionPreceding = function (a, b) { + return compareDocumentPosition(a, b, node().DOCUMENT_POSITION_PRECEDING); + }; + + var documentPositionContainedBy = function (a, b) { + return compareDocumentPosition(a, b, node().DOCUMENT_POSITION_CONTAINED_BY); + }; + + return { + documentPositionPreceding: documentPositionPreceding, + documentPositionContainedBy: documentPositionContainedBy + }; + } +); +define( + 'ephox.katamari.api.Thunk', + + [ + ], + + function () { + + var cached = function (f) { + var called = false; + var r; + return function() { + if (!called) { + called = true; + r = f.apply(null, arguments); + } + return r; + }; + }; + + return { + cached: cached + }; + } +); + +defineGlobal("global!Number", Number); +define( + 'ephox.sand.detect.Version', + + [ + 'ephox.katamari.api.Arr', + 'global!Number', + 'global!String' + ], + + function (Arr, Number, String) { + var firstMatch = function (regexes, s) { + for (var i = 0; i < regexes.length; i++) { + var x = regexes[i]; + if (x.test(s)) return x; + } + return undefined; + }; + + var find = function (regexes, agent) { + var r = firstMatch(regexes, agent); + if (!r) return { major : 0, minor : 0 }; + var group = function(i) { + return Number(agent.replace(r, '$' + i)); + }; + return nu(group(1), group(2)); + }; + + var detect = function (versionRegexes, agent) { + var cleanedAgent = String(agent).toLowerCase(); + + if (versionRegexes.length === 0) return unknown(); + return find(versionRegexes, cleanedAgent); + }; + + var unknown = function () { + return nu(0, 0); + }; + + var nu = function (major, minor) { + return { major: major, minor: minor }; + }; + + return { + nu: nu, + detect: detect, + unknown: unknown + }; + } +); +define( + 'ephox.sand.core.Browser', + + [ + 'ephox.katamari.api.Fun', + 'ephox.sand.detect.Version' + ], + + function (Fun, Version) { + var edge = 'Edge'; + var chrome = 'Chrome'; + var ie = 'IE'; + var opera = 'Opera'; + var firefox = 'Firefox'; + var safari = 'Safari'; + + var isBrowser = function (name, current) { + return function () { + return current === name; + }; + }; + + var unknown = function () { + return nu({ + current: undefined, + version: Version.unknown() + }); + }; + + var nu = function (info) { + var current = info.current; + var version = info.version; + + return { + current: current, + version: version, + + // INVESTIGATE: Rename to Edge ? + isEdge: isBrowser(edge, current), + isChrome: isBrowser(chrome, current), + // NOTE: isIe just looks too weird + isIE: isBrowser(ie, current), + isOpera: isBrowser(opera, current), + isFirefox: isBrowser(firefox, current), + isSafari: isBrowser(safari, current) + }; + }; + + return { + unknown: unknown, + nu: nu, + edge: Fun.constant(edge), + chrome: Fun.constant(chrome), + ie: Fun.constant(ie), + opera: Fun.constant(opera), + firefox: Fun.constant(firefox), + safari: Fun.constant(safari) + }; + } +); +define( + 'ephox.sand.core.OperatingSystem', + + [ + 'ephox.katamari.api.Fun', + 'ephox.sand.detect.Version' + ], + + function (Fun, Version) { + var windows = 'Windows'; + var ios = 'iOS'; + var android = 'Android'; + var linux = 'Linux'; + var osx = 'OSX'; + var solaris = 'Solaris'; + var freebsd = 'FreeBSD'; + + // Though there is a bit of dupe with this and Browser, trying to + // reuse code makes it much harder to follow and change. + var isOS = function (name, current) { + return function () { + return current === name; + }; + }; + + var unknown = function () { + return nu({ + current: undefined, + version: Version.unknown() + }); + }; + + var nu = function (info) { + var current = info.current; + var version = info.version; + + return { + current: current, + version: version, + + isWindows: isOS(windows, current), + // TODO: Fix capitalisation + isiOS: isOS(ios, current), + isAndroid: isOS(android, current), + isOSX: isOS(osx, current), + isLinux: isOS(linux, current), + isSolaris: isOS(solaris, current), + isFreeBSD: isOS(freebsd, current) + }; + }; + + return { + unknown: unknown, + nu: nu, + + windows: Fun.constant(windows), + ios: Fun.constant(ios), + android: Fun.constant(android), + linux: Fun.constant(linux), + osx: Fun.constant(osx), + solaris: Fun.constant(solaris), + freebsd: Fun.constant(freebsd) + }; + } +); +define( + 'ephox.sand.detect.DeviceType', + + [ + 'ephox.katamari.api.Fun' + ], + + function (Fun) { + return function (os, browser, userAgent) { + var isiPad = os.isiOS() && /ipad/i.test(userAgent) === true; + var isiPhone = os.isiOS() && !isiPad; + var isAndroid3 = os.isAndroid() && os.version.major === 3; + var isAndroid4 = os.isAndroid() && os.version.major === 4; + var isTablet = isiPad || isAndroid3 || ( isAndroid4 && /mobile/i.test(userAgent) === true ); + var isTouch = os.isiOS() || os.isAndroid(); + var isPhone = isTouch && !isTablet; + + var iOSwebview = browser.isSafari() && os.isiOS() && /safari/i.test(userAgent) === false; + + return { + isiPad : Fun.constant(isiPad), + isiPhone: Fun.constant(isiPhone), + isTablet: Fun.constant(isTablet), + isPhone: Fun.constant(isPhone), + isTouch: Fun.constant(isTouch), + isAndroid: os.isAndroid, + isiOS: os.isiOS, + isWebView: Fun.constant(iOSwebview) + }; + }; + } +); +define( + 'ephox.sand.detect.UaString', + + [ + 'ephox.katamari.api.Arr', + 'ephox.sand.detect.Version', + 'global!String' + ], + + function (Arr, Version, String) { + var detect = function (candidates, userAgent) { + var agent = String(userAgent).toLowerCase(); + return Arr.find(candidates, function (candidate) { + return candidate.search(agent); + }); + }; + + // They (browser and os) are the same at the moment, but they might + // not stay that way. + var detectBrowser = function (browsers, userAgent) { + return detect(browsers, userAgent).map(function (browser) { + var version = Version.detect(browser.versionRegexes, userAgent); + return { + current: browser.name, + version: version + }; + }); + }; + + var detectOs = function (oses, userAgent) { + return detect(oses, userAgent).map(function (os) { + var version = Version.detect(os.versionRegexes, userAgent); + return { + current: os.name, + version: version + }; + }); + }; + + return { + detectBrowser: detectBrowser, + detectOs: detectOs + }; + } +); +define( + 'ephox.katamari.str.StrAppend', + + [ + + ], + + function () { + var addToStart = function (str, prefix) { + return prefix + str; + }; + + var addToEnd = function (str, suffix) { + return str + suffix; + }; + + var removeFromStart = function (str, numChars) { + return str.substring(numChars); + }; + + var removeFromEnd = function (str, numChars) { + return str.substring(0, str.length - numChars); + }; + + return { + addToStart: addToStart, + addToEnd: addToEnd, + removeFromStart: removeFromStart, + removeFromEnd: removeFromEnd + }; + } +); +define( + 'ephox.katamari.str.StringParts', + + [ + 'ephox.katamari.api.Option', + 'global!Error' + ], + + function (Option, Error) { + /** Return the first 'count' letters from 'str'. +- * e.g. first("abcde", 2) === "ab" +- */ + var first = function(str, count) { + return str.substr(0, count); + }; + + /** Return the last 'count' letters from 'str'. + * e.g. last("abcde", 2) === "de" + */ + var last = function(str, count) { + return str.substr(str.length - count, str.length); + }; + + var head = function(str) { + return str === '' ? Option.none() : Option.some(str.substr(0, 1)); + }; + + var tail = function(str) { + return str === '' ? Option.none() : Option.some(str.substring(1)); + }; + + return { + first: first, + last: last, + head: head, + tail: tail + }; + } +); +define( + 'ephox.katamari.api.Strings', + + [ + 'ephox.katamari.str.StrAppend', + 'ephox.katamari.str.StringParts', + 'global!Error' + ], + + function (StrAppend, StringParts, Error) { + var checkRange = function(str, substr, start) { + if (substr === '') return true; + if (str.length < substr.length) return false; + var x = str.substr(start, start + substr.length); + return x === substr; + }; + + /** Given a string and object, perform template-replacements on the string, as specified by the object. + * Any template fields of the form ${name} are replaced by the string or number specified as obj["name"] + * Based on Douglas Crockford's 'supplant' method for template-replace of strings. Uses different template format. + */ + var supplant = function(str, obj) { + var isStringOrNumber = function(a) { + var t = typeof a; + return t === 'string' || t === 'number'; + }; + + return str.replace(/\${([^{}]*)}/g, + function (a, b) { + var value = obj[b]; + return isStringOrNumber(value) ? value : a; + } + ); + }; + + var removeLeading = function (str, prefix) { + return startsWith(str, prefix) ? StrAppend.removeFromStart(str, prefix.length) : str; + }; + + var removeTrailing = function (str, prefix) { + return endsWith(str, prefix) ? StrAppend.removeFromEnd(str, prefix.length) : str; + }; + + var ensureLeading = function (str, prefix) { + return startsWith(str, prefix) ? str : StrAppend.addToStart(str, prefix); + }; + + var ensureTrailing = function (str, prefix) { + return endsWith(str, prefix) ? str : StrAppend.addToEnd(str, prefix); + }; + + var contains = function(str, substr) { + return str.indexOf(substr) !== -1; + }; + + var capitalize = function(str) { + return StringParts.head(str).bind(function (head) { + return StringParts.tail(str).map(function (tail) { + return head.toUpperCase() + tail; + }); + }).getOr(str); + }; + + /** Does 'str' start with 'prefix'? + * Note: all strings start with the empty string. + * More formally, for all strings x, startsWith(x, ""). + * This is so that for all strings x and y, startsWith(y + x, y) + */ + var startsWith = function(str, prefix) { + return checkRange(str, prefix, 0); + }; + + /** Does 'str' end with 'suffix'? + * Note: all strings end with the empty string. + * More formally, for all strings x, endsWith(x, ""). + * This is so that for all strings x and y, endsWith(x + y, y) + */ + var endsWith = function(str, suffix) { + return checkRange(str, suffix, str.length - suffix.length); + }; + + + /** removes all leading and trailing spaces */ + var trim = function(str) { + return str.replace(/^\s+|\s+$/g, ''); + }; + + var lTrim = function(str) { + return str.replace(/^\s+/g, ''); + }; + + var rTrim = function(str) { + return str.replace(/\s+$/g, ''); + }; + + return { + supplant: supplant, + startsWith: startsWith, + removeLeading: removeLeading, + removeTrailing: removeTrailing, + ensureLeading: ensureLeading, + ensureTrailing: ensureTrailing, + endsWith: endsWith, + contains: contains, + trim: trim, + lTrim: lTrim, + rTrim: rTrim, + capitalize: capitalize + }; + } +); + +define( + 'ephox.sand.info.PlatformInfo', + + [ + 'ephox.katamari.api.Fun', + 'ephox.katamari.api.Strings' + ], + + function (Fun, Strings) { + var normalVersionRegex = /.*?version\/\ ?([0-9]+)\.([0-9]+).*/; + + var checkContains = function (target) { + return function (uastring) { + return Strings.contains(uastring, target); + }; + }; + + var browsers = [ + { + name : 'Edge', + versionRegexes: [/.*?edge\/ ?([0-9]+)\.([0-9]+)$/], + search: function (uastring) { + var monstrosity = Strings.contains(uastring, 'edge/') && Strings.contains(uastring, 'chrome') && Strings.contains(uastring, 'safari') && Strings.contains(uastring, 'applewebkit'); + return monstrosity; + } + }, + { + name : 'Chrome', + versionRegexes: [/.*?chrome\/([0-9]+)\.([0-9]+).*/, normalVersionRegex], + search : function (uastring) { + return Strings.contains(uastring, 'chrome') && !Strings.contains(uastring, 'chromeframe'); + } + }, + { + name : 'IE', + versionRegexes: [/.*?msie\ ?([0-9]+)\.([0-9]+).*/, /.*?rv:([0-9]+)\.([0-9]+).*/], + search: function (uastring) { + return Strings.contains(uastring, 'msie') || Strings.contains(uastring, 'trident'); + } + }, + // INVESTIGATE: Is this still the Opera user agent? + { + name : 'Opera', + versionRegexes: [normalVersionRegex, /.*?opera\/([0-9]+)\.([0-9]+).*/], + search : checkContains('opera') + }, + { + name : 'Firefox', + versionRegexes: [/.*?firefox\/\ ?([0-9]+)\.([0-9]+).*/], + search : checkContains('firefox') + }, + { + name : 'Safari', + versionRegexes: [normalVersionRegex, /.*?cpu os ([0-9]+)_([0-9]+).*/], + search : function (uastring) { + return (Strings.contains(uastring, 'safari') || Strings.contains(uastring, 'mobile/')) && Strings.contains(uastring, 'applewebkit'); + } + } + ]; + + var oses = [ + { + name : 'Windows', + search : checkContains('win'), + versionRegexes: [/.*?windows\ nt\ ?([0-9]+)\.([0-9]+).*/] + }, + { + name : 'iOS', + search : function (uastring) { + return Strings.contains(uastring, 'iphone') || Strings.contains(uastring, 'ipad'); + }, + versionRegexes: [/.*?version\/\ ?([0-9]+)\.([0-9]+).*/, /.*cpu os ([0-9]+)_([0-9]+).*/, /.*cpu iphone os ([0-9]+)_([0-9]+).*/] + }, + { + name : 'Android', + search : checkContains('android'), + versionRegexes: [/.*?android\ ?([0-9]+)\.([0-9]+).*/] + }, + { + name : 'OSX', + search : checkContains('os x'), + versionRegexes: [/.*?os\ x\ ?([0-9]+)_([0-9]+).*/] + }, + { + name : 'Linux', + search : checkContains('linux'), + versionRegexes: [ ] + }, + { name : 'Solaris', + search : checkContains('sunos'), + versionRegexes: [ ] + }, + { + name : 'FreeBSD', + search : checkContains('freebsd'), + versionRegexes: [ ] + } + ]; + + return { + browsers: Fun.constant(browsers), + oses: Fun.constant(oses) + }; + } +); +define( + 'ephox.sand.core.PlatformDetection', + + [ + 'ephox.sand.core.Browser', + 'ephox.sand.core.OperatingSystem', + 'ephox.sand.detect.DeviceType', + 'ephox.sand.detect.UaString', + 'ephox.sand.info.PlatformInfo' + ], + + function (Browser, OperatingSystem, DeviceType, UaString, PlatformInfo) { + var detect = function (userAgent) { + var browsers = PlatformInfo.browsers(); + var oses = PlatformInfo.oses(); + + var browser = UaString.detectBrowser(browsers, userAgent).fold( + Browser.unknown, + Browser.nu + ); + var os = UaString.detectOs(oses, userAgent).fold( + OperatingSystem.unknown, + OperatingSystem.nu + ); + var deviceType = DeviceType(os, browser, userAgent); + + return { + browser: browser, + os: os, + deviceType: deviceType + }; + }; + + return { + detect: detect + }; + } +); +defineGlobal("global!navigator", navigator); +define( + 'ephox.sand.api.PlatformDetection', + + [ + 'ephox.katamari.api.Thunk', + 'ephox.sand.core.PlatformDetection', + 'global!navigator' + ], + + function (Thunk, PlatformDetection, navigator) { + var detect = Thunk.cached(function () { + var userAgent = navigator.userAgent; + return PlatformDetection.detect(userAgent); + }); + + return { + detect: detect + }; + } +); +define("global!console", [], function () { if (typeof console === "undefined") console = { log: function () {} }; return console; }); +defineGlobal("global!document", document); +define( + 'ephox.sugar.api.node.Element', + + [ + 'ephox.katamari.api.Fun', + 'global!Error', + 'global!console', + 'global!document' + ], + + function (Fun, Error, console, document) { + var fromHtml = function (html, scope) { + var doc = scope || document; + var div = doc.createElement('div'); + div.innerHTML = html; + if (!div.hasChildNodes() || div.childNodes.length > 1) { + console.error('HTML does not have a single root node', html); + throw 'HTML must have a single root node'; + } + return fromDom(div.childNodes[0]); + }; + + var fromTag = function (tag, scope) { + var doc = scope || document; + var node = doc.createElement(tag); + return fromDom(node); + }; + + var fromText = function (text, scope) { + var doc = scope || document; + var node = doc.createTextNode(text); + return fromDom(node); + }; + + var fromDom = function (node) { + if (node === null || node === undefined) throw new Error('Node cannot be null or undefined'); + return { + dom: Fun.constant(node) + }; + }; + + return { + fromHtml: fromHtml, + fromTag: fromTag, + fromText: fromText, + fromDom: fromDom + }; + } +); + +define( + 'ephox.sugar.api.node.NodeTypes', + + [ + + ], + + function () { + return { + ATTRIBUTE: 2, + CDATA_SECTION: 4, + COMMENT: 8, + DOCUMENT: 9, + DOCUMENT_TYPE: 10, + DOCUMENT_FRAGMENT: 11, + ELEMENT: 1, + TEXT: 3, + PROCESSING_INSTRUCTION: 7, + ENTITY_REFERENCE: 5, + ENTITY: 6, + NOTATION: 12 + }; + } +); +define( + 'ephox.sugar.api.search.Selectors', + + [ + 'ephox.katamari.api.Arr', + 'ephox.katamari.api.Option', + 'ephox.sugar.api.node.Element', + 'ephox.sugar.api.node.NodeTypes', + 'global!Error', + 'global!document' + ], + + function (Arr, Option, Element, NodeTypes, Error, document) { + /* + * There's a lot of code here; the aim is to allow the browser to optimise constant comparisons, + * instead of doing object lookup feature detection on every call + */ + var STANDARD = 0; + var MSSTANDARD = 1; + var WEBKITSTANDARD = 2; + var FIREFOXSTANDARD = 3; + + var selectorType = (function () { + var test = document.createElement('span'); + // As of Chrome 34 / Safari 7.1 / FireFox 34, everyone except IE has the unprefixed function. + // Still check for the others, but do it last. + return test.matches !== undefined ? STANDARD : + test.msMatchesSelector !== undefined ? MSSTANDARD : + test.webkitMatchesSelector !== undefined ? WEBKITSTANDARD : + test.mozMatchesSelector !== undefined ? FIREFOXSTANDARD : + -1; + })(); + + + var ELEMENT = NodeTypes.ELEMENT; + var DOCUMENT = NodeTypes.DOCUMENT; + + var is = function (element, selector) { + var elem = element.dom(); + if (elem.nodeType !== ELEMENT) return false; // documents have querySelector but not matches + + // As of Chrome 34 / Safari 7.1 / FireFox 34, everyone except IE has the unprefixed function. + // Still check for the others, but do it last. + else if (selectorType === STANDARD) return elem.matches(selector); + else if (selectorType === MSSTANDARD) return elem.msMatchesSelector(selector); + else if (selectorType === WEBKITSTANDARD) return elem.webkitMatchesSelector(selector); + else if (selectorType === FIREFOXSTANDARD) return elem.mozMatchesSelector(selector); + else throw new Error('Browser lacks native selectors'); // unfortunately we can't throw this on startup :( + }; + + var bypassSelector = function (dom) { + // Only elements and documents support querySelector + return dom.nodeType !== ELEMENT && dom.nodeType !== DOCUMENT || + // IE fix for complex queries on empty nodes: http://jsfiddle.net/spyder/fv9ptr5L/ + dom.childElementCount === 0; + }; + + var all = function (selector, scope) { + var base = scope === undefined ? document : scope.dom(); + return bypassSelector(base) ? [] : Arr.map(base.querySelectorAll(selector), Element.fromDom); + }; + + var one = function (selector, scope) { + var base = scope === undefined ? document : scope.dom(); + return bypassSelector(base) ? Option.none() : Option.from(base.querySelector(selector)).map(Element.fromDom); + }; + + return { + all: all, + is: is, + one: one + }; + } +); + +define( + 'ephox.sugar.api.dom.Compare', + + [ + 'ephox.katamari.api.Arr', + 'ephox.katamari.api.Fun', + 'ephox.sand.api.Node', + 'ephox.sand.api.PlatformDetection', + 'ephox.sugar.api.search.Selectors' + ], + + function (Arr, Fun, Node, PlatformDetection, Selectors) { + + var eq = function (e1, e2) { + return e1.dom() === e2.dom(); + }; + + var isEqualNode = function (e1, e2) { + return e1.dom().isEqualNode(e2.dom()); + }; + + var member = function (element, elements) { + return Arr.exists(elements, Fun.curry(eq, element)); + }; + + // DOM contains() method returns true if e1===e2, we define our contains() to return false (a node does not contain itself). + var regularContains = function (e1, e2) { + var d1 = e1.dom(), d2 = e2.dom(); + return d1 === d2 ? false : d1.contains(d2); + }; + + var ieContains = function (e1, e2) { + // IE only implements the contains() method for Element nodes. + // It fails for Text nodes, so implement it using compareDocumentPosition() + // https://connect.microsoft.com/IE/feedback/details/780874/node-contains-is-incorrect + // Note that compareDocumentPosition returns CONTAINED_BY if 'e2 *is_contained_by* e1': + // Also, compareDocumentPosition defines a node containing itself as false. + return Node.documentPositionContainedBy(e1.dom(), e2.dom()); + }; + + var browser = PlatformDetection.detect().browser; + + // Returns: true if node e1 contains e2, otherwise false. + // (returns false if e1===e2: A node does not contain itself). + var contains = browser.isIE() ? ieContains : regularContains; + + return { + eq: eq, + isEqualNode: isEqualNode, + member: member, + contains: contains, + + // Only used by DomUniverse. Remove (or should Selectors.is move here?) + is: Selectors.is + }; + } +); + +define( + 'ephox.sugar.api.node.Node', + + [ + 'ephox.sugar.api.node.NodeTypes' + ], + + function (NodeTypes) { + var name = function (element) { + var r = element.dom().nodeName; + return r.toLowerCase(); + }; + + var type = function (element) { + return element.dom().nodeType; + }; + + var value = function (element) { + return element.dom().nodeValue; + }; + + var isType = function (t) { + return function (element) { + return type(element) === t; + }; + }; + + var isComment = function (element) { + return type(element) === NodeTypes.COMMENT || name(element) === '#comment'; + }; + + var isElement = isType(NodeTypes.ELEMENT); + var isText = isType(NodeTypes.TEXT); + var isDocument = isType(NodeTypes.DOCUMENT); + + return { + name: name, + type: type, + value: value, + isElement: isElement, + isText: isText, + isDocument: isDocument, + isComment: isComment + }; + } +); + +define( + 'ephox.sugar.api.node.Body', + + [ + 'ephox.katamari.api.Thunk', + 'ephox.sugar.api.node.Element', + 'ephox.sugar.api.node.Node', + 'global!document' + ], + + function (Thunk, Element, Node, document) { + + // Node.contains() is very, very, very good performance + // http://jsperf.com/closest-vs-contains/5 + var inBody = function (element) { + // Technically this is only required on IE, where contains() returns false for text nodes. + // But it's cheap enough to run everywhere and Sugar doesn't have platform detection (yet). + var dom = Node.isText(element) ? element.dom().parentNode : element.dom(); + + // use ownerDocument.body to ensure this works inside iframes. + // Normally contains is bad because an element "contains" itself, but here we want that. + return dom !== undefined && dom !== null && dom.ownerDocument.body.contains(dom); + }; + + var body = Thunk.cached(function() { + return getBody(Element.fromDom(document)); + }); + + var getBody = function (doc) { + var body = doc.dom().body; + if (body === null || body === undefined) throw 'Body is not available yet'; + return Element.fromDom(body); + }; + + return { + body: body, + getBody: getBody, + inBody: inBody + }; + } +); + +define( + 'ephox.sugar.impl.ClosestOrAncestor', + + [ + 'ephox.katamari.api.Type', + 'ephox.katamari.api.Option' + ], + + function (Type, Option) { + return function (is, ancestor, scope, a, isRoot) { + return is(scope, a) ? + Option.some(scope) : + Type.isFunction(isRoot) && isRoot(scope) ? + Option.none() : + ancestor(scope, a, isRoot); + }; + } +); +define( + 'ephox.sugar.api.search.PredicateFind', + + [ + 'ephox.katamari.api.Type', + 'ephox.katamari.api.Arr', + 'ephox.katamari.api.Fun', + 'ephox.katamari.api.Option', + 'ephox.sugar.api.node.Body', + 'ephox.sugar.api.dom.Compare', + 'ephox.sugar.api.node.Element', + 'ephox.sugar.impl.ClosestOrAncestor' + ], + + function (Type, Arr, Fun, Option, Body, Compare, Element, ClosestOrAncestor) { + var first = function (predicate) { + return descendant(Body.body(), predicate); + }; + + var ancestor = function (scope, predicate, isRoot) { + var element = scope.dom(); + var stop = Type.isFunction(isRoot) ? isRoot : Fun.constant(false); + + while (element.parentNode) { + element = element.parentNode; + var el = Element.fromDom(element); + + if (predicate(el)) return Option.some(el); + else if (stop(el)) break; + } + return Option.none(); + }; + + var closest = function (scope, predicate, isRoot) { + // This is required to avoid ClosestOrAncestor passing the predicate to itself + var is = function (scope) { + return predicate(scope); + }; + return ClosestOrAncestor(is, ancestor, scope, predicate, isRoot); + }; + + var sibling = function (scope, predicate) { + var element = scope.dom(); + if (!element.parentNode) return Option.none(); + + return child(Element.fromDom(element.parentNode), function (x) { + return !Compare.eq(scope, x) && predicate(x); + }); + }; + + var child = function (scope, predicate) { + var result = Arr.find(scope.dom().childNodes, + Fun.compose(predicate, Element.fromDom)); + return result.map(Element.fromDom); + }; + + var descendant = function (scope, predicate) { + var descend = function (element) { + for (var i = 0; i < element.childNodes.length; i++) { + if (predicate(Element.fromDom(element.childNodes[i]))) + return Option.some(Element.fromDom(element.childNodes[i])); + + var res = descend(element.childNodes[i]); + if (res.isSome()) + return res; + } + + return Option.none(); + }; + + return descend(scope.dom()); + }; + + return { + first: first, + ancestor: ancestor, + closest: closest, + sibling: sibling, + child: child, + descendant: descendant + }; + } +); + +define( + 'ephox.sugar.alien.Recurse', + + [ + + ], + + function () { + /** + * Applies f repeatedly until it completes (by returning Option.none()). + * + * Normally would just use recursion, but JavaScript lacks tail call optimisation. + * + * This is what recursion looks like when manually unravelled :) + */ + var toArray = function (target, f) { + var r = []; + + var recurse = function (e) { + r.push(e); + return f(e); + }; + + var cur = f(target); + do { + cur = cur.bind(recurse); + } while (cur.isSome()); + + return r; + }; + + return { + toArray: toArray + }; + } +); +define( + 'ephox.sugar.api.search.Traverse', + + [ + 'ephox.katamari.api.Type', + 'ephox.katamari.api.Arr', + 'ephox.katamari.api.Fun', + 'ephox.katamari.api.Option', + 'ephox.katamari.api.Struct', + 'ephox.sugar.alien.Recurse', + 'ephox.sugar.api.dom.Compare', + 'ephox.sugar.api.node.Element' + ], + + function (Type, Arr, Fun, Option, Struct, Recurse, Compare, Element) { + // The document associated with the current element + var owner = function (element) { + return Element.fromDom(element.dom().ownerDocument); + }; + + var documentElement = function (element) { + // TODO: Avoid unnecessary wrap/unwrap here + var doc = owner(element); + return Element.fromDom(doc.dom().documentElement); + }; + + // The window element associated with the element + var defaultView = function (element) { + var el = element.dom(); + var defaultView = el.ownerDocument.defaultView; + return Element.fromDom(defaultView); + }; + + var parent = function (element) { + var dom = element.dom(); + return Option.from(dom.parentNode).map(Element.fromDom); + }; + + var findIndex = function (element) { + return parent(element).bind(function (p) { + // TODO: Refactor out children so we can avoid the constant unwrapping + var kin = children(p); + return Arr.findIndex(kin, function (elem) { + return Compare.eq(element, elem); + }); + }); + }; + + var parents = function (element, isRoot) { + var stop = Type.isFunction(isRoot) ? isRoot : Fun.constant(false); + + // This is used a *lot* so it needs to be performant, not recursive + var dom = element.dom(); + var ret = []; + + while (dom.parentNode !== null && dom.parentNode !== undefined) { + var rawParent = dom.parentNode; + var parent = Element.fromDom(rawParent); + ret.push(parent); + + if (stop(parent) === true) break; + else dom = rawParent; + } + return ret; + }; + + var siblings = function (element) { + // TODO: Refactor out children so we can just not add self instead of filtering afterwards + var filterSelf = function (elements) { + return Arr.filter(elements, function (x) { + return !Compare.eq(element, x); + }); + }; + + return parent(element).map(children).map(filterSelf).getOr([]); + }; + + var offsetParent = function (element) { + var dom = element.dom(); + return Option.from(dom.offsetParent).map(Element.fromDom); + }; + + var prevSibling = function (element) { + var dom = element.dom(); + return Option.from(dom.previousSibling).map(Element.fromDom); + }; + + var nextSibling = function (element) { + var dom = element.dom(); + return Option.from(dom.nextSibling).map(Element.fromDom); + }; + + var prevSiblings = function (element) { + // This one needs to be reversed, so they're still in DOM order + return Arr.reverse(Recurse.toArray(element, prevSibling)); + }; + + var nextSiblings = function (element) { + return Recurse.toArray(element, nextSibling); + }; + + var children = function (element) { + var dom = element.dom(); + return Arr.map(dom.childNodes, Element.fromDom); + }; + + var child = function (element, index) { + var children = element.dom().childNodes; + return Option.from(children[index]).map(Element.fromDom); + }; + + var firstChild = function (element) { + return child(element, 0); + }; + + var lastChild = function (element) { + return child(element, element.dom().childNodes.length - 1); + }; + + var spot = Struct.immutable('element', 'offset'); + var leaf = function (element, offset) { + var cs = children(element); + return cs.length > 0 && offset < cs.length ? spot(cs[offset], 0) : spot(element, offset); + }; + + return { + owner: owner, + defaultView: defaultView, + documentElement: documentElement, + parent: parent, + findIndex: findIndex, + parents: parents, + siblings: siblings, + prevSibling: prevSibling, + offsetParent: offsetParent, + prevSiblings: prevSiblings, + nextSibling: nextSibling, + nextSiblings: nextSiblings, + children: children, + child: child, + firstChild: firstChild, + lastChild: lastChild, + leaf: leaf + }; + } +); /** * CaretUtils.js * * Released under LGPL License. - * Copyright (c) 1999-2015 Ephox Corp. All rights reserved + * Copyright (c) 1999-2017 Ephox Corp. All rights reserved * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing @@ -23330,313 +25244,315 @@ define("tinymce/ForceBlocks", [], function() { * @private * @class tinymce.caret.CaretUtils */ -define("tinymce/caret/CaretUtils", [ - "tinymce/util/Fun", - "tinymce/dom/TreeWalker", - "tinymce/dom/NodeType", - "tinymce/caret/CaretPosition", - "tinymce/caret/CaretContainer", - "tinymce/caret/CaretCandidate" -], function(Fun, TreeWalker, NodeType, CaretPosition, CaretContainer, CaretCandidate) { - var isContentEditableTrue = NodeType.isContentEditableTrue, - isContentEditableFalse = NodeType.isContentEditableFalse, - isBlockLike = NodeType.matchStyleValues('display', 'block table table-cell table-caption'), - isCaretContainer = CaretContainer.isCaretContainer, - isCaretContainerBlock = CaretContainer.isCaretContainerBlock, - curry = Fun.curry, - isElement = NodeType.isElement, - isCaretCandidate = CaretCandidate.isCaretCandidate; +define( + 'tinymce.core.caret.CaretUtils', + [ + "tinymce.core.util.Fun", + "tinymce.core.dom.TreeWalker", + "tinymce.core.dom.NodeType", + "tinymce.core.caret.CaretPosition", + "tinymce.core.caret.CaretContainer", + "tinymce.core.caret.CaretCandidate" + ], + function (Fun, TreeWalker, NodeType, CaretPosition, CaretContainer, CaretCandidate) { + var isContentEditableTrue = NodeType.isContentEditableTrue, + isContentEditableFalse = NodeType.isContentEditableFalse, + isBlockLike = NodeType.matchStyleValues('display', 'block table table-cell table-caption'), + isCaretContainer = CaretContainer.isCaretContainer, + isCaretContainerBlock = CaretContainer.isCaretContainerBlock, + curry = Fun.curry, + isElement = NodeType.isElement, + isCaretCandidate = CaretCandidate.isCaretCandidate; - function isForwards(direction) { - return direction > 0; - } + function isForwards(direction) { + return direction > 0; + } - function isBackwards(direction) { - return direction < 0; - } + function isBackwards(direction) { + return direction < 0; + } - function skipCaretContainers(walk, shallow) { - var node; + function skipCaretContainers(walk, shallow) { + var node; - while ((node = walk(shallow))) { - if (!isCaretContainerBlock(node)) { - return node; - } - } + while ((node = walk(shallow))) { + if (!isCaretContainerBlock(node)) { + return node; + } + } - return null; - } + return null; + } - function findNode(node, direction, predicateFn, rootNode, shallow) { - var walker = new TreeWalker(node, rootNode); + function findNode(node, direction, predicateFn, rootNode, shallow) { + var walker = new TreeWalker(node, rootNode); - if (isBackwards(direction)) { - if (isContentEditableFalse(node) || isCaretContainerBlock(node)) { - node = skipCaretContainers(walker.prev, true); - if (predicateFn(node)) { - return node; - } - } + if (isBackwards(direction)) { + if (isContentEditableFalse(node) || isCaretContainerBlock(node)) { + node = skipCaretContainers(walker.prev, true); + if (predicateFn(node)) { + return node; + } + } - while ((node = skipCaretContainers(walker.prev, shallow))) { - if (predicateFn(node)) { - return node; - } - } - } + while ((node = skipCaretContainers(walker.prev, shallow))) { + if (predicateFn(node)) { + return node; + } + } + } - if (isForwards(direction)) { - if (isContentEditableFalse(node) || isCaretContainerBlock(node)) { - node = skipCaretContainers(walker.next, true); - if (predicateFn(node)) { - return node; - } - } + if (isForwards(direction)) { + if (isContentEditableFalse(node) || isCaretContainerBlock(node)) { + node = skipCaretContainers(walker.next, true); + if (predicateFn(node)) { + return node; + } + } - while ((node = skipCaretContainers(walker.next, shallow))) { - if (predicateFn(node)) { - return node; - } - } - } + while ((node = skipCaretContainers(walker.next, shallow))) { + if (predicateFn(node)) { + return node; + } + } + } - return null; - } + return null; + } - function getEditingHost(node, rootNode) { - for (node = node.parentNode; node && node != rootNode; node = node.parentNode) { - if (isContentEditableTrue(node)) { - return node; - } - } + function getEditingHost(node, rootNode) { + for (node = node.parentNode; node && node != rootNode; node = node.parentNode) { + if (isContentEditableTrue(node)) { + return node; + } + } - return rootNode; - } + return rootNode; + } - function getParentBlock(node, rootNode) { - while (node && node != rootNode) { - if (isBlockLike(node)) { - return node; - } + function getParentBlock(node, rootNode) { + while (node && node != rootNode) { + if (isBlockLike(node)) { + return node; + } - node = node.parentNode; - } + node = node.parentNode; + } - return null; - } + return null; + } - function isInSameBlock(caretPosition1, caretPosition2, rootNode) { - return getParentBlock(caretPosition1.container(), rootNode) == getParentBlock(caretPosition2.container(), rootNode); - } + function isInSameBlock(caretPosition1, caretPosition2, rootNode) { + return getParentBlock(caretPosition1.container(), rootNode) == getParentBlock(caretPosition2.container(), rootNode); + } - function isInSameEditingHost(caretPosition1, caretPosition2, rootNode) { - return getEditingHost(caretPosition1.container(), rootNode) == getEditingHost(caretPosition2.container(), rootNode); - } + function isInSameEditingHost(caretPosition1, caretPosition2, rootNode) { + return getEditingHost(caretPosition1.container(), rootNode) == getEditingHost(caretPosition2.container(), rootNode); + } - function getChildNodeAtRelativeOffset(relativeOffset, caretPosition) { - var container, offset; + function getChildNodeAtRelativeOffset(relativeOffset, caretPosition) { + var container, offset; - if (!caretPosition) { - return null; - } + if (!caretPosition) { + return null; + } - container = caretPosition.container(); - offset = caretPosition.offset(); + container = caretPosition.container(); + offset = caretPosition.offset(); - if (!isElement(container)) { - return null; - } + if (!isElement(container)) { + return null; + } - return container.childNodes[offset + relativeOffset]; - } + return container.childNodes[offset + relativeOffset]; + } - function beforeAfter(before, node) { - var range = node.ownerDocument.createRange(); + function beforeAfter(before, node) { + var range = node.ownerDocument.createRange(); - if (before) { - range.setStartBefore(node); - range.setEndBefore(node); - } else { - range.setStartAfter(node); - range.setEndAfter(node); - } + if (before) { + range.setStartBefore(node); + range.setEndBefore(node); + } else { + range.setStartAfter(node); + range.setEndAfter(node); + } - return range; - } + return range; + } - function isNodesInSameBlock(rootNode, node1, node2) { - return getParentBlock(node1, rootNode) == getParentBlock(node2, rootNode); - } + function isNodesInSameBlock(rootNode, node1, node2) { + return getParentBlock(node1, rootNode) == getParentBlock(node2, rootNode); + } - function lean(left, rootNode, node) { - var sibling, siblingName; + function lean(left, rootNode, node) { + var sibling, siblingName; - if (left) { - siblingName = 'previousSibling'; - } else { - siblingName = 'nextSibling'; - } + if (left) { + siblingName = 'previousSibling'; + } else { + siblingName = 'nextSibling'; + } - while (node && node != rootNode) { - sibling = node[siblingName]; + while (node && node != rootNode) { + sibling = node[siblingName]; - if (isCaretContainer(sibling)) { - sibling = sibling[siblingName]; - } + if (isCaretContainer(sibling)) { + sibling = sibling[siblingName]; + } - if (isContentEditableFalse(sibling)) { - if (isNodesInSameBlock(rootNode, sibling, node)) { - return sibling; - } + if (isContentEditableFalse(sibling)) { + if (isNodesInSameBlock(rootNode, sibling, node)) { + return sibling; + } - break; - } + break; + } - if (isCaretCandidate(sibling)) { - break; - } + if (isCaretCandidate(sibling)) { + break; + } - node = node.parentNode; - } + node = node.parentNode; + } - return null; - } + return null; + } - var before = curry(beforeAfter, true); - var after = curry(beforeAfter, false); + var before = curry(beforeAfter, true); + var after = curry(beforeAfter, false); - function normalizeRange(direction, rootNode, range) { - var node, container, offset, location; - var leanLeft = curry(lean, true, rootNode); - var leanRight = curry(lean, false, rootNode); + function normalizeRange(direction, rootNode, range) { + var node, container, offset, location; + var leanLeft = curry(lean, true, rootNode); + var leanRight = curry(lean, false, rootNode); - container = range.startContainer; - offset = range.startOffset; + container = range.startContainer; + offset = range.startOffset; - if (CaretContainer.isCaretContainerBlock(container)) { - if (!isElement(container)) { - container = container.parentNode; - } + if (CaretContainer.isCaretContainerBlock(container)) { + if (!isElement(container)) { + container = container.parentNode; + } - location = container.getAttribute('data-mce-caret'); + location = container.getAttribute('data-mce-caret'); - if (location == 'before') { - node = container.nextSibling; - if (isContentEditableFalse(node)) { - return before(node); - } - } + if (location == 'before') { + node = container.nextSibling; + if (isContentEditableFalse(node)) { + return before(node); + } + } - if (location == 'after') { - node = container.previousSibling; - if (isContentEditableFalse(node)) { - return after(node); - } - } - } + if (location == 'after') { + node = container.previousSibling; + if (isContentEditableFalse(node)) { + return after(node); + } + } + } - if (!range.collapsed) { - return range; - } + if (!range.collapsed) { + return range; + } - if (NodeType.isText(container)) { - if (isCaretContainer(container)) { - if (direction === 1) { - node = leanRight(container); - if (node) { - return before(node); - } + if (NodeType.isText(container)) { + if (isCaretContainer(container)) { + if (direction === 1) { + node = leanRight(container); + if (node) { + return before(node); + } - node = leanLeft(container); - if (node) { - return after(node); - } - } + node = leanLeft(container); + if (node) { + return after(node); + } + } - if (direction === -1) { - node = leanLeft(container); - if (node) { - return after(node); - } + if (direction === -1) { + node = leanLeft(container); + if (node) { + return after(node); + } - node = leanRight(container); - if (node) { - return before(node); - } - } + node = leanRight(container); + if (node) { + return before(node); + } + } - return range; - } + return range; + } - if (CaretContainer.endsWithCaretContainer(container) && offset >= container.data.length - 1) { - if (direction === 1) { - node = leanRight(container); - if (node) { - return before(node); - } - } + if (CaretContainer.endsWithCaretContainer(container) && offset >= container.data.length - 1) { + if (direction === 1) { + node = leanRight(container); + if (node) { + return before(node); + } + } - return range; - } + return range; + } - if (CaretContainer.startsWithCaretContainer(container) && offset <= 1) { - if (direction === -1) { - node = leanLeft(container); - if (node) { - return after(node); - } - } + if (CaretContainer.startsWithCaretContainer(container) && offset <= 1) { + if (direction === -1) { + node = leanLeft(container); + if (node) { + return after(node); + } + } - return range; - } + return range; + } - if (offset === container.data.length) { - node = leanRight(container); - if (node) { - return before(node); - } + if (offset === container.data.length) { + node = leanRight(container); + if (node) { + return before(node); + } - return range; - } + return range; + } - if (offset === 0) { - node = leanLeft(container); - if (node) { - return after(node); - } + if (offset === 0) { + node = leanLeft(container); + if (node) { + return after(node); + } - return range; - } - } + return range; + } + } - return range; - } + return range; + } - function isNextToContentEditableFalse(relativeOffset, caretPosition) { - return isContentEditableFalse(getChildNodeAtRelativeOffset(relativeOffset, caretPosition)); - } + function isNextToContentEditableFalse(relativeOffset, caretPosition) { + return isContentEditableFalse(getChildNodeAtRelativeOffset(relativeOffset, caretPosition)); + } - return { - isForwards: isForwards, - isBackwards: isBackwards, - findNode: findNode, - getEditingHost: getEditingHost, - getParentBlock: getParentBlock, - isInSameBlock: isInSameBlock, - isInSameEditingHost: isInSameEditingHost, - isBeforeContentEditableFalse: curry(isNextToContentEditableFalse, 0), - isAfterContentEditableFalse: curry(isNextToContentEditableFalse, -1), - normalizeRange: normalizeRange - }; -}); - -// Included from: js/tinymce/classes/caret/CaretWalker.js + return { + isForwards: isForwards, + isBackwards: isBackwards, + findNode: findNode, + getEditingHost: getEditingHost, + getParentBlock: getParentBlock, + isInSameBlock: isInSameBlock, + isInSameEditingHost: isInSameEditingHost, + isBeforeContentEditableFalse: curry(isNextToContentEditableFalse, 0), + isAfterContentEditableFalse: curry(isNextToContentEditableFalse, -1), + normalizeRange: normalizeRange + }; + } +); /** * CaretWalker.js * * Released under LGPL License. - * Copyright (c) 1999-2015 Ephox Corp. All rights reserved + * Copyright (c) 1999-2017 Ephox Corp. All rights reserved * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing @@ -23656,230 +25572,2408 @@ define("tinymce/caret/CaretUtils", [ * var prevLogicalCaretPosition = caretWalker.prev(CaretPosition.fromRangeStart(range)); * var nextLogicalCaretPosition = caretWalker.next(CaretPosition.fromRangeEnd(range)); */ -define("tinymce/caret/CaretWalker", [ - "tinymce/dom/NodeType", - "tinymce/caret/CaretCandidate", - "tinymce/caret/CaretPosition", - "tinymce/caret/CaretUtils", - "tinymce/util/Arr", - "tinymce/util/Fun" -], function(NodeType, CaretCandidate, CaretPosition, CaretUtils, Arr, Fun) { - var isContentEditableFalse = NodeType.isContentEditableFalse, - isText = NodeType.isText, - isElement = NodeType.isElement, - isBr = NodeType.isBr, - isForwards = CaretUtils.isForwards, - isBackwards = CaretUtils.isBackwards, - isCaretCandidate = CaretCandidate.isCaretCandidate, - isAtomic = CaretCandidate.isAtomic, - isEditableCaretCandidate = CaretCandidate.isEditableCaretCandidate; +define( + 'tinymce.core.caret.CaretWalker', + [ + "tinymce.core.dom.NodeType", + "tinymce.core.caret.CaretCandidate", + "tinymce.core.caret.CaretPosition", + "tinymce.core.caret.CaretUtils", + "tinymce.core.util.Arr", + "tinymce.core.util.Fun" + ], + function (NodeType, CaretCandidate, CaretPosition, CaretUtils, Arr, Fun) { + var isContentEditableFalse = NodeType.isContentEditableFalse, + isText = NodeType.isText, + isElement = NodeType.isElement, + isBr = NodeType.isBr, + isForwards = CaretUtils.isForwards, + isBackwards = CaretUtils.isBackwards, + isCaretCandidate = CaretCandidate.isCaretCandidate, + isAtomic = CaretCandidate.isAtomic, + isEditableCaretCandidate = CaretCandidate.isEditableCaretCandidate; - function getParents(node, rootNode) { - var parents = []; + function getParents(node, rootNode) { + var parents = []; - while (node && node != rootNode) { - parents.push(node); - node = node.parentNode; - } + while (node && node != rootNode) { + parents.push(node); + node = node.parentNode; + } - return parents; - } + return parents; + } - function nodeAtIndex(container, offset) { - if (container.hasChildNodes() && offset < container.childNodes.length) { - return container.childNodes[offset]; - } + function nodeAtIndex(container, offset) { + if (container.hasChildNodes() && offset < container.childNodes.length) { + return container.childNodes[offset]; + } - return null; - } + return null; + } - function getCaretCandidatePosition(direction, node) { - if (isForwards(direction)) { - if (isCaretCandidate(node.previousSibling) && !isText(node.previousSibling)) { - return CaretPosition.before(node); - } + function getCaretCandidatePosition(direction, node) { + if (isForwards(direction)) { + if (isCaretCandidate(node.previousSibling) && !isText(node.previousSibling)) { + return CaretPosition.before(node); + } - if (isText(node)) { - return CaretPosition(node, 0); - } - } + if (isText(node)) { + return CaretPosition(node, 0); + } + } - if (isBackwards(direction)) { - if (isCaretCandidate(node.nextSibling) && !isText(node.nextSibling)) { - return CaretPosition.after(node); - } + if (isBackwards(direction)) { + if (isCaretCandidate(node.nextSibling) && !isText(node.nextSibling)) { + return CaretPosition.after(node); + } - if (isText(node)) { - return CaretPosition(node, node.data.length); - } - } + if (isText(node)) { + return CaretPosition(node, node.data.length); + } + } - if (isBackwards(direction)) { - if (isBr(node)) { - return CaretPosition.before(node); - } + if (isBackwards(direction)) { + if (isBr(node)) { + return CaretPosition.before(node); + } - return CaretPosition.after(node); - } + return CaretPosition.after(node); + } - return CaretPosition.before(node); - } + return CaretPosition.before(node); + } - // Jumps over BR elements|
a
->|a
- function isBrBeforeBlock(node, rootNode) { - var next; + // Jumps over BR elements|
a
->|a
+ function isBrBeforeBlock(node, rootNode) { + var next; - if (!NodeType.isBr(node)) { - return false; - } + if (!NodeType.isBr(node)) { + return false; + } - next = findCaretPosition(1, CaretPosition.after(node), rootNode); - if (!next) { - return false; - } + next = findCaretPosition(1, CaretPosition.after(node), rootNode); + if (!next) { + return false; + } - return !CaretUtils.isInSameBlock(CaretPosition.before(node), CaretPosition.before(next), rootNode); - } + return !CaretUtils.isInSameBlock(CaretPosition.before(node), CaretPosition.before(next), rootNode); + } - function findCaretPosition(direction, startCaretPosition, rootNode) { - var container, offset, node, nextNode, innerNode, - rootContentEditableFalseElm, caretPosition; + function findCaretPosition(direction, startCaretPosition, rootNode) { + var container, offset, node, nextNode, innerNode, + rootContentEditableFalseElm, caretPosition; - if (!isElement(rootNode) || !startCaretPosition) { - return null; - } + if (!isElement(rootNode) || !startCaretPosition) { + return null; + } - caretPosition = startCaretPosition; - container = caretPosition.container(); - offset = caretPosition.offset(); + if (startCaretPosition.isEqual(CaretPosition.after(rootNode)) && rootNode.lastChild) { + caretPosition = CaretPosition.after(rootNode.lastChild); + } else { + caretPosition = startCaretPosition; + } - if (isText(container)) { - if (isBackwards(direction) && offset > 0) { - return CaretPosition(container, --offset); - } + container = caretPosition.container(); + offset = caretPosition.offset(); - if (isForwards(direction) && offset < container.length) { - return CaretPosition(container, ++offset); - } + if (isText(container)) { + if (isBackwards(direction) && offset > 0) { + return CaretPosition(container, --offset); + } - node = container; - } else { - if (isBackwards(direction) && offset > 0) { - nextNode = nodeAtIndex(container, offset - 1); - if (isCaretCandidate(nextNode)) { - if (!isAtomic(nextNode)) { - innerNode = CaretUtils.findNode(nextNode, direction, isEditableCaretCandidate, nextNode); - if (innerNode) { - if (isText(innerNode)) { - return CaretPosition(innerNode, innerNode.data.length); - } + if (isForwards(direction) && offset < container.length) { + return CaretPosition(container, ++offset); + } - return CaretPosition.after(innerNode); - } - } + node = container; + } else { + if (isBackwards(direction) && offset > 0) { + nextNode = nodeAtIndex(container, offset - 1); + if (isCaretCandidate(nextNode)) { + if (!isAtomic(nextNode)) { + innerNode = CaretUtils.findNode(nextNode, direction, isEditableCaretCandidate, nextNode); + if (innerNode) { + if (isText(innerNode)) { + return CaretPosition(innerNode, innerNode.data.length); + } - if (isText(nextNode)) { - return CaretPosition(nextNode, nextNode.data.length); - } + return CaretPosition.after(innerNode); + } + } - return CaretPosition.before(nextNode); - } - } + if (isText(nextNode)) { + return CaretPosition(nextNode, nextNode.data.length); + } - if (isForwards(direction) && offset < container.childNodes.length) { - nextNode = nodeAtIndex(container, offset); - if (isCaretCandidate(nextNode)) { - if (isBrBeforeBlock(nextNode, rootNode)) { - return findCaretPosition(direction, CaretPosition.after(nextNode), rootNode); - } + return CaretPosition.before(nextNode); + } + } - if (!isAtomic(nextNode)) { - innerNode = CaretUtils.findNode(nextNode, direction, isEditableCaretCandidate, nextNode); - if (innerNode) { - if (isText(innerNode)) { - return CaretPosition(innerNode, 0); - } + if (isForwards(direction) && offset < container.childNodes.length) { + nextNode = nodeAtIndex(container, offset); + if (isCaretCandidate(nextNode)) { + if (isBrBeforeBlock(nextNode, rootNode)) { + return findCaretPosition(direction, CaretPosition.after(nextNode), rootNode); + } - return CaretPosition.before(innerNode); - } - } + if (!isAtomic(nextNode)) { + innerNode = CaretUtils.findNode(nextNode, direction, isEditableCaretCandidate, nextNode); + if (innerNode) { + if (isText(innerNode)) { + return CaretPosition(innerNode, 0); + } - if (isText(nextNode)) { - return CaretPosition(nextNode, 0); - } + return CaretPosition.before(innerNode); + } + } - return CaretPosition.after(nextNode); - } - } + if (isText(nextNode)) { + return CaretPosition(nextNode, 0); + } - node = caretPosition.getNode(); - } + return CaretPosition.after(nextNode); + } + } - if ((isForwards(direction) && caretPosition.isAtEnd()) || (isBackwards(direction) && caretPosition.isAtStart())) { - node = CaretUtils.findNode(node, direction, Fun.constant(true), rootNode, true); - if (isEditableCaretCandidate(node)) { - return getCaretCandidatePosition(direction, node); - } - } + node = caretPosition.getNode(); + } - nextNode = CaretUtils.findNode(node, direction, isEditableCaretCandidate, rootNode); + if ((isForwards(direction) && caretPosition.isAtEnd()) || (isBackwards(direction) && caretPosition.isAtStart())) { + node = CaretUtils.findNode(node, direction, Fun.constant(true), rootNode, true); + if (isEditableCaretCandidate(node)) { + return getCaretCandidatePosition(direction, node); + } + } - rootContentEditableFalseElm = Arr.last(Arr.filter(getParents(container, rootNode), isContentEditableFalse)); - if (rootContentEditableFalseElm && (!nextNode || !rootContentEditableFalseElm.contains(nextNode))) { - if (isForwards(direction)) { - caretPosition = CaretPosition.after(rootContentEditableFalseElm); - } else { - caretPosition = CaretPosition.before(rootContentEditableFalseElm); - } + nextNode = CaretUtils.findNode(node, direction, isEditableCaretCandidate, rootNode); - return caretPosition; - } + rootContentEditableFalseElm = Arr.last(Arr.filter(getParents(container, rootNode), isContentEditableFalse)); + if (rootContentEditableFalseElm && (!nextNode || !rootContentEditableFalseElm.contains(nextNode))) { + if (isForwards(direction)) { + caretPosition = CaretPosition.after(rootContentEditableFalseElm); + } else { + caretPosition = CaretPosition.before(rootContentEditableFalseElm); + } - if (nextNode) { - return getCaretCandidatePosition(direction, nextNode); - } + return caretPosition; + } - return null; - } + if (nextNode) { + return getCaretCandidatePosition(direction, nextNode); + } - return function(rootNode) { - return { - /** - * Returns the next logical caret position from the specificed input - * caretPoisiton or null if there isn't any more positions left for example - * at the end specified root element. - * - * @method next - * @param {tinymce.caret.CaretPosition} caretPosition Caret position to start from. - * @return {tinymce.caret.CaretPosition} CaretPosition or null if no position was found. - */ - next: function(caretPosition) { - return findCaretPosition(1, caretPosition, rootNode); - }, + return null; + } - /** - * Returns the previous logical caret position from the specificed input - * caretPoisiton or null if there isn't any more positions left for example - * at the end specified root element. - * - * @method prev - * @param {tinymce.caret.CaretPosition} caretPosition Caret position to start from. - * @return {tinymce.caret.CaretPosition} CaretPosition or null if no position was found. - */ - prev: function(caretPosition) { - return findCaretPosition(-1, caretPosition, rootNode); - } - }; - }; -}); + return function (rootNode) { + return { + /** + * Returns the next logical caret position from the specificed input + * caretPoisiton or null if there isn't any more positions left for example + * at the end specified root element. + * + * @method next + * @param {tinymce.caret.CaretPosition} caretPosition Caret position to start from. + * @return {tinymce.caret.CaretPosition} CaretPosition or null if no position was found. + */ + next: function (caretPosition) { + return findCaretPosition(1, caretPosition, rootNode); + }, -// Included from: js/tinymce/classes/InsertList.js + /** + * Returns the previous logical caret position from the specificed input + * caretPoisiton or null if there isn't any more positions left for example + * at the end specified root element. + * + * @method prev + * @param {tinymce.caret.CaretPosition} caretPosition Caret position to start from. + * @return {tinymce.caret.CaretPosition} CaretPosition or null if no position was found. + */ + prev: function (caretPosition) { + return findCaretPosition(-1, caretPosition, rootNode); + } + }; + }; + } +); +/** + * CaretFinder.js + * + * Released under LGPL License. + * Copyright (c) 1999-2017 Ephox Corp. All rights reserved + * + * License: http://www.tinymce.com/license + * Contributing: http://www.tinymce.com/contributing + */ +define( + 'tinymce.core.caret.CaretFinder', + [ + 'ephox.katamari.api.Fun', + 'ephox.katamari.api.Option', + 'tinymce.core.caret.CaretPosition', + 'tinymce.core.caret.CaretWalker' + ], + function (Fun, Option, CaretPosition, CaretWalker) { + var fromPosition = function (forward, rootElement, position) { + var walker = new CaretWalker(rootElement); + return Option.from(forward ? walker.next(position) : walker.prev(position)); + }; + + var positionIn = function (forward, element) { + var caretWalker = new CaretWalker(element); + var startPos = forward ? CaretPosition.before(element) : CaretPosition.after(element); + return Option.from(forward ? caretWalker.next(startPos) : caretWalker.prev(startPos)); + }; + + return { + fromPosition: fromPosition, + positionIn: positionIn + }; + } +); + +/** + * DeleteUtils.js + * + * Released under LGPL License. + * Copyright (c) 1999-2017 Ephox Corp. All rights reserved + * + * License: http://www.tinymce.com/license + * Contributing: http://www.tinymce.com/contributing + */ + +define( + 'tinymce.core.delete.DeleteUtils', + [ + 'ephox.katamari.api.Arr', + 'ephox.katamari.api.Option', + 'ephox.sugar.api.dom.Compare', + 'ephox.sugar.api.node.Element', + 'ephox.sugar.api.node.Node', + 'ephox.sugar.api.search.PredicateFind' + ], + function (Arr, Option, Compare, Element, Node, PredicateFind) { + var toLookup = function (names) { + var lookup = Arr.foldl(names, function (acc, name) { + acc[name] = true; + return acc; + }, { }); + + return function (elm) { + return lookup[Node.name(elm)] === true; + }; + }; + + var isTextBlock = toLookup([ + 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'div', 'address', 'pre', 'form', 'blockquote', 'center', + 'dir', 'fieldset', 'header', 'footer', 'article', 'section', 'hgroup', 'aside', 'nav', 'figure' + ]); + + var isBeforeRoot = function (rootNode) { + return function (elm) { + return Compare.eq(rootNode, Element.fromDom(elm.dom().parentNode)); + }; + }; + + var getParentTextBlock = function (rootNode, elm) { + return Compare.contains(rootNode, elm) ? PredicateFind.closest(elm, isTextBlock, isBeforeRoot(rootNode)) : Option.none(); + }; + + return { + getParentTextBlock: getParentTextBlock + }; + } +); + +define( + 'ephox.sugar.api.search.SelectorFind', + + [ + 'ephox.sugar.api.search.PredicateFind', + 'ephox.sugar.api.search.Selectors', + 'ephox.sugar.impl.ClosestOrAncestor' + ], + + function (PredicateFind, Selectors, ClosestOrAncestor) { + // TODO: An internal SelectorFilter module that doesn't Element.fromDom() everything + + var first = function (selector) { + return Selectors.one(selector); + }; + + var ancestor = function (scope, selector, isRoot) { + return PredicateFind.ancestor(scope, function (e) { + return Selectors.is(e, selector); + }, isRoot); + }; + + var sibling = function (scope, selector) { + return PredicateFind.sibling(scope, function (e) { + return Selectors.is(e, selector); + }); + }; + + var child = function (scope, selector) { + return PredicateFind.child(scope, function (e) { + return Selectors.is(e, selector); + }); + }; + + var descendant = function (scope, selector) { + return Selectors.one(selector, scope); + }; + + var closest = function (scope, selector, isRoot) { + return ClosestOrAncestor(Selectors.is, ancestor, scope, selector, isRoot); + }; + + return { + first: first, + ancestor: ancestor, + sibling: sibling, + child: child, + descendant: descendant, + closest: closest + }; + } +); + +define( + 'ephox.sugar.api.search.SelectorExists', + + [ + 'ephox.sugar.api.search.SelectorFind' + ], + + function (SelectorFind) { + var any = function (selector) { + return SelectorFind.first(selector).isSome(); + }; + + var ancestor = function (scope, selector, isRoot) { + return SelectorFind.ancestor(scope, selector, isRoot).isSome(); + }; + + var sibling = function (scope, selector) { + return SelectorFind.sibling(scope, selector).isSome(); + }; + + var child = function (scope, selector) { + return SelectorFind.child(scope, selector).isSome(); + }; + + var descendant = function (scope, selector) { + return SelectorFind.descendant(scope, selector).isSome(); + }; + + var closest = function (scope, selector, isRoot) { + return SelectorFind.closest(scope, selector, isRoot).isSome(); + }; + + return { + any: any, + ancestor: ancestor, + sibling: sibling, + child: child, + descendant: descendant, + closest: closest + }; + } +); + +/** + * Empty.js + * + * Released under LGPL License. + * Copyright (c) 1999-2017 Ephox Corp. All rights reserved + * + * License: http://www.tinymce.com/license + * Contributing: http://www.tinymce.com/contributing + */ + +define( + 'tinymce.core.dom.Empty', + [ + 'ephox.katamari.api.Fun', + 'ephox.sugar.api.dom.Compare', + 'ephox.sugar.api.node.Element', + 'ephox.sugar.api.search.SelectorExists', + 'tinymce.core.caret.CaretCandidate', + 'tinymce.core.dom.NodeType', + 'tinymce.core.dom.TreeWalker' + ], + function (Fun, Compare, Element, SelectorExists, CaretCandidate, NodeType, TreeWalker) { + var hasWhitespacePreserveParent = function (rootNode, node) { + var rootElement = Element.fromDom(rootNode); + var startNode = Element.fromDom(node); + return SelectorExists.ancestor(startNode, 'pre,code', Fun.curry(Compare.eq, rootElement)); + }; + + var isWhitespace = function (rootNode, node) { + return NodeType.isText(node) && /^[ \t\r\n]*$/.test(node.data) && hasWhitespacePreserveParent(rootNode, node) === false; + }; + + var isNamedAnchor = function (node) { + return NodeType.isElement(node) && node.nodeName === 'A' && node.hasAttribute('name'); + }; + + var isContent = function (rootNode, node) { + return (CaretCandidate.isCaretCandidate(node) && isWhitespace(rootNode, node) === false) || isNamedAnchor(node) || isBookmark(node); + }; + + var isBookmark = NodeType.hasAttribute('data-mce-bookmark'); + var isBogus = NodeType.hasAttribute('data-mce-bogus'); + var isBogusAll = NodeType.hasAttributeValue('data-mce-bogus', 'all'); + + var isEmptyNode = function (targetNode) { + var walker, node, brCount = 0; + + if (isContent(targetNode, targetNode)) { + return false; + } else { + node = targetNode.firstChild; + if (!node) { + return true; + } + + walker = new TreeWalker(node, targetNode); + do { + if (isBogusAll(node)) { + node = walker.next(true); + continue; + } + + if (isBogus(node)) { + node = walker.next(); + continue; + } + + if (NodeType.isBr(node)) { + brCount++; + node = walker.next(); + continue; + } + + if (isContent(targetNode, node)) { + return false; + } + + node = walker.next(); + } while (node); + + return brCount <= 1; + } + }; + + var isEmpty = function (elm) { + return isEmptyNode(elm.dom()); + }; + + return { + isEmpty: isEmpty + }; + } +); + +/** + * BlockBoundary.js + * + * Released under LGPL License. + * Copyright (c) 1999-2017 Ephox Corp. All rights reserved + * + * License: http://www.tinymce.com/license + * Contributing: http://www.tinymce.com/contributing + */ + +define( + 'tinymce.core.delete.BlockBoundary', + [ + 'ephox.katamari.api.Arr', + 'ephox.katamari.api.Fun', + 'ephox.katamari.api.Option', + 'ephox.katamari.api.Options', + 'ephox.katamari.api.Struct', + 'ephox.sugar.api.dom.Compare', + 'ephox.sugar.api.node.Element', + 'ephox.sugar.api.node.Node', + 'ephox.sugar.api.search.PredicateFind', + 'ephox.sugar.api.search.Traverse', + 'tinymce.core.caret.CaretFinder', + 'tinymce.core.caret.CaretPosition', + 'tinymce.core.delete.DeleteUtils', + 'tinymce.core.dom.Empty', + 'tinymce.core.dom.NodeType' + ], + function (Arr, Fun, Option, Options, Struct, Compare, Element, Node, PredicateFind, Traverse, CaretFinder, CaretPosition, DeleteUtils, Empty, NodeType) { + var BlockPosition = Struct.immutable('block', 'position'); + var BlockBoundary = Struct.immutable('from', 'to'); + + var getBlockPosition = function (rootNode, pos) { + var rootElm = Element.fromDom(rootNode); + var containerElm = Element.fromDom(pos.container()); + return DeleteUtils.getParentTextBlock(rootElm, containerElm).map(function (block) { + return BlockPosition(block, pos); + }); + }; + + var isDifferentBlocks = function (blockBoundary) { + return Compare.eq(blockBoundary.from().block(), blockBoundary.to().block()) === false; + }; + + var hasSameParent = function (blockBoundary) { + return Traverse.parent(blockBoundary.from().block()).bind(function (parent1) { + return Traverse.parent(blockBoundary.to().block()).filter(function (parent2) { + return Compare.eq(parent1, parent2); + }); + }).isSome(); + }; + + var isEditable = function (blockBoundary) { + return NodeType.isContentEditableFalse(blockBoundary.from().block()) === false && NodeType.isContentEditableFalse(blockBoundary.to().block()) === false; + }; + + var skipLastBr = function (rootNode, forward, blockPosition) { + if (NodeType.isBr(blockPosition.position().getNode()) && Empty.isEmpty(blockPosition.block()) === false) { + return CaretFinder.positionIn(false, blockPosition.block().dom()).bind(function (lastPositionInBlock) { + if (lastPositionInBlock.isEqual(blockPosition.position())) { + return CaretFinder.fromPosition(forward, rootNode, lastPositionInBlock).bind(function (to) { + return getBlockPosition(rootNode, to); + }); + } else { + return Option.some(blockPosition); + } + }).getOr(blockPosition); + } else { + return blockPosition; + } + }; + + var readFromRange = function (rootNode, forward, rng) { + var fromBlockPos = getBlockPosition(rootNode, CaretPosition.fromRangeStart(rng)); + var toBlockPos = fromBlockPos.bind(function (blockPos) { + return CaretFinder.fromPosition(forward, rootNode, blockPos.position()).bind(function (to) { + return getBlockPosition(rootNode, to).map(function (blockPos) { + return skipLastBr(rootNode, forward, blockPos); + }); + }); + }); + + return Options.liftN([fromBlockPos, toBlockPos], BlockBoundary).filter(function (blockBoundary) { + return isDifferentBlocks(blockBoundary) && hasSameParent(blockBoundary) && isEditable(blockBoundary); + }); + }; + + var read = function (rootNode, forward, rng) { + return rng.collapsed ? readFromRange(rootNode, forward, rng) : Option.none(); + }; + + return { + read: read + }; + } +); + +define( + 'ephox.sugar.api.dom.Insert', + + [ + 'ephox.sugar.api.search.Traverse' + ], + + function (Traverse) { + var before = function (marker, element) { + var parent = Traverse.parent(marker); + parent.each(function (v) { + v.dom().insertBefore(element.dom(), marker.dom()); + }); + }; + + var after = function (marker, element) { + var sibling = Traverse.nextSibling(marker); + sibling.fold(function () { + var parent = Traverse.parent(marker); + parent.each(function (v) { + append(v, element); + }); + }, function (v) { + before(v, element); + }); + }; + + var prepend = function (parent, element) { + var firstChild = Traverse.firstChild(parent); + firstChild.fold(function () { + append(parent, element); + }, function (v) { + parent.dom().insertBefore(element.dom(), v.dom()); + }); + }; + + var append = function (parent, element) { + parent.dom().appendChild(element.dom()); + }; + + var appendAt = function (parent, element, index) { + Traverse.child(parent, index).fold(function () { + append(parent, element); + }, function (v) { + before(v, element); + }); + }; + + var wrap = function (element, wrapper) { + before(element, wrapper); + append(wrapper, element); + }; + + return { + before: before, + after: after, + prepend: prepend, + append: append, + appendAt: appendAt, + wrap: wrap + }; + } +); + +define( + 'ephox.sugar.api.dom.InsertAll', + + [ + 'ephox.katamari.api.Arr', + 'ephox.sugar.api.dom.Insert' + ], + + function (Arr, Insert) { + var before = function (marker, elements) { + Arr.each(elements, function (x) { + Insert.before(marker, x); + }); + }; + + var after = function (marker, elements) { + Arr.each(elements, function (x, i) { + var e = i === 0 ? marker : elements[i - 1]; + Insert.after(e, x); + }); + }; + + var prepend = function (parent, elements) { + Arr.each(elements.slice().reverse(), function (x) { + Insert.prepend(parent, x); + }); + }; + + var append = function (parent, elements) { + Arr.each(elements, function (x) { + Insert.append(parent, x); + }); + }; + + return { + before: before, + after: after, + prepend: prepend, + append: append + }; + } +); + +define( + 'ephox.sugar.api.dom.Remove', + + [ + 'ephox.katamari.api.Arr', + 'ephox.sugar.api.dom.InsertAll', + 'ephox.sugar.api.search.Traverse' + ], + + function (Arr, InsertAll, Traverse) { + var empty = function (element) { + // shortcut "empty node" trick. Requires IE 9. + element.dom().textContent = ''; + + // If the contents was a single empty text node, the above doesn't remove it. But, it's still faster in general + // than removing every child node manually. + // The following is (probably) safe for performance as 99.9% of the time the trick works and + // Traverse.children will return an empty array. + Arr.each(Traverse.children(element), function (rogue) { + remove(rogue); + }); + }; + + var remove = function (element) { + var dom = element.dom(); + if (dom.parentNode !== null) + dom.parentNode.removeChild(dom); + }; + + var unwrap = function (wrapper) { + var children = Traverse.children(wrapper); + if (children.length > 0) + InsertAll.before(wrapper, children); + remove(wrapper); + }; + + return { + empty: empty, + remove: remove, + unwrap: unwrap + }; + } +); + +/** + * MergeBlocks.js + * + * Released under LGPL License. + * Copyright (c) 1999-2017 Ephox Corp. All rights reserved + * + * License: http://www.tinymce.com/license + * Contributing: http://www.tinymce.com/contributing + */ + +define( + 'tinymce.core.delete.MergeBlocks', + [ + 'ephox.katamari.api.Arr', + 'ephox.katamari.api.Option', + 'ephox.sugar.api.dom.Insert', + 'ephox.sugar.api.dom.Remove', + 'ephox.sugar.api.node.Element', + 'ephox.sugar.api.search.Traverse', + 'tinymce.core.caret.CaretFinder', + 'tinymce.core.caret.CaretPosition', + 'tinymce.core.dom.Empty', + 'tinymce.core.dom.NodeType' + ], + function (Arr, Option, Insert, Remove, Element, Traverse, CaretFinder, CaretPosition, Empty, NodeType) { + var mergeBlocksAndReposition = function (fromBlock, toBlock, toPosition) { + var children = Traverse.children(fromBlock); + + if (NodeType.isBr(toPosition.getNode())) { + Remove.remove(Element.fromDom(toPosition.getNode())); + } + + Arr.each(children, function (node) { + Insert.append(toBlock, node); + }); + + if (Empty.isEmpty(fromBlock)) { + Remove.remove(fromBlock); + } + + return children.length > 0 ? Option.some(toPosition) : Option.none(); + }; + + var mergeBlocks = function (forward, block1, block2) { + if (forward) { + return CaretFinder.positionIn(false, block1.dom()).bind(function (toPosition) { + return mergeBlocksAndReposition(block2, block1, toPosition); + }); + } else { + return CaretFinder.positionIn(false, block2.dom()).bind(function (toPosition) { + return mergeBlocksAndReposition(block1, block2, toPosition); + }); + } + }; + + return { + mergeBlocks: mergeBlocks + }; + } +); + +/** + * BlockBoundaryDelete.js + * + * Released under LGPL License. + * Copyright (c) 1999-2017 Ephox Corp. All rights reserved + * + * License: http://www.tinymce.com/license + * Contributing: http://www.tinymce.com/contributing + */ + +define( + 'tinymce.core.delete.BlockBoundaryDelete', + [ + 'tinymce.core.delete.BlockBoundary', + 'tinymce.core.delete.MergeBlocks' + ], + function (BlockBoundary, MergeBlocks) { + var backspaceDelete = function (editor, forward) { + var position; + + position = BlockBoundary.read(editor.getBody(), forward, editor.selection.getRng()).bind(function (blockBoundary) { + return MergeBlocks.mergeBlocks(forward, blockBoundary.from().block(), blockBoundary.to().block()); + }); + + position.each(function (pos) { + editor.selection.setRng(pos.toRange()); + }); + + return position.isSome(); + }; + + return { + backspaceDelete: backspaceDelete + }; + } +); + +/** + * BlockRangeDelete.js + * + * Released under LGPL License. + * Copyright (c) 1999-2017 Ephox Corp. All rights reserved + * + * License: http://www.tinymce.com/license + * Contributing: http://www.tinymce.com/contributing + */ + +define( + 'tinymce.core.delete.BlockRangeDelete', + [ + 'ephox.katamari.api.Options', + 'ephox.sugar.api.dom.Compare', + 'ephox.sugar.api.node.Element', + 'tinymce.core.delete.DeleteUtils', + 'tinymce.core.delete.MergeBlocks' + ], + function (Options, Compare, Element, DeleteUtils, MergeBlocks) { + var deleteRange = function (rootNode, selection) { + var rng = selection.getRng(); + + return Options.liftN([ + DeleteUtils.getParentTextBlock(rootNode, Element.fromDom(rng.startContainer)), + DeleteUtils.getParentTextBlock(rootNode, Element.fromDom(rng.endContainer)) + ], function (block1, block2) { + if (Compare.eq(block1, block2) === false) { + rng.deleteContents(); + + MergeBlocks.mergeBlocks(true, block1, block2).each(function (pos) { + selection.setRng(pos.toRange()); + }); + + return true; + } else { + return false; + } + }).getOr(false); + }; + + var backspaceDelete = function (editor, forward) { + var rootNode = Element.fromDom(editor.getBody()); + + if (editor.selection.isCollapsed() === false) { + return deleteRange(rootNode, editor.selection); + } else { + return false; + } + }; + + return { + backspaceDelete: backspaceDelete + }; + } +); + +define( + 'ephox.katamari.api.Adt', + + [ + 'ephox.katamari.api.Arr', + 'ephox.katamari.api.Obj', + 'ephox.katamari.api.Type', + 'global!Array', + 'global!Error', + 'global!console' + ], + + function (Arr, Obj, Type, Array, Error, console) { + /* + * Generates a church encoded ADT (https://en.wikipedia.org/wiki/Church_encoding) + * For syntax and use, look at the test code. + */ + var generate = function (cases) { + // validation + if (!Type.isArray(cases)) { + throw new Error('cases must be an array'); + } + if (cases.length === 0) { + throw new Error('there must be at least one case'); + } + + var constructors = [ ]; + + // adt is mutated to add the individual cases + var adt = {}; + Arr.each(cases, function (acase, count) { + var keys = Obj.keys(acase); + + // validation + if (keys.length !== 1) { + throw new Error('one and only one name per case'); + } + + var key = keys[0]; + var value = acase[key]; + + // validation + if (adt[key] !== undefined) { + throw new Error('duplicate key detected:' + key); + } else if (key === 'cata') { + throw new Error('cannot have a case named cata (sorry)'); + } else if (!Type.isArray(value)) { + // this implicitly checks if acase is an object + throw new Error('case arguments must be an array'); + } + + constructors.push(key); + // + // constructor for key + // + adt[key] = function () { + var argLength = arguments.length; + + // validation + if (argLength !== value.length) { + throw new Error('Wrong number of arguments to case ' + key + '. Expected ' + value.length + ' (' + value + '), got ' + argLength); + } + + // Don't use array slice(arguments), makes the whole function unoptimisable on Chrome + var args = new Array(argLength); + for (var i = 0; i < args.length; i++) args[i] = arguments[i]; + + + var match = function (branches) { + var branchKeys = Obj.keys(branches); + if (constructors.length !== branchKeys.length) { + throw new Error('Wrong number of arguments to match. Expected: ' + constructors.join(',') + '\nActual: ' + branchKeys.join(',')); + } + + var allReqd = Arr.forall(constructors, function (reqKey) { + return Arr.contains(branchKeys, reqKey); + }); + + if (!allReqd) throw new Error('Not all branches were specified when using match. Specified: ' + branchKeys.join(', ') + '\nRequired: ' + constructors.join(', ')); + + return branches[key].apply(null, args); + }; + + // + // the fold function for key + // + return { + fold: function (/* arguments */) { + // runtime validation + if (arguments.length !== cases.length) { + throw new Error('Wrong number of arguments to fold. Expected ' + cases.length + ', got ' + arguments.length); + } + var target = arguments[count]; + return target.apply(null, args); + }, + match: match, + + // NOTE: Only for debugging. + log: function (label) { + console.log(label, { + constructors: constructors, + constructor: key, + params: args + }); + } + }; + }; + }); + + return adt; + }; + return { + generate: generate + }; + } +); +/** + * CefDeleteAction.js + * + * Released under LGPL License. + * Copyright (c) 1999-2017 Ephox Corp. All rights reserved + * + * License: http://www.tinymce.com/license + * Contributing: http://www.tinymce.com/contributing + */ + +define( + 'tinymce.core.delete.CefDeleteAction', + [ + 'ephox.katamari.api.Adt', + 'ephox.katamari.api.Option', + 'tinymce.core.caret.CaretFinder', + 'tinymce.core.caret.CaretPosition', + 'tinymce.core.caret.CaretUtils', + 'tinymce.core.dom.NodeType' + ], + function (Adt, Option, CaretFinder, CaretPosition, CaretUtils, NodeType) { + var DeleteAction = Adt.generate([ + { remove: [ 'element' ] }, + { moveToElement: [ 'element' ] }, + { moveToPosition: [ 'position' ] } + ]); + + var isAtContentEditableBlockCaret = function (forward, from) { + var elm = from.getNode(forward === false); + var caretLocation = forward ? 'after' : 'before'; + return NodeType.isElement(elm) && elm.getAttribute('data-mce-caret') === caretLocation; + }; + + var findCefPosition = function (rootNode, forward, from) { + return CaretFinder.fromPosition(forward, rootNode, from).bind(function (to) { + if (forward && NodeType.isContentEditableFalse(to.getNode())) { + return Option.some(DeleteAction.moveToElement(to.getNode())); + } else if (forward === false && NodeType.isContentEditableFalse(to.getNode(true))) { + return Option.some(DeleteAction.moveToElement(to.getNode(true))); + } else if (forward && CaretUtils.isAfterContentEditableFalse(from)) { + return Option.some(DeleteAction.moveToPosition(to)); + } else if (forward === false && CaretUtils.isBeforeContentEditableFalse(from)) { + return Option.some(DeleteAction.moveToPosition(to)); + } else { + return Option.none(); + } + }); + }; + + var getContentEditableBlockAction = function (forward, elm) { + if (forward && NodeType.isContentEditableFalse(elm.nextSibling)) { + return Option.some(DeleteAction.moveToElement(elm.nextSibling)); + } else if (forward === false && NodeType.isContentEditableFalse(elm.previousSibling)) { + return Option.some(DeleteAction.moveToElement(elm.previousSibling)); + } else { + return Option.none(); + } + }; + + var getContentEditableAction = function (rootNode, forward, from) { + if (isAtContentEditableBlockCaret(forward, from)) { + return getContentEditableBlockAction(forward, from.getNode(forward === false)) + .fold( + function () { + return findCefPosition(rootNode, forward, from); + }, + Option.some + ); + } else { + return findCefPosition(rootNode, forward, from); + } + }; + + var read = function (rootNode, forward, rng) { + var normalizedRange = CaretUtils.normalizeRange(forward ? 1 : -1, rootNode, rng); + var from = CaretPosition.fromRangeStart(normalizedRange); + + if (forward === false && CaretUtils.isAfterContentEditableFalse(from)) { + return Option.some(DeleteAction.remove(from.getNode(true))); + } else if (forward && CaretUtils.isBeforeContentEditableFalse(from)) { + return Option.some(DeleteAction.remove(from.getNode())); + } else { + return getContentEditableAction(rootNode, forward, from); + } + }; + + return { + read: read + }; + } +); + +/** + * Bidi.js + * + * Released under LGPL License. + * Copyright (c) 1999-2017 Ephox Corp. All rights reserved + * + * License: http://www.tinymce.com/license + * Contributing: http://www.tinymce.com/contributing + */ + +define( + 'tinymce.core.text.Bidi', + [ + ], + function () { + var strongRtl = /[\u0591-\u07FF\uFB1D-\uFDFF\uFE70-\uFEFC]/; + + var hasStrongRtl = function (text) { + return strongRtl.test(text); + }; + + return { + hasStrongRtl: hasStrongRtl + }; + } +); +/** + * InlineUtils.js + * + * Released under LGPL License. + * Copyright (c) 1999-2017 Ephox Corp. All rights reserved + * + * License: http://www.tinymce.com/license + * Contributing: http://www.tinymce.com/contributing + */ + +define( + 'tinymce.core.keyboard.InlineUtils', + [ + 'ephox.katamari.api.Fun', + 'ephox.katamari.api.Option', + 'ephox.katamari.api.Options', + 'tinymce.core.caret.CaretContainer', + 'tinymce.core.caret.CaretFinder', + 'tinymce.core.caret.CaretPosition', + 'tinymce.core.caret.CaretUtils', + 'tinymce.core.caret.CaretWalker', + 'tinymce.core.dom.DOMUtils', + 'tinymce.core.text.Bidi' + ], + function (Fun, Option, Options, CaretContainer, CaretFinder, CaretPosition, CaretUtils, CaretWalker, DOMUtils, Bidi) { + var isInlineTarget = function (elm) { + return DOMUtils.DOM.is(elm, 'a[href],code'); + }; + + var isRtl = function (element) { + return DOMUtils.DOM.getStyle(element, 'direction', true) === 'rtl' || Bidi.hasStrongRtl(element.textContent); + }; + + var findInline = function (rootNode, pos) { + return Option.from(DOMUtils.DOM.getParent(pos.container(), isInlineTarget, rootNode)); + }; + + var hasSameParentBlock = function (rootNode, node1, node2) { + var block1 = CaretUtils.getParentBlock(node1, rootNode); + var block2 = CaretUtils.getParentBlock(node2, rootNode); + return block1 && block1 === block2; + }; + + var isInInline = function (rootNode, pos) { + return pos ? findInline(rootNode, pos).isSome() : false; + }; + + var isAtInlineEndPoint = function (rootNode, pos) { + return findInline(rootNode, pos).map(function (inline) { + return findCaretPosition(inline, false, pos).isNone() || findCaretPosition(inline, true, pos).isNone(); + }).getOr(false); + }; + + var isAtZwsp = function (pos) { + return CaretContainer.isBeforeInline(pos) || CaretContainer.isAfterInline(pos); + }; + + var findCaretPositionIn = function (node, forward) { + return CaretFinder.positionIn(forward, node); + }; + + var findCaretPosition = function (rootNode, forward, from) { + return CaretFinder.fromPosition(forward, rootNode, from); + }; + + var normalizePosition = function (forward, pos) { + var container = pos.container(), offset = pos.offset(); + + if (forward) { + return CaretContainer.isBeforeInline(pos) ? new CaretPosition(container, offset + 1) : pos; + } else { + return CaretContainer.isAfterInline(pos) ? new CaretPosition(container, offset - 1) : pos; + } + }; + + var normalizeForwards = Fun.curry(normalizePosition, true); + var normalizeBackwards = Fun.curry(normalizePosition, false); + + return { + isInlineTarget: isInlineTarget, + findInline: findInline, + isInInline: isInInline, + isRtl: isRtl, + isAtInlineEndPoint: isAtInlineEndPoint, + isAtZwsp: isAtZwsp, + findCaretPositionIn: findCaretPositionIn, + findCaretPosition: findCaretPosition, + normalizePosition: normalizePosition, + normalizeForwards: normalizeForwards, + normalizeBackwards: normalizeBackwards, + hasSameParentBlock: hasSameParentBlock + }; + } +); +/** + * DeleteElement.js + * + * Released under LGPL License. + * Copyright (c) 1999-2017 Ephox Corp. All rights reserved + * + * License: http://www.tinymce.com/license + * Contributing: http://www.tinymce.com/contributing + */ + +define( + 'tinymce.core.delete.DeleteElement', + [ + 'ephox.katamari.api.Fun', + 'ephox.katamari.api.Option', + 'ephox.sugar.api.dom.Insert', + 'ephox.sugar.api.dom.Remove', + 'ephox.sugar.api.node.Element', + 'ephox.sugar.api.node.Node', + 'ephox.sugar.api.search.PredicateFind', + 'tinymce.core.caret.CaretCandidate', + 'tinymce.core.caret.CaretPosition', + 'tinymce.core.dom.Empty', + 'tinymce.core.dom.NodeType', + 'tinymce.core.keyboard.InlineUtils' + ], + function (Fun, Option, Insert, Remove, Element, Node, PredicateFind, CaretCandidate, CaretPosition, Empty, NodeType, InlineUtils) { + var needsReposition = function (pos, elm) { + var container = pos.container(); + var offset = pos.offset(); + return CaretPosition.isTextPosition(pos) === false && container === elm.parentNode && offset > CaretPosition.before(elm).offset(); + }; + + var reposition = function (elm, pos) { + return needsReposition(pos, elm) ? new CaretPosition(pos.container(), pos.offset() - 1) : pos; + }; + + var beforeOrStartOf = function (node) { + return NodeType.isText(node) ? new CaretPosition(node, 0) : CaretPosition.before(node); + }; + + var afterOrEndOf = function (node) { + return NodeType.isText(node) ? new CaretPosition(node, node.data.length) : CaretPosition.after(node); + }; + + var findCaretPosition = function (forward, rootElement, elm) { + if (CaretCandidate.isCaretCandidate(elm.previousSibling)) { + return Option.some(afterOrEndOf(elm.previousSibling)); + } else if (CaretCandidate.isCaretCandidate(elm.nextSibling)) { + return Option.some(beforeOrStartOf(elm)); + } else { + return InlineUtils.findCaretPosition(rootElement, forward, CaretPosition.before(elm)).fold( + function () { + return InlineUtils.findCaretPosition(rootElement, !forward, CaretPosition.after(elm)); + }, + Option.some + ); + } + }; + + var findCaretPosOutsideElmAfterDelete = function (forward, rootElement, elm) { + return findCaretPosition(forward, rootElement, elm).map(Fun.curry(reposition, elm)); + }; + + var setSelection = function (editor, pos) { + pos.fold( + function () { + editor.focus(); + }, + function (pos) { + editor.selection.setRng(pos.toRange()); + } + ); + }; + + var eqRawNode = function (rawNode) { + return function (elm) { + return elm.dom() === rawNode; + }; + }; + + var isBlock = function (editor, elm) { + return elm && editor.schema.getBlockElements().hasOwnProperty(Node.name(elm)); + }; + + var paddEmptyBlock = function (elm) { + if (Empty.isEmpty(elm)) { + var br = Element.fromHtml('a
b
+ // It would become this range in webkit: + //[a
]b
+ // We would want it to be: + //[a]
b
+ // Since it would otherwise produces spans out of thin air on insertContent for example. + var normalizeBlockSelection = function (rng) { + var startPos = CaretPosition.fromRangeStart(rng); + var endPos = CaretPosition.fromRangeEnd(rng); + var rootNode = rng.commonAncestorContainer; + + if (rng.collapsed === false && matchEndContainer(rng, isTextBlock) && rng.endOffset === 0) { + return CaretFinder.fromPosition(false, rootNode, endPos) + .map(function (newEndPos) { + if (!CaretUtils.isInSameBlock(startPos, endPos, rootNode) && CaretUtils.isInSameBlock(startPos, newEndPos, rootNode)) { + return createRange(startPos.container(), startPos.offset(), newEndPos.container(), newEndPos.offset()); + } else { + return rng; + } + }).getOr(rng); + } else { + return rng; + } + }; + + var normalize = function (rng) { + return normalizeBlockSelection(rng); + }; + + return { + normalize: normalize + }; + } +); /** * InsertList.js * * Released under LGPL License. - * Copyright (c) 1999-2016 Ephox Corp. All rights reserved + * Copyright (c) 1999-2017 Ephox Corp. All rights reserved * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing @@ -23891,176 +27985,177 @@ define("tinymce/caret/CaretWalker", [ * @class tinymce.InsertList * @private */ -define("tinymce/InsertList", [ - "tinymce/util/Tools", - "tinymce/caret/CaretWalker", - "tinymce/caret/CaretPosition" -], function(Tools, CaretWalker, CaretPosition) { - var isListFragment = function(fragment) { - var firstChild = fragment.firstChild; - var lastChild = fragment.lastChild; +define( + 'tinymce.core.InsertList', + [ + "tinymce.core.util.Tools", + "tinymce.core.caret.CaretWalker", + "tinymce.core.caret.CaretPosition" + ], + function (Tools, CaretWalker, CaretPosition) { + var isListFragment = function (fragment) { + var firstChild = fragment.firstChild; + var lastChild = fragment.lastChild; - // Skip meta since it's likely|
- rng = selection.getRng(); - var caretElement = rng.startContainer || (rng.parentElement ? rng.parentElement() : null); - var body = editor.getBody(); - if (caretElement === body && selection.isCollapsed()) { - if (dom.isBlock(body.firstChild) && canHaveChildren(body.firstChild) && dom.isEmpty(body.firstChild)) { - rng = dom.createRng(); - rng.setStart(body.firstChild, 0); - rng.setEnd(body.firstChild, 0); - selection.setRng(rng); - } - } - - // Insert node maker where we will insert the new HTML and get it's parent - if (!selection.isCollapsed()) { - // Fix for #2595 seems that delete removes one extra character on - // WebKit for some odd reason if you double click select a word - editor.selection.setRng(editor.selection.getRng()); - editor.getDoc().execCommand('Delete', false, null); - trimNbspAfterDeleteAndPaddValue(); - } - - parentNode = selection.getNode(); - - // Parse the fragment within the context of the parent node - var parserArgs = {context: parentNode.nodeName.toLowerCase(), data: details.data}; - fragment = parser.parse(value, parserArgs); - - // Custom handling of lists - if (details.paste === true && InsertList.isListFragment(fragment) && InsertList.isParentBlockLi(dom, parentNode)) { - rng = InsertList.insertAtCaret(serializer, dom, editor.selection.getRng(true), fragment); - editor.selection.setRng(rng); - editor.fire('SetContent', args); - return; - } - - markFragmentElements(fragment); - - // Move the caret to a more suitable location - node = fragment.lastChild; - if (node.attr('id') == 'mce_marker') { - marker = node; - - for (node = node.prev; node; node = node.walk(true)) { - if (node.type == 3 || !dom.isBlock(node.name)) { - if (editor.schema.isValidChild(node.parent.name, 'span')) { - node.parent.insert(marker, node, node.name === 'br'); - } - break; - } - } - } - - editor._selectionOverrides.showBlockCaretContainer(parentNode); - - // If parser says valid we can insert the contents into that parent - if (!parserArgs.invalid) { - value = serializer.serialize(fragment); - validInsertion(editor, value, parentNode); - } else { - // If the fragment was invalid within that context then we need - // to parse and process the parent it's inserted into - - // Insert bookmark node and get the parent - selection.setContent(bookmarkHtml); - parentNode = selection.getNode(); - rootNode = editor.getBody(); - - // Opera will return the document node when selection is in root - if (parentNode.nodeType == 9) { - parentNode = node = rootNode; - } else { - node = parentNode; - } - - // Find the ancestor just before the root element - while (node !== rootNode) { - parentNode = node; - node = node.parentNode; - } - - // Get the outer/inner HTML depending on if we are in the root and parser and serialize that - value = parentNode == rootNode ? rootNode.innerHTML : dom.getOuterHTML(parentNode); - value = serializer.serialize( - parser.parse( - // Need to replace by using a function since $ in the contents would otherwise be a problem - value.replace(//i, function() { - return serializer.serialize(fragment); - }) - ) - ); - - // Set the inner/outer HTML depending on if we are in the root or not - if (parentNode == rootNode) { - dom.setHTML(rootNode, value); - } else { - dom.setOuterHTML(parentNode, value); - } - } - - reduceInlineTextElements(); - moveSelectionToMarker(dom.get('mce_marker')); - umarkFragmentElements(editor.getBody()); - editor.fire('SetContent', args); - editor.addVisual(); - }; - - var processValue = function (value) { - var details; - - if (typeof value !== 'string') { - details = Tools.extend({ - paste: value.paste, - data: { - paste: value.paste - } - }, value); - - return { - content: value.content, - details: details - }; - } - - return { - content: value, - details: {} - }; - }; - - var insertAtCaret = function (editor, value) { - var result = processValue(value); - insertHtmlAtCaret(editor, result.content, result.details); - }; - - return { - insertAtCaret: insertAtCaret - }; -}); - -// Included from: js/tinymce/classes/EditorCommands.js - +define( + 'tinymce.core.InsertContent', + [ + 'tinymce.core.caret.CaretPosition', + 'tinymce.core.caret.CaretWalker', + 'tinymce.core.dom.ElementUtils', + 'tinymce.core.dom.NodeType', + 'tinymce.core.dom.RangeNormalizer', + 'tinymce.core.Env', + 'tinymce.core.html.Serializer', + 'tinymce.core.InsertList', + 'tinymce.core.util.Tools' + ], + function (CaretPosition, CaretWalker, ElementUtils, NodeType, RangeNormalizer, Env, Serializer, InsertList, Tools) { + var isTableCell = NodeType.matchNodeNames('td th'); + + var validInsertion = function (editor, value, parentNode) { + // Should never insert content into bogus elements, since these can + // be resize handles or similar + if (parentNode.getAttribute('data-mce-bogus') === 'all') { + parentNode.parentNode.insertBefore(editor.dom.createFragment(value), parentNode); + } else { + // Check if parent is empty or only has one BR element then set the innerHTML of that parent + var node = parentNode.firstChild; + var node2 = parentNode.lastChild; + if (!node || (node === node2 && node.nodeName === 'BR')) {/// + editor.dom.setHTML(parentNode, value); + } else { + editor.selection.setContent(value); + } + } + }; + + var insertHtmlAtCaret = function (editor, value, details) { + var parser, serializer, parentNode, rootNode, fragment, args; + var marker, rng, node, node2, bookmarkHtml, merge; + var textInlineElements = editor.schema.getTextInlineElements(); + var selection = editor.selection, dom = editor.dom; + + function trimOrPaddLeftRight(html) { + var rng, container, offset; + + rng = selection.getRng(true); + container = rng.startContainer; + offset = rng.startOffset; + + function hasSiblingText(siblingName) { + return container[siblingName] && container[siblingName].nodeType == 3; + } + + if (container.nodeType == 3) { + if (offset > 0) { + html = html.replace(/^ /, ' '); + } else if (!hasSiblingText('previousSibling')) { + html = html.replace(/^ /, ' '); + } + + if (offset < container.length) { + html = html.replace(/ (|
+ rng = selection.getRng(); + var caretElement = rng.startContainer || (rng.parentElement ? rng.parentElement() : null); + var body = editor.getBody(); + if (caretElement === body && selection.isCollapsed()) { + if (dom.isBlock(body.firstChild) && canHaveChildren(body.firstChild) && dom.isEmpty(body.firstChild)) { + rng = dom.createRng(); + rng.setStart(body.firstChild, 0); + rng.setEnd(body.firstChild, 0); + selection.setRng(rng); + } + } + + // Insert node maker where we will insert the new HTML and get it's parent + if (!selection.isCollapsed()) { + // Fix for #2595 seems that delete removes one extra character on + // WebKit for some odd reason if you double click select a word + editor.selection.setRng(RangeNormalizer.normalize(editor.selection.getRng())); + editor.getDoc().execCommand('Delete', false, null); + trimNbspAfterDeleteAndPaddValue(); + } + + parentNode = selection.getNode(); + + // Parse the fragment within the context of the parent node + var parserArgs = { context: parentNode.nodeName.toLowerCase(), data: details.data }; + fragment = parser.parse(value, parserArgs); + + // Custom handling of lists + if (details.paste === true && InsertList.isListFragment(fragment) && InsertList.isParentBlockLi(dom, parentNode)) { + rng = InsertList.insertAtCaret(serializer, dom, editor.selection.getRng(true), fragment); + editor.selection.setRng(rng); + editor.fire('SetContent', args); + return; + } + + markFragmentElements(fragment); + + // Move the caret to a more suitable location + node = fragment.lastChild; + if (node.attr('id') == 'mce_marker') { + marker = node; + + for (node = node.prev; node; node = node.walk(true)) { + if (node.type == 3 || !dom.isBlock(node.name)) { + if (editor.schema.isValidChild(node.parent.name, 'span')) { + node.parent.insert(marker, node, node.name === 'br'); + } + break; + } + } + } + + editor._selectionOverrides.showBlockCaretContainer(parentNode); + + // If parser says valid we can insert the contents into that parent + if (!parserArgs.invalid) { + value = serializer.serialize(fragment); + validInsertion(editor, value, parentNode); + } else { + // If the fragment was invalid within that context then we need + // to parse and process the parent it's inserted into + + // Insert bookmark node and get the parent + selection.setContent(bookmarkHtml); + parentNode = selection.getNode(); + rootNode = editor.getBody(); + + // Opera will return the document node when selection is in root + if (parentNode.nodeType == 9) { + parentNode = node = rootNode; + } else { + node = parentNode; + } + + // Find the ancestor just before the root element + while (node !== rootNode) { + parentNode = node; + node = node.parentNode; + } + + // Get the outer/inner HTML depending on if we are in the root and parser and serialize that + value = parentNode == rootNode ? rootNode.innerHTML : dom.getOuterHTML(parentNode); + value = serializer.serialize( + parser.parse( + // Need to replace by using a function since $ in the contents would otherwise be a problem + value.replace(//i, function () { + return serializer.serialize(fragment); + }) + ) + ); + + // Set the inner/outer HTML depending on if we are in the root or not + if (parentNode == rootNode) { + dom.setHTML(rootNode, value); + } else { + dom.setOuterHTML(parentNode, value); + } + } + + reduceInlineTextElements(); + moveSelectionToMarker(dom.get('mce_marker')); + umarkFragmentElements(editor.getBody()); + editor.fire('SetContent', args); + editor.addVisual(); + }; + + var processValue = function (value) { + var details; + + if (typeof value !== 'string') { + details = Tools.extend({ + paste: value.paste, + data: { + paste: value.paste + } + }, value); + + return { + content: value.content, + details: details + }; + } + + return { + content: value, + details: {} + }; + }; + + var insertAtCaret = function (editor, value) { + var result = processValue(value); + insertHtmlAtCaret(editor, result.content, result.details); + }; + + return { + insertAtCaret: insertAtCaret + }; + } +); /** * EditorCommands.js * * Released under LGPL License. - * Copyright (c) 1999-2015 Ephox Corp. All rights reserved + * Copyright (c) 1999-2017 Ephox Corp. All rights reserved * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing @@ -24465,756 +28562,748 @@ define("tinymce/InsertContent", [ * * @class tinymce.EditorCommands */ -define("tinymce/EditorCommands", [ - "tinymce/Env", - "tinymce/util/Tools", - "tinymce/dom/RangeUtils", - "tinymce/dom/TreeWalker", - "tinymce/InsertContent", - "tinymce/dom/NodeType" -], function(Env, Tools, RangeUtils, TreeWalker, InsertContent, NodeType) { - // Added for compression purposes - var each = Tools.each, extend = Tools.extend; - var map = Tools.map, inArray = Tools.inArray, explode = Tools.explode; - var isOldIE = Env.ie && Env.ie < 11; - var TRUE = true, FALSE = false; - - return function(editor) { - var dom, selection, formatter, - commands = {state: {}, exec: {}, value: {}}, - settings = editor.settings, - bookmark; - - editor.on('PreInit', function() { - dom = editor.dom; - selection = editor.selection; - settings = editor.settings; - formatter = editor.formatter; - }); - - /** - * Executes the specified command. - * - * @method execCommand - * @param {String} command Command to execute. - * @param {Boolean} ui Optional user interface state. - * @param {Object} value Optional value for command. - * @param {Object} args Optional extra arguments to the execCommand. - * @return {Boolean} true/false if the command was found or not. - */ - function execCommand(command, ui, value, args) { - var func, customCommand, state = 0; - - if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint)$/.test(command) && (!args || !args.skip_focus)) { - editor.focus(); - } - - args = editor.fire('BeforeExecCommand', {command: command, ui: ui, value: value}); - if (args.isDefaultPrevented()) { - return false; - } - - customCommand = command.toLowerCase(); - if ((func = commands.exec[customCommand])) { - func(customCommand, ui, value); - editor.fire('ExecCommand', {command: command, ui: ui, value: value}); - return true; - } - - // Plugin commands - each(editor.plugins, function(p) { - if (p.execCommand && p.execCommand(command, ui, value)) { - editor.fire('ExecCommand', {command: command, ui: ui, value: value}); - state = true; - return false; - } - }); - - if (state) { - return state; - } - - // Theme commands - if (editor.theme && editor.theme.execCommand && editor.theme.execCommand(command, ui, value)) { - editor.fire('ExecCommand', {command: command, ui: ui, value: value}); - return true; - } - - // Browser commands - try { - state = editor.getDoc().execCommand(command, ui, value); - } catch (ex) { - // Ignore old IE errors - } - - if (state) { - editor.fire('ExecCommand', {command: command, ui: ui, value: value}); - return true; - } - - return false; - } - - /** - * Queries the current state for a command for example if the current selection is "bold". - * - * @method queryCommandState - * @param {String} command Command to check the state of. - * @return {Boolean/Number} true/false if the selected contents is bold or not, -1 if it's not found. - */ - function queryCommandState(command) { - var func; - - // Is hidden then return undefined - if (editor.quirks.isHidden()) { - return; - } - - command = command.toLowerCase(); - if ((func = commands.state[command])) { - return func(command); - } - - // Browser commands - try { - return editor.getDoc().queryCommandState(command); - } catch (ex) { - // Fails sometimes see bug: 1896577 - } - - return false; - } - - /** - * Queries the command value for example the current fontsize. - * - * @method queryCommandValue - * @param {String} command Command to check the value of. - * @return {Object} Command value of false if it's not found. - */ - function queryCommandValue(command) { - var func; - - // Is hidden then return undefined - if (editor.quirks.isHidden()) { - return; - } - - command = command.toLowerCase(); - if ((func = commands.value[command])) { - return func(command); - } - - // Browser commands - try { - return editor.getDoc().queryCommandValue(command); - } catch (ex) { - // Fails sometimes see bug: 1896577 - } - } - - /** - * Adds commands to the command collection. - * - * @method addCommands - * @param {Object} command_list Name/value collection with commands to add, the names can also be comma separated. - * @param {String} type Optional type to add, defaults to exec. Can be value or state as well. - */ - function addCommands(command_list, type) { - type = type || 'exec'; - - each(command_list, function(callback, command) { - each(command.toLowerCase().split(','), function(command) { - commands[type][command] = callback; - }); - }); - } - - function addCommand(command, callback, scope) { - command = command.toLowerCase(); - commands.exec[command] = function(command, ui, value, args) { - return callback.call(scope || editor, ui, value, args); - }; - } - - /** - * Returns true/false if the command is supported or not. - * - * @method queryCommandSupported - * @param {String} command Command that we check support for. - * @return {Boolean} true/false if the command is supported or not. - */ - function queryCommandSupported(command) { - command = command.toLowerCase(); - - if (commands.exec[command]) { - return true; - } - - // Browser commands - try { - return editor.getDoc().queryCommandSupported(command); - } catch (ex) { - // Fails sometimes see bug: 1896577 - } - - return false; - } - - function addQueryStateHandler(command, callback, scope) { - command = command.toLowerCase(); - commands.state[command] = function() { - return callback.call(scope || editor); - }; - } - - function addQueryValueHandler(command, callback, scope) { - command = command.toLowerCase(); - commands.value[command] = function() { - return callback.call(scope || editor); - }; - } - - function hasCustomCommand(command) { - command = command.toLowerCase(); - return !!commands.exec[command]; - } - - // Expose public methods - extend(this, { - execCommand: execCommand, - queryCommandState: queryCommandState, - queryCommandValue: queryCommandValue, - queryCommandSupported: queryCommandSupported, - addCommands: addCommands, - addCommand: addCommand, - addQueryStateHandler: addQueryStateHandler, - addQueryValueHandler: addQueryValueHandler, - hasCustomCommand: hasCustomCommand - }); - - // Private methods - - function execNativeCommand(command, ui, value) { - if (ui === undefined) { - ui = FALSE; - } - - if (value === undefined) { - value = null; - } - - return editor.getDoc().execCommand(command, ui, value); - } - - function isFormatMatch(name) { - return formatter.match(name); - } - - function toggleFormat(name, value) { - formatter.toggle(name, value ? {value: value} : undefined); - editor.nodeChanged(); - } - - function storeSelection(type) { - bookmark = selection.getBookmark(type); - } - - function restoreSelection() { - selection.moveToBookmark(bookmark); - } - - // Add execCommand overrides - addCommands({ - // Ignore these, added for compatibility - 'mceResetDesignMode,mceBeginUndoLevel': function() {}, - - // Add undo manager logic - 'mceEndUndoLevel,mceAddUndoLevel': function() { - editor.undoManager.add(); - }, - - 'Cut,Copy,Paste': function(command) { - var doc = editor.getDoc(), failed; - - // Try executing the native command - try { - execNativeCommand(command); - } catch (ex) { - // Command failed - failed = TRUE; - } - - // Chrome reports the paste command as supported however older IE:s will return false for cut/paste - if (command === 'paste' && !doc.queryCommandEnabled(command)) { - failed = true; - } - - // Present alert message about clipboard access not being available - if (failed || !doc.queryCommandSupported(command)) { - var msg = editor.translate( - "Your browser doesn't support direct access to the clipboard. " + - "Please use the Ctrl+X/C/V keyboard shortcuts instead." - ); - - if (Env.mac) { - msg = msg.replace(/Ctrl\+/g, '\u2318+'); - } - - editor.notificationManager.open({text: msg, type: 'error'}); - } - }, - - // Override unlink command - unlink: function() { - if (selection.isCollapsed()) { - var elm = editor.dom.getParent(editor.selection.getStart(), 'a'); - if (elm) { - editor.dom.remove(elm, true); - } - - return; - } - - formatter.remove("link"); - }, - - // Override justify commands to use the text formatter engine - 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull,JustifyNone': function(command) { - var align = command.substring(7); - - if (align == 'full') { - align = 'justify'; - } - - // Remove all other alignments first - each('left,center,right,justify'.split(','), function(name) { - if (align != name) { - formatter.remove('align' + name); - } - }); - - if (align != 'none') { - toggleFormat('align' + align); - } - }, - - // Override list commands to fix WebKit bug - 'InsertUnorderedList,InsertOrderedList': function(command) { - var listElm, listParent; - - execNativeCommand(command); - - // WebKit produces lists within block elements so we need to split them - // we will replace the native list creation logic to custom logic later on - // TODO: Remove this when the list creation logic is removed - listElm = dom.getParent(selection.getNode(), 'ol,ul'); - if (listElm) { - listParent = listElm.parentNode; - - // If list is within a text block then split that block - if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) { - storeSelection(); - dom.split(listParent, listElm); - restoreSelection(); - } - } - }, - - // Override commands to use the text formatter engine - 'Bold,Italic,Underline,Strikethrough,Superscript,Subscript': function(command) { - toggleFormat(command); - }, - - // Override commands to use the text formatter engine - 'ForeColor,HiliteColor,FontName': function(command, ui, value) { - toggleFormat(command, value); - }, - - FontSize: function(command, ui, value) { - var fontClasses, fontSizes; - - // Convert font size 1-7 to styles - if (value >= 1 && value <= 7) { - fontSizes = explode(settings.font_size_style_values); - fontClasses = explode(settings.font_size_classes); - - if (fontClasses) { - value = fontClasses[value - 1] || value; - } else { - value = fontSizes[value - 1] || value; - } - } - - toggleFormat(command, value); - }, - - RemoveFormat: function(command) { - formatter.remove(command); - }, - - mceBlockQuote: function() { - toggleFormat('blockquote'); - }, - - FormatBlock: function(command, ui, value) { - return toggleFormat(value || 'p'); - }, - - mceCleanup: function() { - var bookmark = selection.getBookmark(); - - editor.setContent(editor.getContent({cleanup: TRUE}), {cleanup: TRUE}); - - selection.moveToBookmark(bookmark); - }, - - mceRemoveNode: function(command, ui, value) { - var node = value || selection.getNode(); - - // Make sure that the body node isn't removed - if (node != editor.getBody()) { - storeSelection(); - editor.dom.remove(node, TRUE); - restoreSelection(); - } - }, - - mceSelectNodeDepth: function(command, ui, value) { - var counter = 0; - - dom.getParent(selection.getNode(), function(node) { - if (node.nodeType == 1 && counter++ == value) { - selection.select(node); - return FALSE; - } - }, editor.getBody()); - }, - - mceSelectNode: function(command, ui, value) { - selection.select(value); - }, - - mceInsertContent: function(command, ui, value) { - InsertContent.insertAtCaret(editor, value); - }, - - mceInsertRawHTML: function(command, ui, value) { - selection.setContent('tiny_mce_marker'); - editor.setContent( - editor.getContent().replace(/tiny_mce_marker/g, function() { - return value; - }) - ); - }, - - mceToggleFormat: function(command, ui, value) { - toggleFormat(value); - }, - - mceSetContent: function(command, ui, value) { - editor.setContent(value); - }, - - 'Indent,Outdent': function(command) { - var intentValue, indentUnit, value; - - // Setup indent level - intentValue = settings.indentation; - indentUnit = /[a-z%]+$/i.exec(intentValue); - intentValue = parseInt(intentValue, 10); - - if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) { - // If forced_root_blocks is set to false we don't have a block to indent so lets create a div - if (!settings.forced_root_block && !dom.getParent(selection.getNode(), dom.isBlock)) { - formatter.apply('div'); - } - - each(selection.getSelectedBlocks(), function(element) { - if (dom.getContentEditable(element) === "false") { - return; - } - - if (element.nodeName !== "LI") { - var indentStyleName = editor.getParam('indent_use_margin', false) ? 'margin' : 'padding'; - indentStyleName = element.nodeName === 'TABLE' ? 'margin' : indentStyleName; - indentStyleName += dom.getStyle(element, 'direction', true) == 'rtl' ? 'Right' : 'Left'; - - if (command == 'outdent') { - value = Math.max(0, parseInt(element.style[indentStyleName] || 0, 10) - intentValue); - dom.setStyle(element, indentStyleName, value ? value + indentUnit : ''); - } else { - value = (parseInt(element.style[indentStyleName] || 0, 10) + intentValue) + indentUnit; - dom.setStyle(element, indentStyleName, value); - } - } - }); - } else { - execNativeCommand(command); - } - }, - - mceRepaint: function() { - }, - - InsertHorizontalRule: function() { - editor.execCommand('mceInsertContent', false, '|
- rng = selection.getRng(); - if (!rng.item) { - rng.moveToElementText(root); - rng.select(); - } - } - }, - - "delete": function() { - execNativeCommand("Delete"); - - // Check if body is empty after the delete call if so then set the contents - // to an empty string and move the caret to any block produced by that operation - // this fixes the issue with root blocks not being properly produced after a delete call on IE - var body = editor.getBody(); - - if (dom.isEmpty(body)) { - editor.setContent(''); - - if (body.firstChild && dom.isBlock(body.firstChild)) { - editor.selection.setCursorLocation(body.firstChild, 0); - } else { - editor.selection.setCursorLocation(body, 0); - } - } - }, - - mceNewDocument: function() { - editor.setContent(''); - }, - - InsertLineBreak: function(command, ui, value) { - // We load the current event in from EnterKey.js when appropriate to heed - // certain event-specific variations such as ctrl-enter in a list - var evt = value; - var brElm, extraBr, marker; - var rng = selection.getRng(true); - new RangeUtils(dom).normalize(rng); - - var offset = rng.startOffset; - var container = rng.startContainer; - - // Resolve node index - if (container.nodeType == 1 && container.hasChildNodes()) { - var isAfterLastNodeInContainer = offset > container.childNodes.length - 1; - - container = container.childNodes[Math.min(offset, container.childNodes.length - 1)] || container; - if (isAfterLastNodeInContainer && container.nodeType == 3) { - offset = container.nodeValue.length; - } else { - offset = 0; - } - } - - var parentBlock = dom.getParent(container, dom.isBlock); - var parentBlockName = parentBlock ? parentBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5 - var containerBlock = parentBlock ? dom.getParent(parentBlock.parentNode, dom.isBlock) : null; - var containerBlockName = containerBlock ? containerBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5 - - // Enter inside block contained within a LI then split or insert before/after LI - var isControlKey = evt && evt.ctrlKey; - if (containerBlockName == 'LI' && !isControlKey) { - parentBlock = containerBlock; - parentBlockName = containerBlockName; - } - - // Walks the parent block to the right and look for BR elements - function hasRightSideContent() { - var walker = new TreeWalker(container, parentBlock), node; - var nonEmptyElementsMap = editor.schema.getNonEmptyElements(); - - while ((node = walker.next())) { - if (nonEmptyElementsMap[node.nodeName.toLowerCase()] || node.length > 0) { - return true; - } - } - } - - if (container && container.nodeType == 3 && offset >= container.nodeValue.length) { - // Insert extra BR element at the end block elements - if (!isOldIE && !hasRightSideContent()) { - brElm = dom.create('br'); - rng.insertNode(brElm); - rng.setStartAfter(brElm); - rng.setEndAfter(brElm); - extraBr = true; - } - } - - brElm = dom.create('br'); - rng.insertNode(brElm); - - // Rendering modes below IE8 doesn't display BR elements in PRE unless we have a \n before it - var documentMode = dom.doc.documentMode; - if (isOldIE && parentBlockName == 'PRE' && (!documentMode || documentMode < 8)) { - brElm.parentNode.insertBefore(dom.doc.createTextNode('\r'), brElm); - } - - // Insert temp marker and scroll to that - marker = dom.create('span', {}, ' '); - brElm.parentNode.insertBefore(marker, brElm); - selection.scrollIntoView(marker); - dom.remove(marker); - - if (!extraBr) { - rng.setStartAfter(brElm); - rng.setEndAfter(brElm); - } else { - rng.setStartBefore(brElm); - rng.setEndBefore(brElm); - } - - selection.setRng(rng); - editor.undoManager.add(); - - return TRUE; - } - }); - - // Add queryCommandState overrides - addCommands({ - // Override justify commands - 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull': function(command) { - var name = 'align' + command.substring(7); - var nodes = selection.isCollapsed() ? [dom.getParent(selection.getNode(), dom.isBlock)] : selection.getSelectedBlocks(); - var matches = map(nodes, function(node) { - return !!formatter.matchNode(node, name); - }); - return inArray(matches, TRUE) !== -1; - }, - - 'Bold,Italic,Underline,Strikethrough,Superscript,Subscript': function(command) { - return isFormatMatch(command); - }, - - mceBlockQuote: function() { - return isFormatMatch('blockquote'); - }, - - Outdent: function() { - var node; - - if (settings.inline_styles) { - if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft, 10) > 0) { - return TRUE; - } - - if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft, 10) > 0) { - return TRUE; - } - } - - return ( - queryCommandState('InsertUnorderedList') || - queryCommandState('InsertOrderedList') || - (!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE')) - ); - }, - - 'InsertUnorderedList,InsertOrderedList': function(command) { - var list = dom.getParent(selection.getNode(), 'ul,ol'); - - return list && - ( - command === 'insertunorderedlist' && list.tagName === 'UL' || - command === 'insertorderedlist' && list.tagName === 'OL' - ); - } - }, 'state'); - - // Add queryCommandValue overrides - addCommands({ - 'FontSize,FontName': function(command) { - var value = 0, parent; - - if ((parent = dom.getParent(selection.getNode(), 'span'))) { - if (command == 'fontsize') { - value = parent.style.fontSize; - } else { - value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase(); - } - } - - return value; - } - }, 'value'); - - // Add undo manager logic - addCommands({ - Undo: function() { - editor.undoManager.undo(); - }, - - Redo: function() { - editor.undoManager.redo(); - } - }); - }; -}); - -// Included from: js/tinymce/classes/util/URI.js +define( + 'tinymce.core.EditorCommands', + [ + 'tinymce.core.delete.DeleteCommands', + 'tinymce.core.dom.NodeType', + 'tinymce.core.dom.RangeUtils', + 'tinymce.core.dom.TreeWalker', + 'tinymce.core.Env', + 'tinymce.core.InsertContent', + 'tinymce.core.util.Tools' + ], + function (DeleteCommands, NodeType, RangeUtils, TreeWalker, Env, InsertContent, Tools) { + // Added for compression purposes + var each = Tools.each, extend = Tools.extend; + var map = Tools.map, inArray = Tools.inArray, explode = Tools.explode; + var isOldIE = Env.ie && Env.ie < 11; + var TRUE = true, FALSE = false; + + return function (editor) { + var dom, selection, formatter, + commands = { state: {}, exec: {}, value: {} }, + settings = editor.settings, + bookmark; + + editor.on('PreInit', function () { + dom = editor.dom; + selection = editor.selection; + settings = editor.settings; + formatter = editor.formatter; + }); + + /** + * Executes the specified command. + * + * @method execCommand + * @param {String} command Command to execute. + * @param {Boolean} ui Optional user interface state. + * @param {Object} value Optional value for command. + * @param {Object} args Optional extra arguments to the execCommand. + * @return {Boolean} true/false if the command was found or not. + */ + function execCommand(command, ui, value, args) { + var func, customCommand, state = 0; + + if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint)$/.test(command) && (!args || !args.skip_focus)) { + editor.focus(); + } + + args = editor.fire('BeforeExecCommand', { command: command, ui: ui, value: value }); + if (args.isDefaultPrevented()) { + return false; + } + + customCommand = command.toLowerCase(); + if ((func = commands.exec[customCommand])) { + func(customCommand, ui, value); + editor.fire('ExecCommand', { command: command, ui: ui, value: value }); + return true; + } + + // Plugin commands + each(editor.plugins, function (p) { + if (p.execCommand && p.execCommand(command, ui, value)) { + editor.fire('ExecCommand', { command: command, ui: ui, value: value }); + state = true; + return false; + } + }); + + if (state) { + return state; + } + + // Theme commands + if (editor.theme && editor.theme.execCommand && editor.theme.execCommand(command, ui, value)) { + editor.fire('ExecCommand', { command: command, ui: ui, value: value }); + return true; + } + + // Browser commands + try { + state = editor.getDoc().execCommand(command, ui, value); + } catch (ex) { + // Ignore old IE errors + } + + if (state) { + editor.fire('ExecCommand', { command: command, ui: ui, value: value }); + return true; + } + + return false; + } + + /** + * Queries the current state for a command for example if the current selection is "bold". + * + * @method queryCommandState + * @param {String} command Command to check the state of. + * @return {Boolean/Number} true/false if the selected contents is bold or not, -1 if it's not found. + */ + function queryCommandState(command) { + var func; + + // Is hidden then return undefined + if (editor.quirks.isHidden()) { + return; + } + + command = command.toLowerCase(); + if ((func = commands.state[command])) { + return func(command); + } + + // Browser commands + try { + return editor.getDoc().queryCommandState(command); + } catch (ex) { + // Fails sometimes see bug: 1896577 + } + + return false; + } + + /** + * Queries the command value for example the current fontsize. + * + * @method queryCommandValue + * @param {String} command Command to check the value of. + * @return {Object} Command value of false if it's not found. + */ + function queryCommandValue(command) { + var func; + + // Is hidden then return undefined + if (editor.quirks.isHidden()) { + return; + } + + command = command.toLowerCase(); + if ((func = commands.value[command])) { + return func(command); + } + + // Browser commands + try { + return editor.getDoc().queryCommandValue(command); + } catch (ex) { + // Fails sometimes see bug: 1896577 + } + } + + /** + * Adds commands to the command collection. + * + * @method addCommands + * @param {Object} commandList Name/value collection with commands to add, the names can also be comma separated. + * @param {String} type Optional type to add, defaults to exec. Can be value or state as well. + */ + function addCommands(commandList, type) { + type = type || 'exec'; + + each(commandList, function (callback, command) { + each(command.toLowerCase().split(','), function (command) { + commands[type][command] = callback; + }); + }); + } + + function addCommand(command, callback, scope) { + command = command.toLowerCase(); + commands.exec[command] = function (command, ui, value, args) { + return callback.call(scope || editor, ui, value, args); + }; + } + + /** + * Returns true/false if the command is supported or not. + * + * @method queryCommandSupported + * @param {String} command Command that we check support for. + * @return {Boolean} true/false if the command is supported or not. + */ + function queryCommandSupported(command) { + command = command.toLowerCase(); + + if (commands.exec[command]) { + return true; + } + + // Browser commands + try { + return editor.getDoc().queryCommandSupported(command); + } catch (ex) { + // Fails sometimes see bug: 1896577 + } + + return false; + } + + function addQueryStateHandler(command, callback, scope) { + command = command.toLowerCase(); + commands.state[command] = function () { + return callback.call(scope || editor); + }; + } + + function addQueryValueHandler(command, callback, scope) { + command = command.toLowerCase(); + commands.value[command] = function () { + return callback.call(scope || editor); + }; + } + + function hasCustomCommand(command) { + command = command.toLowerCase(); + return !!commands.exec[command]; + } + + // Expose public methods + extend(this, { + execCommand: execCommand, + queryCommandState: queryCommandState, + queryCommandValue: queryCommandValue, + queryCommandSupported: queryCommandSupported, + addCommands: addCommands, + addCommand: addCommand, + addQueryStateHandler: addQueryStateHandler, + addQueryValueHandler: addQueryValueHandler, + hasCustomCommand: hasCustomCommand + }); + + // Private methods + + function execNativeCommand(command, ui, value) { + if (ui === undefined) { + ui = FALSE; + } + + if (value === undefined) { + value = null; + } + + return editor.getDoc().execCommand(command, ui, value); + } + + function isFormatMatch(name) { + return formatter.match(name); + } + + function toggleFormat(name, value) { + formatter.toggle(name, value ? { value: value } : undefined); + editor.nodeChanged(); + } + + function storeSelection(type) { + bookmark = selection.getBookmark(type); + } + + function restoreSelection() { + selection.moveToBookmark(bookmark); + } + + // Add execCommand overrides + addCommands({ + // Ignore these, added for compatibility + 'mceResetDesignMode,mceBeginUndoLevel': function () { }, + + // Add undo manager logic + 'mceEndUndoLevel,mceAddUndoLevel': function () { + editor.undoManager.add(); + }, + + 'Cut,Copy,Paste': function (command) { + var doc = editor.getDoc(), failed; + + // Try executing the native command + try { + execNativeCommand(command); + } catch (ex) { + // Command failed + failed = TRUE; + } + + // Chrome reports the paste command as supported however older IE:s will return false for cut/paste + if (command === 'paste' && !doc.queryCommandEnabled(command)) { + failed = true; + } + + // Present alert message about clipboard access not being available + if (failed || !doc.queryCommandSupported(command)) { + var msg = editor.translate( + "Your browser doesn't support direct access to the clipboard. " + + "Please use the Ctrl+X/C/V keyboard shortcuts instead." + ); + + if (Env.mac) { + msg = msg.replace(/Ctrl\+/g, '\u2318+'); + } + + editor.notificationManager.open({ text: msg, type: 'error' }); + } + }, + + // Override unlink command + unlink: function () { + if (selection.isCollapsed()) { + var elm = editor.dom.getParent(editor.selection.getStart(), 'a'); + if (elm) { + editor.dom.remove(elm, true); + } + + return; + } + + formatter.remove("link"); + }, + + // Override justify commands to use the text formatter engine + 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull,JustifyNone': function (command) { + var align = command.substring(7); + + if (align == 'full') { + align = 'justify'; + } + + // Remove all other alignments first + each('left,center,right,justify'.split(','), function (name) { + if (align != name) { + formatter.remove('align' + name); + } + }); + + if (align != 'none') { + toggleFormat('align' + align); + } + }, + + // Override list commands to fix WebKit bug + 'InsertUnorderedList,InsertOrderedList': function (command) { + var listElm, listParent; + + execNativeCommand(command); + + // WebKit produces lists within block elements so we need to split them + // we will replace the native list creation logic to custom logic later on + // TODO: Remove this when the list creation logic is removed + listElm = dom.getParent(selection.getNode(), 'ol,ul'); + if (listElm) { + listParent = listElm.parentNode; + + // If list is within a text block then split that block + if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) { + storeSelection(); + dom.split(listParent, listElm); + restoreSelection(); + } + } + }, + + // Override commands to use the text formatter engine + 'Bold,Italic,Underline,Strikethrough,Superscript,Subscript': function (command) { + toggleFormat(command); + }, + + // Override commands to use the text formatter engine + 'ForeColor,HiliteColor,FontName': function (command, ui, value) { + toggleFormat(command, value); + }, + + FontSize: function (command, ui, value) { + var fontClasses, fontSizes; + + // Convert font size 1-7 to styles + if (value >= 1 && value <= 7) { + fontSizes = explode(settings.font_size_style_values); + fontClasses = explode(settings.font_size_classes); + + if (fontClasses) { + value = fontClasses[value - 1] || value; + } else { + value = fontSizes[value - 1] || value; + } + } + + toggleFormat(command, value); + }, + + RemoveFormat: function (command) { + formatter.remove(command); + }, + + mceBlockQuote: function () { + toggleFormat('blockquote'); + }, + + FormatBlock: function (command, ui, value) { + return toggleFormat(value || 'p'); + }, + + mceCleanup: function () { + var bookmark = selection.getBookmark(); + + editor.setContent(editor.getContent({ cleanup: TRUE }), { cleanup: TRUE }); + + selection.moveToBookmark(bookmark); + }, + + mceRemoveNode: function (command, ui, value) { + var node = value || selection.getNode(); + + // Make sure that the body node isn't removed + if (node != editor.getBody()) { + storeSelection(); + editor.dom.remove(node, TRUE); + restoreSelection(); + } + }, + + mceSelectNodeDepth: function (command, ui, value) { + var counter = 0; + + dom.getParent(selection.getNode(), function (node) { + if (node.nodeType == 1 && counter++ == value) { + selection.select(node); + return FALSE; + } + }, editor.getBody()); + }, + + mceSelectNode: function (command, ui, value) { + selection.select(value); + }, + + mceInsertContent: function (command, ui, value) { + InsertContent.insertAtCaret(editor, value); + }, + + mceInsertRawHTML: function (command, ui, value) { + selection.setContent('tiny_mce_marker'); + editor.setContent( + editor.getContent().replace(/tiny_mce_marker/g, function () { + return value; + }) + ); + }, + + mceToggleFormat: function (command, ui, value) { + toggleFormat(value); + }, + + mceSetContent: function (command, ui, value) { + editor.setContent(value); + }, + + 'Indent,Outdent': function (command) { + var intentValue, indentUnit, value; + + // Setup indent level + intentValue = settings.indentation; + indentUnit = /[a-z%]+$/i.exec(intentValue); + intentValue = parseInt(intentValue, 10); + + if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) { + // If forced_root_blocks is set to false we don't have a block to indent so lets create a div + if (!settings.forced_root_block && !dom.getParent(selection.getNode(), dom.isBlock)) { + formatter.apply('div'); + } + + each(selection.getSelectedBlocks(), function (element) { + if (dom.getContentEditable(element) === "false") { + return; + } + + if (element.nodeName !== "LI") { + var indentStyleName = editor.getParam('indent_use_margin', false) ? 'margin' : 'padding'; + indentStyleName = element.nodeName === 'TABLE' ? 'margin' : indentStyleName; + indentStyleName += dom.getStyle(element, 'direction', true) == 'rtl' ? 'Right' : 'Left'; + + if (command == 'outdent') { + value = Math.max(0, parseInt(element.style[indentStyleName] || 0, 10) - intentValue); + dom.setStyle(element, indentStyleName, value ? value + indentUnit : ''); + } else { + value = (parseInt(element.style[indentStyleName] || 0, 10) + intentValue) + indentUnit; + dom.setStyle(element, indentStyleName, value); + } + } + }); + } else { + execNativeCommand(command); + } + }, + + mceRepaint: function () { + }, + + InsertHorizontalRule: function () { + editor.execCommand('mceInsertContent', false, '|
+ rng = selection.getRng(); + if (!rng.item) { + rng.moveToElementText(root); + rng.select(); + } + } + }, + + "delete": function () { + DeleteCommands.deleteCommand(editor); + }, + + "forwardDelete": function () { + DeleteCommands.forwardDeleteCommand(editor); + }, + + mceNewDocument: function () { + editor.setContent(''); + }, + + InsertLineBreak: function (command, ui, value) { + // We load the current event in from EnterKey.js when appropriate to heed + // certain event-specific variations such as ctrl-enter in a list + var evt = value; + var brElm, extraBr, marker; + var rng = selection.getRng(true); + new RangeUtils(dom).normalize(rng); + + var offset = rng.startOffset; + var container = rng.startContainer; + + // Resolve node index + if (container.nodeType == 1 && container.hasChildNodes()) { + var isAfterLastNodeInContainer = offset > container.childNodes.length - 1; + + container = container.childNodes[Math.min(offset, container.childNodes.length - 1)] || container; + if (isAfterLastNodeInContainer && container.nodeType == 3) { + offset = container.nodeValue.length; + } else { + offset = 0; + } + } + + var parentBlock = dom.getParent(container, dom.isBlock); + var parentBlockName = parentBlock ? parentBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5 + var containerBlock = parentBlock ? dom.getParent(parentBlock.parentNode, dom.isBlock) : null; + var containerBlockName = containerBlock ? containerBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5 + + // Enter inside block contained within a LI then split or insert before/after LI + var isControlKey = evt && evt.ctrlKey; + if (containerBlockName == 'LI' && !isControlKey) { + parentBlock = containerBlock; + parentBlockName = containerBlockName; + } + + // Walks the parent block to the right and look for BR elements + function hasRightSideContent() { + var walker = new TreeWalker(container, parentBlock), node; + var nonEmptyElementsMap = editor.schema.getNonEmptyElements(); + + while ((node = walker.next())) { + if (nonEmptyElementsMap[node.nodeName.toLowerCase()] || node.length > 0) { + return true; + } + } + } + + if (container && container.nodeType == 3 && offset >= container.nodeValue.length) { + // Insert extra BR element at the end block elements + if (!isOldIE && !hasRightSideContent()) { + brElm = dom.create('br'); + rng.insertNode(brElm); + rng.setStartAfter(brElm); + rng.setEndAfter(brElm); + extraBr = true; + } + } + + brElm = dom.create('br'); + rng.insertNode(brElm); + + // Rendering modes below IE8 doesn't display BR elements in PRE unless we have a \n before it + var documentMode = dom.doc.documentMode; + if (isOldIE && parentBlockName == 'PRE' && (!documentMode || documentMode < 8)) { + brElm.parentNode.insertBefore(dom.doc.createTextNode('\r'), brElm); + } + + // Insert temp marker and scroll to that + marker = dom.create('span', {}, ' '); + brElm.parentNode.insertBefore(marker, brElm); + selection.scrollIntoView(marker); + dom.remove(marker); + + if (!extraBr) { + rng.setStartAfter(brElm); + rng.setEndAfter(brElm); + } else { + rng.setStartBefore(brElm); + rng.setEndBefore(brElm); + } + + selection.setRng(rng); + editor.undoManager.add(); + + return TRUE; + } + }); + + // Add queryCommandState overrides + addCommands({ + // Override justify commands + 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull': function (command) { + var name = 'align' + command.substring(7); + var nodes = selection.isCollapsed() ? [dom.getParent(selection.getNode(), dom.isBlock)] : selection.getSelectedBlocks(); + var matches = map(nodes, function (node) { + return !!formatter.matchNode(node, name); + }); + return inArray(matches, TRUE) !== -1; + }, + + 'Bold,Italic,Underline,Strikethrough,Superscript,Subscript': function (command) { + return isFormatMatch(command); + }, + + mceBlockQuote: function () { + return isFormatMatch('blockquote'); + }, + + Outdent: function () { + var node; + + if (settings.inline_styles) { + if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft, 10) > 0) { + return TRUE; + } + + if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft, 10) > 0) { + return TRUE; + } + } + + return ( + queryCommandState('InsertUnorderedList') || + queryCommandState('InsertOrderedList') || + (!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE')) + ); + }, + + 'InsertUnorderedList,InsertOrderedList': function (command) { + var list = dom.getParent(selection.getNode(), 'ul,ol'); + + return list && + ( + command === 'insertunorderedlist' && list.tagName === 'UL' || + command === 'insertorderedlist' && list.tagName === 'OL' + ); + } + }, 'state'); + + // Add queryCommandValue overrides + addCommands({ + 'FontSize,FontName': function (command) { + var value = 0, parent; + + if ((parent = dom.getParent(selection.getNode(), 'span'))) { + if (command == 'fontsize') { + value = parent.style.fontSize; + } else { + value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase(); + } + } + + return value; + } + }, 'value'); + + // Add undo manager logic + addCommands({ + Undo: function () { + editor.undoManager.undo(); + }, + + Redo: function () { + editor.undoManager.redo(); + } + }); + }; + } +); /** * URI.js * * Released under LGPL License. - * Copyright (c) 1999-2015 Ephox Corp. All rights reserved + * Copyright (c) 1999-2017 Ephox Corp. All rights reserved * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing @@ -25224,432 +29313,435 @@ define("tinymce/EditorCommands", [ * This class handles parsing, modification and serialization of URI/URL strings. * @class tinymce.util.URI */ -define("tinymce/util/URI", [ - "tinymce/util/Tools" -], function(Tools) { - var each = Tools.each, trim = Tools.trim; - var queryParts = "source protocol authority userInfo user password host port relative path directory file query anchor".split(' '); - var DEFAULT_PORTS = { - 'ftp': 21, - 'http': 80, - 'https': 443, - 'mailto': 25 - }; - - /** - * Constructs a new URI instance. - * - * @constructor - * @method URI - * @param {String} url URI string to parse. - * @param {Object} settings Optional settings object. - */ - function URI(url, settings) { - var self = this, baseUri, base_url; - - url = trim(url); - settings = self.settings = settings || {}; - baseUri = settings.base_uri; - - // Strange app protocol that isn't http/https or local anchor - // For example: mailto,skype,tel etc. - if (/^([\w\-]+):([^\/]{2})/i.test(url) || /^\s*#/.test(url)) { - self.source = url; - return; - } - - var isProtocolRelative = url.indexOf('//') === 0; - - // Absolute path with no host, fake host and protocol - if (url.indexOf('/') === 0 && !isProtocolRelative) { - url = (baseUri ? baseUri.protocol || 'http' : 'http') + '://mce_host' + url; - } - - // Relative path http:// or protocol relative //path - if (!/^[\w\-]*:?\/\//.test(url)) { - base_url = settings.base_uri ? settings.base_uri.path : new URI(location.href).directory; - if (settings.base_uri.protocol === "") { - url = '//mce_host' + self.toAbsPath(base_url, url); - } else { - url = /([^#?]*)([#?]?.*)/.exec(url); - url = ((baseUri && baseUri.protocol) || 'http') + '://mce_host' + self.toAbsPath(base_url, url[1]) + url[2]; - } - } - - // Parse URL (Credits goes to Steave, http://blog.stevenlevithan.com/archives/parseuri) - url = url.replace(/@@/g, '(mce_at)'); // Zope 3 workaround, they use @@something - - /*jshint maxlen: 255 */ - /*eslint max-len: 0 */ - url = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(url); - - each(queryParts, function(v, i) { - var part = url[i]; - - // Zope 3 workaround, they use @@something - if (part) { - part = part.replace(/\(mce_at\)/g, '@@'); - } - - self[v] = part; - }); - - if (baseUri) { - if (!self.protocol) { - self.protocol = baseUri.protocol; - } - - if (!self.userInfo) { - self.userInfo = baseUri.userInfo; - } - - if (!self.port && self.host === 'mce_host') { - self.port = baseUri.port; - } - - if (!self.host || self.host === 'mce_host') { - self.host = baseUri.host; - } - - self.source = ''; - } - - if (isProtocolRelative) { - self.protocol = ''; - } - - //t.path = t.path || '/'; - } - - URI.prototype = { - /** - * Sets the internal path part of the URI. - * - * @method setPath - * @param {string} path Path string to set. - */ - setPath: function(path) { - var self = this; - - path = /^(.*?)\/?(\w+)?$/.exec(path); - - // Update path parts - self.path = path[0]; - self.directory = path[1]; - self.file = path[2]; - - // Rebuild source - self.source = ''; - self.getURI(); - }, - - /** - * Converts the specified URI into a relative URI based on the current URI instance location. - * - * @method toRelative - * @param {String} uri URI to convert into a relative path/URI. - * @return {String} Relative URI from the point specified in the current URI instance. - * @example - * // Converts an absolute URL to an relative URL url will be somedir/somefile.htm - * var url = new tinymce.util.URI('http://www.site.com/dir/').toRelative('http://www.site.com/dir/somedir/somefile.htm'); - */ - toRelative: function(uri) { - var self = this, output; - - if (uri === "./") { - return uri; - } - - uri = new URI(uri, {base_uri: self}); - - // Not on same domain/port or protocol - if ((uri.host != 'mce_host' && self.host != uri.host && uri.host) || self.port != uri.port || - (self.protocol != uri.protocol && uri.protocol !== "")) { - return uri.getURI(); - } - - var tu = self.getURI(), uu = uri.getURI(); - - // Allow usage of the base_uri when relative_urls = true - if (tu == uu || (tu.charAt(tu.length - 1) == "/" && tu.substr(0, tu.length - 1) == uu)) { - return tu; - } - - output = self.toRelPath(self.path, uri.path); - - // Add query - if (uri.query) { - output += '?' + uri.query; - } - - // Add anchor - if (uri.anchor) { - output += '#' + uri.anchor; - } - - return output; - }, - - /** - * Converts the specified URI into a absolute URI based on the current URI instance location. - * - * @method toAbsolute - * @param {String} uri URI to convert into a relative path/URI. - * @param {Boolean} noHost No host and protocol prefix. - * @return {String} Absolute URI from the point specified in the current URI instance. - * @example - * // Converts an relative URL to an absolute URL url will be http://www.site.com/dir/somedir/somefile.htm - * var url = new tinymce.util.URI('http://www.site.com/dir/').toAbsolute('somedir/somefile.htm'); - */ - toAbsolute: function(uri, noHost) { - uri = new URI(uri, {base_uri: this}); - - return uri.getURI(noHost && this.isSameOrigin(uri)); - }, - - /** - * Determine whether the given URI has the same origin as this URI. Based on RFC-6454. - * Supports default ports for protocols listed in DEFAULT_PORTS. Unsupported protocols will fail safe: they - * won't match, if the port specifications differ. - * - * @method isSameOrigin - * @param {tinymce.util.URI} uri Uri instance to compare. - * @returns {Boolean} True if the origins are the same. - */ - isSameOrigin: function(uri) { - if (this.host == uri.host && this.protocol == uri.protocol) { - if (this.port == uri.port) { - return true; - } - - var defaultPort = DEFAULT_PORTS[this.protocol]; - if (defaultPort && ((this.port || defaultPort) == (uri.port || defaultPort))) { - return true; - } - } - - return false; - }, - - /** - * Converts a absolute path into a relative path. - * - * @method toRelPath - * @param {String} base Base point to convert the path from. - * @param {String} path Absolute path to convert into a relative path. - */ - toRelPath: function(base, path) { - var items, breakPoint = 0, out = '', i, l; - - // Split the paths - base = base.substring(0, base.lastIndexOf('/')); - base = base.split('/'); - items = path.split('/'); - - if (base.length >= items.length) { - for (i = 0, l = base.length; i < l; i++) { - if (i >= items.length || base[i] != items[i]) { - breakPoint = i + 1; - break; - } - } - } - - if (base.length < items.length) { - for (i = 0, l = items.length; i < l; i++) { - if (i >= base.length || base[i] != items[i]) { - breakPoint = i + 1; - break; - } - } - } - - if (breakPoint === 1) { - return path; - } - - for (i = 0, l = base.length - (breakPoint - 1); i < l; i++) { - out += "../"; - } - - for (i = breakPoint - 1, l = items.length; i < l; i++) { - if (i != breakPoint - 1) { - out += "/" + items[i]; - } else { - out += items[i]; - } - } - - return out; - }, - - /** - * Converts a relative path into a absolute path. - * - * @method toAbsPath - * @param {String} base Base point to convert the path from. - * @param {String} path Relative path to convert into an absolute path. - */ - toAbsPath: function(base, path) { - var i, nb = 0, o = [], tr, outPath; - - // Split paths - tr = /\/$/.test(path) ? '/' : ''; - base = base.split('/'); - path = path.split('/'); - - // Remove empty chunks - each(base, function(k) { - if (k) { - o.push(k); - } - }); - - base = o; - - // Merge relURLParts chunks - for (i = path.length - 1, o = []; i >= 0; i--) { - // Ignore empty or . - if (path[i].length === 0 || path[i] === ".") { - continue; - } - - // Is parent - if (path[i] === '..') { - nb++; - continue; - } - - // Move up - if (nb > 0) { - nb--; - continue; - } - - o.push(path[i]); - } - - i = base.length - nb; - - // If /a/b/c or / - if (i <= 0) { - outPath = o.reverse().join('/'); - } else { - outPath = base.slice(0, i).join('/') + '/' + o.reverse().join('/'); - } - - // Add front / if it's needed - if (outPath.indexOf('/') !== 0) { - outPath = '/' + outPath; - } - - // Add traling / if it's needed - if (tr && outPath.lastIndexOf('/') !== outPath.length - 1) { - outPath += tr; - } - - return outPath; - }, - - /** - * Returns the full URI of the internal structure. - * - * @method getURI - * @param {Boolean} noProtoHost Optional no host and protocol part. Defaults to false. - */ - getURI: function(noProtoHost) { - var s, self = this; - - // Rebuild source - if (!self.source || noProtoHost) { - s = ''; - - if (!noProtoHost) { - if (self.protocol) { - s += self.protocol + '://'; - } else { - s += '//'; - } - - if (self.userInfo) { - s += self.userInfo + '@'; - } - - if (self.host) { - s += self.host; - } - - if (self.port) { - s += ':' + self.port; - } - } - - if (self.path) { - s += self.path; - } - - if (self.query) { - s += '?' + self.query; - } - - if (self.anchor) { - s += '#' + self.anchor; - } - - self.source = s; - } - - return self.source; - } - }; - - URI.parseDataUri = function(uri) { - var type, matches; - - uri = decodeURIComponent(uri).split(','); - - matches = /data:([^;]+)/.exec(uri[0]); - if (matches) { - type = matches[1]; - } - - return { - type: type, - data: uri[1] - }; - }; - - URI.getDocumentBaseUrl = function(loc) { - var baseUrl; - - // Pass applewebdata:// and other non web protocols though - if (loc.protocol.indexOf('http') !== 0 && loc.protocol !== 'file:') { - baseUrl = loc.href; - } else { - baseUrl = loc.protocol + '//' + loc.host + loc.pathname; - } - - if (/^[^:]+:\/\/\/?[^\/]+\//.test(baseUrl)) { - baseUrl = baseUrl.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, ''); - - if (!/[\/\\]$/.test(baseUrl)) { - baseUrl += '/'; - } - } - - return baseUrl; - }; - - return URI; -}); - -// Included from: js/tinymce/classes/util/Class.js +define( + 'tinymce.core.util.URI', + [ + 'global!document', + 'tinymce.core.util.Tools' + ], + function (document, Tools) { + var each = Tools.each, trim = Tools.trim; + var queryParts = "source protocol authority userInfo user password host port relative path directory file query anchor".split(' '); + var DEFAULT_PORTS = { + 'ftp': 21, + 'http': 80, + 'https': 443, + 'mailto': 25 + }; + + /** + * Constructs a new URI instance. + * + * @constructor + * @method URI + * @param {String} url URI string to parse. + * @param {Object} settings Optional settings object. + */ + function URI(url, settings) { + var self = this, baseUri, baseUrl; + + url = trim(url); + settings = self.settings = settings || {}; + baseUri = settings.base_uri; + + // Strange app protocol that isn't http/https or local anchor + // For example: mailto,skype,tel etc. + if (/^([\w\-]+):([^\/]{2})/i.test(url) || /^\s*#/.test(url)) { + self.source = url; + return; + } + + var isProtocolRelative = url.indexOf('//') === 0; + + // Absolute path with no host, fake host and protocol + if (url.indexOf('/') === 0 && !isProtocolRelative) { + url = (baseUri ? baseUri.protocol || 'http' : 'http') + '://mce_host' + url; + } + + // Relative path http:// or protocol relative //path + if (!/^[\w\-]*:?\/\//.test(url)) { + baseUrl = settings.base_uri ? settings.base_uri.path : new URI(document.location.href).directory; + if (settings.base_uri.protocol === "") { + url = '//mce_host' + self.toAbsPath(baseUrl, url); + } else { + url = /([^#?]*)([#?]?.*)/.exec(url); + url = ((baseUri && baseUri.protocol) || 'http') + '://mce_host' + self.toAbsPath(baseUrl, url[1]) + url[2]; + } + } + + // Parse URL (Credits goes to Steave, http://blog.stevenlevithan.com/archives/parseuri) + url = url.replace(/@@/g, '(mce_at)'); // Zope 3 workaround, they use @@something + + /*jshint maxlen: 255 */ + /*eslint max-len: 0 */ + url = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(url); + + each(queryParts, function (v, i) { + var part = url[i]; + + // Zope 3 workaround, they use @@something + if (part) { + part = part.replace(/\(mce_at\)/g, '@@'); + } + + self[v] = part; + }); + + if (baseUri) { + if (!self.protocol) { + self.protocol = baseUri.protocol; + } + + if (!self.userInfo) { + self.userInfo = baseUri.userInfo; + } + + if (!self.port && self.host === 'mce_host') { + self.port = baseUri.port; + } + + if (!self.host || self.host === 'mce_host') { + self.host = baseUri.host; + } + + self.source = ''; + } + + if (isProtocolRelative) { + self.protocol = ''; + } + + //t.path = t.path || '/'; + } + + URI.prototype = { + /** + * Sets the internal path part of the URI. + * + * @method setPath + * @param {string} path Path string to set. + */ + setPath: function (path) { + var self = this; + + path = /^(.*?)\/?(\w+)?$/.exec(path); + + // Update path parts + self.path = path[0]; + self.directory = path[1]; + self.file = path[2]; + + // Rebuild source + self.source = ''; + self.getURI(); + }, + + /** + * Converts the specified URI into a relative URI based on the current URI instance location. + * + * @method toRelative + * @param {String} uri URI to convert into a relative path/URI. + * @return {String} Relative URI from the point specified in the current URI instance. + * @example + * // Converts an absolute URL to an relative URL url will be somedir/somefile.htm + * var url = new tinymce.util.URI('http://www.site.com/dir/').toRelative('http://www.site.com/dir/somedir/somefile.htm'); + */ + toRelative: function (uri) { + var self = this, output; + + if (uri === "./") { + return uri; + } + + uri = new URI(uri, { base_uri: self }); + + // Not on same domain/port or protocol + if ((uri.host != 'mce_host' && self.host != uri.host && uri.host) || self.port != uri.port || + (self.protocol != uri.protocol && uri.protocol !== "")) { + return uri.getURI(); + } + + var tu = self.getURI(), uu = uri.getURI(); + + // Allow usage of the base_uri when relative_urls = true + if (tu == uu || (tu.charAt(tu.length - 1) == "/" && tu.substr(0, tu.length - 1) == uu)) { + return tu; + } + + output = self.toRelPath(self.path, uri.path); + + // Add query + if (uri.query) { + output += '?' + uri.query; + } + + // Add anchor + if (uri.anchor) { + output += '#' + uri.anchor; + } + + return output; + }, + + /** + * Converts the specified URI into a absolute URI based on the current URI instance location. + * + * @method toAbsolute + * @param {String} uri URI to convert into a relative path/URI. + * @param {Boolean} noHost No host and protocol prefix. + * @return {String} Absolute URI from the point specified in the current URI instance. + * @example + * // Converts an relative URL to an absolute URL url will be http://www.site.com/dir/somedir/somefile.htm + * var url = new tinymce.util.URI('http://www.site.com/dir/').toAbsolute('somedir/somefile.htm'); + */ + toAbsolute: function (uri, noHost) { + uri = new URI(uri, { base_uri: this }); + + return uri.getURI(noHost && this.isSameOrigin(uri)); + }, + + /** + * Determine whether the given URI has the same origin as this URI. Based on RFC-6454. + * Supports default ports for protocols listed in DEFAULT_PORTS. Unsupported protocols will fail safe: they + * won't match, if the port specifications differ. + * + * @method isSameOrigin + * @param {tinymce.util.URI} uri Uri instance to compare. + * @returns {Boolean} True if the origins are the same. + */ + isSameOrigin: function (uri) { + if (this.host == uri.host && this.protocol == uri.protocol) { + if (this.port == uri.port) { + return true; + } + + var defaultPort = DEFAULT_PORTS[this.protocol]; + if (defaultPort && ((this.port || defaultPort) == (uri.port || defaultPort))) { + return true; + } + } + + return false; + }, + + /** + * Converts a absolute path into a relative path. + * + * @method toRelPath + * @param {String} base Base point to convert the path from. + * @param {String} path Absolute path to convert into a relative path. + */ + toRelPath: function (base, path) { + var items, breakPoint = 0, out = '', i, l; + + // Split the paths + base = base.substring(0, base.lastIndexOf('/')); + base = base.split('/'); + items = path.split('/'); + + if (base.length >= items.length) { + for (i = 0, l = base.length; i < l; i++) { + if (i >= items.length || base[i] != items[i]) { + breakPoint = i + 1; + break; + } + } + } + + if (base.length < items.length) { + for (i = 0, l = items.length; i < l; i++) { + if (i >= base.length || base[i] != items[i]) { + breakPoint = i + 1; + break; + } + } + } + + if (breakPoint === 1) { + return path; + } + + for (i = 0, l = base.length - (breakPoint - 1); i < l; i++) { + out += "../"; + } + + for (i = breakPoint - 1, l = items.length; i < l; i++) { + if (i != breakPoint - 1) { + out += "/" + items[i]; + } else { + out += items[i]; + } + } + + return out; + }, + + /** + * Converts a relative path into a absolute path. + * + * @method toAbsPath + * @param {String} base Base point to convert the path from. + * @param {String} path Relative path to convert into an absolute path. + */ + toAbsPath: function (base, path) { + var i, nb = 0, o = [], tr, outPath; + + // Split paths + tr = /\/$/.test(path) ? '/' : ''; + base = base.split('/'); + path = path.split('/'); + + // Remove empty chunks + each(base, function (k) { + if (k) { + o.push(k); + } + }); + + base = o; + + // Merge relURLParts chunks + for (i = path.length - 1, o = []; i >= 0; i--) { + // Ignore empty or . + if (path[i].length === 0 || path[i] === ".") { + continue; + } + + // Is parent + if (path[i] === '..') { + nb++; + continue; + } + + // Move up + if (nb > 0) { + nb--; + continue; + } + + o.push(path[i]); + } + + i = base.length - nb; + + // If /a/b/c or / + if (i <= 0) { + outPath = o.reverse().join('/'); + } else { + outPath = base.slice(0, i).join('/') + '/' + o.reverse().join('/'); + } + + // Add front / if it's needed + if (outPath.indexOf('/') !== 0) { + outPath = '/' + outPath; + } + + // Add traling / if it's needed + if (tr && outPath.lastIndexOf('/') !== outPath.length - 1) { + outPath += tr; + } + + return outPath; + }, + + /** + * Returns the full URI of the internal structure. + * + * @method getURI + * @param {Boolean} noProtoHost Optional no host and protocol part. Defaults to false. + */ + getURI: function (noProtoHost) { + var s, self = this; + + // Rebuild source + if (!self.source || noProtoHost) { + s = ''; + + if (!noProtoHost) { + if (self.protocol) { + s += self.protocol + '://'; + } else { + s += '//'; + } + + if (self.userInfo) { + s += self.userInfo + '@'; + } + + if (self.host) { + s += self.host; + } + + if (self.port) { + s += ':' + self.port; + } + } + + if (self.path) { + s += self.path; + } + + if (self.query) { + s += '?' + self.query; + } + + if (self.anchor) { + s += '#' + self.anchor; + } + + self.source = s; + } + + return self.source; + } + }; + + URI.parseDataUri = function (uri) { + var type, matches; + + uri = decodeURIComponent(uri).split(','); + + matches = /data:([^;]+)/.exec(uri[0]); + if (matches) { + type = matches[1]; + } + + return { + type: type, + data: uri[1] + }; + }; + + URI.getDocumentBaseUrl = function (loc) { + var baseUrl; + + // Pass applewebdata:// and other non web protocols though + if (loc.protocol.indexOf('http') !== 0 && loc.protocol !== 'file:') { + baseUrl = loc.href; + } else { + baseUrl = loc.protocol + '//' + loc.host + loc.pathname; + } + + if (/^[^:]+:\/\/\/?[^\/]+\//.test(baseUrl)) { + baseUrl = baseUrl.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, ''); + + if (!/[\/\\]$/.test(baseUrl)) { + baseUrl += '/'; + } + } + + return baseUrl; + }; + + return URI; + } +); /** * Class.js * * Released under LGPL License. - * Copyright (c) 1999-2015 Ephox Corp. All rights reserved + * Copyright (c) 1999-2017 Ephox Corp. All rights reserved * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing @@ -25666,160 +29758,161 @@ define("tinymce/util/URI", [ * * Static functions * * Defaults settings */ -define("tinymce/util/Class", [ - "tinymce/util/Tools" -], function(Tools) { - var each = Tools.each, extend = Tools.extend; +define( + 'tinymce.core.util.Class', + [ + "tinymce.core.util.Tools" + ], + function (Tools) { + var each = Tools.each, extend = Tools.extend; - var extendClass, initializing; + var extendClass, initializing; - function Class() { - } + function Class() { + } - // Provides classical inheritance, based on code made by John Resig - Class.extend = extendClass = function(prop) { - var self = this, _super = self.prototype, prototype, name, member; + // Provides classical inheritance, based on code made by John Resig + Class.extend = extendClass = function (prop) { + var self = this, _super = self.prototype, prototype, name, member; - // The dummy class constructor - function Class() { - var i, mixins, mixin, self = this; + // The dummy class constructor + function Class() { + var i, mixins, mixin, self = this; - // All construction is actually done in the init method - if (!initializing) { - // Run class constuctor - if (self.init) { - self.init.apply(self, arguments); - } + // All construction is actually done in the init method + if (!initializing) { + // Run class constuctor + if (self.init) { + self.init.apply(self, arguments); + } - // Run mixin constructors - mixins = self.Mixins; - if (mixins) { - i = mixins.length; - while (i--) { - mixin = mixins[i]; - if (mixin.init) { - mixin.init.apply(self, arguments); - } - } - } - } - } + // Run mixin constructors + mixins = self.Mixins; + if (mixins) { + i = mixins.length; + while (i--) { + mixin = mixins[i]; + if (mixin.init) { + mixin.init.apply(self, arguments); + } + } + } + } + } - // Dummy function, needs to be extended in order to provide functionality - function dummy() { - return this; - } + // Dummy function, needs to be extended in order to provide functionality + function dummy() { + return this; + } - // Creates a overloaded method for the class - // this enables you to use this._super(); to call the super function - function createMethod(name, fn) { - return function() { - var self = this, tmp = self._super, ret; + // Creates a overloaded method for the class + // this enables you to use this._super(); to call the super function + function createMethod(name, fn) { + return function () { + var self = this, tmp = self._super, ret; - self._super = _super[name]; - ret = fn.apply(self, arguments); - self._super = tmp; + self._super = _super[name]; + ret = fn.apply(self, arguments); + self._super = tmp; - return ret; - }; - } + return ret; + }; + } - // Instantiate a base class (but only create the instance, - // don't run the init constructor) - initializing = true; + // Instantiate a base class (but only create the instance, + // don't run the init constructor) + initializing = true; - /*eslint new-cap:0 */ - prototype = new self(); - initializing = false; + /*eslint new-cap:0 */ + prototype = new self(); + initializing = false; - // Add mixins - if (prop.Mixins) { - each(prop.Mixins, function(mixin) { - for (var name in mixin) { - if (name !== "init") { - prop[name] = mixin[name]; - } - } - }); + // Add mixins + if (prop.Mixins) { + each(prop.Mixins, function (mixin) { + for (var name in mixin) { + if (name !== "init") { + prop[name] = mixin[name]; + } + } + }); - if (_super.Mixins) { - prop.Mixins = _super.Mixins.concat(prop.Mixins); - } - } + if (_super.Mixins) { + prop.Mixins = _super.Mixins.concat(prop.Mixins); + } + } - // Generate dummy methods - if (prop.Methods) { - each(prop.Methods.split(','), function(name) { - prop[name] = dummy; - }); - } + // Generate dummy methods + if (prop.Methods) { + each(prop.Methods.split(','), function (name) { + prop[name] = dummy; + }); + } - // Generate property methods - if (prop.Properties) { - each(prop.Properties.split(','), function(name) { - var fieldName = '_' + name; + // Generate property methods + if (prop.Properties) { + each(prop.Properties.split(','), function (name) { + var fieldName = '_' + name; - prop[name] = function(value) { - var self = this, undef; + prop[name] = function (value) { + var self = this, undef; - // Set value - if (value !== undef) { - self[fieldName] = value; + // Set value + if (value !== undef) { + self[fieldName] = value; - return self; - } + return self; + } - // Get value - return self[fieldName]; - }; - }); - } + // Get value + return self[fieldName]; + }; + }); + } - // Static functions - if (prop.Statics) { - each(prop.Statics, function(func, name) { - Class[name] = func; - }); - } + // Static functions + if (prop.Statics) { + each(prop.Statics, function (func, name) { + Class[name] = func; + }); + } - // Default settings - if (prop.Defaults && _super.Defaults) { - prop.Defaults = extend({}, _super.Defaults, prop.Defaults); - } + // Default settings + if (prop.Defaults && _super.Defaults) { + prop.Defaults = extend({}, _super.Defaults, prop.Defaults); + } - // Copy the properties over onto the new prototype - for (name in prop) { - member = prop[name]; + // Copy the properties over onto the new prototype + for (name in prop) { + member = prop[name]; - if (typeof member == "function" && _super[name]) { - prototype[name] = createMethod(name, member); - } else { - prototype[name] = member; - } - } + if (typeof member == "function" && _super[name]) { + prototype[name] = createMethod(name, member); + } else { + prototype[name] = member; + } + } - // Populate our constructed prototype object - Class.prototype = prototype; + // Populate our constructed prototype object + Class.prototype = prototype; - // Enforce the constructor to be what we expect - Class.constructor = Class; + // Enforce the constructor to be what we expect + Class.constructor = Class; - // And make this class extendible - Class.extend = extendClass; + // And make this class extendible + Class.extend = extendClass; - return Class; - }; - - return Class; -}); - -// Included from: js/tinymce/classes/util/EventDispatcher.js + return Class; + }; + return Class; + } +); /** * EventDispatcher.js * * Released under LGPL License. - * Copyright (c) 1999-2015 Ephox Corp. All rights reserved + * Copyright (c) 1999-2017 Ephox Corp. All rights reserved * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing @@ -25836,287 +29929,426 @@ define("tinymce/util/Class", [ * eventDispatcher.on('click', function() {console.log('data');}); * eventDispatcher.fire('click', {data: 123}); */ -define("tinymce/util/EventDispatcher", [ - "tinymce/util/Tools" -], function(Tools) { - var nativeEvents = Tools.makeMap( - "focus blur focusin focusout click dblclick mousedown mouseup mousemove mouseover beforepaste paste cut copy selectionchange " + - "mouseout mouseenter mouseleave wheel keydown keypress keyup input contextmenu dragstart dragend dragover " + - "draggesture dragdrop drop drag submit " + - "compositionstart compositionend compositionupdate touchstart touchmove touchend", - ' ' - ); +define( + 'tinymce.core.util.EventDispatcher', + [ + "tinymce.core.util.Tools" + ], + function (Tools) { + var nativeEvents = Tools.makeMap( + "focus blur focusin focusout click dblclick mousedown mouseup mousemove mouseover beforepaste paste cut copy selectionchange " + + "mouseout mouseenter mouseleave wheel keydown keypress keyup input contextmenu dragstart dragend dragover " + + "draggesture dragdrop drop drag submit " + + "compositionstart compositionend compositionupdate touchstart touchmove touchend", + ' ' + ); - function Dispatcher(settings) { - var self = this, scope, bindings = {}, toggleEvent; + function Dispatcher(settings) { + var self = this, scope, bindings = {}, toggleEvent; - function returnFalse() { - return false; - } + function returnFalse() { + return false; + } - function returnTrue() { - return true; - } + function returnTrue() { + return true; + } - settings = settings || {}; - scope = settings.scope || self; - toggleEvent = settings.toggleEvent || returnFalse; + settings = settings || {}; + scope = settings.scope || self; + toggleEvent = settings.toggleEvent || returnFalse; - /** - * Fires the specified event by name. - * - * @method fire - * @param {String} name Name of the event to fire. - * @param {Object?} args Event arguments. - * @return {Object} Event args instance passed in. - * @example - * instance.fire('event', {...}); - */ - function fire(name, args) { - var handlers, i, l, callback; + /** + * Fires the specified event by name. + * + * @method fire + * @param {String} name Name of the event to fire. + * @param {Object?} args Event arguments. + * @return {Object} Event args instance passed in. + * @example + * instance.fire('event', {...}); + */ + function fire(name, args) { + var handlers, i, l, callback; - name = name.toLowerCase(); - args = args || {}; - args.type = name; + name = name.toLowerCase(); + args = args || {}; + args.type = name; - // Setup target is there isn't one - if (!args.target) { - args.target = scope; - } + // Setup target is there isn't one + if (!args.target) { + args.target = scope; + } - // Add event delegation methods if they are missing - if (!args.preventDefault) { - // Add preventDefault method - args.preventDefault = function() { - args.isDefaultPrevented = returnTrue; - }; + // Add event delegation methods if they are missing + if (!args.preventDefault) { + // Add preventDefault method + args.preventDefault = function () { + args.isDefaultPrevented = returnTrue; + }; - // Add stopPropagation - args.stopPropagation = function() { - args.isPropagationStopped = returnTrue; - }; + // Add stopPropagation + args.stopPropagation = function () { + args.isPropagationStopped = returnTrue; + }; - // Add stopImmediatePropagation - args.stopImmediatePropagation = function() { - args.isImmediatePropagationStopped = returnTrue; - }; + // Add stopImmediatePropagation + args.stopImmediatePropagation = function () { + args.isImmediatePropagationStopped = returnTrue; + }; - // Add event delegation states - args.isDefaultPrevented = returnFalse; - args.isPropagationStopped = returnFalse; - args.isImmediatePropagationStopped = returnFalse; - } + // Add event delegation states + args.isDefaultPrevented = returnFalse; + args.isPropagationStopped = returnFalse; + args.isImmediatePropagationStopped = returnFalse; + } - if (settings.beforeFire) { - settings.beforeFire(args); - } + if (settings.beforeFire) { + settings.beforeFire(args); + } - handlers = bindings[name]; - if (handlers) { - for (i = 0, l = handlers.length; i < l; i++) { - callback = handlers[i]; + handlers = bindings[name]; + if (handlers) { + for (i = 0, l = handlers.length; i < l; i++) { + callback = handlers[i]; - // Unbind handlers marked with "once" - if (callback.once) { - off(name, callback.func); - } + // Unbind handlers marked with "once" + if (callback.once) { + off(name, callback.func); + } - // Stop immediate propagation if needed - if (args.isImmediatePropagationStopped()) { - args.stopPropagation(); - return args; - } + // Stop immediate propagation if needed + if (args.isImmediatePropagationStopped()) { + args.stopPropagation(); + return args; + } - // If callback returns false then prevent default and stop all propagation - if (callback.func.call(scope, args) === false) { - args.preventDefault(); - return args; - } - } - } + // If callback returns false then prevent default and stop all propagation + if (callback.func.call(scope, args) === false) { + args.preventDefault(); + return args; + } + } + } - return args; - } + return args; + } - /** - * Binds an event listener to a specific event by name. - * - * @method on - * @param {String} name Event name or space separated list of events to bind. - * @param {callback} callback Callback to be executed when the event occurs. - * @param {Boolean} first Optional flag if the event should be prepended. Use this with care. - * @return {Object} Current class instance. - * @example - * instance.on('event', function(e) { - * // Callback logic - * }); - */ - function on(name, callback, prepend, extra) { - var handlers, names, i; + /** + * Binds an event listener to a specific event by name. + * + * @method on + * @param {String} name Event name or space separated list of events to bind. + * @param {callback} callback Callback to be executed when the event occurs. + * @param {Boolean} first Optional flag if the event should be prepended. Use this with care. + * @return {Object} Current class instance. + * @example + * instance.on('event', function(e) { + * // Callback logic + * }); + */ + function on(name, callback, prepend, extra) { + var handlers, names, i; - if (callback === false) { - callback = returnFalse; - } + if (callback === false) { + callback = returnFalse; + } - if (callback) { - callback = { - func: callback - }; + if (callback) { + callback = { + func: callback + }; - if (extra) { - Tools.extend(callback, extra); - } + if (extra) { + Tools.extend(callback, extra); + } - names = name.toLowerCase().split(' '); - i = names.length; - while (i--) { - name = names[i]; - handlers = bindings[name]; - if (!handlers) { - handlers = bindings[name] = []; - toggleEvent(name, true); - } + names = name.toLowerCase().split(' '); + i = names.length; + while (i--) { + name = names[i]; + handlers = bindings[name]; + if (!handlers) { + handlers = bindings[name] = []; + toggleEvent(name, true); + } - if (prepend) { - handlers.unshift(callback); - } else { - handlers.push(callback); - } - } - } + if (prepend) { + handlers.unshift(callback); + } else { + handlers.push(callback); + } + } + } - return self; - } + return self; + } - /** - * Unbinds an event listener to a specific event by name. - * - * @method off - * @param {String?} name Name of the event to unbind. - * @param {callback?} callback Callback to unbind. - * @return {Object} Current class instance. - * @example - * // Unbind specific callback - * instance.off('event', handler); - * - * // Unbind all listeners by name - * instance.off('event'); - * - * // Unbind all events - * instance.off(); - */ - function off(name, callback) { - var i, handlers, bindingName, names, hi; + /** + * Unbinds an event listener to a specific event by name. + * + * @method off + * @param {String?} name Name of the event to unbind. + * @param {callback?} callback Callback to unbind. + * @return {Object} Current class instance. + * @example + * // Unbind specific callback + * instance.off('event', handler); + * + * // Unbind all listeners by name + * instance.off('event'); + * + * // Unbind all events + * instance.off(); + */ + function off(name, callback) { + var i, handlers, bindingName, names, hi; - if (name) { - names = name.toLowerCase().split(' '); - i = names.length; - while (i--) { - name = names[i]; - handlers = bindings[name]; + if (name) { + names = name.toLowerCase().split(' '); + i = names.length; + while (i--) { + name = names[i]; + handlers = bindings[name]; - // Unbind all handlers - if (!name) { - for (bindingName in bindings) { - toggleEvent(bindingName, false); - delete bindings[bindingName]; - } + // Unbind all handlers + if (!name) { + for (bindingName in bindings) { + toggleEvent(bindingName, false); + delete bindings[bindingName]; + } - return self; - } + return self; + } - if (handlers) { - // Unbind all by name - if (!callback) { - handlers.length = 0; - } else { - // Unbind specific ones - hi = handlers.length; - while (hi--) { - if (handlers[hi].func === callback) { - handlers = handlers.slice(0, hi).concat(handlers.slice(hi + 1)); - bindings[name] = handlers; - } - } - } + if (handlers) { + // Unbind all by name + if (!callback) { + handlers.length = 0; + } else { + // Unbind specific ones + hi = handlers.length; + while (hi--) { + if (handlers[hi].func === callback) { + handlers = handlers.slice(0, hi).concat(handlers.slice(hi + 1)); + bindings[name] = handlers; + } + } + } - if (!handlers.length) { - toggleEvent(name, false); - delete bindings[name]; - } - } - } - } else { - for (name in bindings) { - toggleEvent(name, false); - } + if (!handlers.length) { + toggleEvent(name, false); + delete bindings[name]; + } + } + } + } else { + for (name in bindings) { + toggleEvent(name, false); + } - bindings = {}; - } + bindings = {}; + } - return self; - } + return self; + } - /** - * Binds an event listener to a specific event by name - * and automatically unbind the event once the callback fires. - * - * @method once - * @param {String} name Event name or space separated list of events to bind. - * @param {callback} callback Callback to be executed when the event occurs. - * @param {Boolean} first Optional flag if the event should be prepended. Use this with care. - * @return {Object} Current class instance. - * @example - * instance.once('event', function(e) { - * // Callback logic - * }); - */ - function once(name, callback, prepend) { - return on(name, callback, prepend, {once: true}); - } + /** + * Binds an event listener to a specific event by name + * and automatically unbind the event once the callback fires. + * + * @method once + * @param {String} name Event name or space separated list of events to bind. + * @param {callback} callback Callback to be executed when the event occurs. + * @param {Boolean} first Optional flag if the event should be prepended. Use this with care. + * @return {Object} Current class instance. + * @example + * instance.once('event', function(e) { + * // Callback logic + * }); + */ + function once(name, callback, prepend) { + return on(name, callback, prepend, { once: true }); + } - /** - * Returns true/false if the dispatcher has a event of the specified name. - * - * @method has - * @param {String} name Name of the event to check for. - * @return {Boolean} true/false if the event exists or not. - */ - function has(name) { - name = name.toLowerCase(); - return !(!bindings[name] || bindings[name].length === 0); - } + /** + * Returns true/false if the dispatcher has a event of the specified name. + * + * @method has + * @param {String} name Name of the event to check for. + * @return {Boolean} true/false if the event exists or not. + */ + function has(name) { + name = name.toLowerCase(); + return !(!bindings[name] || bindings[name].length === 0); + } - // Expose - self.fire = fire; - self.on = on; - self.off = off; - self.once = once; - self.has = has; - } + // Expose + self.fire = fire; + self.on = on; + self.off = off; + self.once = once; + self.has = has; + } - /** - * Returns true/false if the specified event name is a native browser event or not. - * - * @method isNative - * @param {String} name Name to check if it's native. - * @return {Boolean} true/false if the event is native or not. - * @static - */ - Dispatcher.isNative = function(name) { - return !!nativeEvents[name.toLowerCase()]; - }; + /** + * Returns true/false if the specified event name is a native browser event or not. + * + * @method isNative + * @param {String} name Name to check if it's native. + * @return {Boolean} true/false if the event is native or not. + * @static + */ + Dispatcher.isNative = function (name) { + return !!nativeEvents[name.toLowerCase()]; + }; - return Dispatcher; -}); + return Dispatcher; + } +); -// Included from: js/tinymce/classes/data/Binding.js +/** + * Observable.js + * + * Released under LGPL License. + * Copyright (c) 1999-2017 Ephox Corp. All rights reserved + * + * License: http://www.tinymce.com/license + * Contributing: http://www.tinymce.com/contributing + */ +/** + * This mixin will add event binding logic to classes. + * + * @mixin tinymce.util.Observable + */ +define( + 'tinymce.core.util.Observable', + [ + "tinymce.core.util.EventDispatcher" + ], + function (EventDispatcher) { + function getEventDispatcher(obj) { + if (!obj._eventDispatcher) { + obj._eventDispatcher = new EventDispatcher({ + scope: obj, + toggleEvent: function (name, state) { + if (EventDispatcher.isNative(name) && obj.toggleNativeEvent) { + obj.toggleNativeEvent(name, state); + } + } + }); + } + + return obj._eventDispatcher; + } + + return { + /** + * Fires the specified event by name. Consult the + * event reference for more details on each event. + * + * @method fire + * @param {String} name Name of the event to fire. + * @param {Object?} args Event arguments. + * @param {Boolean?} bubble True/false if the event is to be bubbled. + * @return {Object} Event args instance passed in. + * @example + * instance.fire('event', {...}); + */ + fire: function (name, args, bubble) { + var self = this; + + // Prevent all events except the remove event after the instance has been removed + if (self.removed && name !== "remove") { + return args; + } + + args = getEventDispatcher(self).fire(name, args, bubble); + + // Bubble event up to parents + if (bubble !== false && self.parent) { + var parent = self.parent(); + while (parent && !args.isPropagationStopped()) { + parent.fire(name, args, false); + parent = parent.parent(); + } + } + + return args; + }, + + /** + * Binds an event listener to a specific event by name. Consult the + * event reference for more details on each event. + * + * @method on + * @param {String} name Event name or space separated list of events to bind. + * @param {callback} callback Callback to be executed when the event occurs. + * @param {Boolean} first Optional flag if the event should be prepended. Use this with care. + * @return {Object} Current class instance. + * @example + * instance.on('event', function(e) { + * // Callback logic + * }); + */ + on: function (name, callback, prepend) { + return getEventDispatcher(this).on(name, callback, prepend); + }, + + /** + * Unbinds an event listener to a specific event by name. Consult the + * event reference for more details on each event. + * + * @method off + * @param {String?} name Name of the event to unbind. + * @param {callback?} callback Callback to unbind. + * @return {Object} Current class instance. + * @example + * // Unbind specific callback + * instance.off('event', handler); + * + * // Unbind all listeners by name + * instance.off('event'); + * + * // Unbind all events + * instance.off(); + */ + off: function (name, callback) { + return getEventDispatcher(this).off(name, callback); + }, + + /** + * Bind the event callback and once it fires the callback is removed. Consult the + * event reference for more details on each event. + * + * @method once + * @param {String} name Name of the event to bind. + * @param {callback} callback Callback to bind only once. + * @return {Object} Current class instance. + */ + once: function (name, callback) { + return getEventDispatcher(this).once(name, callback); + }, + + /** + * Returns true/false if the object has a event of the specified name. + * + * @method hasEventListeners + * @param {String} name Name of the event to check for. + * @return {Boolean} true/false if the event exists or not. + */ + hasEventListeners: function (name) { + return getEventDispatcher(this).has(name); + } + }; + } +); /** * Binding.js * * Released under LGPL License. - * Copyright (c) 1999-2015 Ephox Corp. All rights reserved + * Copyright (c) 1999-2017 Ephox Corp. All rights reserved * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing @@ -26129,212 +30361,78 @@ define("tinymce/util/EventDispatcher", [ * @private * @class tinymce.data.Binding */ -define("tinymce/data/Binding", [], function() { - /** - * Constructs a new bidning. - * - * @constructor - * @method Binding - * @param {Object} settings Settings to the binding. - */ - function Binding(settings) { - this.create = settings.create; - } +define( + 'tinymce.core.data.Binding', + [ + ], + function () { + /** + * Constructs a new bidning. + * + * @constructor + * @method Binding + * @param {Object} settings Settings to the binding. + */ + function Binding(settings) { + this.create = settings.create; + } - /** - * Creates a binding for a property on a model. - * - * @method create - * @param {tinymce.data.ObservableObject} model Model to create binding to. - * @param {String} name Name of property to bind. - * @return {tinymce.data.Binding} Binding instance. - */ - Binding.create = function(model, name) { - return new Binding({ - create: function(otherModel, otherName) { - var bindings; + /** + * Creates a binding for a property on a model. + * + * @method create + * @param {tinymce.data.ObservableObject} model Model to create binding to. + * @param {String} name Name of property to bind. + * @return {tinymce.data.Binding} Binding instance. + */ + Binding.create = function (model, name) { + return new Binding({ + create: function (otherModel, otherName) { + var bindings; - function fromSelfToOther(e) { - otherModel.set(otherName, e.value); - } + function fromSelfToOther(e) { + otherModel.set(otherName, e.value); + } - function fromOtherToSelf(e) { - model.set(name, e.value); - } + function fromOtherToSelf(e) { + model.set(name, e.value); + } - otherModel.on('change:' + otherName, fromOtherToSelf); - model.on('change:' + name, fromSelfToOther); + otherModel.on('change:' + otherName, fromOtherToSelf); + model.on('change:' + name, fromSelfToOther); - // Keep track of the bindings - bindings = otherModel._bindings; + // Keep track of the bindings + bindings = otherModel._bindings; - if (!bindings) { - bindings = otherModel._bindings = []; + if (!bindings) { + bindings = otherModel._bindings = []; - otherModel.on('destroy', function() { - var i = bindings.length; + otherModel.on('destroy', function () { + var i = bindings.length; - while (i--) { - bindings[i](); - } - }); - } + while (i--) { + bindings[i](); + } + }); + } - bindings.push(function() { - model.off('change:' + name, fromSelfToOther); - }); + bindings.push(function () { + model.off('change:' + name, fromSelfToOther); + }); - return model.get(name); - } - }); - }; - - return Binding; -}); - -// Included from: js/tinymce/classes/util/Observable.js - -/** - * Observable.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 mixin will add event binding logic to classes. - * - * @mixin tinymce.util.Observable - */ -define("tinymce/util/Observable", [ - "tinymce/util/EventDispatcher" -], function(EventDispatcher) { - function getEventDispatcher(obj) { - if (!obj._eventDispatcher) { - obj._eventDispatcher = new EventDispatcher({ - scope: obj, - toggleEvent: function(name, state) { - if (EventDispatcher.isNative(name) && obj.toggleNativeEvent) { - obj.toggleNativeEvent(name, state); - } - } - }); - } - - return obj._eventDispatcher; - } - - return { - /** - * Fires the specified event by name. Consult the - * event reference for more details on each event. - * - * @method fire - * @param {String} name Name of the event to fire. - * @param {Object?} args Event arguments. - * @param {Boolean?} bubble True/false if the event is to be bubbled. - * @return {Object} Event args instance passed in. - * @example - * instance.fire('event', {...}); - */ - fire: function(name, args, bubble) { - var self = this; - - // Prevent all events except the remove event after the instance has been removed - if (self.removed && name !== "remove") { - return args; - } - - args = getEventDispatcher(self).fire(name, args, bubble); - - // Bubble event up to parents - if (bubble !== false && self.parent) { - var parent = self.parent(); - while (parent && !args.isPropagationStopped()) { - parent.fire(name, args, false); - parent = parent.parent(); - } - } - - return args; - }, - - /** - * Binds an event listener to a specific event by name. Consult the - * event reference for more details on each event. - * - * @method on - * @param {String} name Event name or space separated list of events to bind. - * @param {callback} callback Callback to be executed when the event occurs. - * @param {Boolean} first Optional flag if the event should be prepended. Use this with care. - * @return {Object} Current class instance. - * @example - * instance.on('event', function(e) { - * // Callback logic - * }); - */ - on: function(name, callback, prepend) { - return getEventDispatcher(this).on(name, callback, prepend); - }, - - /** - * Unbinds an event listener to a specific event by name. Consult the - * event reference for more details on each event. - * - * @method off - * @param {String?} name Name of the event to unbind. - * @param {callback?} callback Callback to unbind. - * @return {Object} Current class instance. - * @example - * // Unbind specific callback - * instance.off('event', handler); - * - * // Unbind all listeners by name - * instance.off('event'); - * - * // Unbind all events - * instance.off(); - */ - off: function(name, callback) { - return getEventDispatcher(this).off(name, callback); - }, - - /** - * Bind the event callback and once it fires the callback is removed. Consult the - * event reference for more details on each event. - * - * @method once - * @param {String} name Name of the event to bind. - * @param {callback} callback Callback to bind only once. - * @return {Object} Current class instance. - */ - once: function(name, callback) { - return getEventDispatcher(this).once(name, callback); - }, - - /** - * Returns true/false if the object has a event of the specified name. - * - * @method hasEventListeners - * @param {String} name Name of the event to check for. - * @return {Boolean} true/false if the event exists or not. - */ - hasEventListeners: function(name) { - return getEventDispatcher(this).has(name); - } - }; -}); - -// Included from: js/tinymce/classes/data/ObservableObject.js + return model.get(name); + } + }); + }; + return Binding; + } +); /** * ObservableObject.js * * Released under LGPL License. - * Copyright (c) 1999-2015 Ephox Corp. All rights reserved + * Copyright (c) 1999-2017 Ephox Corp. All rights reserved * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing @@ -26346,192 +30444,192 @@ define("tinymce/util/Observable", [ * @private * @class tinymce.data.ObservableObject */ -define("tinymce/data/ObservableObject", [ - "tinymce/data/Binding", - "tinymce/util/Observable", - "tinymce/util/Class", - "tinymce/util/Tools" -], function(Binding, Observable, Class, Tools) { - function isNode(node) { - return node.nodeType > 0; - } +define( + 'tinymce.core.data.ObservableObject', + [ + 'tinymce.core.data.Binding', + 'tinymce.core.util.Class', + 'tinymce.core.util.Observable', + 'tinymce.core.util.Tools' + ], function (Binding, Class, Observable, Tools) { + function isNode(node) { + return node.nodeType > 0; + } - // Todo: Maybe this should be shallow compare since it might be huge object references - function isEqual(a, b) { - var k, checked; + // Todo: Maybe this should be shallow compare since it might be huge object references + function isEqual(a, b) { + var k, checked; - // Strict equals - if (a === b) { - return true; - } + // Strict equals + if (a === b) { + return true; + } - // Compare null - if (a === null || b === null) { - return a === b; - } + // Compare null + if (a === null || b === null) { + return a === b; + } - // Compare number, boolean, string, undefined - if (typeof a !== "object" || typeof b !== "object") { - return a === b; - } + // Compare number, boolean, string, undefined + if (typeof a !== "object" || typeof b !== "object") { + return a === b; + } - // Compare arrays - if (Tools.isArray(b)) { - if (a.length !== b.length) { - return false; - } + // Compare arrays + if (Tools.isArray(b)) { + if (a.length !== b.length) { + return false; + } - k = a.length; - while (k--) { - if (!isEqual(a[k], b[k])) { - return false; - } - } - } + k = a.length; + while (k--) { + if (!isEqual(a[k], b[k])) { + return false; + } + } + } - // Shallow compare nodes - if (isNode(a) || isNode(b)) { - return a === b; - } + // Shallow compare nodes + if (isNode(a) || isNode(b)) { + return a === b; + } - // Compare objects - checked = {}; - for (k in b) { - if (!isEqual(a[k], b[k])) { - return false; - } + // Compare objects + checked = {}; + for (k in b) { + if (!isEqual(a[k], b[k])) { + return false; + } - checked[k] = true; - } + checked[k] = true; + } - for (k in a) { - if (!checked[k] && !isEqual(a[k], b[k])) { - return false; - } - } + for (k in a) { + if (!checked[k] && !isEqual(a[k], b[k])) { + return false; + } + } - return true; - } + return true; + } - return Class.extend({ - Mixins: [Observable], + return Class.extend({ + Mixins: [Observable], - /** - * Constructs a new observable object instance. - * - * @constructor - * @param {Object} data Initial data for the object. - */ - init: function(data) { - var name, value; + /** + * Constructs a new observable object instance. + * + * @constructor + * @param {Object} data Initial data for the object. + */ + init: function (data) { + var name, value; - data = data || {}; + data = data || {}; - for (name in data) { - value = data[name]; + for (name in data) { + value = data[name]; - if (value instanceof Binding) { - data[name] = value.create(this, name); - } - } + if (value instanceof Binding) { + data[name] = value.create(this, name); + } + } - this.data = data; - }, + this.data = data; + }, - /** - * Sets a property on the value this will call - * observers if the value is a change from the current value. - * - * @method set - * @param {String/object} name Name of the property to set or a object of items to set. - * @param {Object} value Value to set for the property. - * @return {tinymce.data.ObservableObject} Observable object instance. - */ - set: function(name, value) { - var key, args, oldValue = this.data[name]; + /** + * Sets a property on the value this will call + * observers if the value is a change from the current value. + * + * @method set + * @param {String/object} name Name of the property to set or a object of items to set. + * @param {Object} value Value to set for the property. + * @return {tinymce.data.ObservableObject} Observable object instance. + */ + set: function (name, value) { + var key, args, oldValue = this.data[name]; - if (value instanceof Binding) { - value = value.create(this, name); - } + if (value instanceof Binding) { + value = value.create(this, name); + } - if (typeof name === "object") { - for (key in name) { - this.set(key, name[key]); - } + if (typeof name === "object") { + for (key in name) { + this.set(key, name[key]); + } - return this; - } + return this; + } - if (!isEqual(oldValue, value)) { - this.data[name] = value; + if (!isEqual(oldValue, value)) { + this.data[name] = value; - args = { - target: this, - name: name, - value: value, - oldValue: oldValue - }; + args = { + target: this, + name: name, + value: value, + oldValue: oldValue + }; - this.fire('change:' + name, args); - this.fire('change', args); - } + this.fire('change:' + name, args); + this.fire('change', args); + } - return this; - }, + return this; + }, - /** - * Gets a property by name. - * - * @method get - * @param {String} name Name of the property to get. - * @return {Object} Object value of propery. - */ - get: function(name) { - return this.data[name]; - }, + /** + * Gets a property by name. + * + * @method get + * @param {String} name Name of the property to get. + * @return {Object} Object value of propery. + */ + get: function (name) { + return this.data[name]; + }, - /** - * Returns true/false if the specified property exists. - * - * @method has - * @param {String} name Name of the property to check for. - * @return {Boolean} true/false if the item exists. - */ - has: function(name) { - return name in this.data; - }, + /** + * Returns true/false if the specified property exists. + * + * @method has + * @param {String} name Name of the property to check for. + * @return {Boolean} true/false if the item exists. + */ + has: function (name) { + return name in this.data; + }, - /** - * Returns a dynamic property binding for the specified property name. This makes - * it possible to sync the state of two properties in two ObservableObject instances. - * - * @method bind - * @param {String} name Name of the property to sync with the property it's inserted to. - * @return {tinymce.data.Binding} Data binding instance. - */ - bind: function(name) { - return Binding.create(this, name); - }, - - /** - * Destroys the observable object and fires the "destroy" - * event and clean up any internal resources. - * - * @method destroy - */ - destroy: function() { - this.fire('destroy'); - } - }); -}); - -// Included from: js/tinymce/classes/ui/Selector.js + /** + * Returns a dynamic property binding for the specified property name. This makes + * it possible to sync the state of two properties in two ObservableObject instances. + * + * @method bind + * @param {String} name Name of the property to sync with the property it's inserted to. + * @return {tinymce.data.Binding} Data binding instance. + */ + bind: function (name) { + return Binding.create(this, name); + }, + /** + * Destroys the observable object and fires the "destroy" + * event and clean up any internal resources. + * + * @method destroy + */ + destroy: function () { + this.fire('destroy'); + } + }); + } +); /** * Selector.js * * Released under LGPL License. - * Copyright (c) 1999-2015 Ephox Corp. All rights reserved + * Copyright (c) 1999-2017 Ephox Corp. All rights reserved * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing @@ -26566,344 +30664,346 @@ define("tinymce/data/ObservableObject", [ * * @class tinymce.ui.Selector */ -define("tinymce/ui/Selector", [ - "tinymce/util/Class" -], function(Class) { - "use strict"; +define( + 'tinymce.core.ui.Selector', + [ + "tinymce.core.util.Class" + ], + function (Class) { + "use strict"; - /** - * Produces an array with a unique set of objects. It will not compare the values - * but the references of the objects. - * - * @private - * @method unqiue - * @param {Array} array Array to make into an array with unique items. - * @return {Array} Array with unique items. - */ - function unique(array) { - var uniqueItems = [], i = array.length, item; + /** + * Produces an array with a unique set of objects. It will not compare the values + * but the references of the objects. + * + * @private + * @method unqiue + * @param {Array} array Array to make into an array with unique items. + * @return {Array} Array with unique items. + */ + function unique(array) { + var uniqueItems = [], i = array.length, item; - while (i--) { - item = array[i]; + while (i--) { + item = array[i]; - if (!item.__checked) { - uniqueItems.push(item); - item.__checked = 1; - } - } + if (!item.__checked) { + uniqueItems.push(item); + item.__checked = 1; + } + } - i = uniqueItems.length; - while (i--) { - delete uniqueItems[i].__checked; - } + i = uniqueItems.length; + while (i--) { + delete uniqueItems[i].__checked; + } - return uniqueItems; - } + return uniqueItems; + } - var expression = /^([\w\\*]+)?(?:#([\w\-\\]+))?(?:\.([\w\\\.]+))?(?:\[\@?([\w\\]+)([\^\$\*!~]?=)([\w\\]+)\])?(?:\:(.+))?/i; + var expression = /^([\w\\*]+)?(?:#([\w\-\\]+))?(?:\.([\w\\\.]+))?(?:\[\@?([\w\\]+)([\^\$\*!~]?=)([\w\\]+)\])?(?:\:(.+))?/i; - /*jshint maxlen:255 */ - /*eslint max-len:0 */ - var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, - whiteSpace = /^\s*|\s*$/g, - Collection; + /*jshint maxlen:255 */ + /*eslint max-len:0 */ + var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, + whiteSpace = /^\s*|\s*$/g, + Collection; - var Selector = Class.extend({ - /** - * Constructs a new Selector instance. - * - * @constructor - * @method init - * @param {String} selector CSS like selector expression. - */ - init: function(selector) { - var match = this.match; + var Selector = Class.extend({ + /** + * Constructs a new Selector instance. + * + * @constructor + * @method init + * @param {String} selector CSS like selector expression. + */ + init: function (selector) { + var match = this.match; - function compileNameFilter(name) { - if (name) { - name = name.toLowerCase(); + function compileNameFilter(name) { + if (name) { + name = name.toLowerCase(); - return function(item) { - return name === '*' || item.type === name; - }; - } - } + return function (item) { + return name === '*' || item.type === name; + }; + } + } - function compileIdFilter(id) { - if (id) { - return function(item) { - return item._name === id; - }; - } - } + function compileIdFilter(id) { + if (id) { + return function (item) { + return item._name === id; + }; + } + } - function compileClassesFilter(classes) { - if (classes) { - classes = classes.split('.'); + function compileClassesFilter(classes) { + if (classes) { + classes = classes.split('.'); - return function(item) { - var i = classes.length; + return function (item) { + var i = classes.length; - while (i--) { - if (!item.classes.contains(classes[i])) { - return false; - } - } + while (i--) { + if (!item.classes.contains(classes[i])) { + return false; + } + } - return true; - }; - } - } + return true; + }; + } + } - function compileAttrFilter(name, cmp, check) { - if (name) { - return function(item) { - var value = item[name] ? item[name]() : ''; + function compileAttrFilter(name, cmp, check) { + if (name) { + return function (item) { + var value = item[name] ? item[name]() : ''; - return !cmp ? !!check : - cmp === "=" ? value === check : - cmp === "*=" ? value.indexOf(check) >= 0 : - cmp === "~=" ? (" " + value + " ").indexOf(" " + check + " ") >= 0 : - cmp === "!=" ? value != check : - cmp === "^=" ? value.indexOf(check) === 0 : - cmp === "$=" ? value.substr(value.length - check.length) === check : - false; - }; - } - } + return !cmp ? !!check : + cmp === "=" ? value === check : + cmp === "*=" ? value.indexOf(check) >= 0 : + cmp === "~=" ? (" " + value + " ").indexOf(" " + check + " ") >= 0 : + cmp === "!=" ? value != check : + cmp === "^=" ? value.indexOf(check) === 0 : + cmp === "$=" ? value.substr(value.length - check.length) === check : + false; + }; + } + } - function compilePsuedoFilter(name) { - var notSelectors; + function compilePsuedoFilter(name) { + var notSelectors; - if (name) { - name = /(?:not\((.+)\))|(.+)/i.exec(name); + if (name) { + name = /(?:not\((.+)\))|(.+)/i.exec(name); - if (!name[1]) { - name = name[2]; + if (!name[1]) { + name = name[2]; - return function(item, index, length) { - return name === 'first' ? index === 0 : - name === 'last' ? index === length - 1 : - name === 'even' ? index % 2 === 0 : - name === 'odd' ? index % 2 === 1 : - item[name] ? item[name]() : - false; - }; - } + return function (item, index, length) { + return name === 'first' ? index === 0 : + name === 'last' ? index === length - 1 : + name === 'even' ? index % 2 === 0 : + name === 'odd' ? index % 2 === 1 : + item[name] ? item[name]() : + false; + }; + } - // Compile not expression - notSelectors = parseChunks(name[1], []); + // Compile not expression + notSelectors = parseChunks(name[1], []); - return function(item) { - return !match(item, notSelectors); - }; - } - } + return function (item) { + return !match(item, notSelectors); + }; + } + } - function compile(selector, filters, direct) { - var parts; + function compile(selector, filters, direct) { + var parts; - function add(filter) { - if (filter) { - filters.push(filter); - } - } + function add(filter) { + if (filter) { + filters.push(filter); + } + } - // Parse expression into parts - parts = expression.exec(selector.replace(whiteSpace, '')); + // Parse expression into parts + parts = expression.exec(selector.replace(whiteSpace, '')); - add(compileNameFilter(parts[1])); - add(compileIdFilter(parts[2])); - add(compileClassesFilter(parts[3])); - add(compileAttrFilter(parts[4], parts[5], parts[6])); - add(compilePsuedoFilter(parts[7])); + add(compileNameFilter(parts[1])); + add(compileIdFilter(parts[2])); + add(compileClassesFilter(parts[3])); + add(compileAttrFilter(parts[4], parts[5], parts[6])); + add(compilePsuedoFilter(parts[7])); - // Mark the filter with pseudo for performance - filters.pseudo = !!parts[7]; - filters.direct = direct; + // Mark the filter with pseudo for performance + filters.pseudo = !!parts[7]; + filters.direct = direct; - return filters; - } + return filters; + } - // Parser logic based on Sizzle by John Resig - function parseChunks(selector, selectors) { - var parts = [], extra, matches, i; + // Parser logic based on Sizzle by John Resig + function parseChunks(selector, selectors) { + var parts = [], extra, matches, i; - do { - chunker.exec(""); - matches = chunker.exec(selector); + do { + chunker.exec(""); + matches = chunker.exec(selector); - if (matches) { - selector = matches[3]; - parts.push(matches[1]); + if (matches) { + selector = matches[3]; + parts.push(matches[1]); - if (matches[2]) { - extra = matches[3]; - break; - } - } - } while (matches); + if (matches[2]) { + extra = matches[3]; + break; + } + } + } while (matches); - if (extra) { - parseChunks(extra, selectors); - } + if (extra) { + parseChunks(extra, selectors); + } - selector = []; - for (i = 0; i < parts.length; i++) { - if (parts[i] != '>') { - selector.push(compile(parts[i], [], parts[i - 1] === '>')); - } - } + selector = []; + for (i = 0; i < parts.length; i++) { + if (parts[i] != '>') { + selector.push(compile(parts[i], [], parts[i - 1] === '>')); + } + } - selectors.push(selector); + selectors.push(selector); - return selectors; - } + return selectors; + } - this._selectors = parseChunks(selector, []); - }, + this._selectors = parseChunks(selector, []); + }, - /** - * Returns true/false if the selector matches the specified control. - * - * @method match - * @param {tinymce.ui.Control} control Control to match against the selector. - * @param {Array} selectors Optional array of selectors, mostly used internally. - * @return {Boolean} true/false state if the control matches or not. - */ - match: function(control, selectors) { - var i, l, si, sl, selector, fi, fl, filters, index, length, siblings, count, item; + /** + * Returns true/false if the selector matches the specified control. + * + * @method match + * @param {tinymce.ui.Control} control Control to match against the selector. + * @param {Array} selectors Optional array of selectors, mostly used internally. + * @return {Boolean} true/false state if the control matches or not. + */ + match: function (control, selectors) { + var i, l, si, sl, selector, fi, fl, filters, index, length, siblings, count, item; - selectors = selectors || this._selectors; - for (i = 0, l = selectors.length; i < l; i++) { - selector = selectors[i]; - sl = selector.length; - item = control; - count = 0; + selectors = selectors || this._selectors; + for (i = 0, l = selectors.length; i < l; i++) { + selector = selectors[i]; + sl = selector.length; + item = control; + count = 0; - for (si = sl - 1; si >= 0; si--) { - filters = selector[si]; + for (si = sl - 1; si >= 0; si--) { + filters = selector[si]; - while (item) { - // Find the index and length since a pseudo filter like :first needs it - if (filters.pseudo) { - siblings = item.parent().items(); - index = length = siblings.length; - while (index--) { - if (siblings[index] === item) { - break; - } - } - } + while (item) { + // Find the index and length since a pseudo filter like :first needs it + if (filters.pseudo) { + siblings = item.parent().items(); + index = length = siblings.length; + while (index--) { + if (siblings[index] === item) { + break; + } + } + } - for (fi = 0, fl = filters.length; fi < fl; fi++) { - if (!filters[fi](item, index, length)) { - fi = fl + 1; - break; - } - } + for (fi = 0, fl = filters.length; fi < fl; fi++) { + if (!filters[fi](item, index, length)) { + fi = fl + 1; + break; + } + } - if (fi === fl) { - count++; - break; - } else { - // If it didn't match the right most expression then - // break since it's no point looking at the parents - if (si === sl - 1) { - break; - } - } + if (fi === fl) { + count++; + break; + } else { + // If it didn't match the right most expression then + // break since it's no point looking at the parents + if (si === sl - 1) { + break; + } + } - item = item.parent(); - } - } + item = item.parent(); + } + } - // If we found all selectors then return true otherwise continue looking - if (count === sl) { - return true; - } - } + // If we found all selectors then return true otherwise continue looking + if (count === sl) { + return true; + } + } - return false; - }, + return false; + }, - /** - * Returns a tinymce.ui.Collection with matches of the specified selector inside the specified container. - * - * @method find - * @param {tinymce.ui.Control} container Container to look for items in. - * @return {tinymce.ui.Collection} Collection with matched elements. - */ - find: function(container) { - var matches = [], i, l, selectors = this._selectors; + /** + * Returns a tinymce.ui.Collection with matches of the specified selector inside the specified container. + * + * @method find + * @param {tinymce.ui.Control} container Container to look for items in. + * @return {tinymce.ui.Collection} Collection with matched elements. + */ + find: function (container) { + var matches = [], i, l, selectors = this._selectors; - function collect(items, selector, index) { - var i, l, fi, fl, item, filters = selector[index]; + function collect(items, selector, index) { + var i, l, fi, fl, item, filters = selector[index]; - for (i = 0, l = items.length; i < l; i++) { - item = items[i]; + for (i = 0, l = items.length; i < l; i++) { + item = items[i]; - // Run each filter against the item - for (fi = 0, fl = filters.length; fi < fl; fi++) { - if (!filters[fi](item, i, l)) { - fi = fl + 1; - break; - } - } + // Run each filter against the item + for (fi = 0, fl = filters.length; fi < fl; fi++) { + if (!filters[fi](item, i, l)) { + fi = fl + 1; + break; + } + } - // All filters matched the item - if (fi === fl) { - // Matched item is on the last expression like: panel toolbar [button] - if (index == selector.length - 1) { - matches.push(item); - } else { - // Collect next expression type - if (item.items) { - collect(item.items(), selector, index + 1); - } - } - } else if (filters.direct) { - return; - } + // All filters matched the item + if (fi === fl) { + // Matched item is on the last expression like: panel toolbar [button] + if (index == selector.length - 1) { + matches.push(item); + } else { + // Collect next expression type + if (item.items) { + collect(item.items(), selector, index + 1); + } + } + } else if (filters.direct) { + return; + } - // Collect child items - if (item.items) { - collect(item.items(), selector, index); - } - } - } + // Collect child items + if (item.items) { + collect(item.items(), selector, index); + } + } + } - if (container.items) { - for (i = 0, l = selectors.length; i < l; i++) { - collect(container.items(), selectors[i], 0); - } + if (container.items) { + for (i = 0, l = selectors.length; i < l; i++) { + collect(container.items(), selectors[i], 0); + } - // Unique the matches if needed - if (l > 1) { - matches = unique(matches); - } - } + // Unique the matches if needed + if (l > 1) { + matches = unique(matches); + } + } - // Fix for circular reference - if (!Collection) { - // TODO: Fix me! - Collection = Selector.Collection; - } + // Fix for circular reference + if (!Collection) { + // TODO: Fix me! + Collection = Selector.Collection; + } - return new Collection(matches); - } - }); + return new Collection(matches); + } + }); - return Selector; -}); - -// Included from: js/tinymce/classes/ui/Collection.js + return Selector; + } +); /** * Collection.js * * Released under LGPL License. - * Copyright (c) 1999-2015 Ephox Corp. All rights reserved + * Copyright (c) 1999-2017 Ephox Corp. All rights reserved * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing @@ -26918,433 +31018,434 @@ define("tinymce/ui/Selector", [ * * @class tinymce.ui.Collection */ -define("tinymce/ui/Collection", [ - "tinymce/util/Tools", - "tinymce/ui/Selector", - "tinymce/util/Class" -], function(Tools, Selector, Class) { - "use strict"; +define( + 'tinymce.core.ui.Collection', + [ + "tinymce.core.util.Tools", + "tinymce.core.ui.Selector", + "tinymce.core.util.Class" + ], + function (Tools, Selector, Class) { + "use strict"; - var Collection, proto, push = Array.prototype.push, slice = Array.prototype.slice; + var Collection, proto, push = Array.prototype.push, slice = Array.prototype.slice; - proto = { - /** - * Current number of contained control instances. - * - * @field length - * @type Number - */ - length: 0, + proto = { + /** + * Current number of contained control instances. + * + * @field length + * @type Number + */ + length: 0, - /** - * Constructor for the collection. - * - * @constructor - * @method init - * @param {Array} items Optional array with items to add. - */ - init: function(items) { - if (items) { - this.add(items); - } - }, + /** + * Constructor for the collection. + * + * @constructor + * @method init + * @param {Array} items Optional array with items to add. + */ + init: function (items) { + if (items) { + this.add(items); + } + }, - /** - * Adds new items to the control collection. - * - * @method add - * @param {Array} items Array if items to add to collection. - * @return {tinymce.ui.Collection} Current collection instance. - */ - add: function(items) { - var self = this; + /** + * Adds new items to the control collection. + * + * @method add + * @param {Array} items Array if items to add to collection. + * @return {tinymce.ui.Collection} Current collection instance. + */ + add: function (items) { + var self = this; - // Force single item into array - if (!Tools.isArray(items)) { - if (items instanceof Collection) { - self.add(items.toArray()); - } else { - push.call(self, items); - } - } else { - push.apply(self, items); - } + // Force single item into array + if (!Tools.isArray(items)) { + if (items instanceof Collection) { + self.add(items.toArray()); + } else { + push.call(self, items); + } + } else { + push.apply(self, items); + } - return self; - }, + return self; + }, - /** - * Sets the contents of the collection. This will remove any existing items - * and replace them with the ones specified in the input array. - * - * @method set - * @param {Array} items Array with items to set into the Collection. - * @return {tinymce.ui.Collection} Collection instance. - */ - set: function(items) { - var self = this, len = self.length, i; + /** + * Sets the contents of the collection. This will remove any existing items + * and replace them with the ones specified in the input array. + * + * @method set + * @param {Array} items Array with items to set into the Collection. + * @return {tinymce.ui.Collection} Collection instance. + */ + set: function (items) { + var self = this, len = self.length, i; - self.length = 0; - self.add(items); + self.length = 0; + self.add(items); - // Remove old entries - for (i = self.length; i < len; i++) { - delete self[i]; - } + // Remove old entries + for (i = self.length; i < len; i++) { + delete self[i]; + } - return self; - }, + return self; + }, - /** - * Filters the collection item based on the specified selector expression or selector function. - * - * @method filter - * @param {String} selector Selector expression to filter items by. - * @return {tinymce.ui.Collection} Collection containing the filtered items. - */ - filter: function(selector) { - var self = this, i, l, matches = [], item, match; + /** + * Filters the collection item based on the specified selector expression or selector function. + * + * @method filter + * @param {String} selector Selector expression to filter items by. + * @return {tinymce.ui.Collection} Collection containing the filtered items. + */ + filter: function (selector) { + var self = this, i, l, matches = [], item, match; - // Compile string into selector expression - if (typeof selector === "string") { - selector = new Selector(selector); + // Compile string into selector expression + if (typeof selector === "string") { + selector = new Selector(selector); - match = function(item) { - return selector.match(item); - }; - } else { - // Use selector as matching function - match = selector; - } + match = function (item) { + return selector.match(item); + }; + } else { + // Use selector as matching function + match = selector; + } - for (i = 0, l = self.length; i < l; i++) { - item = self[i]; + for (i = 0, l = self.length; i < l; i++) { + item = self[i]; - if (match(item)) { - matches.push(item); - } - } + if (match(item)) { + matches.push(item); + } + } - return new Collection(matches); - }, + return new Collection(matches); + }, - /** - * Slices the items within the collection. - * - * @method slice - * @param {Number} index Index to slice at. - * @param {Number} len Optional length to slice. - * @return {tinymce.ui.Collection} Current collection. - */ - slice: function() { - return new Collection(slice.apply(this, arguments)); - }, + /** + * Slices the items within the collection. + * + * @method slice + * @param {Number} index Index to slice at. + * @param {Number} len Optional length to slice. + * @return {tinymce.ui.Collection} Current collection. + */ + slice: function () { + return new Collection(slice.apply(this, arguments)); + }, - /** - * Makes the current collection equal to the specified index. - * - * @method eq - * @param {Number} index Index of the item to set the collection to. - * @return {tinymce.ui.Collection} Current collection. - */ - eq: function(index) { - return index === -1 ? this.slice(index) : this.slice(index, +index + 1); - }, + /** + * Makes the current collection equal to the specified index. + * + * @method eq + * @param {Number} index Index of the item to set the collection to. + * @return {tinymce.ui.Collection} Current collection. + */ + eq: function (index) { + return index === -1 ? this.slice(index) : this.slice(index, +index + 1); + }, - /** - * Executes the specified callback on each item in collection. - * - * @method each - * @param {function} callback Callback to execute for each item in collection. - * @return {tinymce.ui.Collection} Current collection instance. - */ - each: function(callback) { - Tools.each(this, callback); + /** + * Executes the specified callback on each item in collection. + * + * @method each + * @param {function} callback Callback to execute for each item in collection. + * @return {tinymce.ui.Collection} Current collection instance. + */ + each: function (callback) { + Tools.each(this, callback); - return this; - }, + return this; + }, - /** - * Returns an JavaScript array object of the contents inside the collection. - * - * @method toArray - * @return {Array} Array with all items from collection. - */ - toArray: function() { - return Tools.toArray(this); - }, + /** + * Returns an JavaScript array object of the contents inside the collection. + * + * @method toArray + * @return {Array} Array with all items from collection. + */ + toArray: function () { + return Tools.toArray(this); + }, - /** - * Finds the index of the specified control or return -1 if it isn't in the collection. - * - * @method indexOf - * @param {Control} ctrl Control instance to look for. - * @return {Number} Index of the specified control or -1. - */ - indexOf: function(ctrl) { - var self = this, i = self.length; + /** + * Finds the index of the specified control or return -1 if it isn't in the collection. + * + * @method indexOf + * @param {Control} ctrl Control instance to look for. + * @return {Number} Index of the specified control or -1. + */ + indexOf: function (ctrl) { + var self = this, i = self.length; - while (i--) { - if (self[i] === ctrl) { - break; - } - } + while (i--) { + if (self[i] === ctrl) { + break; + } + } - return i; - }, + return i; + }, - /** - * Returns a new collection of the contents in reverse order. - * - * @method reverse - * @return {tinymce.ui.Collection} Collection instance with reversed items. - */ - reverse: function() { - return new Collection(Tools.toArray(this).reverse()); - }, + /** + * Returns a new collection of the contents in reverse order. + * + * @method reverse + * @return {tinymce.ui.Collection} Collection instance with reversed items. + */ + reverse: function () { + return new Collection(Tools.toArray(this).reverse()); + }, - /** - * Returns true/false if the class exists or not. - * - * @method hasClass - * @param {String} cls Class to check for. - * @return {Boolean} true/false state if the class exists or not. - */ - hasClass: function(cls) { - return this[0] ? this[0].classes.contains(cls) : false; - }, + /** + * Returns true/false if the class exists or not. + * + * @method hasClass + * @param {String} cls Class to check for. + * @return {Boolean} true/false state if the class exists or not. + */ + hasClass: function (cls) { + return this[0] ? this[0].classes.contains(cls) : false; + }, - /** - * Sets/gets the specific property on the items in the collection. The same as executing control.bug on IE 8 #6178 - DOMUtils.DOM.setHTML(elm, html); - } - }; - - return funcs; -}); - -// Included from: js/tinymce/classes/ui/BoxUtils.js + innerHtml: function (elm, html) { + // Workaround for
bug on IE 8 #6178 + DOMUtils.DOM.setHTML(elm, html); + } + }; + return funcs; + } +); /** * BoxUtils.js * * Released under LGPL License. - * Copyright (c) 1999-2015 Ephox Corp. All rights reserved + * Copyright (c) 1999-2017 Ephox Corp. All rights reserved * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing @@ -27491,96 +31593,98 @@ define("tinymce/ui/DomUtils", [ * @private * @class tinymce.ui.BoxUtils */ -define("tinymce/ui/BoxUtils", [ -], function() { - "use strict"; +define( + 'tinymce.core.ui.BoxUtils', + [ + ], + function () { + "use strict"; - return { - /** - * Parses the specified box value. A box value contains 1-4 properties in clockwise order. - * - * @method parseBox - * @param {String/Number} value Box value "0 1 2 3" or "0" etc. - * @return {Object} Object with top/right/bottom/left properties. - * @private - */ - parseBox: function(value) { - var len, radix = 10; + return { + /** + * Parses the specified box value. A box value contains 1-4 properties in clockwise order. + * + * @method parseBox + * @param {String/Number} value Box value "0 1 2 3" or "0" etc. + * @return {Object} Object with top/right/bottom/left properties. + * @private + */ + parseBox: function (value) { + var len, radix = 10; - if (!value) { - return; - } + if (!value) { + return; + } - if (typeof value === "number") { - value = value || 0; + if (typeof value === "number") { + value = value || 0; - return { - top: value, - left: value, - bottom: value, - right: value - }; - } + return { + top: value, + left: value, + bottom: value, + right: value + }; + } - value = value.split(' '); - len = value.length; + value = value.split(' '); + len = value.length; - if (len === 1) { - value[1] = value[2] = value[3] = value[0]; - } else if (len === 2) { - value[2] = value[0]; - value[3] = value[1]; - } else if (len === 3) { - value[3] = value[1]; - } + if (len === 1) { + value[1] = value[2] = value[3] = value[0]; + } else if (len === 2) { + value[2] = value[0]; + value[3] = value[1]; + } else if (len === 3) { + value[3] = value[1]; + } - return { - top: parseInt(value[0], radix) || 0, - right: parseInt(value[1], radix) || 0, - bottom: parseInt(value[2], radix) || 0, - left: parseInt(value[3], radix) || 0 - }; - }, + return { + top: parseInt(value[0], radix) || 0, + right: parseInt(value[1], radix) || 0, + bottom: parseInt(value[2], radix) || 0, + left: parseInt(value[3], radix) || 0 + }; + }, - measureBox: function(elm, prefix) { - function getStyle(name) { - var defaultView = document.defaultView; + measureBox: function (elm, prefix) { + function getStyle(name) { + var defaultView = document.defaultView; - if (defaultView) { - // Remove camelcase - name = name.replace(/[A-Z]/g, function(a) { - return '-' + a; - }); + if (defaultView) { + // Remove camelcase + name = name.replace(/[A-Z]/g, function (a) { + return '-' + a; + }); - return defaultView.getComputedStyle(elm, null).getPropertyValue(name); - } + return defaultView.getComputedStyle(elm, null).getPropertyValue(name); + } - return elm.currentStyle[name]; - } + return elm.currentStyle[name]; + } - function getSide(name) { - var val = parseFloat(getStyle(name), 10); + function getSide(name) { + var val = parseFloat(getStyle(name), 10); - return isNaN(val) ? 0 : val; - } + return isNaN(val) ? 0 : val; + } - return { - top: getSide(prefix + "TopWidth"), - right: getSide(prefix + "RightWidth"), - bottom: getSide(prefix + "BottomWidth"), - left: getSide(prefix + "LeftWidth") - }; - } - }; -}); - -// Included from: js/tinymce/classes/ui/ClassList.js + return { + top: getSide(prefix + "TopWidth"), + right: getSide(prefix + "RightWidth"), + bottom: getSide(prefix + "BottomWidth"), + left: getSide(prefix + "LeftWidth") + }; + } + }; + } +); /** * ClassList.js * * Released under LGPL License. - * Copyright (c) 1999-2015 Ephox Corp. All rights reserved + * Copyright (c) 1999-2017 Ephox Corp. All rights reserved * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing @@ -27592,147 +31696,148 @@ define("tinymce/ui/BoxUtils", [ * @private * @class tinymce.ui.ClassList */ -define("tinymce/ui/ClassList", [ - "tinymce/util/Tools" -], function(Tools) { - "use strict"; +define( + 'tinymce.core.ui.ClassList', + [ + "tinymce.core.util.Tools" + ], + function (Tools) { + "use strict"; - function noop() { - } + function noop() { + } - /** - * Constructs a new class list the specified onchange - * callback will be executed when the class list gets modifed. - * - * @constructor ClassList - * @param {function} onchange Onchange callback to be executed. - */ - function ClassList(onchange) { - this.cls = []; - this.cls._map = {}; - this.onchange = onchange || noop; - this.prefix = ''; - } + /** + * Constructs a new class list the specified onchange + * callback will be executed when the class list gets modifed. + * + * @constructor ClassList + * @param {function} onchange Onchange callback to be executed. + */ + function ClassList(onchange) { + this.cls = []; + this.cls._map = {}; + this.onchange = onchange || noop; + this.prefix = ''; + } - Tools.extend(ClassList.prototype, { - /** - * Adds a new class to the class list. - * - * @method add - * @param {String} cls Class to be added. - * @return {tinymce.ui.ClassList} Current class list instance. - */ - add: function(cls) { - if (cls && !this.contains(cls)) { - this.cls._map[cls] = true; - this.cls.push(cls); - this._change(); - } + Tools.extend(ClassList.prototype, { + /** + * Adds a new class to the class list. + * + * @method add + * @param {String} cls Class to be added. + * @return {tinymce.ui.ClassList} Current class list instance. + */ + add: function (cls) { + if (cls && !this.contains(cls)) { + this.cls._map[cls] = true; + this.cls.push(cls); + this._change(); + } - return this; - }, + return this; + }, - /** - * Removes the specified class from the class list. - * - * @method remove - * @param {String} cls Class to be removed. - * @return {tinymce.ui.ClassList} Current class list instance. - */ - remove: function(cls) { - if (this.contains(cls)) { - for (var i = 0; i < this.cls.length; i++) { - if (this.cls[i] === cls) { - break; - } - } + /** + * Removes the specified class from the class list. + * + * @method remove + * @param {String} cls Class to be removed. + * @return {tinymce.ui.ClassList} Current class list instance. + */ + remove: function (cls) { + if (this.contains(cls)) { + for (var i = 0; i < this.cls.length; i++) { + if (this.cls[i] === cls) { + break; + } + } - this.cls.splice(i, 1); - delete this.cls._map[cls]; - this._change(); - } + this.cls.splice(i, 1); + delete this.cls._map[cls]; + this._change(); + } - return this; - }, + return this; + }, - /** - * Toggles a class in the class list. - * - * @method toggle - * @param {String} cls Class to be added/removed. - * @param {Boolean} state Optional state if it should be added/removed. - * @return {tinymce.ui.ClassList} Current class list instance. - */ - toggle: function(cls, state) { - var curState = this.contains(cls); + /** + * Toggles a class in the class list. + * + * @method toggle + * @param {String} cls Class to be added/removed. + * @param {Boolean} state Optional state if it should be added/removed. + * @return {tinymce.ui.ClassList} Current class list instance. + */ + toggle: function (cls, state) { + var curState = this.contains(cls); - if (curState !== state) { - if (curState) { - this.remove(cls); - } else { - this.add(cls); - } + if (curState !== state) { + if (curState) { + this.remove(cls); + } else { + this.add(cls); + } - this._change(); - } + this._change(); + } - return this; - }, + return this; + }, - /** - * Returns true if the class list has the specified class. - * - * @method contains - * @param {String} cls Class to look for. - * @return {Boolean} true/false if the class exists or not. - */ - contains: function(cls) { - return !!this.cls._map[cls]; - }, + /** + * Returns true if the class list has the specified class. + * + * @method contains + * @param {String} cls Class to look for. + * @return {Boolean} true/false if the class exists or not. + */ + contains: function (cls) { + return !!this.cls._map[cls]; + }, - /** - * Returns a space separated list of classes. - * - * @method toString - * @return {String} Space separated list of classes. - */ + /** + * Returns a space separated list of classes. + * + * @method toString + * @return {String} Space separated list of classes. + */ - _change: function() { - delete this.clsValue; - this.onchange.call(this); - } - }); + _change: function () { + delete this.clsValue; + this.onchange.call(this); + } + }); - // IE 8 compatibility - ClassList.prototype.toString = function() { - var value; + // IE 8 compatibility + ClassList.prototype.toString = function () { + var value; - if (this.clsValue) { - return this.clsValue; - } + if (this.clsValue) { + return this.clsValue; + } - value = ''; - for (var i = 0; i < this.cls.length; i++) { - if (i > 0) { - value += ' '; - } + value = ''; + for (var i = 0; i < this.cls.length; i++) { + if (i > 0) { + value += ' '; + } - value += this.prefix + this.cls[i]; - } + value += this.prefix + this.cls[i]; + } - return value; - }; - - return ClassList; -}); - -// Included from: js/tinymce/classes/ui/ReflowQueue.js + return value; + }; + return ClassList; + } +); /** * ReflowQueue.js * * Released under LGPL License. - * Copyright (c) 1999-2015 Ephox Corp. All rights reserved + * Copyright (c) 1999-2017 Ephox Corp. All rights reserved * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing @@ -27746,75 +31851,77 @@ define("tinymce/ui/ClassList", [ * @class tinymce.ui.ReflowQueue * @static */ -define("tinymce/ui/ReflowQueue", [ - "tinymce/util/Delay" -], function(Delay) { - var dirtyCtrls = {}, animationFrameRequested; +define( + 'tinymce.core.ui.ReflowQueue', + [ + "tinymce.core.util.Delay" + ], + function (Delay) { + var dirtyCtrls = {}, animationFrameRequested; - return { - /** - * Adds a control to the next automatic reflow call. This is the control that had a state - * change for example if the control was hidden/shown. - * - * @method add - * @param {tinymce.ui.Control} ctrl Control to add to queue. - */ - add: function(ctrl) { - var parent = ctrl.parent(); + return { + /** + * Adds a control to the next automatic reflow call. This is the control that had a state + * change for example if the control was hidden/shown. + * + * @method add + * @param {tinymce.ui.Control} ctrl Control to add to queue. + */ + add: function (ctrl) { + var parent = ctrl.parent(); - if (parent) { - if (!parent._layout || parent._layout.isNative()) { - return; - } + if (parent) { + if (!parent._layout || parent._layout.isNative()) { + return; + } - if (!dirtyCtrls[parent._id]) { - dirtyCtrls[parent._id] = parent; - } + if (!dirtyCtrls[parent._id]) { + dirtyCtrls[parent._id] = parent; + } - if (!animationFrameRequested) { - animationFrameRequested = true; + if (!animationFrameRequested) { + animationFrameRequested = true; - Delay.requestAnimationFrame(function() { - var id, ctrl; + Delay.requestAnimationFrame(function () { + var id, ctrl; - animationFrameRequested = false; + animationFrameRequested = false; - for (id in dirtyCtrls) { - ctrl = dirtyCtrls[id]; + for (id in dirtyCtrls) { + ctrl = dirtyCtrls[id]; - if (ctrl.state.get('rendered')) { - ctrl.reflow(); - } - } + if (ctrl.state.get('rendered')) { + ctrl.reflow(); + } + } - dirtyCtrls = {}; - }, document.body); - } - } - }, + dirtyCtrls = {}; + }, document.body); + } + } + }, - /** - * Removes the specified control from the automatic reflow. This will happen when for example the user - * manually triggers a reflow. - * - * @method remove - * @param {tinymce.ui.Control} ctrl Control to remove from queue. - */ - remove: function(ctrl) { - if (dirtyCtrls[ctrl._id]) { - delete dirtyCtrls[ctrl._id]; - } - } - }; -}); - -// Included from: js/tinymce/classes/ui/Control.js + /** + * Removes the specified control from the automatic reflow. This will happen when for example the user + * manually triggers a reflow. + * + * @method remove + * @param {tinymce.ui.Control} ctrl Control to remove from queue. + */ + remove: function (ctrl) { + if (dirtyCtrls[ctrl._id]) { + delete dirtyCtrls[ctrl._id]; + } + } + }; + } +); /** * Control.js * * Released under LGPL License. - * Copyright (c) 1999-2015 Ephox Corp. All rights reserved + * Copyright (c) 1999-2017 Ephox Corp. All rights reserved * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing @@ -27828,1304 +31935,1304 @@ define("tinymce/ui/ReflowQueue", [ * * @class tinymce.ui.Control */ -define("tinymce/ui/Control", [ - "tinymce/util/Class", - "tinymce/util/Tools", - "tinymce/util/EventDispatcher", - "tinymce/data/ObservableObject", - "tinymce/ui/Collection", - "tinymce/ui/DomUtils", - "tinymce/dom/DomQuery", - "tinymce/ui/BoxUtils", - "tinymce/ui/ClassList", - "tinymce/ui/ReflowQueue" -], function(Class, Tools, EventDispatcher, ObservableObject, Collection, DomUtils, $, BoxUtils, ClassList, ReflowQueue) { - "use strict"; - - var hasMouseWheelEventSupport = "onmousewheel" in document; - var hasWheelEventSupport = false; - var classPrefix = "mce-"; - var Control, idCounter = 0; - - var proto = { - Statics: { - classPrefix: classPrefix - }, - - isRtl: function() { - return Control.rtl; - }, - - /** - * Class/id prefix to use for all controls. - * - * @final - * @field {String} classPrefix - */ - classPrefix: classPrefix, - - /** - * Constructs a new control instance with the specified settings. - * - * @constructor - * @param {Object} settings Name/value object with settings. - * @setting {String} style Style CSS properties to add. - * @setting {String} border Border box values example: 1 1 1 1 - * @setting {String} padding Padding box values example: 1 1 1 1 - * @setting {String} margin Margin box values example: 1 1 1 1 - * @setting {Number} minWidth Minimal width for the control. - * @setting {Number} minHeight Minimal height for the control. - * @setting {String} classes Space separated list of classes to add. - * @setting {String} role WAI-ARIA role to use for control. - * @setting {Boolean} hidden Is the control hidden by default. - * @setting {Boolean} disabled Is the control disabled by default. - * @setting {String} name Name of the control instance. - */ - init: function(settings) { - var self = this, classes, defaultClasses; - - function applyClasses(classes) { - var i; - - classes = classes.split(' '); - for (i = 0; i < classes.length; i++) { - self.classes.add(classes[i]); - } - } - - self.settings = settings = Tools.extend({}, self.Defaults, settings); - - // Initial states - self._id = settings.id || ('mceu_' + (idCounter++)); - self._aria = {role: settings.role}; - self._elmCache = {}; - self.$ = $; - - self.state = new ObservableObject({ - visible: true, - active: false, - disabled: false, - value: '' - }); - - self.data = new ObservableObject(settings.data); - - self.classes = new ClassList(function() { - if (self.state.get('rendered')) { - self.getEl().className = this.toString(); - } - }); - self.classes.prefix = self.classPrefix; - - // Setup classes - classes = settings.classes; - if (classes) { - if (self.Defaults) { - defaultClasses = self.Defaults.classes; - - if (defaultClasses && classes != defaultClasses) { - applyClasses(defaultClasses); - } - } - - applyClasses(classes); - } - - Tools.each('title text name visible disabled active value'.split(' '), function(name) { - if (name in settings) { - self[name](settings[name]); - } - }); - - self.on('click', function() { - if (self.disabled()) { - return false; - } - }); - - /** - * Name/value object with settings for the current control. - * - * @field {Object} settings - */ - self.settings = settings; - - self.borderBox = BoxUtils.parseBox(settings.border); - self.paddingBox = BoxUtils.parseBox(settings.padding); - self.marginBox = BoxUtils.parseBox(settings.margin); - - if (settings.hidden) { - self.hide(); - } - }, - - // Will generate getter/setter methods for these properties - Properties: 'parent,name', - - /** - * Returns the root element to render controls into. - * - * @method getContainerElm - * @return {Element} HTML DOM element to render into. - */ - getContainerElm: function() { - return DomUtils.getContainer(); - }, - - /** - * Returns a control instance for the current DOM element. - * - * @method getParentCtrl - * @param {Element} elm HTML dom element to get parent control from. - * @return {tinymce.ui.Control} Control instance or undefined. - */ - getParentCtrl: function(elm) { - var ctrl, lookup = this.getRoot().controlIdLookup; - - while (elm && lookup) { - ctrl = lookup[elm.id]; - if (ctrl) { - break; - } - - elm = elm.parentNode; - } - - return ctrl; - }, - - /** - * Initializes the current controls layout rect. - * This will be executed by the layout managers to determine the - * default minWidth/minHeight etc. - * - * @method initLayoutRect - * @return {Object} Layout rect instance. - */ - initLayoutRect: function() { - var self = this, settings = self.settings, borderBox, layoutRect; - var elm = self.getEl(), width, height, minWidth, minHeight, autoResize; - var startMinWidth, startMinHeight, initialSize; - - // Measure the current element - borderBox = self.borderBox = self.borderBox || BoxUtils.measureBox(elm, 'border'); - self.paddingBox = self.paddingBox || BoxUtils.measureBox(elm, 'padding'); - self.marginBox = self.marginBox || BoxUtils.measureBox(elm, 'margin'); - initialSize = DomUtils.getSize(elm); - - // Setup minWidth/minHeight and width/height - startMinWidth = settings.minWidth; - startMinHeight = settings.minHeight; - minWidth = startMinWidth || initialSize.width; - minHeight = startMinHeight || initialSize.height; - width = settings.width; - height = settings.height; - autoResize = settings.autoResize; - autoResize = typeof autoResize != "undefined" ? autoResize : !width && !height; - - width = width || minWidth; - height = height || minHeight; - - var deltaW = borderBox.left + borderBox.right; - var deltaH = borderBox.top + borderBox.bottom; - - var maxW = settings.maxWidth || 0xFFFF; - var maxH = settings.maxHeight || 0xFFFF; - - // Setup initial layout rect - self._layoutRect = layoutRect = { - x: settings.x || 0, - y: settings.y || 0, - w: width, - h: height, - deltaW: deltaW, - deltaH: deltaH, - contentW: width - deltaW, - contentH: height - deltaH, - innerW: width - deltaW, - innerH: height - deltaH, - startMinWidth: startMinWidth || 0, - startMinHeight: startMinHeight || 0, - minW: Math.min(minWidth, maxW), - minH: Math.min(minHeight, maxH), - maxW: maxW, - maxH: maxH, - autoResize: autoResize, - scrollW: 0 - }; - - self._lastLayoutRect = {}; - - return layoutRect; - }, - - /** - * Getter/setter for the current layout rect. - * - * @method layoutRect - * @param {Object} [newRect] Optional new layout rect. - * @return {tinymce.ui.Control/Object} Current control or rect object. - */ - layoutRect: function(newRect) { - var self = this, curRect = self._layoutRect, lastLayoutRect, size, deltaWidth, deltaHeight, undef, repaintControls; - - // Initialize default layout rect - if (!curRect) { - curRect = self.initLayoutRect(); - } - - // Set new rect values - if (newRect) { - // Calc deltas between inner and outer sizes - deltaWidth = curRect.deltaW; - deltaHeight = curRect.deltaH; - - // Set x position - if (newRect.x !== undef) { - curRect.x = newRect.x; - } - - // Set y position - if (newRect.y !== undef) { - curRect.y = newRect.y; - } - - // Set minW - if (newRect.minW !== undef) { - curRect.minW = newRect.minW; - } - - // Set minH - if (newRect.minH !== undef) { - curRect.minH = newRect.minH; - } - - // Set new width and calculate inner width - size = newRect.w; - if (size !== undef) { - size = size < curRect.minW ? curRect.minW : size; - size = size > curRect.maxW ? curRect.maxW : size; - curRect.w = size; - curRect.innerW = size - deltaWidth; - } - - // Set new height and calculate inner height - size = newRect.h; - if (size !== undef) { - size = size < curRect.minH ? curRect.minH : size; - size = size > curRect.maxH ? curRect.maxH : size; - curRect.h = size; - curRect.innerH = size - deltaHeight; - } - - // Set new inner width and calculate width - size = newRect.innerW; - if (size !== undef) { - size = size < curRect.minW - deltaWidth ? curRect.minW - deltaWidth : size; - size = size > curRect.maxW - deltaWidth ? curRect.maxW - deltaWidth : size; - curRect.innerW = size; - curRect.w = size + deltaWidth; - } - - // Set new height and calculate inner height - size = newRect.innerH; - if (size !== undef) { - size = size < curRect.minH - deltaHeight ? curRect.minH - deltaHeight : size; - size = size > curRect.maxH - deltaHeight ? curRect.maxH - deltaHeight : size; - curRect.innerH = size; - curRect.h = size + deltaHeight; - } - - // Set new contentW - if (newRect.contentW !== undef) { - curRect.contentW = newRect.contentW; - } - - // Set new contentH - if (newRect.contentH !== undef) { - curRect.contentH = newRect.contentH; - } - - // Compare last layout rect with the current one to see if we need to repaint or not - lastLayoutRect = self._lastLayoutRect; - if (lastLayoutRect.x !== curRect.x || lastLayoutRect.y !== curRect.y || - lastLayoutRect.w !== curRect.w || lastLayoutRect.h !== curRect.h) { - repaintControls = Control.repaintControls; - - if (repaintControls) { - if (repaintControls.map && !repaintControls.map[self._id]) { - repaintControls.push(self); - repaintControls.map[self._id] = true; - } - } - - lastLayoutRect.x = curRect.x; - lastLayoutRect.y = curRect.y; - lastLayoutRect.w = curRect.w; - lastLayoutRect.h = curRect.h; - } - - return self; - } - - return curRect; - }, - - /** - * Repaints the control after a layout operation. - * - * @method repaint - */ - repaint: function() { - var self = this, style, bodyStyle, bodyElm, rect, borderBox; - var borderW, borderH, lastRepaintRect, round, value; - - // Use Math.round on all values on IE < 9 - round = !document.createRange ? Math.round : function(value) { - return value; - }; - - style = self.getEl().style; - rect = self._layoutRect; - lastRepaintRect = self._lastRepaintRect || {}; - - borderBox = self.borderBox; - borderW = borderBox.left + borderBox.right; - borderH = borderBox.top + borderBox.bottom; - - if (rect.x !== lastRepaintRect.x) { - style.left = round(rect.x) + 'px'; - lastRepaintRect.x = rect.x; - } - - if (rect.y !== lastRepaintRect.y) { - style.top = round(rect.y) + 'px'; - lastRepaintRect.y = rect.y; - } - - if (rect.w !== lastRepaintRect.w) { - value = round(rect.w - borderW); - style.width = (value >= 0 ? value : 0) + 'px'; - lastRepaintRect.w = rect.w; - } - - if (rect.h !== lastRepaintRect.h) { - value = round(rect.h - borderH); - style.height = (value >= 0 ? value : 0) + 'px'; - lastRepaintRect.h = rect.h; - } - - // Update body if needed - if (self._hasBody && rect.innerW !== lastRepaintRect.innerW) { - value = round(rect.innerW); - - bodyElm = self.getEl('body'); - if (bodyElm) { - bodyStyle = bodyElm.style; - bodyStyle.width = (value >= 0 ? value : 0) + 'px'; - } - - lastRepaintRect.innerW = rect.innerW; - } - - if (self._hasBody && rect.innerH !== lastRepaintRect.innerH) { - value = round(rect.innerH); - - bodyElm = bodyElm || self.getEl('body'); - if (bodyElm) { - bodyStyle = bodyStyle || bodyElm.style; - bodyStyle.height = (value >= 0 ? value : 0) + 'px'; - } - - lastRepaintRect.innerH = rect.innerH; - } - - self._lastRepaintRect = lastRepaintRect; - self.fire('repaint', {}, false); - }, - - /** - * Updates the controls layout rect by re-measuing it. - */ - updateLayoutRect: function() { - var self = this; - - self.parent()._lastRect = null; - - DomUtils.css(self.getEl(), {width: '', height: ''}); - - self._layoutRect = self._lastRepaintRect = self._lastLayoutRect = null; - self.initLayoutRect(); - }, - - /** - * Binds a callback to the specified event. This event can both be - * native browser events like "click" or custom ones like PostRender. - * - * The callback function will be passed a DOM event like object that enables yout do stop propagation. - * - * @method on - * @param {String} name Name of the event to bind. For example "click". - * @param {String/function} callback Callback function to execute ones the event occurs. - * @return {tinymce.ui.Control} Current control object. - */ - on: function(name, callback) { - var self = this; - - function resolveCallbackName(name) { - var callback, scope; - - if (typeof name != 'string') { - return name; - } - - return function(e) { - if (!callback) { - self.parentsAndSelf().each(function(ctrl) { - var callbacks = ctrl.settings.callbacks; - - if (callbacks && (callback = callbacks[name])) { - scope = ctrl; - return false; - } - }); - } - - if (!callback) { - e.action = name; - this.fire('execute', e); - return; - } - - return callback.call(scope, e); - }; - } - - getEventDispatcher(self).on(name, resolveCallbackName(callback)); - - return self; - }, - - /** - * Unbinds the specified event and optionally a specific callback. If you omit the name - * parameter all event handlers will be removed. If you omit the callback all event handles - * by the specified name will be removed. - * - * @method off - * @param {String} [name] Name for the event to unbind. - * @param {function} [callback] Callback function to unbind. - * @return {tinymce.ui.Control} Current control object. - */ - off: function(name, callback) { - getEventDispatcher(this).off(name, callback); - return this; - }, - - /** - * Fires the specified event by name and arguments on the control. This will execute all - * bound event handlers. - * - * @method fire - * @param {String} name Name of the event to fire. - * @param {Object} [args] Arguments to pass to the event. - * @param {Boolean} [bubble] Value to control bubbling. Defaults to true. - * @return {Object} Current arguments object. - */ - fire: function(name, args, bubble) { - var self = this; - - args = args || {}; - - if (!args.control) { - args.control = self; - } - - args = getEventDispatcher(self).fire(name, args); - - // Bubble event up to parents - if (bubble !== false && self.parent) { - var parent = self.parent(); - while (parent && !args.isPropagationStopped()) { - parent.fire(name, args, false); - parent = parent.parent(); - } - } - - return args; - }, - - /** - * Returns true/false if the specified event has any listeners. - * - * @method hasEventListeners - * @param {String} name Name of the event to check for. - * @return {Boolean} True/false state if the event has listeners. - */ - hasEventListeners: function(name) { - return getEventDispatcher(this).has(name); - }, - - /** - * Returns a control collection with all parent controls. - * - * @method parents - * @param {String} selector Optional selector expression to find parents. - * @return {tinymce.ui.Collection} Collection with all parent controls. - */ - parents: function(selector) { - var self = this, ctrl, parents = new Collection(); - - // Add each parent to collection - for (ctrl = self.parent(); ctrl; ctrl = ctrl.parent()) { - parents.add(ctrl); - } - - // Filter away everything that doesn't match the selector - if (selector) { - parents = parents.filter(selector); - } - - return parents; - }, - - /** - * Returns the current control and it's parents. - * - * @method parentsAndSelf - * @param {String} selector Optional selector expression to find parents. - * @return {tinymce.ui.Collection} Collection with all parent controls. - */ - parentsAndSelf: function(selector) { - return new Collection(this).add(this.parents(selector)); - }, - - /** - * Returns the control next to the current control. - * - * @method next - * @return {tinymce.ui.Control} Next control instance. - */ - next: function() { - var parentControls = this.parent().items(); - - return parentControls[parentControls.indexOf(this) + 1]; - }, - - /** - * Returns the control previous to the current control. - * - * @method prev - * @return {tinymce.ui.Control} Previous control instance. - */ - prev: function() { - var parentControls = this.parent().items(); - - return parentControls[parentControls.indexOf(this) - 1]; - }, - - /** - * Sets the inner HTML of the control element. - * - * @method innerHtml - * @param {String} html Html string to set as inner html. - * @return {tinymce.ui.Control} Current control object. - */ - innerHtml: function(html) { - this.$el.html(html); - return this; - }, - - /** - * Returns the control DOM element or sub element. - * - * @method getEl - * @param {String} [suffix] Suffix to get element by. - * @return {Element} HTML DOM element for the current control or it's children. - */ - getEl: function(suffix) { - var id = suffix ? this._id + '-' + suffix : this._id; - - if (!this._elmCache[id]) { - this._elmCache[id] = $('#' + id)[0]; - } - - return this._elmCache[id]; - }, - - /** - * Sets the visible state to true. - * - * @method show - * @return {tinymce.ui.Control} Current control instance. - */ - show: function() { - return this.visible(true); - }, - - /** - * Sets the visible state to false. - * - * @method hide - * @return {tinymce.ui.Control} Current control instance. - */ - hide: function() { - return this.visible(false); - }, - - /** - * Focuses the current control. - * - * @method focus - * @return {tinymce.ui.Control} Current control instance. - */ - focus: function() { - try { - this.getEl().focus(); - } catch (ex) { - // Ignore IE error - } - - return this; - }, - - /** - * Blurs the current control. - * - * @method blur - * @return {tinymce.ui.Control} Current control instance. - */ - blur: function() { - this.getEl().blur(); - - return this; - }, - - /** - * Sets the specified aria property. - * - * @method aria - * @param {String} name Name of the aria property to set. - * @param {String} value Value of the aria property. - * @return {tinymce.ui.Control} Current control instance. - */ - aria: function(name, value) { - var self = this, elm = self.getEl(self.ariaTarget); - - if (typeof value === "undefined") { - return self._aria[name]; - } - - self._aria[name] = value; - - if (self.state.get('rendered')) { - elm.setAttribute(name == 'role' ? name : 'aria-' + name, value); - } - - return self; - }, - - /** - * Encodes the specified string with HTML entities. It will also - * translate the string to different languages. - * - * @method encode - * @param {String/Object/Array} text Text to entity encode. - * @param {Boolean} [translate=true] False if the contents shouldn't be translated. - * @return {String} Encoded and possible traslated string. - */ - encode: function(text, translate) { - if (translate !== false) { - text = this.translate(text); - } - - return (text || '').replace(/[&<>"]/g, function(match) { - return '' + match.charCodeAt(0) + ';'; - }); - }, - - /** - * Returns the translated string. - * - * @method translate - * @param {String} text Text to translate. - * @return {String} Translated string or the same as the input. - */ - translate: function(text) { - return Control.translate ? Control.translate(text) : text; - }, - - /** - * Adds items before the current control. - * - * @method before - * @param {Array/tinymce.ui.Collection} items Array of items to prepend before this control. - * @return {tinymce.ui.Control} Current control instance. - */ - before: function(items) { - var self = this, parent = self.parent(); - - if (parent) { - parent.insert(items, parent.items().indexOf(self), true); - } - - return self; - }, - - /** - * Adds items after the current control. - * - * @method after - * @param {Array/tinymce.ui.Collection} items Array of items to append after this control. - * @return {tinymce.ui.Control} Current control instance. - */ - after: function(items) { - var self = this, parent = self.parent(); - - if (parent) { - parent.insert(items, parent.items().indexOf(self)); - } - - return self; - }, - - /** - * Removes the current control from DOM and from UI collections. - * - * @method remove - * @return {tinymce.ui.Control} Current control instance. - */ - remove: function() { - var self = this, elm = self.getEl(), parent = self.parent(), newItems, i; - - if (self.items) { - var controls = self.items().toArray(); - i = controls.length; - while (i--) { - controls[i].remove(); - } - } - - if (parent && parent.items) { - newItems = []; - - parent.items().each(function(item) { - if (item !== self) { - newItems.push(item); - } - }); - - parent.items().set(newItems); - parent._lastRect = null; - } - - if (self._eventsRoot && self._eventsRoot == self) { - $(elm).off(); - } - - var lookup = self.getRoot().controlIdLookup; - if (lookup) { - delete lookup[self._id]; - } - - if (elm && elm.parentNode) { - elm.parentNode.removeChild(elm); - } - - self.state.set('rendered', false); - self.state.destroy(); - - self.fire('remove'); - - return self; - }, - - /** - * Renders the control before the specified element. - * - * @method renderBefore - * @param {Element} elm Element to render before. - * @return {tinymce.ui.Control} Current control instance. - */ - renderBefore: function(elm) { - $(elm).before(this.renderHtml()); - this.postRender(); - return this; - }, - - /** - * Renders the control to the specified element. - * - * @method renderBefore - * @param {Element} elm Element to render to. - * @return {tinymce.ui.Control} Current control instance. - */ - renderTo: function(elm) { - $(elm || this.getContainerElm()).append(this.renderHtml()); - this.postRender(); - return this; - }, - - preRender: function() { - }, - - render: function() { - }, - - renderHtml: function() { - return '
'; - }, - - /** - * Post render method. Called after the control has been rendered to the target. - * - * @method postRender - * @return {tinymce.ui.Control} Current control instance. - */ - postRender: function() { - var self = this, settings = self.settings, elm, box, parent, name, parentEventsRoot; - - self.$el = $(self.getEl()); - self.state.set('rendered', true); - - // Bind on|b
- * - * Will produce this on backspace: - *a|
would become|
in WebKit. - * With this patch:|
|
- * - * Or: - *|
- if (!isDefaultPrevented(e) && (keyCode == DELETE || keyCode == BACKSPACE)) { - isCollapsed = editor.selection.isCollapsed(); - body = editor.getBody(); - - // Selection is collapsed but the editor isn't empty - if (isCollapsed && !dom.isEmpty(body)) { - return; - } - - // Selection isn't collapsed but not all the contents is selected - if (!isCollapsed && !allContentsSelected(editor.selection.getRng())) { - return; - } - - // Manually empty the editor - e.preventDefault(); - editor.setContent(''); - - if (body.firstChild && dom.isBlock(body.firstChild)) { - editor.selection.setCursorLocation(body.firstChild, 0); - } else { - editor.selection.setCursorLocation(body, 0); - } - - editor.nodeChanged(); - } - }); - } - - /** - * WebKit doesn't select all the nodes in the body when you press Ctrl+A. - * IE selects more than the contents [a
] instead of[a]
see bug #6438 - * This selects the whole body so that backspace/delete logic will delete everything - */ - function selectAll() { - editor.shortcuts.add('meta+a', null, 'SelectAll'); - } - - /** - * WebKit has a weird issue where it some times fails to properly convert keypresses to input method keystrokes. - * The IME on Mac doesn't initialize when it doesn't fire a proper focus event. - * - * This seems to happen when the user manages to click the documentElement element then the window doesn't get proper focus until - * you enter a character into the editor. - * - * It also happens when the first focus in made to the body. - * - * See: https://bugs.webkit.org/show_bug.cgi?id=83566 - */ - function inputMethodFocus() { - if (!editor.settings.content_editable) { - // Case 1 IME doesn't initialize if you focus the document - // Disabled since it was interferring with the cE=false logic - // Also coultn't reproduce the issue on Safari 9 - /*dom.bind(editor.getDoc(), 'focusin', function() { - selection.setRng(selection.getRng()); - });*/ - - // Case 2 IME doesn't initialize if you click the documentElement it also doesn't properly fire the focusin event - // Needs to be both down/up due to weird rendering bug on Chrome Windows - dom.bind(editor.getDoc(), 'mousedown mouseup', function(e) { - var rng; - - if (e.target == editor.getDoc().documentElement) { - rng = selection.getRng(); - editor.getBody().focus(); - - if (e.type == 'mousedown') { - if (CaretContainer.isCaretContainer(rng.startContainer)) { - return; - } - - // Edge case for mousedown, drag select and mousedown again within selection on Chrome Windows to render caret - selection.placeCaretAt(e.clientX, e.clientY); - } else { - selection.setRng(rng); - } - } - }); - } - } - - /** - * Backspacing in FireFox/IE from a paragraph into a horizontal rule results in a floating text node because the - * browser just deletes the paragraph - the browser fails to merge the text node with a horizontal rule so it is - * left there. TinyMCE sees a floating text node and wraps it in a paragraph on the key up event (ForceBlocks.js - * addRootBlocks), meaning the action does nothing. With this code, FireFox/IE matche the behaviour of other - * browsers. - * - * It also fixes a bug on Firefox where it's impossible to delete HR elements. - */ - function removeHrOnBackspace() { - editor.on('keydown', function(e) { - if (!isDefaultPrevented(e) && e.keyCode === BACKSPACE) { - // Check if there is any HR elements this is faster since getRng on IE 7 & 8 is slow - if (!editor.getBody().getElementsByTagName('hr').length) { - return; - } - - if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) { - var node = selection.getNode(); - var previousSibling = node.previousSibling; - - if (node.nodeName == 'HR') { - dom.remove(node); - e.preventDefault(); - return; - } - - if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "hr") { - dom.remove(previousSibling); - e.preventDefault(); - } - } - } - }); - } - - /** - * Firefox 3.x has an issue where the body element won't get proper focus if you click out - * side it's rectangle. - */ - function focusBody() { - // Fix for a focus bug in FF 3.x where the body element - // wouldn't get proper focus if the user clicked on the HTML element - if (!window.Range.prototype.getClientRects) { // Detect getClientRects got introduced in FF 4 - editor.on('mousedown', function(e) { - if (!isDefaultPrevented(e) && e.target.nodeName === "HTML") { - var body = editor.getBody(); - - // Blur the body it's focused but not correctly focused - body.blur(); - - // Refocus the body after a little while - Delay.setEditorTimeout(editor, function() { - body.focus(); - }); - } - }); - } - } - - /** - * WebKit has a bug where it isn't possible to select image, hr or anchor elements - * by clicking on them so we need to fake that. - */ - function selectControlElements() { - editor.on('click', function(e) { - var target = e.target; - - // Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250 - // WebKit can't even do simple things like selecting an image - if (/^(IMG|HR)$/.test(target.nodeName) && dom.getContentEditableParent(target) !== "false") { - e.preventDefault(); - selection.select(target); - editor.nodeChanged(); - } - - if (target.nodeName == 'A' && dom.hasClass(target, 'mce-item-anchor')) { - e.preventDefault(); - selection.select(target); - } - }); - } - - /** - * Fixes a Gecko bug where the style attribute gets added to the wrong element when deleting between two block elements. - * - * Fixes do backspace/delete on this: - *bla[ck
r]ed
- * - * Would become: - *bla|ed
- * - * Instead of: - *bla|ed
- */ - function removeStylesWhenDeletingAcrossBlockElements() { - function getAttributeApplyFunction() { - var template = dom.getAttribs(selection.getStart().cloneNode(false)); - - return function() { - var target = selection.getStart(); - - if (target !== editor.getBody()) { - dom.setAttrib(target, "style", null); - - each(template, function(attr) { - target.setAttributeNode(attr.cloneNode(true)); - }); - } - }; - } - - function isSelectionAcrossElements() { - return !selection.isCollapsed() && - dom.getParent(selection.getStart(), dom.isBlock) != dom.getParent(selection.getEnd(), dom.isBlock); - } - - editor.on('keypress', function(e) { - var applyAttributes; - - if (!isDefaultPrevented(e) && (e.keyCode == 8 || e.keyCode == 46) && isSelectionAcrossElements()) { - applyAttributes = getAttributeApplyFunction(); - editor.getDoc().execCommand('delete', false, null); - applyAttributes(); - e.preventDefault(); - return false; - } - }); - - dom.bind(editor.getDoc(), 'cut', function(e) { - var applyAttributes; - - if (!isDefaultPrevented(e) && isSelectionAcrossElements()) { - applyAttributes = getAttributeApplyFunction(); - - Delay.setEditorTimeout(editor, function() { - applyAttributes(); - }); - } - }); - } - - /** - * Screen readers on IE needs to have the role application set on the body. - */ - function ensureBodyHasRoleApplication() { - document.body.setAttribute("role", "application"); - } - - /** - * Backspacing into a table behaves differently depending upon browser type. - * Therefore, disable Backspace when cursor immediately follows a table. - */ - function disableBackspaceIntoATable() { - editor.on('keydown', function(e) { - if (!isDefaultPrevented(e) && e.keyCode === BACKSPACE) { - if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) { - var previousSibling = selection.getNode().previousSibling; - if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "table") { - e.preventDefault(); - return false; - } - } - } - }); - } - - /** - * Old IE versions can't properly render BR elements in PRE tags white in contentEditable mode. So this - * logic adds a \n before the BR so that it will get rendered. - */ - function addNewLinesBeforeBrInPre() { - // IE8+ rendering mode does the right thing with BR in PRE - if (getDocumentMode() > 7) { - return; - } - - // Enable display: none in area and add a specific class that hides all BR elements in PRE to - // avoid the caret from getting stuck at the BR elements while pressing the right arrow key - setEditorCommandState('RespectVisibilityInDesign', true); - editor.contentStyles.push('.mceHideBrInPre pre br {display: none}'); - dom.addClass(editor.getBody(), 'mceHideBrInPre'); - - // Adds a \n before all BR elements in PRE to get them visual - parser.addNodeFilter('pre', function(nodes) { - var i = nodes.length, brNodes, j, brElm, sibling; - - while (i--) { - brNodes = nodes[i].getAll('br'); - j = brNodes.length; - while (j--) { - brElm = brNodes[j]; - - // Add \n before BR in PRE elements on older IE:s so the new lines get rendered - sibling = brElm.prev; - if (sibling && sibling.type === 3 && sibling.value.charAt(sibling.value - 1) != '\n') { - sibling.value += '\n'; - } else { - brElm.parent.insert(new Node('#text', 3), brElm, true).value = '\n'; - } - } - } - }); - - // Removes any \n before BR elements in PRE since other browsers and in contentEditable=false mode they will be visible - serializer.addNodeFilter('pre', function(nodes) { - var i = nodes.length, brNodes, j, brElm, sibling; - - while (i--) { - brNodes = nodes[i].getAll('br'); - j = brNodes.length; - while (j--) { - brElm = brNodes[j]; - sibling = brElm.prev; - if (sibling && sibling.type == 3) { - sibling.value = sibling.value.replace(/\r?\n$/, ''); - } - } - } - }); - } - - /** - * Moves style width/height to attribute width/height when the user resizes an image on IE. - */ - function removePreSerializedStylesWhenSelectingControls() { - dom.bind(editor.getBody(), 'mouseup', function() { - var value, node = selection.getNode(); - - // Moved styles to attributes on IMG eements - if (node.nodeName == 'IMG') { - // Convert style width to width attribute - if ((value = dom.getStyle(node, 'width'))) { - dom.setAttrib(node, 'width', value.replace(/[^0-9%]+/g, '')); - dom.setStyle(node, 'width', ''); - } - - // Convert style height to height attribute - if ((value = dom.getStyle(node, 'height'))) { - dom.setAttrib(node, 'height', value.replace(/[^0-9%]+/g, '')); - dom.setStyle(node, 'height', ''); - } - } - }); - } - - /** - * Removes a blockquote when backspace is pressed at the beginning of it. - * - * For example: - *- * - * Becomes: - *|x
|x
- */ - function removeBlockQuoteOnBackSpace() { - // Add block quote deletion handler - editor.on('keydown', function(e) { - var rng, container, offset, root, parent; - - if (isDefaultPrevented(e) || e.keyCode != VK.BACKSPACE) { - return; - } - - rng = selection.getRng(); - container = rng.startContainer; - offset = rng.startOffset; - root = dom.getRoot(); - parent = container; - - if (!rng.collapsed || offset !== 0) { - return; - } - - while (parent && parent.parentNode && parent.parentNode.firstChild == parent && parent.parentNode != root) { - parent = parent.parentNode; - } - - // Is the cursor at the beginning of a blockquote? - if (parent.tagName === 'BLOCKQUOTE') { - // Remove the blockquote - editor.formatter.toggle('blockquote', null, parent); - - // Move the caret to the beginning of container - rng = dom.createRng(); - rng.setStart(container, 0); - rng.setEnd(container, 0); - selection.setRng(rng); - } - }); - } - - /** - * Sets various Gecko editing options on mouse down and before a execCommand to disable inline table editing that is broken etc. - */ - function setGeckoEditingOptions() { - function setOpts() { - refreshContentEditable(); - - setEditorCommandState("StyleWithCSS", false); - setEditorCommandState("enableInlineTableEditing", false); - - if (!settings.object_resizing) { - setEditorCommandState("enableObjectResizing", false); - } - } - - if (!settings.readonly) { - editor.on('BeforeExecCommand MouseDown', setOpts); - } - } - - /** - * Fixes a gecko link bug, when a link is placed at the end of block elements there is - * no way to move the caret behind the link. This fix adds a bogus br element after the link. - * - * For example this: - * - * - * Becomes this: - * - */ - function addBrAfterLastLinks() { - function fixLinks() { - each(dom.select('a'), function(node) { - var parentNode = node.parentNode, root = dom.getRoot(); - - if (parentNode.lastChild === node) { - while (parentNode && !dom.isBlock(parentNode)) { - if (parentNode.parentNode.lastChild !== parentNode || parentNode === root) { - return; - } - - parentNode = parentNode.parentNode; - } - - dom.add(parentNode, 'br', {'data-mce-bogus': 1}); - } - }); - } - - editor.on('SetContent ExecCommand', function(e) { - if (e.type == "setcontent" || e.command === 'mceInsertLink') { - fixLinks(); - } - }); - } - - /** - * WebKit will produce DIV elements here and there by default. But since TinyMCE uses paragraphs by - * default we want to change that behavior. - */ - function setDefaultBlockType() { - if (settings.forced_root_block) { - editor.on('init', function() { - setEditorCommandState('DefaultParagraphSeparator', settings.forced_root_block); - }); - } - } - - /** - * Deletes the selected image on IE instead of navigating to previous page. - */ - function deleteControlItemOnBackSpace() { - editor.on('keydown', function(e) { - var rng; - - if (!isDefaultPrevented(e) && e.keyCode == BACKSPACE) { - rng = editor.getDoc().selection.createRange(); - if (rng && rng.item) { - e.preventDefault(); - editor.undoManager.beforeChange(); - dom.remove(rng.item(0)); - editor.undoManager.add(); - } - } - }); - } - - /** - * IE10 doesn't properly render block elements with the right height until you add contents to them. - * This fixes that by adding a padding-right to all empty text block elements. - * See: https://connect.microsoft.com/IE/feedback/details/743881 - */ - function renderEmptyBlocksFix() { - var emptyBlocksCSS; - - // IE10+ - if (getDocumentMode() >= 10) { - emptyBlocksCSS = ''; - each('p div h1 h2 h3 h4 h5 h6'.split(' '), function(name, i) { - emptyBlocksCSS += (i > 0 ? ',' : '') + name + ':empty'; - }); - - editor.contentStyles.push(emptyBlocksCSS + '{padding-right: 1px !important}'); - } - } - - /** - * Old IE versions can't retain contents within noscript elements so this logic will store the contents - * as a attribute and the insert that value as it's raw text when the DOM is serialized. - */ - function keepNoScriptContents() { - if (getDocumentMode() < 9) { - parser.addNodeFilter('noscript', function(nodes) { - var i = nodes.length, node, textNode; - - while (i--) { - node = nodes[i]; - textNode = node.firstChild; - - if (textNode) { - node.attr('data-mce-innertext', textNode.value); - } - } - }); - - serializer.addNodeFilter('noscript', function(nodes) { - var i = nodes.length, node, textNode, value; - - while (i--) { - node = nodes[i]; - textNode = nodes[i].firstChild; - - if (textNode) { - textNode.value = Entities.decode(textNode.value); - } else { - // Old IE can't retain noscript value so an attribute is used to store it - value = node.attributes.map['data-mce-innertext']; - if (value) { - node.attr('data-mce-innertext', null); - textNode = new Node('#text', 3); - textNode.value = value; - textNode.raw = true; - node.append(textNode); - } - } - } - }); - } - } - - /** - * IE has an issue where you can't select/move the caret by clicking outside the body if the document is in standards mode. - */ - function fixCaretSelectionOfDocumentElementOnIe() { - var doc = dom.doc, body = doc.body, started, startRng, htmlElm; - - // Return range from point or null if it failed - function rngFromPoint(x, y) { - var rng = body.createTextRange(); - - try { - rng.moveToPoint(x, y); - } catch (ex) { - // IE sometimes throws and exception, so lets just ignore it - rng = null; - } - - return rng; - } - - // Fires while the selection is changing - function selectionChange(e) { - var pointRng; - - // Check if the button is down or not - if (e.button) { - // Create range from mouse position - pointRng = rngFromPoint(e.x, e.y); - - if (pointRng) { - // Check if pointRange is before/after selection then change the endPoint - if (pointRng.compareEndPoints('StartToStart', startRng) > 0) { - pointRng.setEndPoint('StartToStart', startRng); - } else { - pointRng.setEndPoint('EndToEnd', startRng); - } - - pointRng.select(); - } - } else { - endSelection(); - } - } - - // Removes listeners - function endSelection() { - var rng = doc.selection.createRange(); - - // If the range is collapsed then use the last start range - if (startRng && !rng.item && rng.compareEndPoints('StartToEnd', rng) === 0) { - startRng.select(); - } - - dom.unbind(doc, 'mouseup', endSelection); - dom.unbind(doc, 'mousemove', selectionChange); - startRng = started = 0; - } - - // Make HTML element unselectable since we are going to handle selection by hand - doc.documentElement.unselectable = true; - - // Detect when user selects outside BODY - dom.bind(doc, 'mousedown contextmenu', function(e) { - if (e.target.nodeName === 'HTML') { - if (started) { - endSelection(); - } - - // Detect vertical scrollbar, since IE will fire a mousedown on the scrollbar and have target set as HTML - htmlElm = doc.documentElement; - if (htmlElm.scrollHeight > htmlElm.clientHeight) { - return; - } - - started = 1; - // Setup start position - startRng = rngFromPoint(e.x, e.y); - if (startRng) { - // Listen for selection change events - dom.bind(doc, 'mouseup', endSelection); - dom.bind(doc, 'mousemove', selectionChange); - - dom.getRoot().focus(); - startRng.select(); - } - } - }); - } - - /** - * Fixes selection issues where the caret can be placed between two inline elements like a|b - * this fix will lean the caret right into the closest inline element. - */ - function normalizeSelection() { - // Normalize selection for example a|a becomes a|a except for Ctrl+A since it selects everything - editor.on('keyup focusin mouseup', function(e) { - if (e.keyCode != 65 || !VK.metaKeyPressed(e)) { - selection.normalize(); - } - }, true); - } - - /** - * Forces Gecko to render a broken image icon if it fails to load an image. - */ - function showBrokenImageIcon() { - editor.contentStyles.push( - 'img:-moz-broken {' + - '-moz-force-broken-image-icon:1;' + - 'min-width:24px;' + - 'min-height:24px' + - '}' - ); - } - - /** - * iOS has a bug where it's impossible to type if the document has a touchstart event - * bound and the user touches the document while having the on screen keyboard visible. - * - * The touch event moves the focus to the parent document while having the caret inside the iframe - * this fix moves the focus back into the iframe document. - */ - function restoreFocusOnKeyDown() { - if (!editor.inline) { - editor.on('keydown', function() { - if (document.activeElement == document.body) { - editor.getWin().focus(); - } - }); - } - } - - /** - * IE 11 has an annoying issue where you can't move focus into the editor - * by clicking on the white area HTML element. We used to be able to to fix this with - * the fixCaretSelectionOfDocumentElementOnIe fix. But since M$ removed the selection - * object it's not possible anymore. So we need to hack in a ungly CSS to force the - * body to be at least 150px. If the user clicks the HTML element out side this 150px region - * we simply move the focus into the first paragraph. Not ideal since you loose the - * positioning of the caret but goot enough for most cases. - */ - function bodyHeight() { - if (!editor.inline) { - editor.contentStyles.push('body {min-height: 150px}'); - editor.on('click', function(e) { - var rng; - - if (e.target.nodeName == 'HTML') { - // Edge seems to only need focus if we set the range - // the caret will become invisible and moved out of the iframe!! - if (Env.ie > 11) { - editor.getBody().focus(); - return; - } - - // Need to store away non collapsed ranges since the focus call will mess that up see #7382 - rng = editor.selection.getRng(); - editor.getBody().focus(); - editor.selection.setRng(rng); - editor.selection.normalize(); - editor.nodeChanged(); - } - }); - } - } - - /** - * Firefox on Mac OS will move the browser back to the previous page if you press CMD+Left arrow. - * You might then loose all your work so we need to block that behavior and replace it with our own. - */ - function blockCmdArrowNavigation() { - if (Env.mac) { - editor.on('keydown', function(e) { - if (VK.metaKeyPressed(e) && !e.shiftKey && (e.keyCode == 37 || e.keyCode == 39)) { - e.preventDefault(); - editor.selection.getSel().modify('move', e.keyCode == 37 ? 'backward' : 'forward', 'lineboundary'); - } - }); - } - } - - /** - * Disables the autolinking in IE 9+ this is then re-enabled by the autolink plugin. - */ - function disableAutoUrlDetect() { - setEditorCommandState("AutoUrlDetect", false); - } - - /** - * iOS 7.1 introduced two new bugs: - * 1) It's possible to open links within a contentEditable area by clicking on them. - * 2) If you hold down the finger it will display the link/image touch callout menu. - */ - function tapLinksAndImages() { - editor.on('click', function(e) { - var elm = e.target; - - do { - if (elm.tagName === 'A') { - e.preventDefault(); - return; - } - } while ((elm = elm.parentNode)); - }); - - editor.contentStyles.push('.mce-content-body {-webkit-touch-callout: none}'); - } - - /** - * iOS Safari and possible other browsers have a bug where it won't fire - * a click event when a contentEditable is focused. This function fakes click events - * by using touchstart/touchend and measuring the time and distance travelled. - */ - /* - function touchClickEvent() { - editor.on('touchstart', function(e) { - var elm, time, startTouch, changedTouches; - - elm = e.target; - time = new Date().getTime(); - changedTouches = e.changedTouches; - - if (!changedTouches || changedTouches.length > 1) { - return; - } - - startTouch = changedTouches[0]; - - editor.once('touchend', function(e) { - var endTouch = e.changedTouches[0], args; - - if (new Date().getTime() - time > 500) { - return; - } - - if (Math.abs(startTouch.clientX - endTouch.clientX) > 5) { - return; - } - - if (Math.abs(startTouch.clientY - endTouch.clientY) > 5) { - return; - } - - args = { - target: elm - }; - - each('pageX pageY clientX clientY screenX screenY'.split(' '), function(key) { - args[key] = endTouch[key]; - }); - - args = editor.fire('click', args); - - if (!args.isDefaultPrevented()) { - // iOS WebKit can't place the caret properly once - // you bind touch events so we need to do this manually - // TODO: Expand to the closest word? Touble tap still works. - editor.selection.placeCaretAt(endTouch.clientX, endTouch.clientY); - editor.nodeChanged(); - } - }); - }); - } - */ - - /** - * WebKit has a bug where it will allow forms to be submitted if they are inside a contentEditable element. - * For example this: - */ - function blockFormSubmitInsideEditor() { - editor.on('init', function() { - editor.dom.bind(editor.getBody(), 'submit', function(e) { - e.preventDefault(); - }); - }); - } - - /** - * Sometimes WebKit/Blink generates BR elements with the Apple-interchange-newline class. - * - * Scenario: - * 1) Create a table 2x2. - * 2) Select and copy cells A2-B2. - * 3) Paste and it will add BR element to table cell. - */ - function removeAppleInterchangeBrs() { - parser.addNodeFilter('br', function(nodes) { - var i = nodes.length; - - while (i--) { - if (nodes[i].attr('class') == 'Apple-interchange-newline') { - nodes[i].remove(); - } - } - }); - } - - /** - * IE cannot set custom contentType's on drag events, and also does not properly drag/drop between - * editors. This uses a special data:text/mce-internal URL to pass data when drag/drop between editors. - */ - function ieInternalDragAndDrop() { - editor.on('dragstart', function(e) { - setMceInternalContent(e); - }); - - editor.on('drop', function(e) { - if (!isDefaultPrevented(e)) { - var internalContent = getMceInternalContent(e); - - if (internalContent && internalContent.id != editor.id) { - e.preventDefault(); - - var rng = RangeUtils.getCaretRangeFromPoint(e.x, e.y, editor.getDoc()); - selection.setRng(rng); - insertClipboardContents(internalContent.html); - } - } - }); - } - - function refreshContentEditable() { - // No-op since Mozilla seems to have fixed the caret repaint issues - } - - function isHidden() { - var sel; - - if (!isGecko) { - return 0; - } - - // Weird, wheres that cursor selection? - sel = editor.selection.getSel(); - return (!sel || !sel.rangeCount || sel.rangeCount === 0); - } - - /** - * Properly empties the editor if all contents is selected and deleted this to - * prevent empty paragraphs from being produced at beginning/end of contents. - */ - function emptyEditorOnDeleteEverything() { - function isEverythingSelected(editor) { - var caretWalker = new CaretWalker(editor.getBody()); - var rng = editor.selection.getRng(); - var startCaretPos = CaretPosition.fromRangeStart(rng); - var endCaretPos = CaretPosition.fromRangeEnd(rng); - var prev = caretWalker.prev(startCaretPos); - var next = caretWalker.next(endCaretPos); - - return !editor.selection.isCollapsed() && - (!prev || (prev.isAtStart() && startCaretPos.isEqual(prev))) && - (!next || (next.isAtEnd() && startCaretPos.isEqual(next))); - } - - // Type over case delete and insert this won't cover typeover with a IME but at least it covers the common case - editor.on('keypress', function (e) { - if (!isDefaultPrevented(e) && !selection.isCollapsed() && e.charCode > 31 && !VK.metaKeyPressed(e)) { - if (isEverythingSelected(editor)) { - e.preventDefault(); - editor.setContent(String.fromCharCode(e.charCode)); - editor.selection.select(editor.getBody(), true); - editor.selection.collapse(false); - editor.nodeChanged(); - } - } - }); - - editor.on('keydown', function (e) { - var keyCode = e.keyCode; - - if (!isDefaultPrevented(e) && (keyCode == DELETE || keyCode == BACKSPACE)) { - if (isEverythingSelected(editor)) { - e.preventDefault(); - editor.setContent(''); - editor.nodeChanged(); - } - } - }); - } - - // All browsers - removeBlockQuoteOnBackSpace(); - emptyEditorWhenDeleting(); - - // Windows phone will return a range like [body, 0] on mousedown so - // it will always normalize to the wrong location - if (!Env.windowsPhone) { - normalizeSelection(); - } - - // WebKit - if (isWebKit) { - emptyEditorOnDeleteEverything(); - cleanupStylesWhenDeleting(); - inputMethodFocus(); - selectControlElements(); - setDefaultBlockType(); - blockFormSubmitInsideEditor(); - disableBackspaceIntoATable(); - removeAppleInterchangeBrs(); - - //touchClickEvent(); - - // iOS - if (Env.iOS) { - restoreFocusOnKeyDown(); - bodyHeight(); - tapLinksAndImages(); - } else { - selectAll(); - } - } - - // IE - if (isIE && Env.ie < 11) { - removeHrOnBackspace(); - ensureBodyHasRoleApplication(); - addNewLinesBeforeBrInPre(); - removePreSerializedStylesWhenSelectingControls(); - deleteControlItemOnBackSpace(); - renderEmptyBlocksFix(); - keepNoScriptContents(); - fixCaretSelectionOfDocumentElementOnIe(); - } - - if (Env.ie >= 11) { - bodyHeight(); - disableBackspaceIntoATable(); - } - - if (Env.ie) { - selectAll(); - disableAutoUrlDetect(); - ieInternalDragAndDrop(); - } - - // Gecko - if (isGecko) { - emptyEditorOnDeleteEverything(); - removeHrOnBackspace(); - focusBody(); - removeStylesWhenDeletingAcrossBlockElements(); - setGeckoEditingOptions(); - addBrAfterLastLinks(); - showBrokenImageIcon(); - blockCmdArrowNavigation(); - disableBackspaceIntoATable(); - } - - return { - refreshContentEditable: refreshContentEditable, - isHidden: isHidden - }; - }; -}); - -// Included from: js/tinymce/classes/EditorObservable.js +define( + 'tinymce.core.NotificationManager', + [ + "tinymce.core.ui.Notification", + "tinymce.core.util.Delay", + "tinymce.core.util.Tools" + ], + function (Notification, Delay, Tools) { + return function (editor) { + var self = this, notifications = []; + + function getLastNotification() { + if (notifications.length) { + return notifications[notifications.length - 1]; + } + } + + self.notifications = notifications; + + function resizeWindowEvent() { + Delay.requestAnimationFrame(function () { + prePositionNotifications(); + positionNotifications(); + }); + } + + // Since the viewport will change based on the present notifications, we need to move them all to the + // top left of the viewport to give an accurate size measurement so we can position them later. + function prePositionNotifications() { + for (var i = 0; i < notifications.length; i++) { + notifications[i].moveTo(0, 0); + } + } + + function positionNotifications() { + if (notifications.length > 0) { + var firstItem = notifications.slice(0, 1)[0]; + var container = editor.inline ? editor.getElement() : editor.getContentAreaContainer(); + firstItem.moveRel(container, 'tc-tc'); + if (notifications.length > 1) { + for (var i = 1; i < notifications.length; i++) { + notifications[i].moveRel(notifications[i - 1].getEl(), 'bc-tc'); + } + } + } + } + + editor.on('remove', function () { + var i = notifications.length; + + while (i--) { + notifications[i].close(); + } + }); + + editor.on('ResizeEditor', positionNotifications); + editor.on('ResizeWindow', resizeWindowEvent); + + /** + * Opens a new notification. + * + * @method open + * @param {Object} args Optional name/value settings collection contains things like timeout/color/message etc. + */ + self.open = function (args) { + // Never open notification if editor has been removed. + if (editor.removed) { + return; + } + + var notif; + + editor.editorManager.setActive(editor); + + var duplicate = findDuplicateMessage(notifications, args); + + if (duplicate === null) { + notif = new Notification(args); + notifications.push(notif); + + //If we have a timeout value + if (args.timeout > 0) { + notif.timer = setTimeout(function () { + notif.close(); + }, args.timeout); + } + + notif.on('close', function () { + var i = notifications.length; + + if (notif.timer) { + editor.getWin().clearTimeout(notif.timer); + } + + while (i--) { + if (notifications[i] === notif) { + notifications.splice(i, 1); + } + } + + positionNotifications(); + }); + + notif.renderTo(); + + positionNotifications(); + } else { + notif = duplicate; + } + + return notif; + }; + + /** + * Closes the top most notification. + * + * @method close + */ + self.close = function () { + if (getLastNotification()) { + getLastNotification().close(); + } + }; + + /** + * Returns the currently opened notification objects. + * + * @method getNotifications + * @return {Array} Array of the currently opened notifications. + */ + self.getNotifications = function () { + return notifications; + }; + + editor.on('SkinLoaded', function () { + var serviceMessage = editor.settings.service_message; + + if (serviceMessage) { + editor.notificationManager.open({ + text: serviceMessage, + type: 'warning', + timeout: 0, + icon: '' + }); + } + }); + + /** + * Finds any existing notification with the same properties as the new one. + * Returns either the found notification or null. + * + * @param {Notification[]} notificationArray - Array of current notifications + * @param {type: string, } newNotification - New notification object + * @returns {?Notification} + */ + function findDuplicateMessage(notificationArray, newNotification) { + if (!isPlainTextNotification(newNotification)) { + return null; + } + + var filteredNotifications = Tools.grep(notificationArray, function (notification) { + return isSameNotification(newNotification, notification); + }); + + return filteredNotifications.length === 0 ? null : filteredNotifications[0]; + } + + /** + * Checks if the passed in args object has the same + * type and text properties as the sent in notification. + * + * @param {type: string, text: string} a - New notification args object + * @param {Notification} b - Old notification + * @returns {boolean} + */ + function isSameNotification(a, b) { + return a.type === b.settings.type && a.text === b.settings.text; + } + + /** + * Checks that the notification does not have a progressBar + * or timeour property. + * + * @param {Notification} notification - Notification to check + * @returns {boolean} + */ + function isPlainTextNotification(notification) { + return !notification.progressBar && !notification.timeout; + } + + //self.positionNotifications = positionNotifications; + }; + } +); /** * EditorObservable.js * * Released under LGPL License. - * Copyright (c) 1999-2015 Ephox Corp. All rights reserved + * Copyright (c) 1999-2017 Ephox Corp. All rights reserved * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing @@ -34715,295 +37001,204 @@ define("tinymce/util/Quirks", [ * @mixin tinymce.EditorObservable * @extends tinymce.util.Observable */ -define("tinymce/EditorObservable", [ - "tinymce/util/Observable", - "tinymce/dom/DOMUtils", - "tinymce/util/Tools" -], function(Observable, DOMUtils, Tools) { - var DOM = DOMUtils.DOM, customEventRootDelegates; +define( + 'tinymce.core.EditorObservable', + [ + "tinymce.core.util.Observable", + "tinymce.core.dom.DOMUtils", + "tinymce.core.util.Tools" + ], + function (Observable, DOMUtils, Tools) { + var DOM = DOMUtils.DOM, customEventRootDelegates; - /** - * Returns the event target so for the specified event. Some events fire - * only on document, some fire on documentElement etc. This also handles the - * custom event root setting where it returns that element instead of the body. - * - * @private - * @param {tinymce.Editor} editor Editor instance to get event target from. - * @param {String} eventName Name of the event for example "click". - * @return {Element/Document} HTML Element or document target to bind on. - */ - function getEventTarget(editor, eventName) { - if (eventName == 'selectionchange') { - return editor.getDoc(); - } + /** + * Returns the event target so for the specified event. Some events fire + * only on document, some fire on documentElement etc. This also handles the + * custom event root setting where it returns that element instead of the body. + * + * @private + * @param {tinymce.Editor} editor Editor instance to get event target from. + * @param {String} eventName Name of the event for example "click". + * @return {Element/Document} HTML Element or document target to bind on. + */ + function getEventTarget(editor, eventName) { + if (eventName == 'selectionchange') { + return editor.getDoc(); + } - // Need to bind mousedown/mouseup etc to document not body in iframe mode - // Since the user might click on the HTML element not the BODY - if (!editor.inline && /^mouse|touch|click|contextmenu|drop|dragover|dragend/.test(eventName)) { - return editor.getDoc().documentElement; - } + // Need to bind mousedown/mouseup etc to document not body in iframe mode + // Since the user might click on the HTML element not the BODY + if (!editor.inline && /^mouse|touch|click|contextmenu|drop|dragover|dragend/.test(eventName)) { + return editor.getDoc().documentElement; + } - // Bind to event root instead of body if it's defined - if (editor.settings.event_root) { - if (!editor.eventRoot) { - editor.eventRoot = DOM.select(editor.settings.event_root)[0]; - } + // Bind to event root instead of body if it's defined + if (editor.settings.event_root) { + if (!editor.eventRoot) { + editor.eventRoot = DOM.select(editor.settings.event_root)[0]; + } - return editor.eventRoot; - } + return editor.eventRoot; + } - return editor.getBody(); - } + return editor.getBody(); + } - /** - * Binds a event delegate for the specified name this delegate will fire - * the event to the editor dispatcher. - * - * @private - * @param {tinymce.Editor} editor Editor instance to get event target from. - * @param {String} eventName Name of the event for example "click". - */ - function bindEventDelegate(editor, eventName) { - var eventRootElm = getEventTarget(editor, eventName), delegate; + /** + * Binds a event delegate for the specified name this delegate will fire + * the event to the editor dispatcher. + * + * @private + * @param {tinymce.Editor} editor Editor instance to get event target from. + * @param {String} eventName Name of the event for example "click". + */ + function bindEventDelegate(editor, eventName) { + var eventRootElm = getEventTarget(editor, eventName), delegate; - function isListening(editor) { - return !editor.hidden && !editor.readonly; - } + function isListening(editor) { + return !editor.hidden && !editor.readonly; + } - if (!editor.delegates) { - editor.delegates = {}; - } + if (!editor.delegates) { + editor.delegates = {}; + } - if (editor.delegates[eventName]) { - return; - } + if (editor.delegates[eventName]) { + return; + } - if (editor.settings.event_root) { - if (!customEventRootDelegates) { - customEventRootDelegates = {}; - editor.editorManager.on('removeEditor', function() { - var name; + if (editor.settings.event_root) { + if (!customEventRootDelegates) { + customEventRootDelegates = {}; + editor.editorManager.on('removeEditor', function () { + var name; - if (!editor.editorManager.activeEditor) { - if (customEventRootDelegates) { - for (name in customEventRootDelegates) { - editor.dom.unbind(getEventTarget(editor, name)); - } + if (!editor.editorManager.activeEditor) { + if (customEventRootDelegates) { + for (name in customEventRootDelegates) { + editor.dom.unbind(getEventTarget(editor, name)); + } - customEventRootDelegates = null; - } - } - }); - } + customEventRootDelegates = null; + } + } + }); + } - if (customEventRootDelegates[eventName]) { - return; - } + if (customEventRootDelegates[eventName]) { + return; + } - delegate = function(e) { - var target = e.target, editors = editor.editorManager.editors, i = editors.length; + delegate = function (e) { + var target = e.target, editors = editor.editorManager.editors, i = editors.length; - while (i--) { - var body = editors[i].getBody(); + while (i--) { + var body = editors[i].getBody(); - if (body === target || DOM.isChildOf(target, body)) { - if (isListening(editors[i])) { - editors[i].fire(eventName, e); - } - } - } - }; + if (body === target || DOM.isChildOf(target, body)) { + if (isListening(editors[i])) { + editors[i].fire(eventName, e); + } + } + } + }; - customEventRootDelegates[eventName] = delegate; - DOM.bind(eventRootElm, eventName, delegate); - } else { - delegate = function(e) { - if (isListening(editor)) { - editor.fire(eventName, e); - } - }; + customEventRootDelegates[eventName] = delegate; + DOM.bind(eventRootElm, eventName, delegate); + } else { + delegate = function (e) { + if (isListening(editor)) { + editor.fire(eventName, e); + } + }; - DOM.bind(eventRootElm, eventName, delegate); - editor.delegates[eventName] = delegate; - } - } + DOM.bind(eventRootElm, eventName, delegate); + editor.delegates[eventName] = delegate; + } + } - var EditorObservable = { - /** - * Bind any pending event delegates. This gets executed after the target body/document is created. - * - * @private - */ - bindPendingEventDelegates: function() { - var self = this; + var EditorObservable = { + /** + * Bind any pending event delegates. This gets executed after the target body/document is created. + * + * @private + */ + bindPendingEventDelegates: function () { + var self = this; - Tools.each(self._pendingNativeEvents, function(name) { - bindEventDelegate(self, name); - }); - }, + Tools.each(self._pendingNativeEvents, function (name) { + bindEventDelegate(self, name); + }); + }, - /** - * Toggles a native event on/off this is called by the EventDispatcher when - * the first native event handler is added and when the last native event handler is removed. - * - * @private - */ - toggleNativeEvent: function(name, state) { - var self = this; + /** + * Toggles a native event on/off this is called by the EventDispatcher when + * the first native event handler is added and when the last native event handler is removed. + * + * @private + */ + toggleNativeEvent: function (name, state) { + var self = this; - // Never bind focus/blur since the FocusManager fakes those - if (name == "focus" || name == "blur") { - return; - } + // Never bind focus/blur since the FocusManager fakes those + if (name == "focus" || name == "blur") { + return; + } - if (state) { - if (self.initialized) { - bindEventDelegate(self, name); - } else { - if (!self._pendingNativeEvents) { - self._pendingNativeEvents = [name]; - } else { - self._pendingNativeEvents.push(name); - } - } - } else if (self.initialized) { - self.dom.unbind(getEventTarget(self, name), name, self.delegates[name]); - delete self.delegates[name]; - } - }, + if (state) { + if (self.initialized) { + bindEventDelegate(self, name); + } else { + if (!self._pendingNativeEvents) { + self._pendingNativeEvents = [name]; + } else { + self._pendingNativeEvents.push(name); + } + } + } else if (self.initialized) { + self.dom.unbind(getEventTarget(self, name), name, self.delegates[name]); + delete self.delegates[name]; + } + }, - /** - * Unbinds all native event handlers that means delegates, custom events bound using the Events API etc. - * - * @private - */ - unbindAllNativeEvents: function() { - var self = this, name; + /** + * Unbinds all native event handlers that means delegates, custom events bound using the Events API etc. + * + * @private + */ + unbindAllNativeEvents: function () { + var self = this, name; - if (self.delegates) { - for (name in self.delegates) { - self.dom.unbind(getEventTarget(self, name), name, self.delegates[name]); - } + if (self.delegates) { + for (name in self.delegates) { + self.dom.unbind(getEventTarget(self, name), name, self.delegates[name]); + } - delete self.delegates; - } + delete self.delegates; + } - if (!self.inline) { - self.getBody().onload = null; - self.dom.unbind(self.getWin()); - self.dom.unbind(self.getDoc()); - } + if (!self.inline) { + self.getBody().onload = null; + self.dom.unbind(self.getWin()); + self.dom.unbind(self.getDoc()); + } - self.dom.unbind(self.getBody()); - self.dom.unbind(self.getContainer()); - } - }; + self.dom.unbind(self.getBody()); + self.dom.unbind(self.getContainer()); + } + }; - EditorObservable = Tools.extend({}, Observable, EditorObservable); + EditorObservable = Tools.extend({}, Observable, EditorObservable); - return EditorObservable; -}); - -// Included from: js/tinymce/classes/Mode.js - -/** - * Mode.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 - */ - -/** - * Mode switcher logic. - * - * @private - * @class tinymce.Mode - */ -define("tinymce/Mode", [], function() { - function setEditorCommandState(editor, cmd, state) { - try { - editor.getDoc().execCommand(cmd, false, state); - } catch (ex) { - // Ignore - } - } - - function clickBlocker(editor) { - var target, handler; - - target = editor.getBody(); - - handler = function(e) { - if (editor.dom.getParents(e.target, 'a').length > 0) { - e.preventDefault(); - } - }; - - editor.dom.bind(target, 'click', handler); - - return { - unbind: function() { - editor.dom.unbind(target, 'click', handler); - } - }; - } - - function toggleReadOnly(editor, state) { - if (editor._clickBlocker) { - editor._clickBlocker.unbind(); - editor._clickBlocker = null; - } - - if (state) { - editor._clickBlocker = clickBlocker(editor); - editor.selection.controlSelection.hideResizeRect(); - editor.readonly = true; - editor.getBody().contentEditable = false; - } else { - editor.readonly = false; - editor.getBody().contentEditable = true; - setEditorCommandState(editor, "StyleWithCSS", false); - setEditorCommandState(editor, "enableInlineTableEditing", false); - setEditorCommandState(editor, "enableObjectResizing", false); - editor.focus(); - editor.nodeChanged(); - } - } - - function setMode(editor, mode) { - var currentMode = editor.readonly ? 'readonly' : 'design'; - - if (mode == currentMode) { - return; - } - - if (editor.initialized) { - toggleReadOnly(editor, mode == 'readonly'); - } else { - editor.on('init', function() { - toggleReadOnly(editor, mode == 'readonly'); - }); - } - - // Event is NOT preventable - editor.fire('SwitchMode', {mode: mode}); - } - - return { - setMode: setMode - }; -}); - -// Included from: js/tinymce/classes/Shortcuts.js + return EditorObservable; + } +); /** * Shortcuts.js * * Released under LGPL License. - * Copyright (c) 1999-2015 Ephox Corp. All rights reserved + * Copyright (c) 1999-2017 Ephox Corp. All rights reserved * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing @@ -35019,210 +37214,368 @@ define("tinymce/Mode", [], function() { * editor.shortcuts.add('ctrl+alt+a', function() {}); * editor.shortcuts.add('access+a', function() {}); // "access" maps to ctrl+alt on Mac and shift+alt on PC */ -define("tinymce/Shortcuts", [ - "tinymce/util/Tools", - "tinymce/Env" -], function(Tools, Env) { - var each = Tools.each, explode = Tools.explode; +define( + 'tinymce.core.Shortcuts', + [ + "tinymce.core.util.Tools", + "tinymce.core.Env" + ], + function (Tools, Env) { + var each = Tools.each, explode = Tools.explode; - var keyCodeLookup = { - "f9": 120, - "f10": 121, - "f11": 122 - }; + var keyCodeLookup = { + "f9": 120, + "f10": 121, + "f11": 122 + }; - var modifierNames = Tools.makeMap('alt,ctrl,shift,meta,access'); + var modifierNames = Tools.makeMap('alt,ctrl,shift,meta,access'); - return function(editor) { - var self = this, shortcuts = {}, pendingPatterns = []; + return function (editor) { + var self = this, shortcuts = {}, pendingPatterns = []; - function parseShortcut(pattern) { - var id, key, shortcut = {}; + function parseShortcut(pattern) { + var id, key, shortcut = {}; - // Parse modifiers and keys ctrl+alt+b for example - each(explode(pattern, '+'), function(value) { - if (value in modifierNames) { - shortcut[value] = true; - } else { - // Allow numeric keycodes like ctrl+219 for ctrl+[ - if (/^[0-9]{2,}$/.test(value)) { - shortcut.keyCode = parseInt(value, 10); - } else { - shortcut.charCode = value.charCodeAt(0); - shortcut.keyCode = keyCodeLookup[value] || value.toUpperCase().charCodeAt(0); - } - } - }); + // Parse modifiers and keys ctrl+alt+b for example + each(explode(pattern, '+'), function (value) { + if (value in modifierNames) { + shortcut[value] = true; + } else { + // Allow numeric keycodes like ctrl+219 for ctrl+[ + if (/^[0-9]{2,}$/.test(value)) { + shortcut.keyCode = parseInt(value, 10); + } else { + shortcut.charCode = value.charCodeAt(0); + shortcut.keyCode = keyCodeLookup[value] || value.toUpperCase().charCodeAt(0); + } + } + }); - // Generate unique id for modifier combination and set default state for unused modifiers - id = [shortcut.keyCode]; - for (key in modifierNames) { - if (shortcut[key]) { - id.push(key); - } else { - shortcut[key] = false; - } - } - shortcut.id = id.join(','); + // Generate unique id for modifier combination and set default state for unused modifiers + id = [shortcut.keyCode]; + for (key in modifierNames) { + if (shortcut[key]) { + id.push(key); + } else { + shortcut[key] = false; + } + } + shortcut.id = id.join(','); - // Handle special access modifier differently depending on Mac/Win - if (shortcut.access) { - shortcut.alt = true; + // Handle special access modifier differently depending on Mac/Win + if (shortcut.access) { + shortcut.alt = true; - if (Env.mac) { - shortcut.ctrl = true; - } else { - shortcut.shift = true; - } - } + if (Env.mac) { + shortcut.ctrl = true; + } else { + shortcut.shift = true; + } + } - // Handle special meta modifier differently depending on Mac/Win - if (shortcut.meta) { - if (Env.mac) { - shortcut.meta = true; - } else { - shortcut.ctrl = true; - shortcut.meta = false; - } - } + // Handle special meta modifier differently depending on Mac/Win + if (shortcut.meta) { + if (Env.mac) { + shortcut.meta = true; + } else { + shortcut.ctrl = true; + shortcut.meta = false; + } + } - return shortcut; - } + return shortcut; + } - function createShortcut(pattern, desc, cmdFunc, scope) { - var shortcuts; + function createShortcut(pattern, desc, cmdFunc, scope) { + var shortcuts; - shortcuts = Tools.map(explode(pattern, '>'), parseShortcut); - shortcuts[shortcuts.length - 1] = Tools.extend(shortcuts[shortcuts.length - 1], { - func: cmdFunc, - scope: scope || editor - }); + shortcuts = Tools.map(explode(pattern, '>'), parseShortcut); + shortcuts[shortcuts.length - 1] = Tools.extend(shortcuts[shortcuts.length - 1], { + func: cmdFunc, + scope: scope || editor + }); - return Tools.extend(shortcuts[0], { - desc: editor.translate(desc), - subpatterns: shortcuts.slice(1) - }); - } + return Tools.extend(shortcuts[0], { + desc: editor.translate(desc), + subpatterns: shortcuts.slice(1) + }); + } - function hasModifier(e) { - return e.altKey || e.ctrlKey || e.metaKey; - } + function hasModifier(e) { + return e.altKey || e.ctrlKey || e.metaKey; + } - function isFunctionKey(e) { - return e.type === "keydown" && e.keyCode >= 112 && e.keyCode <= 123; - } + function isFunctionKey(e) { + return e.type === "keydown" && e.keyCode >= 112 && e.keyCode <= 123; + } - function matchShortcut(e, shortcut) { - if (!shortcut) { - return false; - } + function matchShortcut(e, shortcut) { + if (!shortcut) { + return false; + } - if (shortcut.ctrl != e.ctrlKey || shortcut.meta != e.metaKey) { - return false; - } + if (shortcut.ctrl != e.ctrlKey || shortcut.meta != e.metaKey) { + return false; + } - if (shortcut.alt != e.altKey || shortcut.shift != e.shiftKey) { - return false; - } + if (shortcut.alt != e.altKey || shortcut.shift != e.shiftKey) { + return false; + } - if (e.keyCode == shortcut.keyCode || (e.charCode && e.charCode == shortcut.charCode)) { - e.preventDefault(); - return true; - } + if (e.keyCode == shortcut.keyCode || (e.charCode && e.charCode == shortcut.charCode)) { + e.preventDefault(); + return true; + } - return false; - } + return false; + } - function executeShortcutAction(shortcut) { - return shortcut.func ? shortcut.func.call(shortcut.scope) : null; - } + function executeShortcutAction(shortcut) { + return shortcut.func ? shortcut.func.call(shortcut.scope) : null; + } - editor.on('keyup keypress keydown', function(e) { - if ((hasModifier(e) || isFunctionKey(e)) && !e.isDefaultPrevented()) { - each(shortcuts, function(shortcut) { - if (matchShortcut(e, shortcut)) { - pendingPatterns = shortcut.subpatterns.slice(0); + editor.on('keyup keypress keydown', function (e) { + if ((hasModifier(e) || isFunctionKey(e)) && !e.isDefaultPrevented()) { + each(shortcuts, function (shortcut) { + if (matchShortcut(e, shortcut)) { + pendingPatterns = shortcut.subpatterns.slice(0); - if (e.type == "keydown") { - executeShortcutAction(shortcut); - } + if (e.type == "keydown") { + executeShortcutAction(shortcut); + } - return true; - } - }); + return true; + } + }); - if (matchShortcut(e, pendingPatterns[0])) { - if (pendingPatterns.length === 1) { - if (e.type == "keydown") { - executeShortcutAction(pendingPatterns[0]); - } - } + if (matchShortcut(e, pendingPatterns[0])) { + if (pendingPatterns.length === 1) { + if (e.type == "keydown") { + executeShortcutAction(pendingPatterns[0]); + } + } - pendingPatterns.shift(); - } - } - }); + pendingPatterns.shift(); + } + } + }); - /** - * Adds a keyboard shortcut for some command or function. - * - * @method add - * @param {String} pattern Shortcut pattern. Like for example: ctrl+alt+o. - * @param {String} desc Text description for the command. - * @param {String/Function} cmdFunc Command name string or function to execute when the key is pressed. - * @param {Object} scope Optional scope to execute the function in. - * @return {Boolean} true/false state if the shortcut was added or not. - */ - self.add = function(pattern, desc, cmdFunc, scope) { - var cmd; + /** + * Adds a keyboard shortcut for some command or function. + * + * @method add + * @param {String} pattern Shortcut pattern. Like for example: ctrl+alt+o. + * @param {String} desc Text description for the command. + * @param {String/Function} cmdFunc Command name string or function to execute when the key is pressed. + * @param {Object} scope Optional scope to execute the function in. + * @return {Boolean} true/false state if the shortcut was added or not. + */ + self.add = function (pattern, desc, cmdFunc, scope) { + var cmd; - cmd = cmdFunc; + cmd = cmdFunc; - if (typeof cmdFunc === 'string') { - cmdFunc = function() { - editor.execCommand(cmd, false, null); - }; - } else if (Tools.isArray(cmd)) { - cmdFunc = function() { - editor.execCommand(cmd[0], cmd[1], cmd[2]); - }; - } + if (typeof cmdFunc === 'string') { + cmdFunc = function () { + editor.execCommand(cmd, false, null); + }; + } else if (Tools.isArray(cmd)) { + cmdFunc = function () { + editor.execCommand(cmd[0], cmd[1], cmd[2]); + }; + } - each(explode(Tools.trim(pattern.toLowerCase())), function(pattern) { - var shortcut = createShortcut(pattern, desc, cmdFunc, scope); - shortcuts[shortcut.id] = shortcut; - }); + each(explode(Tools.trim(pattern.toLowerCase())), function (pattern) { + var shortcut = createShortcut(pattern, desc, cmdFunc, scope); + shortcuts[shortcut.id] = shortcut; + }); - return true; - }; + return true; + }; - /** - * Remove a keyboard shortcut by pattern. - * - * @method remove - * @param {String} pattern Shortcut pattern. Like for example: ctrl+alt+o. - * @return {Boolean} true/false state if the shortcut was removed or not. - */ - self.remove = function(pattern) { - var shortcut = createShortcut(pattern); + /** + * Remove a keyboard shortcut by pattern. + * + * @method remove + * @param {String} pattern Shortcut pattern. Like for example: ctrl+alt+o. + * @return {Boolean} true/false state if the shortcut was removed or not. + */ + self.remove = function (pattern) { + var shortcut = createShortcut(pattern); - if (shortcuts[shortcut.id]) { - delete shortcuts[shortcut.id]; - return true; - } + if (shortcuts[shortcut.id]) { + delete shortcuts[shortcut.id]; + return true; + } - return false; - }; - }; -}); + return false; + }; + }; + } +); -// Included from: js/tinymce/classes/file/Uploader.js +defineGlobal("global!window", window); +/** + * ErrorReporter.js + * + * Released under LGPL License. + * Copyright (c) 1999-2017 Ephox Corp. All rights reserved + * + * License: http://www.tinymce.com/license + * Contributing: http://www.tinymce.com/contributing + */ +/** + * Various error reporting helper functions. + * + * @class tinymce.ErrorReporter + * @private + */ +define( + 'tinymce.core.ErrorReporter', + [ + "tinymce.core.AddOnManager" + ], + function (AddOnManager) { + var PluginManager = AddOnManager.PluginManager; + + var resolvePluginName = function (targetUrl, suffix) { + for (var name in PluginManager.urls) { + var matchUrl = PluginManager.urls[name] + '/plugin' + suffix + '.js'; + if (matchUrl === targetUrl) { + return name; + } + } + + return null; + }; + + var pluginUrlToMessage = function (editor, url) { + var plugin = resolvePluginName(url, editor.suffix); + return plugin ? + 'Failed to load plugin: ' + plugin + ' from url ' + url : + 'Failed to load plugin url: ' + url; + }; + + var displayNotification = function (editor, message) { + editor.notificationManager.open({ + type: 'error', + text: message + }); + }; + + var displayError = function (editor, message) { + if (editor._skinLoaded) { + displayNotification(editor, message); + } else { + editor.on('SkinLoaded', function () { + displayNotification(editor, message); + }); + } + }; + + var uploadError = function (editor, message) { + displayError(editor, 'Failed to upload image: ' + message); + }; + + var pluginLoadError = function (editor, url) { + displayError(editor, pluginUrlToMessage(editor, url)); + }; + + var contentCssError = function (editor, urls) { + displayError(editor, 'Failed to load content css: ' + urls[0]); + }; + + var initError = function (message) { + var console = window.console; + if (console && !window.test) { // Skip test env + if (console.error) { + console.error.apply(console, arguments); + } else { + console.log.apply(console, arguments); + } + } + }; + + return { + pluginLoadError: pluginLoadError, + uploadError: uploadError, + displayError: displayError, + contentCssError: contentCssError, + initError: initError + }; + } +); +/** + * CaretContainerInput.js + * + * Released under LGPL License. + * Copyright (c) 1999-2017 Ephox Corp. All rights reserved + * + * License: http://www.tinymce.com/license + * Contributing: http://www.tinymce.com/contributing + */ + +/** + * This module shows the invisble block that the caret is currently in when contents is added to that block. + */ +define( + 'tinymce.core.caret.CaretContainerInput', + [ + 'ephox.katamari.api.Fun', + 'tinymce.core.caret.CaretContainer' + ], + function (Fun, CaretContainer) { + var findBlockCaretContainer = function (editor) { + return editor.dom.select('*[data-mce-caret]')[0]; + }; + + var removeIeControlRect = function (editor) { + editor.selection.setRng(editor.selection.getRng()); + }; + + var showBlockCaretContainer = function (editor, blockCaretContainer) { + if (blockCaretContainer.hasAttribute('data-mce-caret')) { + CaretContainer.showCaretContainerBlock(blockCaretContainer); + removeIeControlRect(editor); + editor.selection.scrollIntoView(blockCaretContainer); + } + }; + + var handleBlockContainer = function (editor, e) { + var blockCaretContainer = findBlockCaretContainer(editor); + + if (!blockCaretContainer) { + return; + } + + if (e.type === 'compositionstart') { + e.preventDefault(); + e.stopPropagation(); + showBlockCaretContainer(blockCaretContainer); + return; + } + + if (CaretContainer.hasContent(blockCaretContainer)) { + showBlockCaretContainer(editor, blockCaretContainer); + } + }; + + var setup = function (editor) { + editor.on('keyup compositionstart', Fun.curry(handleBlockContainer, editor)); + }; + + return { + setup: setup + }; + } +); /** * Uploader.js * * Released under LGPL License. - * Copyright (c) 1999-2015 Ephox Corp. All rights reserved + * Copyright (c) 1999-2017 Ephox Corp. All rights reserved * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing @@ -35247,213 +37600,190 @@ define("tinymce/Shortcuts", [ * ... * }); */ -define("tinymce/file/Uploader", [ - "tinymce/util/Promise", - "tinymce/util/Tools", - "tinymce/util/Fun" -], function(Promise, Tools, Fun) { - return function(uploadStatus, settings) { - var pendingPromises = {}; +define( + 'tinymce.core.file.Uploader', + [ + "tinymce.core.util.Promise", + "tinymce.core.util.Tools", + "tinymce.core.util.Fun" + ], + function (Promise, Tools, Fun) { + return function (uploadStatus, settings) { + var pendingPromises = {}; - function filename(blobInfo) { - var ext, extensions; + function pathJoin(path1, path2) { + if (path1) { + return path1.replace(/\/$/, '') + '/' + path2.replace(/^\//, ''); + } - extensions = { - 'image/jpeg': 'jpg', - 'image/jpg': 'jpg', - 'image/gif': 'gif', - 'image/png': 'png' - }; + return path2; + } - ext = extensions[blobInfo.blob().type.toLowerCase()] || 'dat'; + function defaultHandler(blobInfo, success, failure, progress) { + var xhr, formData; - return blobInfo.filename() + '.' + ext; - } + xhr = new XMLHttpRequest(); + xhr.open('POST', settings.url); + xhr.withCredentials = settings.credentials; - function pathJoin(path1, path2) { - if (path1) { - return path1.replace(/\/$/, '') + '/' + path2.replace(/^\//, ''); - } + xhr.upload.onprogress = function (e) { + progress(e.loaded / e.total * 100); + }; - return path2; - } + xhr.onerror = function () { + failure("Image upload failed due to a XHR Transport error. Code: " + xhr.status); + }; - function blobInfoToData(blobInfo) { - return { - id: blobInfo.id, - blob: blobInfo.blob, - base64: blobInfo.base64, - filename: Fun.constant(filename(blobInfo)) - }; - } + xhr.onload = function () { + var json; - function defaultHandler(blobInfo, success, failure, progress) { - var xhr, formData; + if (xhr.status != 200) { + failure("HTTP Error: " + xhr.status); + return; + } - xhr = new XMLHttpRequest(); - xhr.open('POST', settings.url); - xhr.withCredentials = settings.credentials; + json = JSON.parse(xhr.responseText); - xhr.upload.onprogress = function(e) { - progress(e.loaded / e.total * 100); - }; + if (!json || typeof json.location != "string") { + failure("Invalid JSON: " + xhr.responseText); + return; + } - xhr.onerror = function() { - failure("Image upload failed due to a XHR Transport error. Code: " + xhr.status); - }; + success(pathJoin(settings.basePath, json.location)); + }; - xhr.onload = function() { - var json; + formData = new FormData(); + formData.append('file', blobInfo.blob(), blobInfo.filename()); - if (xhr.status != 200) { - failure("HTTP Error: " + xhr.status); - return; - } + xhr.send(formData); + } - json = JSON.parse(xhr.responseText); + function noUpload() { + return new Promise(function (resolve) { + resolve([]); + }); + } - if (!json || typeof json.location != "string") { - failure("Invalid JSON: " + xhr.responseText); - return; - } + function handlerSuccess(blobInfo, url) { + return { + url: url, + blobInfo: blobInfo, + status: true + }; + } - success(pathJoin(settings.basePath, json.location)); - }; + function handlerFailure(blobInfo, error) { + return { + url: '', + blobInfo: blobInfo, + status: false, + error: error + }; + } - formData = new FormData(); - formData.append('file', blobInfo.blob(), blobInfo.filename()); + function resolvePending(blobUri, result) { + Tools.each(pendingPromises[blobUri], function (resolve) { + resolve(result); + }); - xhr.send(formData); - } + delete pendingPromises[blobUri]; + } - function noUpload() { - return new Promise(function(resolve) { - resolve([]); - }); - } + function uploadBlobInfo(blobInfo, handler, openNotification) { + uploadStatus.markPending(blobInfo.blobUri()); - function handlerSuccess(blobInfo, url) { - return { - url: url, - blobInfo: blobInfo, - status: true - }; - } + return new Promise(function (resolve) { + var notification, progress; - function handlerFailure(blobInfo, error) { - return { - url: '', - blobInfo: blobInfo, - status: false, - error: error - }; - } + var noop = function () { + }; - function resolvePending(blobUri, result) { - Tools.each(pendingPromises[blobUri], function(resolve) { - resolve(result); - }); + try { + var closeNotification = function () { + if (notification) { + notification.close(); + progress = noop; // Once it's closed it's closed + } + }; - delete pendingPromises[blobUri]; - } + var success = function (url) { + closeNotification(); + uploadStatus.markUploaded(blobInfo.blobUri(), url); + resolvePending(blobInfo.blobUri(), handlerSuccess(blobInfo, url)); + resolve(handlerSuccess(blobInfo, url)); + }; - function uploadBlobInfo(blobInfo, handler, openNotification) { - uploadStatus.markPending(blobInfo.blobUri()); + var failure = function (error) { + closeNotification(); + uploadStatus.removeFailed(blobInfo.blobUri()); + resolvePending(blobInfo.blobUri(), handlerFailure(blobInfo, error)); + resolve(handlerFailure(blobInfo, error)); + }; - return new Promise(function(resolve) { - var notification, progress; + progress = function (percent) { + if (percent < 0 || percent > 100) { + return; + } - var noop = function() { - }; + if (!notification) { + notification = openNotification(); + } - try { - var closeNotification = function() { - if (notification) { - notification.close(); - progress = noop; // Once it's closed it's closed - } - }; + notification.progressBar.value(percent); + }; - var success = function(url) { - closeNotification(); - uploadStatus.markUploaded(blobInfo.blobUri(), url); - resolvePending(blobInfo.blobUri(), handlerSuccess(blobInfo, url)); - resolve(handlerSuccess(blobInfo, url)); - }; + handler(blobInfo, success, failure, progress); + } catch (ex) { + resolve(handlerFailure(blobInfo, ex.message)); + } + }); + } - var failure = function(error) { - closeNotification(); - uploadStatus.removeFailed(blobInfo.blobUri()); - resolvePending(blobInfo.blobUri(), handlerFailure(blobInfo, error)); - resolve(handlerFailure(blobInfo, error)); - }; + function isDefaultHandler(handler) { + return handler === defaultHandler; + } - progress = function(percent) { - if (percent < 0 || percent > 100) { - return; - } + function pendingUploadBlobInfo(blobInfo) { + var blobUri = blobInfo.blobUri(); - if (!notification) { - notification = openNotification(); - } + return new Promise(function (resolve) { + pendingPromises[blobUri] = pendingPromises[blobUri] || []; + pendingPromises[blobUri].push(resolve); + }); + } - notification.progressBar.value(percent); - }; + function uploadBlobs(blobInfos, openNotification) { + blobInfos = Tools.grep(blobInfos, function (blobInfo) { + return !uploadStatus.isUploaded(blobInfo.blobUri()); + }); - handler(blobInfoToData(blobInfo), success, failure, progress); - } catch (ex) { - resolve(handlerFailure(blobInfo, ex.message)); - } - }); - } + return Promise.all(Tools.map(blobInfos, function (blobInfo) { + return uploadStatus.isPending(blobInfo.blobUri()) ? + pendingUploadBlobInfo(blobInfo) : uploadBlobInfo(blobInfo, settings.handler, openNotification); + })); + } - function isDefaultHandler(handler) { - return handler === defaultHandler; - } + function upload(blobInfos, openNotification) { + return (!settings.url && isDefaultHandler(settings.handler)) ? noUpload() : uploadBlobs(blobInfos, openNotification); + } - function pendingUploadBlobInfo(blobInfo) { - var blobUri = blobInfo.blobUri(); - - return new Promise(function(resolve) { - pendingPromises[blobUri] = pendingPromises[blobUri] || []; - pendingPromises[blobUri].push(resolve); - }); - } - - function uploadBlobs(blobInfos, openNotification) { - blobInfos = Tools.grep(blobInfos, function(blobInfo) { - return !uploadStatus.isUploaded(blobInfo.blobUri()); - }); - - return Promise.all(Tools.map(blobInfos, function(blobInfo) { - return uploadStatus.isPending(blobInfo.blobUri()) ? - pendingUploadBlobInfo(blobInfo) : uploadBlobInfo(blobInfo, settings.handler, openNotification); - })); - } - - function upload(blobInfos, openNotification) { - return (!settings.url && isDefaultHandler(settings.handler)) ? noUpload() : uploadBlobs(blobInfos, openNotification); - } - - settings = Tools.extend({ - credentials: false, - // We are adding a notify argument to this (at the moment, until it doesn't work) - handler: defaultHandler - }, settings); - - return { - upload: upload - }; - }; -}); - -// Included from: js/tinymce/classes/file/Conversions.js + settings = Tools.extend({ + credentials: false, + // We are adding a notify argument to this (at the moment, until it doesn't work) + handler: defaultHandler + }, settings); + return { + upload: upload + }; + }; + } +); /** * Conversions.js * * Released under LGPL License. - * Copyright (c) 1999-2015 Ephox Corp. All rights reserved + * Copyright (c) 1999-2017 Ephox Corp. All rights reserved * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing @@ -35465,104 +37795,121 @@ define("tinymce/file/Uploader", [ * @private * @class tinymce.file.Conversions */ -define("tinymce/file/Conversions", [ - "tinymce/util/Promise" -], function(Promise) { - function blobUriToBlob(url) { - return new Promise(function(resolve) { - var xhr = new XMLHttpRequest(); +define( + 'tinymce.core.file.Conversions', + [ + "tinymce.core.util.Promise" + ], + function (Promise) { + function blobUriToBlob(url) { + return new Promise(function (resolve, reject) { - xhr.open('GET', url, true); - xhr.responseType = 'blob'; + var rejectWithError = function () { + reject("Cannot convert " + url + " to Blob. Resource might not exist or is inaccessible."); + }; - xhr.onload = function() { - if (this.status == 200) { - resolve(this.response); - } - }; + try { + var xhr = new XMLHttpRequest(); - xhr.send(); - }); - } + xhr.open('GET', url, true); + xhr.responseType = 'blob'; - function parseDataUri(uri) { - var type, matches; + xhr.onload = function () { + if (this.status == 200) { + resolve(this.response); + } else { + // IE11 makes it into onload but responds with status 500 + rejectWithError(); + } + }; - uri = decodeURIComponent(uri).split(','); + // Chrome fires an error event instead of the exception + // Also there seems to be no way to intercept the message that is logged to the console + xhr.onerror = rejectWithError; - matches = /data:([^;]+)/.exec(uri[0]); - if (matches) { - type = matches[1]; - } + xhr.send(); + } catch (ex) { + rejectWithError(); + } + }); + } - return { - type: type, - data: uri[1] - }; - } + function parseDataUri(uri) { + var type, matches; - function dataUriToBlob(uri) { - return new Promise(function(resolve) { - var str, arr, i; + uri = decodeURIComponent(uri).split(','); - uri = parseDataUri(uri); + matches = /data:([^;]+)/.exec(uri[0]); + if (matches) { + type = matches[1]; + } - // Might throw error if data isn't proper base64 - try { - str = atob(uri.data); - } catch (e) { - resolve(new Blob([])); - return; - } + return { + type: type, + data: uri[1] + }; + } - arr = new Uint8Array(str.length); + function dataUriToBlob(uri) { + return new Promise(function (resolve) { + var str, arr, i; - for (i = 0; i < arr.length; i++) { - arr[i] = str.charCodeAt(i); - } + uri = parseDataUri(uri); - resolve(new Blob([arr], {type: uri.type})); - }); - } + // Might throw error if data isn't proper base64 + try { + str = atob(uri.data); + } catch (e) { + resolve(new Blob([])); + return; + } - function uriToBlob(url) { - if (url.indexOf('blob:') === 0) { - return blobUriToBlob(url); - } + arr = new Uint8Array(str.length); - if (url.indexOf('data:') === 0) { - return dataUriToBlob(url); - } + for (i = 0; i < arr.length; i++) { + arr[i] = str.charCodeAt(i); + } - return null; - } + resolve(new Blob([arr], { type: uri.type })); + }); + } - function blobToDataUri(blob) { - return new Promise(function(resolve) { - var reader = new FileReader(); + function uriToBlob(url) { + if (url.indexOf('blob:') === 0) { + return blobUriToBlob(url); + } - reader.onloadend = function() { - resolve(reader.result); - }; + if (url.indexOf('data:') === 0) { + return dataUriToBlob(url); + } - reader.readAsDataURL(blob); - }); - } + return null; + } - return { - uriToBlob: uriToBlob, - blobToDataUri: blobToDataUri, - parseDataUri: parseDataUri - }; -}); + function blobToDataUri(blob) { + return new Promise(function (resolve) { + var reader = new FileReader(); -// Included from: js/tinymce/classes/file/ImageScanner.js + reader.onloadend = function () { + resolve(reader.result); + }; + reader.readAsDataURL(blob); + }); + } + + return { + uriToBlob: uriToBlob, + blobToDataUri: blobToDataUri, + parseDataUri: parseDataUri + }; + } +); /** * ImageScanner.js * * Released under LGPL License. - * Copyright (c) 1999-2015 Ephox Corp. All rights reserved + * Copyright (c) 1999-2017 Ephox Corp. All rights reserved * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing @@ -35574,158 +37921,209 @@ define("tinymce/file/Conversions", [ * @private * @class tinymce.file.ImageScanner */ -define("tinymce/file/ImageScanner", [ - "tinymce/util/Promise", - "tinymce/util/Arr", - "tinymce/util/Fun", - "tinymce/file/Conversions", - "tinymce/Env" -], function(Promise, Arr, Fun, Conversions, Env) { - var count = 0; +define( + 'tinymce.core.file.ImageScanner', + [ + "tinymce.core.util.Promise", + "tinymce.core.util.Arr", + "tinymce.core.util.Fun", + "tinymce.core.file.Conversions", + "tinymce.core.Env" + ], + function (Promise, Arr, Fun, Conversions, Env) { + var count = 0; - var uniqueId = function(prefix) { - return (prefix || 'blobid') + (count++); - }; + var uniqueId = function (prefix) { + return (prefix || 'blobid') + (count++); + }; - return function(uploadStatus, blobCache) { - var cachedPromises = {}; + return function (uploadStatus, blobCache) { + var cachedPromises = {}; - function findAll(elm, predicate) { - var images, promises; + function findAll(elm, predicate) { + var images, promises; - function imageToBlobInfo(img, resolve) { - var base64, blobInfo; + function imageToBlobInfo(img, resolve, reject) { + var base64, blobInfo; - if (img.src.indexOf('blob:') === 0) { - blobInfo = blobCache.getByUri(img.src); + if (img.src.indexOf('blob:') === 0) { + blobInfo = blobCache.getByUri(img.src); - if (blobInfo) { - resolve({ - image: img, - blobInfo: blobInfo - }); - } else { - Conversions.uriToBlob(img.src).then(function (blob) { - Conversions.blobToDataUri(blob).then(function (dataUri) { - base64 = Conversions.parseDataUri(dataUri).data; - blobInfo = blobCache.create(uniqueId(), blob, base64); - blobCache.add(blobInfo); + if (blobInfo) { + resolve({ + image: img, + blobInfo: blobInfo + }); + } else { + Conversions.uriToBlob(img.src).then(function (blob) { + Conversions.blobToDataUri(blob).then(function (dataUri) { + base64 = Conversions.parseDataUri(dataUri).data; + blobInfo = blobCache.create(uniqueId(), blob, base64); + blobCache.add(blobInfo); - resolve({ - image: img, - blobInfo: blobInfo - }); - }); - }); - } + resolve({ + image: img, + blobInfo: blobInfo + }); + }); + }, function (err) { + reject(err); + }); + } - return; - } + return; + } - base64 = Conversions.parseDataUri(img.src).data; - blobInfo = blobCache.findFirst(function(cachedBlobInfo) { - return cachedBlobInfo.base64() === base64; - }); + base64 = Conversions.parseDataUri(img.src).data; + blobInfo = blobCache.findFirst(function (cachedBlobInfo) { + return cachedBlobInfo.base64() === base64; + }); - if (blobInfo) { - resolve({ - image: img, - blobInfo: blobInfo - }); - } else { - Conversions.uriToBlob(img.src).then(function(blob) { - blobInfo = blobCache.create(uniqueId(), blob, base64); - blobCache.add(blobInfo); + if (blobInfo) { + resolve({ + image: img, + blobInfo: blobInfo + }); + } else { + Conversions.uriToBlob(img.src).then(function (blob) { + blobInfo = blobCache.create(uniqueId(), blob, base64); + blobCache.add(blobInfo); - resolve({ - image: img, - blobInfo: blobInfo - }); - }); - } - } + resolve({ + image: img, + blobInfo: blobInfo + }); + }, function (err) { + reject(err); + }); + } + } - if (!predicate) { - predicate = Fun.constant(true); - } + if (!predicate) { + predicate = Fun.constant(true); + } - images = Arr.filter(elm.getElementsByTagName('img'), function(img) { - var src = img.src; + images = Arr.filter(elm.getElementsByTagName('img'), function (img) { + var src = img.src; - if (!Env.fileApi) { - return false; - } + if (!Env.fileApi) { + return false; + } - if (img.hasAttribute('data-mce-bogus')) { - return false; - } + if (img.hasAttribute('data-mce-bogus')) { + return false; + } - if (img.hasAttribute('data-mce-placeholder')) { - return false; - } + if (img.hasAttribute('data-mce-placeholder')) { + return false; + } - if (!src || src == Env.transparentSrc) { - return false; - } + if (!src || src == Env.transparentSrc) { + return false; + } - if (src.indexOf('blob:') === 0) { - return !uploadStatus.isUploaded(src); - } + if (src.indexOf('blob:') === 0) { + return !uploadStatus.isUploaded(src); + } - if (src.indexOf('data:') === 0) { - return predicate(img); - } + if (src.indexOf('data:') === 0) { + return predicate(img); + } - return false; - }); + return false; + }); - promises = Arr.map(images, function(img) { - var newPromise; + promises = Arr.map(images, function (img) { + var newPromise; - if (cachedPromises[img.src]) { - // Since the cached promise will return the cached image - // We need to wrap it and resolve with the actual image - return new Promise(function(resolve) { - cachedPromises[img.src].then(function(imageInfo) { - resolve({ - image: img, - blobInfo: imageInfo.blobInfo - }); - }); - }); - } + if (cachedPromises[img.src]) { + // Since the cached promise will return the cached image + // We need to wrap it and resolve with the actual image + return new Promise(function (resolve) { + cachedPromises[img.src].then(function (imageInfo) { + if (typeof imageInfo === 'string') { // error apparently + return imageInfo; + } + resolve({ + image: img, + blobInfo: imageInfo.blobInfo + }); + }); + }); + } - newPromise = new Promise(function(resolve) { - imageToBlobInfo(img, resolve); - }).then(function(result) { - delete cachedPromises[result.image.src]; - return result; - })['catch'](function(error) { - delete cachedPromises[img.src]; - return error; - }); + newPromise = new Promise(function (resolve, reject) { + imageToBlobInfo(img, resolve, reject); + }).then(function (result) { + delete cachedPromises[result.image.src]; + return result; + })['catch'](function (error) { + delete cachedPromises[img.src]; + return error; + }); - cachedPromises[img.src] = newPromise; + cachedPromises[img.src] = newPromise; - return newPromise; - }); + return newPromise; + }); - return Promise.all(promises); - } + return Promise.all(promises); + } - return { - findAll: findAll - }; - }; -}); + return { + findAll: findAll + }; + }; + } +); +/** + * Uuid.js + * + * Released under LGPL License. + * Copyright (c) 1999-2017 Ephox Corp. All rights reserved + * + * License: http://www.tinymce.com/license + * Contributing: http://www.tinymce.com/contributing + */ -// Included from: js/tinymce/classes/file/BlobCache.js +/** + * Generates unique ids. + * + * @class tinymce.util.Uuid + * @private + */ +define( + 'tinymce.core.util.Uuid', + [ + ], + function () { + var count = 0; + var seed = function () { + var rnd = function () { + return Math.round(Math.random() * 0xFFFFFFFF).toString(36); + }; + + var now = new Date().getTime(); + return 's' + now.toString(36) + rnd() + rnd() + rnd(); + }; + + var uuid = function (prefix) { + return prefix + (count++) + seed(); + }; + + return { + uuid: uuid + }; + } +); + +defineGlobal("global!URL", URL); /** * BlobCache.js * * Released under LGPL License. - * Copyright (c) 1999-2015 Ephox Corp. All rights reserved + * Copyright (c) 1999-2017 Ephox Corp. All rights reserved * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing @@ -35737,83 +38135,117 @@ define("tinymce/file/ImageScanner", [ * @private * @class tinymce.file.BlobCache */ -define("tinymce/file/BlobCache", [ - "tinymce/util/Arr", - "tinymce/util/Fun" -], function(Arr, Fun) { - return function() { - var cache = [], constant = Fun.constant; +define( + 'tinymce.core.file.BlobCache', + [ + 'tinymce.core.util.Arr', + 'tinymce.core.util.Fun', + 'tinymce.core.util.Uuid', + 'global!URL' + ], + function (Arr, Fun, Uuid, URL) { + return function () { + var cache = [], constant = Fun.constant; - function create(id, blob, base64, filename) { - return { - id: constant(id), - filename: constant(filename || id), - blob: constant(blob), - base64: constant(base64), - blobUri: constant(URL.createObjectURL(blob)) - }; - } + function mimeToExt(mime) { + var mimes = { + 'image/jpeg': 'jpg', + 'image/jpg': 'jpg', + 'image/gif': 'gif', + 'image/png': 'png' + }; - function add(blobInfo) { - if (!get(blobInfo.id())) { - cache.push(blobInfo); - } - } + return mimes[mime.toLowerCase()] || 'dat'; + } - function get(id) { - return findFirst(function(cachedBlobInfo) { - return cachedBlobInfo.id() === id; - }); - } + function create(o, blob, base64, filename) { + return typeof o === 'object' ? toBlobInfo(o) : toBlobInfo({ + id: o, + name: filename, + blob: blob, + base64: base64 + }); + } - function findFirst(predicate) { - return Arr.filter(cache, predicate)[0]; - } + function toBlobInfo(o) { + var id, name; - function getByUri(blobUri) { - return findFirst(function(blobInfo) { - return blobInfo.blobUri() == blobUri; - }); - } + if (!o.blob || !o.base64) { + throw "blob and base64 representations of the image are required for BlobInfo to be created"; + } - function removeByUri(blobUri) { - cache = Arr.filter(cache, function(blobInfo) { - if (blobInfo.blobUri() === blobUri) { - URL.revokeObjectURL(blobInfo.blobUri()); - return false; - } + id = o.id || Uuid.uuid('blobid'); + name = o.name || id; - return true; - }); - } + return { + id: constant(id), + name: constant(name), + filename: constant(name + '.' + mimeToExt(o.blob.type)), + blob: constant(o.blob), + base64: constant(o.base64), + blobUri: constant(o.blobUri || URL.createObjectURL(o.blob)), + uri: constant(o.uri) + }; + } - function destroy() { - Arr.each(cache, function(cachedBlobInfo) { - URL.revokeObjectURL(cachedBlobInfo.blobUri()); - }); + function add(blobInfo) { + if (!get(blobInfo.id())) { + cache.push(blobInfo); + } + } - cache = []; - } + function get(id) { + return findFirst(function (cachedBlobInfo) { + return cachedBlobInfo.id() === id; + }); + } - return { - create: create, - add: add, - get: get, - getByUri: getByUri, - findFirst: findFirst, - removeByUri: removeByUri, - destroy: destroy - }; - }; -}); + function findFirst(predicate) { + return Arr.filter(cache, predicate)[0]; + } -// Included from: js/tinymce/classes/file/UploadStatus.js + function getByUri(blobUri) { + return findFirst(function (blobInfo) { + return blobInfo.blobUri() == blobUri; + }); + } + function removeByUri(blobUri) { + cache = Arr.filter(cache, function (blobInfo) { + if (blobInfo.blobUri() === blobUri) { + URL.revokeObjectURL(blobInfo.blobUri()); + return false; + } + + return true; + }); + } + + function destroy() { + Arr.each(cache, function (cachedBlobInfo) { + URL.revokeObjectURL(cachedBlobInfo.blobUri()); + }); + + cache = []; + } + + return { + create: create, + add: add, + get: get, + getByUri: getByUri, + findFirst: findFirst, + removeByUri: removeByUri, + destroy: destroy + }; + }; + } +); /** * UploadStatus.js * * Released under LGPL License. - * Copyright (c) 1999-2016 Ephox Corp. All rights reserved + * Copyright (c) 1999-2017 Ephox Corp. All rights reserved * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing @@ -35825,145 +38257,74 @@ define("tinymce/file/BlobCache", [ * @private * @class tinymce.file.UploadStatus */ -define("tinymce/file/UploadStatus", [ -], function() { - return function() { - var PENDING = 1, UPLOADED = 2; - var blobUriStatuses = {}; +define( + 'tinymce.core.file.UploadStatus', + [ + ], + function () { + return function () { + var PENDING = 1, UPLOADED = 2; + var blobUriStatuses = {}; - function createStatus(status, resultUri) { - return { - status: status, - resultUri: resultUri - }; - } + function createStatus(status, resultUri) { + return { + status: status, + resultUri: resultUri + }; + } - function hasBlobUri(blobUri) { - return blobUri in blobUriStatuses; - } + function hasBlobUri(blobUri) { + return blobUri in blobUriStatuses; + } - function getResultUri(blobUri) { - var result = blobUriStatuses[blobUri]; + function getResultUri(blobUri) { + var result = blobUriStatuses[blobUri]; - return result ? result.resultUri : null; - } + return result ? result.resultUri : null; + } - function isPending(blobUri) { - return hasBlobUri(blobUri) ? blobUriStatuses[blobUri].status === PENDING : false; - } + function isPending(blobUri) { + return hasBlobUri(blobUri) ? blobUriStatuses[blobUri].status === PENDING : false; + } - function isUploaded(blobUri) { - return hasBlobUri(blobUri) ? blobUriStatuses[blobUri].status === UPLOADED : false; - } + function isUploaded(blobUri) { + return hasBlobUri(blobUri) ? blobUriStatuses[blobUri].status === UPLOADED : false; + } - function markPending(blobUri) { - blobUriStatuses[blobUri] = createStatus(PENDING, null); - } + function markPending(blobUri) { + blobUriStatuses[blobUri] = createStatus(PENDING, null); + } - function markUploaded(blobUri, resultUri) { - blobUriStatuses[blobUri] = createStatus(UPLOADED, resultUri); - } + function markUploaded(blobUri, resultUri) { + blobUriStatuses[blobUri] = createStatus(UPLOADED, resultUri); + } - function removeFailed(blobUri) { - delete blobUriStatuses[blobUri]; - } + function removeFailed(blobUri) { + delete blobUriStatuses[blobUri]; + } - function destroy() { - blobUriStatuses = {}; - } - - return { - hasBlobUri: hasBlobUri, - getResultUri: getResultUri, - isPending: isPending, - isUploaded: isUploaded, - markPending: markPending, - markUploaded: markUploaded, - removeFailed: removeFailed, - destroy: destroy - }; - }; -}); - -// Included from: js/tinymce/classes/ErrorReporter.js - -/** - * ErrorReporter.js - * - * Released under LGPL License. - * Copyright (c) 1999-2016 Ephox Corp. All rights reserved - * - * License: http://www.tinymce.com/license - * Contributing: http://www.tinymce.com/contributing - */ - -/** - * Various error reporting helper functions. - * - * @class tinymce.ErrorReporter - * @private - */ -define("tinymce/ErrorReporter", [ - "tinymce/AddOnManager" -], function (AddOnManager) { - var PluginManager = AddOnManager.PluginManager; - - var resolvePluginName = function (targetUrl, suffix) { - for (var name in PluginManager.urls) { - var matchUrl = PluginManager.urls[name] + '/plugin' + suffix + '.js'; - if (matchUrl === targetUrl) { - return name; - } - } - - return null; - }; - - var pluginUrlToMessage = function (editor, url) { - var plugin = resolvePluginName(url, editor.suffix); - return plugin ? - 'Failed to load plugin: ' + plugin + ' from url ' + url : - 'Failed to load plugin url: ' + url; - }; - - var displayNotification = function (editor, message) { - editor.notificationManager.open({ - type: 'error', - text: message - }); - }; - - var displayError = function (editor, message) { - if (editor._skinLoaded) { - displayNotification(editor, message); - } else { - editor.on('SkinLoaded', function () { - displayNotification(editor, message); - }); - } - }; - - var uploadError = function (editor, message) { - displayError(editor, 'Failed to upload image: ' + message); - }; - - var pluginLoadError = function (editor, url) { - displayError(editor, pluginUrlToMessage(editor, url)); - }; - - return { - pluginLoadError: pluginLoadError, - uploadError: uploadError - }; -}); - -// Included from: js/tinymce/classes/EditorUpload.js + function destroy() { + blobUriStatuses = {}; + } + return { + hasBlobUri: hasBlobUri, + getResultUri: getResultUri, + isPending: isPending, + isUploaded: isUploaded, + markPending: markPending, + markUploaded: markUploaded, + removeFailed: removeFailed, + destroy: destroy + }; + }; + } +); /** * EditorUpload.js * * Released under LGPL License. - * Copyright (c) 1999-2015 Ephox Corp. All rights reserved + * Copyright (c) 1999-2017 Ephox Corp. All rights reserved * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing @@ -35975,238 +38336,1635 @@ define("tinymce/ErrorReporter", [ * @private * @class tinymce.EditorUpload */ -define("tinymce/EditorUpload", [ - "tinymce/util/Arr", - "tinymce/file/Uploader", - "tinymce/file/ImageScanner", - "tinymce/file/BlobCache", - "tinymce/file/UploadStatus", - "tinymce/ErrorReporter" -], function(Arr, Uploader, ImageScanner, BlobCache, UploadStatus, ErrorReporter) { - return function(editor) { - var blobCache = new BlobCache(), uploader, imageScanner, settings = editor.settings; - var uploadStatus = new UploadStatus(); +define( + 'tinymce.core.EditorUpload', + [ + "tinymce.core.util.Arr", + "tinymce.core.file.Uploader", + "tinymce.core.file.ImageScanner", + "tinymce.core.file.BlobCache", + "tinymce.core.file.UploadStatus", + "tinymce.core.ErrorReporter" + ], + function (Arr, Uploader, ImageScanner, BlobCache, UploadStatus, ErrorReporter) { + return function (editor) { + var blobCache = new BlobCache(), uploader, imageScanner, settings = editor.settings; + var uploadStatus = new UploadStatus(); - function aliveGuard(callback) { - return function(result) { - if (editor.selection) { - return callback(result); - } + function aliveGuard(callback) { + return function (result) { + if (editor.selection) { + return callback(result); + } - return []; - }; - } + return []; + }; + } - function cacheInvalidator() { - return '?' + (new Date()).getTime(); - } + function cacheInvalidator() { + return '?' + (new Date()).getTime(); + } - // Replaces strings without regexps to avoid FF regexp to big issue - function replaceString(content, search, replace) { - var index = 0; + // Replaces strings without regexps to avoid FF regexp to big issue + function replaceString(content, search, replace) { + var index = 0; - do { - index = content.indexOf(search, index); + do { + index = content.indexOf(search, index); - if (index !== -1) { - content = content.substring(0, index) + replace + content.substr(index + search.length); - index += replace.length - search.length + 1; - } - } while (index !== -1); + if (index !== -1) { + content = content.substring(0, index) + replace + content.substr(index + search.length); + index += replace.length - search.length + 1; + } + } while (index !== -1); - return content; - } + return content; + } - function replaceImageUrl(content, targetUrl, replacementUrl) { - content = replaceString(content, 'src="' + targetUrl + '"', 'src="' + replacementUrl + '"'); - content = replaceString(content, 'data-mce-src="' + targetUrl + '"', 'data-mce-src="' + replacementUrl + '"'); + function replaceImageUrl(content, targetUrl, replacementUrl) { + content = replaceString(content, 'src="' + targetUrl + '"', 'src="' + replacementUrl + '"'); + content = replaceString(content, 'data-mce-src="' + targetUrl + '"', 'data-mce-src="' + replacementUrl + '"'); - return content; - } + return content; + } - function replaceUrlInUndoStack(targetUrl, replacementUrl) { - Arr.each(editor.undoManager.data, function(level) { - if (level.type === 'fragmented') { - level.fragments = Arr.map(level.fragments, function (fragment) { - return replaceImageUrl(fragment, targetUrl, replacementUrl); - }); - } else { - level.content = replaceImageUrl(level.content, targetUrl, replacementUrl); - } - }); - } + function replaceUrlInUndoStack(targetUrl, replacementUrl) { + Arr.each(editor.undoManager.data, function (level) { + if (level.type === 'fragmented') { + level.fragments = Arr.map(level.fragments, function (fragment) { + return replaceImageUrl(fragment, targetUrl, replacementUrl); + }); + } else { + level.content = replaceImageUrl(level.content, targetUrl, replacementUrl); + } + }); + } - function openNotification() { - return editor.notificationManager.open({ - text: editor.translate('Image uploading...'), - type: 'info', - timeout: -1, - progressBar: true - }); - } + function openNotification() { + return editor.notificationManager.open({ + text: editor.translate('Image uploading...'), + type: 'info', + timeout: -1, + progressBar: true + }); + } - function replaceImageUri(image, resultUri) { - blobCache.removeByUri(image.src); - replaceUrlInUndoStack(image.src, resultUri); + function replaceImageUri(image, resultUri) { + blobCache.removeByUri(image.src); + replaceUrlInUndoStack(image.src, resultUri); - editor.$(image).attr({ - src: settings.images_reuse_filename ? resultUri + cacheInvalidator() : resultUri, - 'data-mce-src': editor.convertURL(resultUri, 'src') - }); - } + editor.$(image).attr({ + src: settings.images_reuse_filename ? resultUri + cacheInvalidator() : resultUri, + 'data-mce-src': editor.convertURL(resultUri, 'src') + }); + } - function uploadImages(callback) { - if (!uploader) { - uploader = new Uploader(uploadStatus, { - url: settings.images_upload_url, - basePath: settings.images_upload_base_path, - credentials: settings.images_upload_credentials, - handler: settings.images_upload_handler - }); - } + function uploadImages(callback) { + if (!uploader) { + uploader = new Uploader(uploadStatus, { + url: settings.images_upload_url, + basePath: settings.images_upload_base_path, + credentials: settings.images_upload_credentials, + handler: settings.images_upload_handler + }); + } - return scanForImages().then(aliveGuard(function(imageInfos) { - var blobInfos; + return scanForImages().then(aliveGuard(function (imageInfos) { + var blobInfos; - blobInfos = Arr.map(imageInfos, function(imageInfo) { - return imageInfo.blobInfo; - }); + blobInfos = Arr.map(imageInfos, function (imageInfo) { + return imageInfo.blobInfo; + }); - return uploader.upload(blobInfos, openNotification).then(aliveGuard(function(result) { - result = Arr.map(result, function(uploadInfo, index) { - var image = imageInfos[index].image; + return uploader.upload(blobInfos, openNotification).then(aliveGuard(function (result) { + var filteredResult = Arr.map(result, function (uploadInfo, index) { + var image = imageInfos[index].image; - if (uploadInfo.status && editor.settings.images_replace_blob_uris !== false) { - replaceImageUri(image, uploadInfo.url); - } else if (uploadInfo.error) { - ErrorReporter.uploadError(editor, uploadInfo.error); - } + if (uploadInfo.status && editor.settings.images_replace_blob_uris !== false) { + replaceImageUri(image, uploadInfo.url); + } else if (uploadInfo.error) { + ErrorReporter.uploadError(editor, uploadInfo.error); + } - return { - element: image, - status: uploadInfo.status - }; - }); + return { + element: image, + status: uploadInfo.status + }; + }); - if (callback) { - callback(result); - } + if (callback) { + callback(filteredResult); + } - return result; - })); - })); - } + return filteredResult; + })); + })); + } - function uploadImagesAuto(callback) { - if (settings.automatic_uploads !== false) { - return uploadImages(callback); - } - } + function uploadImagesAuto(callback) { + if (settings.automatic_uploads !== false) { + return uploadImages(callback); + } + } - function isValidDataUriImage(imgElm) { - return settings.images_dataimg_filter ? settings.images_dataimg_filter(imgElm) : true; - } + function isValidDataUriImage(imgElm) { + return settings.images_dataimg_filter ? settings.images_dataimg_filter(imgElm) : true; + } - function scanForImages() { - if (!imageScanner) { - imageScanner = new ImageScanner(uploadStatus, blobCache); - } + function scanForImages() { + if (!imageScanner) { + imageScanner = new ImageScanner(uploadStatus, blobCache); + } - return imageScanner.findAll(editor.getBody(), isValidDataUriImage).then(aliveGuard(function(result) { - Arr.each(result, function(resultItem) { - replaceUrlInUndoStack(resultItem.image.src, resultItem.blobInfo.blobUri()); - resultItem.image.src = resultItem.blobInfo.blobUri(); - resultItem.image.removeAttribute('data-mce-src'); - }); + return imageScanner.findAll(editor.getBody(), isValidDataUriImage).then(aliveGuard(function (result) { + result = Arr.filter(result, function (resultItem) { + // ImageScanner internally converts images that it finds, but it may fail to do so if image source is inaccessible. + // In such case resultItem will contain appropriate text error message, instead of image data. + if (typeof resultItem === 'string') { + ErrorReporter.displayError(editor, resultItem); + return false; + } + return true; + }); - return result; - })); - } + Arr.each(result, function (resultItem) { + replaceUrlInUndoStack(resultItem.image.src, resultItem.blobInfo.blobUri()); + resultItem.image.src = resultItem.blobInfo.blobUri(); + resultItem.image.removeAttribute('data-mce-src'); + }); - function destroy() { - blobCache.destroy(); - uploadStatus.destroy(); - imageScanner = uploader = null; - } + return result; + })); + } - function replaceBlobUris(content) { - return content.replace(/src="(blob:[^"]+)"/g, function(match, blobUri) { - var resultUri = uploadStatus.getResultUri(blobUri); + function destroy() { + blobCache.destroy(); + uploadStatus.destroy(); + imageScanner = uploader = null; + } - if (resultUri) { - return 'src="' + resultUri + '"'; - } + function replaceBlobUris(content) { + return content.replace(/src="(blob:[^"]+)"/g, function (match, blobUri) { + var resultUri = uploadStatus.getResultUri(blobUri); - var blobInfo = blobCache.getByUri(blobUri); + if (resultUri) { + return 'src="' + resultUri + '"'; + } - if (!blobInfo) { - blobInfo = Arr.reduce(editor.editorManager.editors, function(result, editor) { - return result || editor.editorUpload.blobCache.getByUri(blobUri); - }, null); - } + var blobInfo = blobCache.getByUri(blobUri); - if (blobInfo) { - return 'src="data:' + blobInfo.blob().type + ';base64,' + blobInfo.base64() + '"'; - } + if (!blobInfo) { + blobInfo = Arr.reduce(editor.editorManager.editors, function (result, editor) { + return result || editor.editorUpload && editor.editorUpload.blobCache.getByUri(blobUri); + }, null); + } - return match; - }); - } + if (blobInfo) { + return 'src="data:' + blobInfo.blob().type + ';base64,' + blobInfo.base64() + '"'; + } - editor.on('setContent', function() { - if (editor.settings.automatic_uploads !== false) { - uploadImagesAuto(); - } else { - scanForImages(); - } - }); + return match; + }); + } - editor.on('RawSaveContent', function(e) { - e.content = replaceBlobUris(e.content); - }); + editor.on('setContent', function () { + if (editor.settings.automatic_uploads !== false) { + uploadImagesAuto(); + } else { + scanForImages(); + } + }); - editor.on('getContent', function(e) { - if (e.source_view || e.format == 'raw') { - return; - } + editor.on('RawSaveContent', function (e) { + e.content = replaceBlobUris(e.content); + }); - e.content = replaceBlobUris(e.content); - }); + editor.on('getContent', function (e) { + if (e.source_view || e.format == 'raw') { + return; + } - editor.on('PostRender', function() { - editor.parser.addNodeFilter('img', function(images) { - Arr.each(images, function(img) { - var src = img.attr('src'); + e.content = replaceBlobUris(e.content); + }); - if (blobCache.getByUri(src)) { - return; - } + editor.on('PostRender', function () { + editor.parser.addNodeFilter('img', function (images) { + Arr.each(images, function (img) { + var src = img.attr('src'); - var resultUri = uploadStatus.getResultUri(src); - if (resultUri) { - img.attr('src', resultUri); - } - }); - }); - }); + if (blobCache.getByUri(src)) { + return; + } - return { - blobCache: blobCache, - uploadImages: uploadImages, - uploadImagesAuto: uploadImagesAuto, - scanForImages: scanForImages, - destroy: destroy - }; - }; -}); + var resultUri = uploadStatus.getResultUri(src); + if (resultUri) { + img.attr('src', resultUri); + } + }); + }); + }); -// Included from: js/tinymce/classes/caret/FakeCaret.js + return { + blobCache: blobCache, + uploadImages: uploadImages, + uploadImagesAuto: uploadImagesAuto, + scanForImages: scanForImages, + destroy: destroy + }; + }; + } +); +/** + * ForceBlocks.js + * + * Released under LGPL License. + * Copyright (c) 1999-2017 Ephox Corp. All rights reserved + * + * License: http://www.tinymce.com/license + * Contributing: http://www.tinymce.com/contributing + */ + +/** + * Makes sure that everything gets wrapped in paragraphs. + * + * @private + * @class tinymce.ForceBlocks + */ +define( + 'tinymce.core.ForceBlocks', + [ + 'ephox.katamari.api.Fun' + ], + function (Fun) { + var addRootBlocks = function (editor) { + var settings = editor.settings, dom = editor.dom, selection = editor.selection; + var schema = editor.schema, blockElements = schema.getBlockElements(); + var node = selection.getStart(), rootNode = editor.getBody(), rng; + var startContainer, startOffset, endContainer, endOffset, rootBlockNode; + var tempNode, offset = -0xFFFFFF, wrapped, restoreSelection; + var tmpRng, rootNodeName, forcedRootBlock; + + forcedRootBlock = settings.forced_root_block; + + if (!node || node.nodeType !== 1 || !forcedRootBlock) { + return; + } + + // Check if node is wrapped in block + while (node && node !== rootNode) { + if (blockElements[node.nodeName]) { + return; + } + + node = node.parentNode; + } + + // Get current selection + rng = selection.getRng(); + if (rng.setStart) { + startContainer = rng.startContainer; + startOffset = rng.startOffset; + endContainer = rng.endContainer; + endOffset = rng.endOffset; + + try { + restoreSelection = editor.getDoc().activeElement === rootNode; + } catch (ex) { + // IE throws unspecified error here sometimes + } + } else { + // Force control range into text range + if (rng.item) { + node = rng.item(0); + rng = editor.getDoc().body.createTextRange(); + rng.moveToElementText(node); + } + + restoreSelection = rng.parentElement().ownerDocument === editor.getDoc(); + tmpRng = rng.duplicate(); + tmpRng.collapse(true); + startOffset = tmpRng.move('character', offset) * -1; + + if (!tmpRng.collapsed) { + tmpRng = rng.duplicate(); + tmpRng.collapse(false); + endOffset = (tmpRng.move('character', offset) * -1) - startOffset; + } + } + + // Wrap non block elements and text nodes + node = rootNode.firstChild; + rootNodeName = rootNode.nodeName.toLowerCase(); + while (node) { + // TODO: Break this up, too complex + if (((node.nodeType === 3 || (node.nodeType == 1 && !blockElements[node.nodeName]))) && + schema.isValidChild(rootNodeName, forcedRootBlock.toLowerCase())) { + // Remove empty text nodes + if (node.nodeType === 3 && node.nodeValue.length === 0) { + tempNode = node; + node = node.nextSibling; + dom.remove(tempNode); + continue; + } + + if (!rootBlockNode) { + rootBlockNode = dom.create(forcedRootBlock, editor.settings.forced_root_block_attrs); + node.parentNode.insertBefore(rootBlockNode, node); + wrapped = true; + } + + tempNode = node; + node = node.nextSibling; + rootBlockNode.appendChild(tempNode); + } else { + rootBlockNode = null; + node = node.nextSibling; + } + } + + if (wrapped && restoreSelection) { + if (rng.setStart) { + rng.setStart(startContainer, startOffset); + rng.setEnd(endContainer, endOffset); + selection.setRng(rng); + } else { + // Only select if the previous selection was inside the document to prevent auto focus in quirks mode + try { + rng = editor.getDoc().body.createTextRange(); + rng.moveToElementText(rootNode); + rng.collapse(true); + rng.moveStart('character', startOffset); + + if (endOffset > 0) { + rng.moveEnd('character', endOffset); + } + + rng.select(); + } catch (ex) { + // Ignore + } + } + + editor.nodeChanged(); + } + }; + + var setup = function (editor) { + if (editor.settings.forced_root_block) { + editor.on('NodeChange', Fun.curry(addRootBlocks, editor)); + } + }; + + return { + setup: setup + }; + } +); +define( + 'ephox.katamari.api.Merger', + + [ + 'ephox.katamari.api.Type', + 'global!Array', + 'global!Error' + ], + + function (Type, Array, Error) { + + var shallow = function (old, nu) { + return nu; + }; + + var deep = function (old, nu) { + var bothObjects = Type.isObject(old) && Type.isObject(nu); + return bothObjects ? deepMerge(old, nu) : nu; + }; + + var baseMerge = function (merger) { + return function() { + // Don't use array slice(arguments), makes the whole function unoptimisable on Chrome + var objects = new Array(arguments.length); + for (var i = 0; i < objects.length; i++) objects[i] = arguments[i]; + + if (objects.length === 0) throw new Error('Can\'t merge zero objects'); + + var ret = {}; + for (var j = 0; j < objects.length; j++) { + var curObject = objects[j]; + for (var key in curObject) if (curObject.hasOwnProperty(key)) { + ret[key] = merger(ret[key], curObject[key]); + } + } + return ret; + }; + }; + + var deepMerge = baseMerge(deep); + var merge = baseMerge(shallow); + + return { + deepMerge: deepMerge, + merge: merge + }; + } +); +/** + * MatchKeys.js + * + * Released under LGPL License. + * Copyright (c) 1999-2017 Ephox Corp. All rights reserved + * + * License: http://www.tinymce.com/license + * Contributing: http://www.tinymce.com/contributing + */ + +define( + 'tinymce.core.keyboard.MatchKeys', + [ + 'ephox.katamari.api.Arr', + 'ephox.katamari.api.Fun', + 'ephox.katamari.api.Merger' + ], + function (Arr, Fun, Merger) { + var defaultPatterns = function (patterns) { + return Arr.map(patterns, function (pattern) { + return Merger.merge({ + shiftKey: false, + altKey: false, + ctrlKey: false, + metaKey: false, + keyCode: 0, + action: Fun.noop + }, pattern); + }); + }; + + var matchesEvent = function (pattern, evt) { + return ( + evt.keyCode === pattern.keyCode && + evt.shiftKey === pattern.shiftKey && + evt.altKey === pattern.altKey && + evt.ctrlKey === pattern.ctrlKey && + evt.metaKey === pattern.metaKey + ); + }; + + var match = function (patterns, evt) { + return Arr.bind(defaultPatterns(patterns), function (pattern) { + return matchesEvent(pattern, evt) ? [pattern] : [ ]; + }); + }; + + var action = function (f) { + var args = Array.prototype.slice.call(arguments, 1); + return function () { + return f.apply(null, args); + }; + }; + + return { + match: match, + action: action + }; + } +); +/** + * ArrowKeys.js + * + * Released under LGPL License. + * Copyright (c) 1999-2017 Ephox Corp. All rights reserved + * + * License: http://www.tinymce.com/license + * Contributing: http://www.tinymce.com/contributing + */ + +define( + 'tinymce.core.keyboard.ArrowKeys', + [ + 'ephox.katamari.api.Arr', + 'ephox.katamari.api.Cell', + 'tinymce.core.keyboard.BoundarySelection', + 'tinymce.core.keyboard.MatchKeys', + 'tinymce.core.util.VK' + ], + function (Arr, Cell, BoundarySelection, MatchKeys, VK) { + var setup = function (editor, caret) { + editor.on('keydown', function (evt) { + var matches = MatchKeys.match([ + { keyCode: VK.RIGHT, action: BoundarySelection.move(editor, caret, true) }, + { keyCode: VK.LEFT, action: BoundarySelection.move(editor, caret, false) } + ], evt); + + Arr.find(matches, function (pattern) { + return pattern.action(); + }).each(function (_) { + evt.preventDefault(); + }); + }); + }; + + return { + setup: setup + }; + } +); + +/** + * DeleteBackspaceKeys.js + * + * Released under LGPL License. + * Copyright (c) 1999-2017 Ephox Corp. All rights reserved + * + * License: http://www.tinymce.com/license + * Contributing: http://www.tinymce.com/contributing + */ + +define( + 'tinymce.core.keyboard.DeleteBackspaceKeys', + [ + 'ephox.katamari.api.Arr', + 'tinymce.core.delete.BlockBoundaryDelete', + 'tinymce.core.delete.BlockRangeDelete', + 'tinymce.core.delete.CefDelete', + 'tinymce.core.delete.InlineBoundaryDelete', + 'tinymce.core.keyboard.MatchKeys', + 'tinymce.core.util.VK' + ], + function (Arr, BlockBoundaryDelete, BlockRangeDelete, CefDelete, BoundaryDelete, MatchKeys, VK) { + var setupKeyDownHandler = function (editor, caret) { + editor.on('keydown', function (evt) { + var matches = MatchKeys.match([ + { keyCode: VK.BACKSPACE, action: MatchKeys.action(BoundaryDelete.backspaceDelete, editor, caret, false) }, + { keyCode: VK.DELETE, action: MatchKeys.action(BoundaryDelete.backspaceDelete, editor, caret, true) }, + { keyCode: VK.BACKSPACE, action: MatchKeys.action(CefDelete.backspaceDelete, editor, false) }, + { keyCode: VK.DELETE, action: MatchKeys.action(CefDelete.backspaceDelete, editor, true) }, + { keyCode: VK.BACKSPACE, action: MatchKeys.action(BlockRangeDelete.backspaceDelete, editor, false) }, + { keyCode: VK.DELETE, action: MatchKeys.action(BlockRangeDelete.backspaceDelete, editor, true) }, + { keyCode: VK.BACKSPACE, action: MatchKeys.action(BlockBoundaryDelete.backspaceDelete, editor, false) }, + { keyCode: VK.DELETE, action: MatchKeys.action(BlockBoundaryDelete.backspaceDelete, editor, true) } + ], evt); + + Arr.find(matches, function (pattern) { + return pattern.action(); + }).each(function (_) { + evt.preventDefault(); + }); + }); + }; + + var setupKeyUpHandler = function (editor) { + editor.on('keyup', function (evt) { + var matches = MatchKeys.match([ + { keyCode: VK.BACKSPACE, action: MatchKeys.action(CefDelete.paddEmptyElement, editor) }, + { keyCode: VK.DELETE, action: MatchKeys.action(CefDelete.paddEmptyElement, editor) } + ], evt); + + Arr.find(matches, function (pattern) { + return pattern.action(); + }); + }); + }; + + var setup = function (editor, caret) { + setupKeyDownHandler(editor, caret); + setupKeyUpHandler(editor); + }; + + return { + setup: setup + }; + } +); + +/** + * EnterKey.js + * + * Released under LGPL License. + * Copyright (c) 1999-2017 Ephox Corp. All rights reserved + * + * License: http://www.tinymce.com/license + * Contributing: http://www.tinymce.com/contributing + */ + +/** + * Contains logic for handling the enter key to split/generate block elements. + */ +define( + 'tinymce.core.keyboard.EnterKey', + [ + 'tinymce.core.caret.CaretContainer', + 'tinymce.core.dom.NodeType', + 'tinymce.core.dom.RangeUtils', + 'tinymce.core.dom.TreeWalker', + 'tinymce.core.Env', + 'tinymce.core.text.Zwsp', + 'tinymce.core.util.Tools' + ], + function (CaretContainer, NodeType, RangeUtils, TreeWalker, Env, Zwsp, Tools) { + var isIE = Env.ie && Env.ie < 11; + + var isEmptyAnchor = function (elm) { + return elm && elm.nodeName === "A" && Tools.trim(Zwsp.trim(elm.innerText || elm.textContent)).length === 0; + }; + + var isTableCell = function (node) { + return node && /^(TD|TH|CAPTION)$/.test(node.nodeName); + }; + + var emptyBlock = function (elm) { + // BR is needed in empty blocks on non IE browsers + elm.innerHTML = !isIE ? 'x
becomes this:x
+ var trimInlineElementsOnLeftSideOfBlock = function (dom, nonEmptyElementsMap, block) { + var node = block, firstChilds = [], i; + + if (!node) { + return; + } + + // Find inner most first child ex:*
+ while ((node = node.firstChild)) { + if (dom.isBlock(node)) { + return; + } + + if (node.nodeType == 1 && !nonEmptyElementsMap[node.nodeName.toLowerCase()]) { + firstChilds.push(node); + } + } + + i = firstChilds.length; + while (i--) { + node = firstChilds[i]; + if (!node.hasChildNodes() || (node.firstChild == node.lastChild && node.firstChild.nodeValue === '')) { + dom.remove(node); + } else { + if (isEmptyAnchor(node)) { + dom.remove(node); + } + } + } + }; + + var normalizeZwspOffset = function (start, container, offset) { + if (NodeType.isText(container) === false) { + return offset; + } if (start) { + return offset === 1 && container.data.charAt(offset - 1) === Zwsp.ZWSP ? 0 : offset; + } else { + return offset === container.data.length - 1 && container.data.charAt(offset) === Zwsp.ZWSP ? container.data.length : offset; + } + }; + + var includeZwspInRange = function (rng) { + var newRng = rng.cloneRange(); + newRng.setStart(rng.startContainer, normalizeZwspOffset(true, rng.startContainer, rng.startOffset)); + newRng.setEnd(rng.endContainer, normalizeZwspOffset(false, rng.endContainer, rng.endOffset)); + return newRng; + }; + + var firstNonWhiteSpaceNodeSibling = function (node) { + while (node) { + if (node.nodeType === 1 || (node.nodeType === 3 && node.data && /[\r\n\s]/.test(node.data))) { + return node; + } + + node = node.nextSibling; + } + }; + + var setup = function (editor) { + var dom = editor.dom, selection = editor.selection, settings = editor.settings; + var undoManager = editor.undoManager, schema = editor.schema, nonEmptyElementsMap = schema.getNonEmptyElements(), + moveCaretBeforeOnEnterElementsMap = schema.getMoveCaretBeforeOnEnterElements(); + + function handleEnterKey(evt) { + var rng, tmpRng, editableRoot, container, offset, parentBlock, documentMode, shiftKey, + newBlock, fragment, containerBlock, parentBlockName, containerBlockName, newBlockName, isAfterLastNodeInContainer; + + // Moves the caret to a suitable position within the root for example in the first non + // pure whitespace text node or before an image + function moveToCaretPosition(root) { + var walker, node, rng, lastNode = root, tempElm; + + if (!root) { + return; + } + + // Old IE versions doesn't properly render blocks with br elements in them + // For exampletext|
text|text2
a |
+ if (dom.isEmpty(newBlock)) { + dom.remove(newBlock); + insertNewBlockAfter(); + } else { + moveToCaretPosition(newBlock); + } + } + + dom.setAttrib(newBlock, 'id', ''); // Remove ID since it needs to be document unique + + // Allow custom handling of new blocks + editor.fire('NewBlock', { newBlock: newBlock }); + + undoManager.typing = false; + undoManager.add(); + } + + editor.on('keydown', function (evt) { + if (evt.keyCode == 13) { + if (handleEnterKey(evt) !== false) { + evt.preventDefault(); + } + } + }); + }; + + return { + setup: setup + }; + } +); + +/** + * InsertSpace.js + * + * Released under LGPL License. + * Copyright (c) 1999-2017 Ephox Corp. All rights reserved + * + * License: http://www.tinymce.com/license + * Contributing: http://www.tinymce.com/contributing + */ + +define( + 'tinymce.core.keyboard.InsertSpace', + [ + 'ephox.katamari.api.Fun', + 'tinymce.core.caret.CaretPosition', + 'tinymce.core.dom.NodeType', + 'tinymce.core.keyboard.BoundaryLocation' + ], + function (Fun, CaretPosition, NodeType, BoundaryLocation) { + var isValidInsertPoint = function (location, caretPosition) { + return isAtStartOrEnd(location) && NodeType.isText(caretPosition.container()); + }; + + var insertNbspAtPosition = function (editor, caretPosition) { + var container = caretPosition.container(); + var offset = caretPosition.offset(); + + container.insertData(offset, '\u00a0'); + editor.selection.setCursorLocation(container, offset + 1); + }; + + var insertAtLocation = function (editor, caretPosition, location) { + if (isValidInsertPoint(location, caretPosition)) { + insertNbspAtPosition(editor, caretPosition); + return true; + } else { + return false; + } + }; + + var insertAtCaret = function (editor) { + var caretPosition = CaretPosition.fromRangeStart(editor.selection.getRng()); + var boundaryLocation = BoundaryLocation.readLocation(editor.getBody(), caretPosition); + return boundaryLocation.map(Fun.curry(insertAtLocation, editor, caretPosition)).getOr(false); + }; + + var isAtStartOrEnd = function (location) { + return location.fold( + Fun.constant(false), // Before + Fun.constant(true), // Start + Fun.constant(true), // End + Fun.constant(false) // After + ); + }; + + var insertAtSelection = function (editor) { + return editor.selection.isCollapsed() ? insertAtCaret(editor) : false; + }; + + return { + insertAtSelection: insertAtSelection + }; + } +); + +/** + * SpaceKey.js + * + * Released under LGPL License. + * Copyright (c) 1999-2017 Ephox Corp. All rights reserved + * + * License: http://www.tinymce.com/license + * Contributing: http://www.tinymce.com/contributing + */ + +define( + 'tinymce.core.keyboard.SpaceKey', + [ + 'ephox.katamari.api.Arr', + 'tinymce.core.keyboard.InsertSpace', + 'tinymce.core.keyboard.MatchKeys', + 'tinymce.core.util.VK' + ], + function (Arr, InsertSpace, MatchKeys, VK) { + var setupKeyDownHandler = function (editor, caret) { + editor.on('keydown', function (evt) { + var matches = MatchKeys.match([ + { keyCode: VK.SPACEBAR, action: MatchKeys.action(InsertSpace.insertAtSelection, editor) } + ], evt); + + Arr.find(matches, function (pattern) { + return pattern.action(); + }).each(function (_) { + evt.preventDefault(); + }); + }); + }; + + var setup = function (editor) { + setupKeyDownHandler(editor); + }; + + return { + setup: setup + }; + } +); + +/** + * KeyboardOverrides.js + * + * Released under LGPL License. + * Copyright (c) 1999-2017 Ephox Corp. All rights reserved + * + * License: http://www.tinymce.com/license + * Contributing: http://www.tinymce.com/contributing + */ + +define( + 'tinymce.core.keyboard.KeyboardOverrides', + [ + 'tinymce.core.keyboard.ArrowKeys', + 'tinymce.core.keyboard.BoundarySelection', + 'tinymce.core.keyboard.DeleteBackspaceKeys', + 'tinymce.core.keyboard.EnterKey', + 'tinymce.core.keyboard.SpaceKey' + ], + function (ArrowKeys, BoundarySelection, DeleteBackspaceKeys, EnterKey, SpaceKey) { + var setup = function (editor) { + var caret = BoundarySelection.setupSelectedState(editor); + + ArrowKeys.setup(editor, caret); + DeleteBackspaceKeys.setup(editor, caret); + EnterKey.setup(editor); + SpaceKey.setup(editor); + }; + + return { + setup: setup + }; + } +); +/** + * NodeChange.js + * + * Released under LGPL License. + * Copyright (c) 1999-2017 Ephox Corp. All rights reserved + * + * License: http://www.tinymce.com/license + * Contributing: http://www.tinymce.com/contributing + */ + +/** + * This class handles the nodechange event dispatching both manual and through selection change events. + * + * @class tinymce.NodeChange + * @private + */ +define( + 'tinymce.core.NodeChange', + [ + "tinymce.core.dom.RangeUtils", + "tinymce.core.Env", + "tinymce.core.util.Delay" + ], + function (RangeUtils, Env, Delay) { + return function (editor) { + var lastRng, lastPath = []; + + /** + * Returns true/false if the current element path has been changed or not. + * + * @private + * @return {Boolean} True if the element path is the same false if it's not. + */ + function isSameElementPath(startElm) { + var i, currentPath; + + currentPath = editor.$(startElm).parentsUntil(editor.getBody()).add(startElm); + if (currentPath.length === lastPath.length) { + for (i = currentPath.length; i >= 0; i--) { + if (currentPath[i] !== lastPath[i]) { + break; + } + } + + if (i === -1) { + lastPath = currentPath; + return true; + } + } + + lastPath = currentPath; + + return false; + } + + // Gecko doesn't support the "selectionchange" event + if (!('onselectionchange' in editor.getDoc())) { + editor.on('NodeChange Click MouseUp KeyUp Focus', function (e) { + var nativeRng, fakeRng; + + // Since DOM Ranges mutate on modification + // of the DOM we need to clone it's contents + nativeRng = editor.selection.getRng(); + fakeRng = { + startContainer: nativeRng.startContainer, + startOffset: nativeRng.startOffset, + endContainer: nativeRng.endContainer, + endOffset: nativeRng.endOffset + }; + + // Always treat nodechange as a selectionchange since applying + // formatting to the current range wouldn't update the range but it's parent + if (e.type == 'nodechange' || !RangeUtils.compareRanges(fakeRng, lastRng)) { + editor.fire('SelectionChange'); + } + + lastRng = fakeRng; + }); + } + + // IE has a bug where it fires a selectionchange on right click that has a range at the start of the body + // When the contextmenu event fires the selection is located at the right location + editor.on('contextmenu', function () { + editor.fire('SelectionChange'); + }); + + // Selection change is delayed ~200ms on IE when you click inside the current range + editor.on('SelectionChange', function () { + var startElm = editor.selection.getStart(true); + + // IE 8 will fire a selectionchange event with an incorrect selection + // when focusing out of table cells. Click inside cell -> toolbar = Invalid SelectionChange event + if (!Env.range && editor.selection.isCollapsed()) { + return; + } + + if (!isSameElementPath(startElm) && editor.dom.isChildOf(startElm, editor.getBody())) { + editor.nodeChanged({ selectionChange: true }); + } + }); + + // Fire an extra nodeChange on mouseup for compatibility reasons + editor.on('MouseUp', function (e) { + if (!e.isDefaultPrevented()) { + // Delay nodeChanged call for WebKit edge case issue where the range + // isn't updated until after you click outside a selected image + if (editor.selection.getNode().nodeName == 'IMG') { + Delay.setEditorTimeout(editor, function () { + editor.nodeChanged(); + }); + } else { + editor.nodeChanged(); + } + } + }); + + /** + * Dispatches out a onNodeChange event to all observers. This method should be called when you + * need to update the UI states or element path etc. + * + * @method nodeChanged + * @param {Object} args Optional args to pass to NodeChange event handlers. + */ + this.nodeChanged = function (args) { + var selection = editor.selection, node, parents, root; + + // Fix for bug #1896577 it seems that this can not be fired while the editor is loading + if (editor.initialized && selection && !editor.settings.disable_nodechange && !editor.readonly) { + // Get start node + root = editor.getBody(); + node = selection.getStart(true) || root; + + // Make sure the node is within the editor root or is the editor root + if (node.ownerDocument != editor.getDoc() || !editor.dom.isChildOf(node, root)) { + node = root; + } + + // Get parents and add them to object + parents = []; + editor.dom.getParent(node, function (node) { + if (node === root) { + return true; + } + + parents.push(node); + }); + + args = args || {}; + args.element = node; + args.parents = parents; + + editor.fire('NodeChange', args); + } + }; + }; + } +); /** * FakeCaret.js * * Released under LGPL License. - * Copyright (c) 1999-2015 Ephox Corp. All rights reserved + * Copyright (c) 1999-2017 Ephox Corp. All rights reserved * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing @@ -36218,189 +39976,191 @@ define("tinymce/EditorUpload", [ * @private * @class tinymce.caret.FakeCaret */ -define("tinymce/caret/FakeCaret", [ - "tinymce/caret/CaretContainer", - "tinymce/caret/CaretPosition", - "tinymce/dom/NodeType", - "tinymce/dom/RangeUtils", - "tinymce/dom/DomQuery", - "tinymce/geom/ClientRect", - "tinymce/util/Delay" -], function(CaretContainer, CaretPosition, NodeType, RangeUtils, $, ClientRect, Delay) { - var isContentEditableFalse = NodeType.isContentEditableFalse; +define( + 'tinymce.core.caret.FakeCaret', + [ + 'tinymce.core.caret.CaretContainer', + 'tinymce.core.caret.CaretContainerRemove', + 'tinymce.core.caret.CaretPosition', + 'tinymce.core.dom.DomQuery', + 'tinymce.core.dom.NodeType', + 'tinymce.core.dom.RangeUtils', + 'tinymce.core.geom.ClientRect', + 'tinymce.core.util.Delay' + ], + function (CaretContainer, CaretContainerRemove, CaretPosition, DomQuery, NodeType, RangeUtils, ClientRect, Delay) { + var isContentEditableFalse = NodeType.isContentEditableFalse; - return function(rootNode, isBlock) { - var cursorInterval, $lastVisualCaret, caretContainerNode; + return function (rootNode, isBlock) { + var cursorInterval, $lastVisualCaret, caretContainerNode; - function getAbsoluteClientRect(node, before) { - var clientRect = ClientRect.collapse(node.getBoundingClientRect(), before), - docElm, scrollX, scrollY, margin, rootRect; + function getAbsoluteClientRect(node, before) { + var clientRect = ClientRect.collapse(node.getBoundingClientRect(), before), + docElm, scrollX, scrollY, margin, rootRect; - if (rootNode.tagName == 'BODY') { - docElm = rootNode.ownerDocument.documentElement; - scrollX = rootNode.scrollLeft || docElm.scrollLeft; - scrollY = rootNode.scrollTop || docElm.scrollTop; - } else { - rootRect = rootNode.getBoundingClientRect(); - scrollX = rootNode.scrollLeft - rootRect.left; - scrollY = rootNode.scrollTop - rootRect.top; - } + if (rootNode.tagName == 'BODY') { + docElm = rootNode.ownerDocument.documentElement; + scrollX = rootNode.scrollLeft || docElm.scrollLeft; + scrollY = rootNode.scrollTop || docElm.scrollTop; + } else { + rootRect = rootNode.getBoundingClientRect(); + scrollX = rootNode.scrollLeft - rootRect.left; + scrollY = rootNode.scrollTop - rootRect.top; + } - clientRect.left += scrollX; - clientRect.right += scrollX; - clientRect.top += scrollY; - clientRect.bottom += scrollY; - clientRect.width = 1; + clientRect.left += scrollX; + clientRect.right += scrollX; + clientRect.top += scrollY; + clientRect.bottom += scrollY; + clientRect.width = 1; - margin = node.offsetWidth - node.clientWidth; + margin = node.offsetWidth - node.clientWidth; - if (margin > 0) { - if (before) { - margin *= -1; - } + if (margin > 0) { + if (before) { + margin *= -1; + } - clientRect.left += margin; - clientRect.right += margin; - } + clientRect.left += margin; + clientRect.right += margin; + } - return clientRect; - } + return clientRect; + } - function trimInlineCaretContainers() { - var contentEditableFalseNodes, node, sibling, i, data; + function trimInlineCaretContainers() { + var contentEditableFalseNodes, node, sibling, i, data; - contentEditableFalseNodes = $('*[contentEditable=false]', rootNode); - for (i = 0; i < contentEditableFalseNodes.length; i++) { - node = contentEditableFalseNodes[i]; + contentEditableFalseNodes = DomQuery('*[contentEditable=false]', rootNode); + for (i = 0; i < contentEditableFalseNodes.length; i++) { + node = contentEditableFalseNodes[i]; - sibling = node.previousSibling; - if (CaretContainer.endsWithCaretContainer(sibling)) { - data = sibling.data; + sibling = node.previousSibling; + if (CaretContainer.endsWithCaretContainer(sibling)) { + data = sibling.data; - if (data.length == 1) { - sibling.parentNode.removeChild(sibling); - } else { - sibling.deleteData(data.length - 1, 1); - } - } + if (data.length == 1) { + sibling.parentNode.removeChild(sibling); + } else { + sibling.deleteData(data.length - 1, 1); + } + } - sibling = node.nextSibling; - if (CaretContainer.startsWithCaretContainer(sibling)) { - data = sibling.data; + sibling = node.nextSibling; + if (CaretContainer.startsWithCaretContainer(sibling)) { + data = sibling.data; - if (data.length == 1) { - sibling.parentNode.removeChild(sibling); - } else { - sibling.deleteData(0, 1); - } - } - } + if (data.length == 1) { + sibling.parentNode.removeChild(sibling); + } else { + sibling.deleteData(0, 1); + } + } + } - return null; - } + return null; + } - function show(before, node) { - var clientRect, rng; + function show(before, node) { + var clientRect, rng; - hide(); + hide(); - if (isBlock(node)) { - caretContainerNode = CaretContainer.insertBlock('p', node, before); - clientRect = getAbsoluteClientRect(node, before); - $(caretContainerNode).css('top', clientRect.top); + if (isBlock(node)) { + caretContainerNode = CaretContainer.insertBlock('p', node, before); + clientRect = getAbsoluteClientRect(node, before); + DomQuery(caretContainerNode).css('top', clientRect.top); - $lastVisualCaret = $('').css(clientRect).appendTo(rootNode); + $lastVisualCaret = DomQuery('').css(clientRect).appendTo(rootNode); - if (before) { - $lastVisualCaret.addClass('mce-visual-caret-before'); - } + if (before) { + $lastVisualCaret.addClass('mce-visual-caret-before'); + } - startBlink(); + startBlink(); - rng = node.ownerDocument.createRange(); - rng.setStart(caretContainerNode, 0); - rng.setEnd(caretContainerNode, 0); - } else { - caretContainerNode = CaretContainer.insertInline(node, before); - rng = node.ownerDocument.createRange(); + rng = node.ownerDocument.createRange(); + rng.setStart(caretContainerNode, 0); + rng.setEnd(caretContainerNode, 0); + } else { + caretContainerNode = CaretContainer.insertInline(node, before); + rng = node.ownerDocument.createRange(); - if (isContentEditableFalse(caretContainerNode.nextSibling)) { - rng.setStart(caretContainerNode, 0); - rng.setEnd(caretContainerNode, 0); - } else { - rng.setStart(caretContainerNode, 1); - rng.setEnd(caretContainerNode, 1); - } + if (isContentEditableFalse(caretContainerNode.nextSibling)) { + rng.setStart(caretContainerNode, 0); + rng.setEnd(caretContainerNode, 0); + } else { + rng.setStart(caretContainerNode, 1); + rng.setEnd(caretContainerNode, 1); + } - return rng; - } + return rng; + } - return rng; - } + return rng; + } - function hide() { - trimInlineCaretContainers(); + function hide() { + trimInlineCaretContainers(); - if (caretContainerNode) { - CaretContainer.remove(caretContainerNode); - caretContainerNode = null; - } + if (caretContainerNode) { + CaretContainerRemove.remove(caretContainerNode); + caretContainerNode = null; + } - if ($lastVisualCaret) { - $lastVisualCaret.remove(); - $lastVisualCaret = null; - } + if ($lastVisualCaret) { + $lastVisualCaret.remove(); + $lastVisualCaret = null; + } - clearInterval(cursorInterval); - } + clearInterval(cursorInterval); + } - function startBlink() { - cursorInterval = Delay.setInterval(function() { - $('div.mce-visual-caret', rootNode).toggleClass('mce-visual-caret-hidden'); - }, 500); - } + function startBlink() { + cursorInterval = Delay.setInterval(function () { + DomQuery('div.mce-visual-caret', rootNode).toggleClass('mce-visual-caret-hidden'); + }, 500); + } - function destroy() { - Delay.clearInterval(cursorInterval); - } + function destroy() { + Delay.clearInterval(cursorInterval); + } - function getCss() { - return ( - '.mce-visual-caret {' + - 'position: absolute;' + - 'background-color: black;' + - 'background-color: currentcolor;' + - '}' + - '.mce-visual-caret-hidden {' + - 'display: none;' + - '}' + - '*[data-mce-caret] {' + - 'position: absolute;' + - 'left: -1000px;' + - 'right: auto;' + - 'top: 0;' + - 'margin: 0;' + - 'padding: 0;' + - '}' - ); - } - - return { - show: show, - hide: hide, - getCss: getCss, - destroy: destroy - }; - }; -}); - -// Included from: js/tinymce/classes/dom/Dimensions.js + function getCss() { + return ( + '.mce-visual-caret {' + + 'position: absolute;' + + 'background-color: black;' + + 'background-color: currentcolor;' + + '}' + + '.mce-visual-caret-hidden {' + + 'display: none;' + + '}' + + '*[data-mce-caret] {' + + 'position: absolute;' + + 'left: -1000px;' + + 'right: auto;' + + 'top: 0;' + + 'margin: 0;' + + 'padding: 0;' + + '}' + ); + } + return { + show: show, + hide: hide, + getCss: getCss, + destroy: destroy + }; + }; + } +); /** * Dimensions.js * * Released under LGPL License. - * Copyright (c) 1999-2015 Ephox Corp. All rights reserved + * Copyright (c) 1999-2017 Ephox Corp. All rights reserved * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing @@ -36413,61 +40173,62 @@ define("tinymce/caret/FakeCaret", [ * @private * @class tinymce.dom.Dimensions */ -define("tinymce/dom/Dimensions", [ - "tinymce/util/Arr", - "tinymce/dom/NodeType", - "tinymce/geom/ClientRect" -], function(Arr, NodeType, ClientRect) { +define( + 'tinymce.core.dom.Dimensions', + [ + "tinymce.core.util.Arr", + "tinymce.core.dom.NodeType", + "tinymce.core.geom.ClientRect" + ], + function (Arr, NodeType, ClientRect) { - function getClientRects(node) { - function toArrayWithNode(clientRects) { - return Arr.map(clientRects, function(clientRect) { - clientRect = ClientRect.clone(clientRect); - clientRect.node = node; + function getClientRects(node) { + function toArrayWithNode(clientRects) { + return Arr.map(clientRects, function (clientRect) { + clientRect = ClientRect.clone(clientRect); + clientRect.node = node; - return clientRect; - }); - } + return clientRect; + }); + } - if (Arr.isArray(node)) { - return Arr.reduce(node, function(result, node) { - return result.concat(getClientRects(node)); - }, []); - } + if (Arr.isArray(node)) { + return Arr.reduce(node, function (result, node) { + return result.concat(getClientRects(node)); + }, []); + } - if (NodeType.isElement(node)) { - return toArrayWithNode(node.getClientRects()); - } + if (NodeType.isElement(node)) { + return toArrayWithNode(node.getClientRects()); + } - if (NodeType.isText(node)) { - var rng = node.ownerDocument.createRange(); + if (NodeType.isText(node)) { + var rng = node.ownerDocument.createRange(); - rng.setStart(node, 0); - rng.setEnd(node, node.data.length); + rng.setStart(node, 0); + rng.setEnd(node, node.data.length); - return toArrayWithNode(rng.getClientRects()); - } - } - - return { - /** - * Returns the client rects for a specific node. - * - * @method getClientRects - * @param {Array/DOMNode} node Node or array of nodes to get client rects on. - * @param {Array} Array of client rects with a extra node property. - */ - getClientRects: getClientRects - }; -}); - -// Included from: js/tinymce/classes/caret/LineWalker.js + return toArrayWithNode(rng.getClientRects()); + } + } + return { + /** + * Returns the client rects for a specific node. + * + * @method getClientRects + * @param {Array/DOMNode} node Node or array of nodes to get client rects on. + * @param {Array} Array of client rects with a extra node property. + */ + getClientRects: getClientRects + }; + } +); /** * LineWalker.js * * Released under LGPL License. - * Copyright (c) 1999-2015 Ephox Corp. All rights reserved + * Copyright (c) 1999-2017 Ephox Corp. All rights reserved * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing @@ -36480,163 +40241,164 @@ define("tinymce/dom/Dimensions", [ * @private * @class tinymce.caret.LineWalker */ -define("tinymce/caret/LineWalker", [ - "tinymce/util/Fun", - "tinymce/util/Arr", - "tinymce/dom/Dimensions", - "tinymce/caret/CaretCandidate", - "tinymce/caret/CaretUtils", - "tinymce/caret/CaretWalker", - "tinymce/caret/CaretPosition", - "tinymce/geom/ClientRect" -], function(Fun, Arr, Dimensions, CaretCandidate, CaretUtils, CaretWalker, CaretPosition, ClientRect) { - var curry = Fun.curry; +define( + 'tinymce.core.caret.LineWalker', + [ + "tinymce.core.util.Fun", + "tinymce.core.util.Arr", + "tinymce.core.dom.Dimensions", + "tinymce.core.caret.CaretCandidate", + "tinymce.core.caret.CaretUtils", + "tinymce.core.caret.CaretWalker", + "tinymce.core.caret.CaretPosition", + "tinymce.core.geom.ClientRect" + ], + function (Fun, Arr, Dimensions, CaretCandidate, CaretUtils, CaretWalker, CaretPosition, ClientRect) { + var curry = Fun.curry; - function findUntil(direction, rootNode, predicateFn, node) { - while ((node = CaretUtils.findNode(node, direction, CaretCandidate.isEditableCaretCandidate, rootNode))) { - if (predicateFn(node)) { - return; - } - } - } + function findUntil(direction, rootNode, predicateFn, node) { + while ((node = CaretUtils.findNode(node, direction, CaretCandidate.isEditableCaretCandidate, rootNode))) { + if (predicateFn(node)) { + return; + } + } + } - function walkUntil(direction, isAboveFn, isBeflowFn, rootNode, predicateFn, caretPosition) { - var line = 0, node, result = [], targetClientRect; + function walkUntil(direction, isAboveFn, isBeflowFn, rootNode, predicateFn, caretPosition) { + var line = 0, node, result = [], targetClientRect; - function add(node) { - var i, clientRect, clientRects; + function add(node) { + var i, clientRect, clientRects; - clientRects = Dimensions.getClientRects(node); - if (direction == -1) { - clientRects = clientRects.reverse(); - } + clientRects = Dimensions.getClientRects(node); + if (direction == -1) { + clientRects = clientRects.reverse(); + } - for (i = 0; i < clientRects.length; i++) { - clientRect = clientRects[i]; - if (isBeflowFn(clientRect, targetClientRect)) { - continue; - } + for (i = 0; i < clientRects.length; i++) { + clientRect = clientRects[i]; + if (isBeflowFn(clientRect, targetClientRect)) { + continue; + } - if (result.length > 0 && isAboveFn(clientRect, Arr.last(result))) { - line++; - } + if (result.length > 0 && isAboveFn(clientRect, Arr.last(result))) { + line++; + } - clientRect.line = line; + clientRect.line = line; - if (predicateFn(clientRect)) { - return true; - } + if (predicateFn(clientRect)) { + return true; + } - result.push(clientRect); - } - } + result.push(clientRect); + } + } - targetClientRect = Arr.last(caretPosition.getClientRects()); - if (!targetClientRect) { - return result; - } + targetClientRect = Arr.last(caretPosition.getClientRects()); + if (!targetClientRect) { + return result; + } - node = caretPosition.getNode(); - add(node); - findUntil(direction, rootNode, add, node); + node = caretPosition.getNode(); + add(node); + findUntil(direction, rootNode, add, node); - return result; - } + return result; + } - function aboveLineNumber(lineNumber, clientRect) { - return clientRect.line > lineNumber; - } + function aboveLineNumber(lineNumber, clientRect) { + return clientRect.line > lineNumber; + } - function isLine(lineNumber, clientRect) { - return clientRect.line === lineNumber; - } + function isLine(lineNumber, clientRect) { + return clientRect.line === lineNumber; + } - var upUntil = curry(walkUntil, -1, ClientRect.isAbove, ClientRect.isBelow); - var downUntil = curry(walkUntil, 1, ClientRect.isBelow, ClientRect.isAbove); + var upUntil = curry(walkUntil, -1, ClientRect.isAbove, ClientRect.isBelow); + var downUntil = curry(walkUntil, 1, ClientRect.isBelow, ClientRect.isAbove); - function positionsUntil(direction, rootNode, predicateFn, node) { - var caretWalker = new CaretWalker(rootNode), walkFn, isBelowFn, isAboveFn, - caretPosition, result = [], line = 0, clientRect, targetClientRect; + function positionsUntil(direction, rootNode, predicateFn, node) { + var caretWalker = new CaretWalker(rootNode), walkFn, isBelowFn, isAboveFn, + caretPosition, result = [], line = 0, clientRect, targetClientRect; - function getClientRect(caretPosition) { - if (direction == 1) { - return Arr.last(caretPosition.getClientRects()); - } + function getClientRect(caretPosition) { + if (direction == 1) { + return Arr.last(caretPosition.getClientRects()); + } - return Arr.last(caretPosition.getClientRects()); - } + return Arr.last(caretPosition.getClientRects()); + } - if (direction == 1) { - walkFn = caretWalker.next; - isBelowFn = ClientRect.isBelow; - isAboveFn = ClientRect.isAbove; - caretPosition = CaretPosition.after(node); - } else { - walkFn = caretWalker.prev; - isBelowFn = ClientRect.isAbove; - isAboveFn = ClientRect.isBelow; - caretPosition = CaretPosition.before(node); - } + if (direction == 1) { + walkFn = caretWalker.next; + isBelowFn = ClientRect.isBelow; + isAboveFn = ClientRect.isAbove; + caretPosition = CaretPosition.after(node); + } else { + walkFn = caretWalker.prev; + isBelowFn = ClientRect.isAbove; + isAboveFn = ClientRect.isBelow; + caretPosition = CaretPosition.before(node); + } - targetClientRect = getClientRect(caretPosition); + targetClientRect = getClientRect(caretPosition); - do { - if (!caretPosition.isVisible()) { - continue; - } + do { + if (!caretPosition.isVisible()) { + continue; + } - clientRect = getClientRect(caretPosition); + clientRect = getClientRect(caretPosition); - if (isAboveFn(clientRect, targetClientRect)) { - continue; - } + if (isAboveFn(clientRect, targetClientRect)) { + continue; + } - if (result.length > 0 && isBelowFn(clientRect, Arr.last(result))) { - line++; - } + if (result.length > 0 && isBelowFn(clientRect, Arr.last(result))) { + line++; + } - clientRect = ClientRect.clone(clientRect); - clientRect.position = caretPosition; - clientRect.line = line; + clientRect = ClientRect.clone(clientRect); + clientRect.position = caretPosition; + clientRect.line = line; - if (predicateFn(clientRect)) { - return result; - } + if (predicateFn(clientRect)) { + return result; + } - result.push(clientRect); - } while ((caretPosition = walkFn(caretPosition))); + result.push(clientRect); + } while ((caretPosition = walkFn(caretPosition))); - return result; - } + return result; + } - return { - upUntil: upUntil, - downUntil: downUntil, + return { + upUntil: upUntil, + downUntil: downUntil, - /** - * Find client rects with line and caret position until the predicate returns true. - * - * @method positionsUntil - * @param {Number} direction Direction forward/backward 1/-1. - * @param {DOMNode} rootNode Root node to walk within. - * @param {function} predicateFn Gets the client rect as it's input. - * @param {DOMNode} node Node to start walking from. - * @return {Array} Array of client rects with line and position properties. - */ - positionsUntil: positionsUntil, - - isAboveLine: curry(aboveLineNumber), - isLine: curry(isLine) - }; -}); - -// Included from: js/tinymce/classes/caret/LineUtils.js + /** + * Find client rects with line and caret position until the predicate returns true. + * + * @method positionsUntil + * @param {Number} direction Direction forward/backward 1/-1. + * @param {DOMNode} rootNode Root node to walk within. + * @param {function} predicateFn Gets the client rect as it's input. + * @param {DOMNode} node Node to start walking from. + * @return {Array} Array of client rects with line and position properties. + */ + positionsUntil: positionsUntil, + isAboveLine: curry(aboveLineNumber), + isLine: curry(isLine) + }; + } +); /** * LineUtils.js * * Released under LGPL License. - * Copyright (c) 1999-2015 Ephox Corp. All rights reserved + * Copyright (c) 1999-2017 Ephox Corp. All rights reserved * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing @@ -36648,133 +40410,134 @@ define("tinymce/caret/LineWalker", [ * @private * @class tinymce.caret.LineUtils */ -define("tinymce/caret/LineUtils", [ - "tinymce/util/Fun", - "tinymce/util/Arr", - "tinymce/dom/NodeType", - "tinymce/dom/Dimensions", - "tinymce/geom/ClientRect", - "tinymce/caret/CaretUtils", - "tinymce/caret/CaretCandidate" -], function(Fun, Arr, NodeType, Dimensions, ClientRect, CaretUtils, CaretCandidate) { - var isContentEditableFalse = NodeType.isContentEditableFalse, - findNode = CaretUtils.findNode, - curry = Fun.curry; +define( + 'tinymce.core.caret.LineUtils', + [ + "tinymce.core.util.Fun", + "tinymce.core.util.Arr", + "tinymce.core.dom.NodeType", + "tinymce.core.dom.Dimensions", + "tinymce.core.geom.ClientRect", + "tinymce.core.caret.CaretUtils", + "tinymce.core.caret.CaretCandidate" + ], + function (Fun, Arr, NodeType, Dimensions, ClientRect, CaretUtils, CaretCandidate) { + var isContentEditableFalse = NodeType.isContentEditableFalse, + findNode = CaretUtils.findNode, + curry = Fun.curry; - function distanceToRectLeft(clientRect, clientX) { - return Math.abs(clientRect.left - clientX); - } + function distanceToRectLeft(clientRect, clientX) { + return Math.abs(clientRect.left - clientX); + } - function distanceToRectRight(clientRect, clientX) { - return Math.abs(clientRect.right - clientX); - } + function distanceToRectRight(clientRect, clientX) { + return Math.abs(clientRect.right - clientX); + } - function findClosestClientRect(clientRects, clientX) { - function isInside(clientX, clientRect) { - return clientX >= clientRect.left && clientX <= clientRect.right; - } + function findClosestClientRect(clientRects, clientX) { + function isInside(clientX, clientRect) { + return clientX >= clientRect.left && clientX <= clientRect.right; + } - return Arr.reduce(clientRects, function(oldClientRect, clientRect) { - var oldDistance, newDistance; + return Arr.reduce(clientRects, function (oldClientRect, clientRect) { + var oldDistance, newDistance; - oldDistance = Math.min(distanceToRectLeft(oldClientRect, clientX), distanceToRectRight(oldClientRect, clientX)); - newDistance = Math.min(distanceToRectLeft(clientRect, clientX), distanceToRectRight(clientRect, clientX)); + oldDistance = Math.min(distanceToRectLeft(oldClientRect, clientX), distanceToRectRight(oldClientRect, clientX)); + newDistance = Math.min(distanceToRectLeft(clientRect, clientX), distanceToRectRight(clientRect, clientX)); - if (isInside(clientX, clientRect)) { - return clientRect; - } + if (isInside(clientX, clientRect)) { + return clientRect; + } - if (isInside(clientX, oldClientRect)) { - return oldClientRect; - } + if (isInside(clientX, oldClientRect)) { + return oldClientRect; + } - // cE=false has higher priority - if (newDistance == oldDistance && isContentEditableFalse(clientRect.node)) { - return clientRect; - } + // cE=false has higher priority + if (newDistance == oldDistance && isContentEditableFalse(clientRect.node)) { + return clientRect; + } - if (newDistance < oldDistance) { - return clientRect; - } + if (newDistance < oldDistance) { + return clientRect; + } - return oldClientRect; - }); - } + return oldClientRect; + }); + } - function walkUntil(direction, rootNode, predicateFn, node) { - while ((node = findNode(node, direction, CaretCandidate.isEditableCaretCandidate, rootNode))) { - if (predicateFn(node)) { - return; - } - } - } + function walkUntil(direction, rootNode, predicateFn, node) { + while ((node = findNode(node, direction, CaretCandidate.isEditableCaretCandidate, rootNode))) { + if (predicateFn(node)) { + return; + } + } + } - function findLineNodeRects(rootNode, targetNodeRect) { - var clientRects = []; + function findLineNodeRects(rootNode, targetNodeRect) { + var clientRects = []; - function collect(checkPosFn, node) { - var lineRects; + function collect(checkPosFn, node) { + var lineRects; - lineRects = Arr.filter(Dimensions.getClientRects(node), function(clientRect) { - return !checkPosFn(clientRect, targetNodeRect); - }); + lineRects = Arr.filter(Dimensions.getClientRects(node), function (clientRect) { + return !checkPosFn(clientRect, targetNodeRect); + }); - clientRects = clientRects.concat(lineRects); + clientRects = clientRects.concat(lineRects); - return lineRects.length === 0; - } + return lineRects.length === 0; + } - clientRects.push(targetNodeRect); - walkUntil(-1, rootNode, curry(collect, ClientRect.isAbove), targetNodeRect.node); - walkUntil(1, rootNode, curry(collect, ClientRect.isBelow), targetNodeRect.node); + clientRects.push(targetNodeRect); + walkUntil(-1, rootNode, curry(collect, ClientRect.isAbove), targetNodeRect.node); + walkUntil(1, rootNode, curry(collect, ClientRect.isBelow), targetNodeRect.node); - return clientRects; - } + return clientRects; + } - function getContentEditableFalseChildren(rootNode) { - return Arr.filter(Arr.toArray(rootNode.getElementsByTagName('*')), isContentEditableFalse); - } + function getContentEditableFalseChildren(rootNode) { + return Arr.filter(Arr.toArray(rootNode.getElementsByTagName('*')), isContentEditableFalse); + } - function caretInfo(clientRect, clientX) { - return { - node: clientRect.node, - before: distanceToRectLeft(clientRect, clientX) < distanceToRectRight(clientRect, clientX) - }; - } + function caretInfo(clientRect, clientX) { + return { + node: clientRect.node, + before: distanceToRectLeft(clientRect, clientX) < distanceToRectRight(clientRect, clientX) + }; + } - function closestCaret(rootNode, clientX, clientY) { - var contentEditableFalseNodeRects, closestNodeRect; + function closestCaret(rootNode, clientX, clientY) { + var contentEditableFalseNodeRects, closestNodeRect; - contentEditableFalseNodeRects = Dimensions.getClientRects(getContentEditableFalseChildren(rootNode)); - contentEditableFalseNodeRects = Arr.filter(contentEditableFalseNodeRects, function(clientRect) { - return clientY >= clientRect.top && clientY <= clientRect.bottom; - }); + contentEditableFalseNodeRects = Dimensions.getClientRects(getContentEditableFalseChildren(rootNode)); + contentEditableFalseNodeRects = Arr.filter(contentEditableFalseNodeRects, function (clientRect) { + return clientY >= clientRect.top && clientY <= clientRect.bottom; + }); - closestNodeRect = findClosestClientRect(contentEditableFalseNodeRects, clientX); - if (closestNodeRect) { - closestNodeRect = findClosestClientRect(findLineNodeRects(rootNode, closestNodeRect), clientX); - if (closestNodeRect && isContentEditableFalse(closestNodeRect.node)) { - return caretInfo(closestNodeRect, clientX); - } - } + closestNodeRect = findClosestClientRect(contentEditableFalseNodeRects, clientX); + if (closestNodeRect) { + closestNodeRect = findClosestClientRect(findLineNodeRects(rootNode, closestNodeRect), clientX); + if (closestNodeRect && isContentEditableFalse(closestNodeRect.node)) { + return caretInfo(closestNodeRect, clientX); + } + } - return null; - } - - return { - findClosestClientRect: findClosestClientRect, - findLineNodeRects: findLineNodeRects, - closestCaret: closestCaret - }; -}); - -// Included from: js/tinymce/classes/dom/MousePosition.js + return null; + } + return { + findClosestClientRect: findClosestClientRect, + findLineNodeRects: findLineNodeRects, + closestCaret: closestCaret + }; + } +); /** * MousePosition.js * * Released under LGPL License. - * Copyright (c) 1999-2016 Ephox Corp. All rights reserved + * Copyright (c) 1999-2017 Ephox Corp. All rights reserved * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing @@ -36786,79 +40549,81 @@ define("tinymce/caret/LineUtils", [ * @private * @class tinymce.dom.MousePosition */ -define("tinymce/dom/MousePosition", [ -], function() { - var getAbsolutePosition = function (elm) { - var doc, docElem, win, clientRect; +define( + 'tinymce.core.dom.MousePosition', + [ + ], + function () { + var getAbsolutePosition = function (elm) { + var doc, docElem, win, clientRect; - clientRect = elm.getBoundingClientRect(); - doc = elm.ownerDocument; - docElem = doc.documentElement; - win = doc.defaultView; + clientRect = elm.getBoundingClientRect(); + doc = elm.ownerDocument; + docElem = doc.documentElement; + win = doc.defaultView; - return { - top: clientRect.top + win.pageYOffset - docElem.clientTop, - left: clientRect.left + win.pageXOffset - docElem.clientLeft - }; - }; + return { + top: clientRect.top + win.pageYOffset - docElem.clientTop, + left: clientRect.left + win.pageXOffset - docElem.clientLeft + }; + }; - var getBodyPosition = function (editor) { - return editor.inline ? getAbsolutePosition(editor.getBody()) : {left: 0, top: 0}; - }; + var getBodyPosition = function (editor) { + return editor.inline ? getAbsolutePosition(editor.getBody()) : { left: 0, top: 0 }; + }; - var getScrollPosition = function (editor) { - var body = editor.getBody(); - return editor.inline ? {left: body.scrollLeft, top: body.scrollTop} : {left: 0, top: 0}; - }; + var getScrollPosition = function (editor) { + var body = editor.getBody(); + return editor.inline ? { left: body.scrollLeft, top: body.scrollTop } : { left: 0, top: 0 }; + }; - var getBodyScroll = function (editor) { - var body = editor.getBody(), docElm = editor.getDoc().documentElement; - var inlineScroll = {left: body.scrollLeft, top: body.scrollTop}; - var iframeScroll = {left: body.scrollLeft || docElm.scrollLeft, top: body.scrollTop || docElm.scrollTop}; + var getBodyScroll = function (editor) { + var body = editor.getBody(), docElm = editor.getDoc().documentElement; + var inlineScroll = { left: body.scrollLeft, top: body.scrollTop }; + var iframeScroll = { left: body.scrollLeft || docElm.scrollLeft, top: body.scrollTop || docElm.scrollTop }; - return editor.inline ? inlineScroll : iframeScroll; - }; + return editor.inline ? inlineScroll : iframeScroll; + }; - var getMousePosition = function (editor, event) { - if (event.target.ownerDocument !== editor.getDoc()) { - var iframePosition = getAbsolutePosition(editor.getContentAreaContainer()); - var scrollPosition = getBodyScroll(editor); + var getMousePosition = function (editor, event) { + if (event.target.ownerDocument !== editor.getDoc()) { + var iframePosition = getAbsolutePosition(editor.getContentAreaContainer()); + var scrollPosition = getBodyScroll(editor); - return { - left: event.pageX - iframePosition.left + scrollPosition.left, - top: event.pageY - iframePosition.top + scrollPosition.top - }; - } + return { + left: event.pageX - iframePosition.left + scrollPosition.left, + top: event.pageY - iframePosition.top + scrollPosition.top + }; + } - return { - left: event.pageX, - top: event.pageY - }; - }; + return { + left: event.pageX, + top: event.pageY + }; + }; - var calculatePosition = function (bodyPosition, scrollPosition, mousePosition) { - return { - pageX: (mousePosition.left - bodyPosition.left) + scrollPosition.left, - pageY: (mousePosition.top - bodyPosition.top) + scrollPosition.top - }; - }; + var calculatePosition = function (bodyPosition, scrollPosition, mousePosition) { + return { + pageX: (mousePosition.left - bodyPosition.left) + scrollPosition.left, + pageY: (mousePosition.top - bodyPosition.top) + scrollPosition.top + }; + }; - var calc = function (editor, event) { - return calculatePosition(getBodyPosition(editor), getScrollPosition(editor), getMousePosition(editor, event)); - }; + var calc = function (editor, event) { + return calculatePosition(getBodyPosition(editor), getScrollPosition(editor), getMousePosition(editor, event)); + }; - return { - calc: calc - }; -}); - -// Included from: js/tinymce/classes/DragDropOverrides.js + return { + calc: calc + }; + } +); /** * DragDropOverrides.js * * Released under LGPL License. - * Copyright (c) 1999-2015 Ephox Corp. All rights reserved + * Copyright (c) 1999-2017 Ephox Corp. All rights reserved * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing @@ -36870,277 +40635,279 @@ define("tinymce/dom/MousePosition", [ * @private * @class tinymce.DragDropOverrides */ -define("tinymce/DragDropOverrides", [ - "tinymce/dom/NodeType", - "tinymce/util/Arr", - "tinymce/util/Fun", - "tinymce/util/Delay", - "tinymce/dom/DOMUtils", - "tinymce/dom/MousePosition" -], function( - NodeType, Arr, Fun, Delay, DOMUtils, MousePosition -) { - var isContentEditableFalse = NodeType.isContentEditableFalse, - isContentEditableTrue = NodeType.isContentEditableTrue; +define( + 'tinymce.core.DragDropOverrides', + [ + "tinymce.core.dom.NodeType", + "tinymce.core.util.Arr", + "tinymce.core.util.Fun", + "tinymce.core.util.Delay", + "tinymce.core.dom.DOMUtils", + "tinymce.core.dom.MousePosition" + ], + function ( + NodeType, Arr, Fun, Delay, DOMUtils, MousePosition + ) { + var isContentEditableFalse = NodeType.isContentEditableFalse, + isContentEditableTrue = NodeType.isContentEditableTrue; - var isDraggable = function (rootElm, elm) { - return isContentEditableFalse(elm) && elm !== rootElm; - }; + var isDraggable = function (rootElm, elm) { + return isContentEditableFalse(elm) && elm !== rootElm; + }; - var isValidDropTarget = function (editor, targetElement, dragElement) { - if (targetElement === dragElement || editor.dom.isChildOf(targetElement, dragElement)) { - return false; - } + var isValidDropTarget = function (editor, targetElement, dragElement) { + if (targetElement === dragElement || editor.dom.isChildOf(targetElement, dragElement)) { + return false; + } - if (isContentEditableFalse(targetElement)) { - return false; - } + if (isContentEditableFalse(targetElement)) { + return false; + } - return true; - }; + return true; + }; - var cloneElement = function (elm) { - var cloneElm = elm.cloneNode(true); - cloneElm.removeAttribute('data-mce-selected'); - return cloneElm; - }; + var cloneElement = function (elm) { + var cloneElm = elm.cloneNode(true); + cloneElm.removeAttribute('data-mce-selected'); + return cloneElm; + }; - var createGhost = function (editor, elm, width, height) { - var clonedElm = elm.cloneNode(true); + var createGhost = function (editor, elm, width, height) { + var clonedElm = elm.cloneNode(true); - editor.dom.setStyles(clonedElm, {width: width, height: height}); - editor.dom.setAttrib(clonedElm, 'data-mce-selected', null); + editor.dom.setStyles(clonedElm, { width: width, height: height }); + editor.dom.setAttrib(clonedElm, 'data-mce-selected', null); - var ghostElm = editor.dom.create('div', { - 'class': 'mce-drag-container', - 'data-mce-bogus': 'all', - unselectable: 'on', - contenteditable: 'false' - }); + var ghostElm = editor.dom.create('div', { + 'class': 'mce-drag-container', + 'data-mce-bogus': 'all', + unselectable: 'on', + contenteditable: 'false' + }); - editor.dom.setStyles(ghostElm, { - position: 'absolute', - opacity: 0.5, - overflow: 'hidden', - border: 0, - padding: 0, - margin: 0, - width: width, - height: height - }); + editor.dom.setStyles(ghostElm, { + position: 'absolute', + opacity: 0.5, + overflow: 'hidden', + border: 0, + padding: 0, + margin: 0, + width: width, + height: height + }); - editor.dom.setStyles(clonedElm, { - margin: 0, - boxSizing: 'border-box' - }); + editor.dom.setStyles(clonedElm, { + margin: 0, + boxSizing: 'border-box' + }); - ghostElm.appendChild(clonedElm); + ghostElm.appendChild(clonedElm); - return ghostElm; - }; + return ghostElm; + }; - var appendGhostToBody = function (ghostElm, bodyElm) { - if (ghostElm.parentNode !== bodyElm) { - bodyElm.appendChild(ghostElm); - } - }; + var appendGhostToBody = function (ghostElm, bodyElm) { + if (ghostElm.parentNode !== bodyElm) { + bodyElm.appendChild(ghostElm); + } + }; - var moveGhost = function (ghostElm, position, width, height, maxX, maxY) { - var overflowX = 0, overflowY = 0; + var moveGhost = function (ghostElm, position, width, height, maxX, maxY) { + var overflowX = 0, overflowY = 0; - ghostElm.style.left = position.pageX + 'px'; - ghostElm.style.top = position.pageY + 'px'; + ghostElm.style.left = position.pageX + 'px'; + ghostElm.style.top = position.pageY + 'px'; - if (position.pageX + width > maxX) { - overflowX = (position.pageX + width) - maxX; - } + if (position.pageX + width > maxX) { + overflowX = (position.pageX + width) - maxX; + } - if (position.pageY + height > maxY) { - overflowY = (position.pageY + height) - maxY; - } + if (position.pageY + height > maxY) { + overflowY = (position.pageY + height) - maxY; + } - ghostElm.style.width = (width - overflowX) + 'px'; - ghostElm.style.height = (height - overflowY) + 'px'; - }; + ghostElm.style.width = (width - overflowX) + 'px'; + ghostElm.style.height = (height - overflowY) + 'px'; + }; - var removeElement = function (elm) { - if (elm && elm.parentNode) { - elm.parentNode.removeChild(elm); - } - }; + var removeElement = function (elm) { + if (elm && elm.parentNode) { + elm.parentNode.removeChild(elm); + } + }; - var isLeftMouseButtonPressed = function (e) { - return e.button === 0; - }; + var isLeftMouseButtonPressed = function (e) { + return e.button === 0; + }; - var hasDraggableElement = function (state) { - return state.element; - }; + var hasDraggableElement = function (state) { + return state.element; + }; - var applyRelPos = function (state, position) { - return { - pageX: position.pageX - state.relX, - pageY: position.pageY + 5 - }; - }; + var applyRelPos = function (state, position) { + return { + pageX: position.pageX - state.relX, + pageY: position.pageY + 5 + }; + }; - var start = function (state, editor) { - return function (e) { - if (isLeftMouseButtonPressed(e)) { - var ceElm = Arr.find(editor.dom.getParents(e.target), Fun.or(isContentEditableFalse, isContentEditableTrue)); + var start = function (state, editor) { + return function (e) { + if (isLeftMouseButtonPressed(e)) { + var ceElm = Arr.find(editor.dom.getParents(e.target), Fun.or(isContentEditableFalse, isContentEditableTrue)); - if (isDraggable(editor.getBody(), ceElm)) { - var elmPos = editor.dom.getPos(ceElm); - var bodyElm = editor.getBody(); - var docElm = editor.getDoc().documentElement; + if (isDraggable(editor.getBody(), ceElm)) { + var elmPos = editor.dom.getPos(ceElm); + var bodyElm = editor.getBody(); + var docElm = editor.getDoc().documentElement; - state.element = ceElm; - state.screenX = e.screenX; - state.screenY = e.screenY; - state.maxX = (editor.inline ? bodyElm.scrollWidth : docElm.offsetWidth) - 2; - state.maxY = (editor.inline ? bodyElm.scrollHeight : docElm.offsetHeight) - 2; - state.relX = e.pageX - elmPos.x; - state.relY = e.pageY - elmPos.y; - state.width = ceElm.offsetWidth; - state.height = ceElm.offsetHeight; - state.ghost = createGhost(editor, ceElm, state.width, state.height); - } - } - }; - }; + state.element = ceElm; + state.screenX = e.screenX; + state.screenY = e.screenY; + state.maxX = (editor.inline ? bodyElm.scrollWidth : docElm.offsetWidth) - 2; + state.maxY = (editor.inline ? bodyElm.scrollHeight : docElm.offsetHeight) - 2; + state.relX = e.pageX - elmPos.x; + state.relY = e.pageY - elmPos.y; + state.width = ceElm.offsetWidth; + state.height = ceElm.offsetHeight; + state.ghost = createGhost(editor, ceElm, state.width, state.height); + } + } + }; + }; - var move = function (state, editor) { - // Reduces laggy drag behavior on Gecko - var throttledPlaceCaretAt = Delay.throttle(function (clientX, clientY) { - editor._selectionOverrides.hideFakeCaret(); - editor.selection.placeCaretAt(clientX, clientY); - }, 0); + var move = function (state, editor) { + // Reduces laggy drag behavior on Gecko + var throttledPlaceCaretAt = Delay.throttle(function (clientX, clientY) { + editor._selectionOverrides.hideFakeCaret(); + editor.selection.placeCaretAt(clientX, clientY); + }, 0); - return function (e) { - var movement = Math.max(Math.abs(e.screenX - state.screenX), Math.abs(e.screenY - state.screenY)); + return function (e) { + var movement = Math.max(Math.abs(e.screenX - state.screenX), Math.abs(e.screenY - state.screenY)); - if (hasDraggableElement(state) && !state.dragging && movement > 10) { - var args = editor.fire('dragstart', {target: state.element}); - if (args.isDefaultPrevented()) { - return; - } + if (hasDraggableElement(state) && !state.dragging && movement > 10) { + var args = editor.fire('dragstart', { target: state.element }); + if (args.isDefaultPrevented()) { + return; + } - state.dragging = true; - editor.focus(); - } + state.dragging = true; + editor.focus(); + } - if (state.dragging) { - var targetPos = applyRelPos(state, MousePosition.calc(editor, e)); + if (state.dragging) { + var targetPos = applyRelPos(state, MousePosition.calc(editor, e)); - appendGhostToBody(state.ghost, editor.getBody()); - moveGhost(state.ghost, targetPos, state.width, state.height, state.maxX, state.maxY); + appendGhostToBody(state.ghost, editor.getBody()); + moveGhost(state.ghost, targetPos, state.width, state.height, state.maxX, state.maxY); - throttledPlaceCaretAt(e.clientX, e.clientY); - } - }; - }; + throttledPlaceCaretAt(e.clientX, e.clientY); + } + }; + }; - // Returns the raw element instead of the fake cE=false element - var getRawTarget = function (selection) { - var rng = selection.getSel().getRangeAt(0); - var startContainer = rng.startContainer; - return startContainer.nodeType === 3 ? startContainer.parentNode : startContainer; - }; + // Returns the raw element instead of the fake cE=false element + var getRawTarget = function (selection) { + var rng = selection.getSel().getRangeAt(0); + var startContainer = rng.startContainer; + return startContainer.nodeType === 3 ? startContainer.parentNode : startContainer; + }; - var drop = function (state, editor) { - return function (e) { - if (state.dragging) { - if (isValidDropTarget(editor, getRawTarget(editor.selection), state.element)) { - var targetClone = cloneElement(state.element); + var drop = function (state, editor) { + return function (e) { + if (state.dragging) { + if (isValidDropTarget(editor, getRawTarget(editor.selection), state.element)) { + var targetClone = cloneElement(state.element); - var args = editor.fire('drop', { - targetClone: targetClone, - clientX: e.clientX, - clientY: e.clientY - }); + var args = editor.fire('drop', { + targetClone: targetClone, + clientX: e.clientX, + clientY: e.clientY + }); - if (!args.isDefaultPrevented()) { - targetClone = args.targetClone; + if (!args.isDefaultPrevented()) { + targetClone = args.targetClone; - editor.undoManager.transact(function() { - removeElement(state.element); - editor.insertContent(editor.dom.getOuterHTML(targetClone)); - editor._selectionOverrides.hideFakeCaret(); - }); - } - } - } + editor.undoManager.transact(function () { + removeElement(state.element); + editor.insertContent(editor.dom.getOuterHTML(targetClone)); + editor._selectionOverrides.hideFakeCaret(); + }); + } + } + } - removeDragState(state); - }; - }; + removeDragState(state); + }; + }; - var stop = function (state, editor) { - return function () { - removeDragState(state); - if (state.dragging) { - editor.fire('dragend'); - } - }; - }; + var stop = function (state, editor) { + return function () { + removeDragState(state); + if (state.dragging) { + editor.fire('dragend'); + } + }; + }; - var removeDragState = function (state) { - state.dragging = false; - state.element = null; - removeElement(state.ghost); - }; + var removeDragState = function (state) { + state.dragging = false; + state.element = null; + removeElement(state.ghost); + }; - var bindFakeDragEvents = function (editor) { - var state = {}, pageDom, dragStartHandler, dragHandler, dropHandler, dragEndHandler, rootDocument; + var bindFakeDragEvents = function (editor) { + var state = {}, pageDom, dragStartHandler, dragHandler, dropHandler, dragEndHandler, rootDocument; - pageDom = DOMUtils.DOM; - rootDocument = document; - dragStartHandler = start(state, editor); - dragHandler = move(state, editor); - dropHandler = drop(state, editor); - dragEndHandler = stop(state, editor); + pageDom = DOMUtils.DOM; + rootDocument = document; + dragStartHandler = start(state, editor); + dragHandler = move(state, editor); + dropHandler = drop(state, editor); + dragEndHandler = stop(state, editor); - editor.on('mousedown', dragStartHandler); - editor.on('mousemove', dragHandler); - editor.on('mouseup', dropHandler); + editor.on('mousedown', dragStartHandler); + editor.on('mousemove', dragHandler); + editor.on('mouseup', dropHandler); - pageDom.bind(rootDocument, 'mousemove', dragHandler); - pageDom.bind(rootDocument, 'mouseup', dragEndHandler); + pageDom.bind(rootDocument, 'mousemove', dragHandler); + pageDom.bind(rootDocument, 'mouseup', dragEndHandler); - editor.on('remove', function () { - pageDom.unbind(rootDocument, 'mousemove', dragHandler); - pageDom.unbind(rootDocument, 'mouseup', dragEndHandler); - }); - }; + editor.on('remove', function () { + pageDom.unbind(rootDocument, 'mousemove', dragHandler); + pageDom.unbind(rootDocument, 'mouseup', dragEndHandler); + }); + }; - var blockIeDrop = function (editor) { - editor.on('drop', function(e) { - // FF doesn't pass out clientX/clientY for drop since this is for IE we just use null instead - var realTarget = typeof e.clientX !== 'undefined' ? editor.getDoc().elementFromPoint(e.clientX, e.clientY) : null; + var blockIeDrop = function (editor) { + editor.on('drop', function (e) { + // FF doesn't pass out clientX/clientY for drop since this is for IE we just use null instead + var realTarget = typeof e.clientX !== 'undefined' ? editor.getDoc().elementFromPoint(e.clientX, e.clientY) : null; - if (isContentEditableFalse(realTarget) || isContentEditableFalse(editor.dom.getContentEditableParent(realTarget))) { - e.preventDefault(); - } - }); - }; + if (isContentEditableFalse(realTarget) || isContentEditableFalse(editor.dom.getContentEditableParent(realTarget))) { + e.preventDefault(); + } + }); + }; - var init = function (editor) { - bindFakeDragEvents(editor); - blockIeDrop(editor); - }; + var init = function (editor) { + bindFakeDragEvents(editor); + blockIeDrop(editor); + }; - return { - init: init - }; -}); - -// Included from: js/tinymce/classes/SelectionOverrides.js + return { + init: init + }; + } +); /** * SelectionOverrides.js * * Released under LGPL License. - * Copyright (c) 1999-2015 Ephox Corp. All rights reserved + * Copyright (c) 1999-2017 Ephox Corp. All rights reserved * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing @@ -37159,1025 +40926,2940 @@ define("tinymce/DragDropOverrides", [ * @private * @class tinymce.SelectionOverrides */ -define("tinymce/SelectionOverrides", [ - "tinymce/Env", - "tinymce/caret/CaretWalker", - "tinymce/caret/CaretPosition", - "tinymce/caret/CaretContainer", - "tinymce/caret/CaretUtils", - "tinymce/caret/FakeCaret", - "tinymce/caret/LineWalker", - "tinymce/caret/LineUtils", - "tinymce/dom/NodeType", - "tinymce/dom/RangeUtils", - "tinymce/geom/ClientRect", - "tinymce/util/VK", - "tinymce/util/Fun", - "tinymce/util/Arr", - "tinymce/util/Delay", - "tinymce/DragDropOverrides" -], function( - Env, CaretWalker, CaretPosition, CaretContainer, CaretUtils, FakeCaret, LineWalker, - LineUtils, NodeType, RangeUtils, ClientRect, VK, Fun, Arr, Delay, DragDropOverrides +define( + 'tinymce.core.SelectionOverrides', + [ + "tinymce.core.Env", + "tinymce.core.caret.CaretWalker", + "tinymce.core.caret.CaretPosition", + "tinymce.core.caret.CaretContainer", + "tinymce.core.caret.CaretContainerRemove", + "tinymce.core.caret.CaretUtils", + "tinymce.core.caret.FakeCaret", + "tinymce.core.caret.LineWalker", + "tinymce.core.caret.LineUtils", + "tinymce.core.dom.NodeType", + "tinymce.core.dom.RangeUtils", + "tinymce.core.geom.ClientRect", + "tinymce.core.util.VK", + "tinymce.core.util.Fun", + "tinymce.core.util.Arr", + "tinymce.core.util.Delay", + "tinymce.core.DragDropOverrides" + ], + function ( + Env, CaretWalker, CaretPosition, CaretContainer, CaretContainerRemove, CaretUtils, FakeCaret, LineWalker, + LineUtils, NodeType, RangeUtils, ClientRect, VK, Fun, Arr, Delay, DragDropOverrides ) { - var curry = Fun.curry, - isContentEditableTrue = NodeType.isContentEditableTrue, - isContentEditableFalse = NodeType.isContentEditableFalse, - isElement = NodeType.isElement, - isAfterContentEditableFalse = CaretUtils.isAfterContentEditableFalse, - isBeforeContentEditableFalse = CaretUtils.isBeforeContentEditableFalse, - getSelectedNode = RangeUtils.getSelectedNode; - - function getVisualCaretPosition(walkFn, caretPosition) { - while ((caretPosition = walkFn(caretPosition))) { - if (caretPosition.isVisible()) { - return caretPosition; - } - } - - return caretPosition; - } - - function SelectionOverrides(editor) { - var rootNode = editor.getBody(), caretWalker = new CaretWalker(rootNode); - var getNextVisualCaretPosition = curry(getVisualCaretPosition, caretWalker.next); - var getPrevVisualCaretPosition = curry(getVisualCaretPosition, caretWalker.prev), - fakeCaret = new FakeCaret(editor.getBody(), isBlock), - realSelectionId = 'sel-' + editor.dom.uniqueId(), - selectedContentEditableNode, $ = editor.$; - - function isFakeSelectionElement(elm) { - return editor.dom.hasClass(elm, 'mce-offscreen-selection'); - } - - function getRealSelectionElement() { - var container = editor.dom.get(realSelectionId); - return container ? container.getElementsByTagName('*')[0] : container; - } - - function isBlock(node) { - return editor.dom.isBlock(node); - } - - function setRange(range) { - //console.log('setRange', range); - if (range) { - editor.selection.setRng(range); - } - } - - function getRange() { - return editor.selection.getRng(); - } - - function scrollIntoView(node, alignToTop) { - editor.selection.scrollIntoView(node, alignToTop); - } - - function showCaret(direction, node, before) { - var e; - - e = editor.fire('ShowCaret', { - target: node, - direction: direction, - before: before - }); - - if (e.isDefaultPrevented()) { - return null; - } - - scrollIntoView(node, direction === -1); - - return fakeCaret.show(before, node); - } - - function selectNode(node) { - var e; - - e = editor.fire('BeforeObjectSelected', {target: node}); - if (e.isDefaultPrevented()) { - return null; - } - - return getNodeRange(node); - } - - function getNodeRange(node) { - var rng = node.ownerDocument.createRange(); - - rng.selectNode(node); - - return rng; - } - - function isMoveInsideSameBlock(fromCaretPosition, toCaretPosition) { - var inSameBlock = CaretUtils.isInSameBlock(fromCaretPosition, toCaretPosition); - - // Handle bogus BRabc|
abc|
\u00a0
').append(targetClone); - range.setStartAfter($realSelectionContainer[0].firstChild.firstChild); - range.setEndAfter(targetClone); - } else { - $realSelectionContainer.empty().append('\u00a0').append(targetClone).append('\u00a0'); - range.setStart($realSelectionContainer[0].firstChild, 1); - range.setEnd($realSelectionContainer[0].lastChild, 0); - } - - $realSelectionContainer.css({ - top: dom.getPos(node, editor.getBody()).y - }); - - $realSelectionContainer[0].focus(); - sel = editor.selection.getSel(); - sel.removeAllRanges(); - sel.addRange(range); - - editor.$('*[data-mce-selected]').removeAttr('data-mce-selected'); - node.setAttribute('data-mce-selected', 1); - selectedContentEditableNode = node; - hideFakeCaret(); - - return range; - } - - function removeContentEditableSelection() { - if (selectedContentEditableNode) { - selectedContentEditableNode.removeAttribute('data-mce-selected'); - editor.$('#' + realSelectionId).remove(); - selectedContentEditableNode = null; - } - } - - function destroy() { - fakeCaret.destroy(); - selectedContentEditableNode = null; - } - - function hideFakeCaret() { - fakeCaret.hide(); - } - - if (Env.ceFalse) { - registerEvents(); - addCss(); - } - - return { - showBlockCaretContainer: showBlockCaretContainer, - hideFakeCaret: hideFakeCaret, - destroy: destroy - }; - } - - return SelectionOverrides; -}); - -// Included from: js/tinymce/classes/util/Uuid.js + if (rangeIsInContainerBlock) { + return renderRangeCaret(caretPosition.toRange()); + } + + return null; + } + + function moveToCeFalseVertically(direction, walkerFn, range) { + var caretPosition, linePositions, nextLinePositions, + closestNextLineRect, caretClientRect, clientX, + dist1, dist2, contentEditableFalseNode; + + contentEditableFalseNode = getSelectedNode(range); + caretPosition = getNormalizedRangeEndPoint(direction, range); + linePositions = walkerFn(rootNode, LineWalker.isAboveLine(1), caretPosition); + nextLinePositions = Arr.filter(linePositions, LineWalker.isLine(1)); + caretClientRect = Arr.last(caretPosition.getClientRects()); + + if (isBeforeContentEditableFalse(caretPosition)) { + contentEditableFalseNode = caretPosition.getNode(); + } + + if (isAfterContentEditableFalse(caretPosition)) { + contentEditableFalseNode = caretPosition.getNode(true); + } + + if (!caretClientRect) { + return null; + } + + clientX = caretClientRect.left; + + closestNextLineRect = LineUtils.findClosestClientRect(nextLinePositions, clientX); + if (closestNextLineRect) { + if (isContentEditableFalse(closestNextLineRect.node)) { + dist1 = Math.abs(clientX - closestNextLineRect.left); + dist2 = Math.abs(clientX - closestNextLineRect.right); + + return showCaret(direction, closestNextLineRect.node, dist1 < dist2); + } + } + + if (contentEditableFalseNode) { + var caretPositions = LineWalker.positionsUntil(direction, rootNode, LineWalker.isAboveLine(1), contentEditableFalseNode); + + closestNextLineRect = LineUtils.findClosestClientRect(Arr.filter(caretPositions, LineWalker.isLine(1)), clientX); + if (closestNextLineRect) { + return renderRangeCaret(closestNextLineRect.position.toRange()); + } + + closestNextLineRect = Arr.last(Arr.filter(caretPositions, LineWalker.isLine(0))); + if (closestNextLineRect) { + return renderRangeCaret(closestNextLineRect.position.toRange()); + } + } + } + + function exitPreBlock(direction, range) { + var pre, caretPos, newBlock; + + function createTextBlock() { + var textBlock = editor.dom.create(editor.settings.forced_root_block); + + if (!Env.ie || Env.ie >= 11) { + textBlock.innerHTML = '\u00a0
').append(targetClone); + range.setStartAfter($realSelectionContainer[0].firstChild.firstChild); + range.setEndAfter(targetClone); + } else { + $realSelectionContainer.empty().append('\u00a0').append(targetClone).append('\u00a0'); + range.setStart($realSelectionContainer[0].firstChild, 1); + range.setEnd($realSelectionContainer[0].lastChild, 0); + } + + $realSelectionContainer.css({ + top: dom.getPos(node, editor.getBody()).y + }); + + $realSelectionContainer[0].focus(); + sel = editor.selection.getSel(); + sel.removeAllRanges(); + sel.addRange(range); + + editor.$('*[data-mce-selected]').removeAttr('data-mce-selected'); + node.setAttribute('data-mce-selected', 1); + selectedContentEditableNode = node; + hideFakeCaret(); + + return range; + } + + function removeContentEditableSelection() { + if (selectedContentEditableNode) { + selectedContentEditableNode.removeAttribute('data-mce-selected'); + editor.$('#' + realSelectionId).remove(); + selectedContentEditableNode = null; + } + } + + function destroy() { + fakeCaret.destroy(); + selectedContentEditableNode = null; + } + + function hideFakeCaret() { + fakeCaret.hide(); + } + + if (Env.ceFalse) { + registerEvents(); + addCss(); + } + + return { + showBlockCaretContainer: showBlockCaretContainer, + hideFakeCaret: hideFakeCaret, + destroy: destroy + }; + } + + return SelectionOverrides; + } +); /** - * Uuid.js + * NodePath.js * * Released under LGPL License. - * Copyright (c) 1999-2016 Ephox Corp. All rights reserved + * Copyright (c) 1999-2017 Ephox Corp. All rights reserved * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ /** - * Generates unique ids. + * Handles paths of nodes within an element. * - * @class tinymce.util.Uuid * @private + * @class tinymce.dom.NodePath */ -define("tinymce/util/Uuid", [ -], function() { - var count = 0; +define( + 'tinymce.core.dom.NodePath', + [ + "tinymce.core.dom.DOMUtils" + ], + function (DOMUtils) { + function create(rootNode, targetNode, normalized) { + var path = []; - var seed = function () { - var rnd = function () { - return Math.round(Math.random() * 0xFFFFFFFF).toString(36); - }; + for (; targetNode && targetNode != rootNode; targetNode = targetNode.parentNode) { + path.push(DOMUtils.nodeIndex(targetNode, normalized)); + } - var now = new Date().getTime(); - return 's' + now.toString(36) + rnd() + rnd() + rnd(); - }; + return path; + } - var uuid = function (prefix) { - return prefix + (count++) + seed(); - }; + function resolve(rootNode, path) { + var i, node, children; - return { - uuid: uuid - }; -}); + for (node = rootNode, i = path.length - 1; i >= 0; i--) { + children = node.childNodes; -// Included from: js/tinymce/classes/ui/Sidebar.js + if (path[i] > children.length - 1) { + return null; + } + node = children[path[i]]; + } + + return node; + } + + return { + create: create, + resolve: resolve + }; + } +); +/** + * Quirks.js + * + * Released under LGPL License. + * Copyright (c) 1999-2017 Ephox Corp. All rights reserved + * + * License: http://www.tinymce.com/license + * Contributing: http://www.tinymce.com/contributing + * + * @ignore-file + */ + +/** + * This file includes fixes for various browser quirks it's made to make it easy to add/remove browser specific fixes. + * + * @private + * @class tinymce.util.Quirks + */ +define( + 'tinymce.core.util.Quirks', + [ + "tinymce.core.util.VK", + "tinymce.core.dom.RangeUtils", + "tinymce.core.dom.TreeWalker", + "tinymce.core.dom.NodePath", + "tinymce.core.html.Node", + "tinymce.core.html.Entities", + "tinymce.core.Env", + "tinymce.core.util.Tools", + "tinymce.core.util.Delay", + "tinymce.core.caret.CaretContainer", + "tinymce.core.caret.CaretPosition", + "tinymce.core.caret.CaretWalker" + ], + function (VK, RangeUtils, TreeWalker, NodePath, Node, Entities, Env, Tools, Delay, CaretContainer, CaretPosition, CaretWalker) { + return function (editor) { + var each = Tools.each; + var BACKSPACE = VK.BACKSPACE, DELETE = VK.DELETE, dom = editor.dom, selection = editor.selection, + settings = editor.settings, parser = editor.parser, serializer = editor.serializer; + var isGecko = Env.gecko, isIE = Env.ie, isWebKit = Env.webkit; + var mceInternalUrlPrefix = 'data:text/mce-internal,'; + var mceInternalDataType = isIE ? 'Text' : 'URL'; + + /** + * Executes a command with a specific state this can be to enable/disable browser editing features. + */ + function setEditorCommandState(cmd, state) { + try { + editor.getDoc().execCommand(cmd, false, state); + } catch (ex) { + // Ignore + } + } + + /** + * Returns current IE document mode. + */ + function getDocumentMode() { + var documentMode = editor.getDoc().documentMode; + + return documentMode ? documentMode : 6; + } + + /** + * Returns true/false if the event is prevented or not. + * + * @private + * @param {Event} e Event object. + * @return {Boolean} true/false if the event is prevented or not. + */ + function isDefaultPrevented(e) { + return e.isDefaultPrevented(); + } + + /** + * Sets Text/URL data on the event's dataTransfer object to a special data:text/mce-internal url. + * This is to workaround the inability to set custom contentType on IE and Safari. + * The editor's selected content is encoded into this url so drag and drop between editors will work. + * + * @private + * @param {DragEvent} e Event object + */ + function setMceInternalContent(e) { + var selectionHtml, internalContent; + + if (e.dataTransfer) { + if (editor.selection.isCollapsed() && e.target.tagName == 'IMG') { + selection.select(e.target); + } + + selectionHtml = editor.selection.getContent(); + + // Safari/IE doesn't support custom dataTransfer items so we can only use URL and Text + if (selectionHtml.length > 0) { + internalContent = mceInternalUrlPrefix + escape(editor.id) + ',' + escape(selectionHtml); + e.dataTransfer.setData(mceInternalDataType, internalContent); + } + } + } + + /** + * Gets content of special data:text/mce-internal url on the event's dataTransfer object. + * This is to workaround the inability to set custom contentType on IE and Safari. + * The editor's selected content is encoded into this url so drag and drop between editors will work. + * + * @private + * @param {DragEvent} e Event object + * @returns {String} mce-internal content + */ + function getMceInternalContent(e) { + var internalContent; + + if (e.dataTransfer) { + internalContent = e.dataTransfer.getData(mceInternalDataType); + + if (internalContent && internalContent.indexOf(mceInternalUrlPrefix) >= 0) { + internalContent = internalContent.substr(mceInternalUrlPrefix.length).split(','); + + return { + id: unescape(internalContent[0]), + html: unescape(internalContent[1]) + }; + } + } + + return null; + } + + /** + * Inserts contents using the paste clipboard command if it's available if it isn't it will fallback + * to the core command. + * + * @private + * @param {String} content Content to insert at selection. + * @param {Boolean} internal State if the paste is to be considered internal or external. + */ + function insertClipboardContents(content, internal) { + if (editor.queryCommandSupported('mceInsertClipboardContent')) { + editor.execCommand('mceInsertClipboardContent', false, { content: content, internal: internal }); + } else { + editor.execCommand('mceInsertContent', false, content); + } + } + + /** + * Makes sure that the editor body becomes empty when backspace or delete is pressed in empty editors. + * + * For example: + *|
+ * + * Or: + *|
+ if (!isDefaultPrevented(e) && (keyCode == DELETE || keyCode == BACKSPACE)) { + isCollapsed = editor.selection.isCollapsed(); + body = editor.getBody(); + + // Selection is collapsed but the editor isn't empty + if (isCollapsed && !dom.isEmpty(body)) { + return; + } + + // Selection isn't collapsed but not all the contents is selected + if (!isCollapsed && !allContentsSelected(editor.selection.getRng())) { + return; + } + + // Manually empty the editor + e.preventDefault(); + editor.setContent(''); + + if (body.firstChild && dom.isBlock(body.firstChild)) { + editor.selection.setCursorLocation(body.firstChild, 0); + } else { + editor.selection.setCursorLocation(body, 0); + } + + editor.nodeChanged(); + } + }); + } + + /** + * WebKit doesn't select all the nodes in the body when you press Ctrl+A. + * IE selects more than the contents [a
] instead of[a]
see bug #6438 + * This selects the whole body so that backspace/delete logic will delete everything + */ + function selectAll() { + editor.shortcuts.add('meta+a', null, 'SelectAll'); + } + + /** + * WebKit has a weird issue where it some times fails to properly convert keypresses to input method keystrokes. + * The IME on Mac doesn't initialize when it doesn't fire a proper focus event. + * + * This seems to happen when the user manages to click the documentElement element then the window doesn't get proper focus until + * you enter a character into the editor. + * + * It also happens when the first focus in made to the body. + * + * See: https://bugs.webkit.org/show_bug.cgi?id=83566 + */ + function inputMethodFocus() { + if (!editor.settings.content_editable) { + // Case 1 IME doesn't initialize if you focus the document + // Disabled since it was interferring with the cE=false logic + // Also coultn't reproduce the issue on Safari 9 + /*dom.bind(editor.getDoc(), 'focusin', function() { + selection.setRng(selection.getRng()); + });*/ + + // Case 2 IME doesn't initialize if you click the documentElement it also doesn't properly fire the focusin event + // Needs to be both down/up due to weird rendering bug on Chrome Windows + dom.bind(editor.getDoc(), 'mousedown mouseup', function (e) { + var rng; + + if (e.target == editor.getDoc().documentElement) { + rng = selection.getRng(); + editor.getBody().focus(); + + if (e.type == 'mousedown') { + if (CaretContainer.isCaretContainer(rng.startContainer)) { + return; + } + + // Edge case for mousedown, drag select and mousedown again within selection on Chrome Windows to render caret + selection.placeCaretAt(e.clientX, e.clientY); + } else { + selection.setRng(rng); + } + } + }); + } + } + + /** + * Backspacing in FireFox/IE from a paragraph into a horizontal rule results in a floating text node because the + * browser just deletes the paragraph - the browser fails to merge the text node with a horizontal rule so it is + * left there. TinyMCE sees a floating text node and wraps it in a paragraph on the key up event (ForceBlocks.js + * addRootBlocks), meaning the action does nothing. With this code, FireFox/IE matche the behaviour of other + * browsers. + * + * It also fixes a bug on Firefox where it's impossible to delete HR elements. + */ + function removeHrOnBackspace() { + editor.on('keydown', function (e) { + if (!isDefaultPrevented(e) && e.keyCode === BACKSPACE) { + // Check if there is any HR elements this is faster since getRng on IE 7 & 8 is slow + if (!editor.getBody().getElementsByTagName('hr').length) { + return; + } + + if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) { + var node = selection.getNode(); + var previousSibling = node.previousSibling; + + if (node.nodeName == 'HR') { + dom.remove(node); + e.preventDefault(); + return; + } + + if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "hr") { + dom.remove(previousSibling); + e.preventDefault(); + } + } + } + }); + } + + /** + * Firefox 3.x has an issue where the body element won't get proper focus if you click out + * side it's rectangle. + */ + function focusBody() { + // Fix for a focus bug in FF 3.x where the body element + // wouldn't get proper focus if the user clicked on the HTML element + if (!window.Range.prototype.getClientRects) { // Detect getClientRects got introduced in FF 4 + editor.on('mousedown', function (e) { + if (!isDefaultPrevented(e) && e.target.nodeName === "HTML") { + var body = editor.getBody(); + + // Blur the body it's focused but not correctly focused + body.blur(); + + // Refocus the body after a little while + Delay.setEditorTimeout(editor, function () { + body.focus(); + }); + } + }); + } + } + + /** + * WebKit has a bug where it isn't possible to select image, hr or anchor elements + * by clicking on them so we need to fake that. + */ + function selectControlElements() { + editor.on('click', function (e) { + var target = e.target; + + // Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250 + // WebKit can't even do simple things like selecting an image + // Needs to be the setBaseAndExtend or it will fail to select floated images + if (/^(IMG|HR)$/.test(target.nodeName) && dom.getContentEditableParent(target) !== "false") { + e.preventDefault(); + editor.selection.select(target); + editor.nodeChanged(); + } + + if (target.nodeName == 'A' && dom.hasClass(target, 'mce-item-anchor')) { + e.preventDefault(); + selection.select(target); + } + }); + } + + /** + * Fixes a Gecko bug where the style attribute gets added to the wrong element when deleting between two block elements. + * + * Fixes do backspace/delete on this: + *bla[ck
r]ed
+ * + * Would become: + *bla|ed
+ * + * Instead of: + *bla|ed
+ */ + function removeStylesWhenDeletingAcrossBlockElements() { + function getAttributeApplyFunction() { + var template = dom.getAttribs(selection.getStart().cloneNode(false)); + + return function () { + var target = selection.getStart(); + + if (target !== editor.getBody()) { + dom.setAttrib(target, "style", null); + + each(template, function (attr) { + target.setAttributeNode(attr.cloneNode(true)); + }); + } + }; + } + + function isSelectionAcrossElements() { + return !selection.isCollapsed() && + dom.getParent(selection.getStart(), dom.isBlock) != dom.getParent(selection.getEnd(), dom.isBlock); + } + + editor.on('keypress', function (e) { + var applyAttributes; + + if (!isDefaultPrevented(e) && (e.keyCode == 8 || e.keyCode == 46) && isSelectionAcrossElements()) { + applyAttributes = getAttributeApplyFunction(); + editor.getDoc().execCommand('delete', false, null); + applyAttributes(); + e.preventDefault(); + return false; + } + }); + + dom.bind(editor.getDoc(), 'cut', function (e) { + var applyAttributes; + + if (!isDefaultPrevented(e) && isSelectionAcrossElements()) { + applyAttributes = getAttributeApplyFunction(); + + Delay.setEditorTimeout(editor, function () { + applyAttributes(); + }); + } + }); + } + + /** + * Screen readers on IE needs to have the role application set on the body. + */ + function ensureBodyHasRoleApplication() { + document.body.setAttribute("role", "application"); + } + + /** + * Backspacing into a table behaves differently depending upon browser type. + * Therefore, disable Backspace when cursor immediately follows a table. + */ + function disableBackspaceIntoATable() { + editor.on('keydown', function (e) { + if (!isDefaultPrevented(e) && e.keyCode === BACKSPACE) { + if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) { + var previousSibling = selection.getNode().previousSibling; + if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "table") { + e.preventDefault(); + return false; + } + } + } + }); + } + + /** + * Old IE versions can't properly render BR elements in PRE tags white in contentEditable mode. So this + * logic adds a \n before the BR so that it will get rendered. + */ + function addNewLinesBeforeBrInPre() { + // IE8+ rendering mode does the right thing with BR in PRE + if (getDocumentMode() > 7) { + return; + } + + // Enable display: none in area and add a specific class that hides all BR elements in PRE to + // avoid the caret from getting stuck at the BR elements while pressing the right arrow key + setEditorCommandState('RespectVisibilityInDesign', true); + editor.contentStyles.push('.mceHideBrInPre pre br {display: none}'); + dom.addClass(editor.getBody(), 'mceHideBrInPre'); + + // Adds a \n before all BR elements in PRE to get them visual + parser.addNodeFilter('pre', function (nodes) { + var i = nodes.length, brNodes, j, brElm, sibling; + + while (i--) { + brNodes = nodes[i].getAll('br'); + j = brNodes.length; + while (j--) { + brElm = brNodes[j]; + + // Add \n before BR in PRE elements on older IE:s so the new lines get rendered + sibling = brElm.prev; + if (sibling && sibling.type === 3 && sibling.value.charAt(sibling.value - 1) != '\n') { + sibling.value += '\n'; + } else { + brElm.parent.insert(new Node('#text', 3), brElm, true).value = '\n'; + } + } + } + }); + + // Removes any \n before BR elements in PRE since other browsers and in contentEditable=false mode they will be visible + serializer.addNodeFilter('pre', function (nodes) { + var i = nodes.length, brNodes, j, brElm, sibling; + + while (i--) { + brNodes = nodes[i].getAll('br'); + j = brNodes.length; + while (j--) { + brElm = brNodes[j]; + sibling = brElm.prev; + if (sibling && sibling.type == 3) { + sibling.value = sibling.value.replace(/\r?\n$/, ''); + } + } + } + }); + } + + /** + * Moves style width/height to attribute width/height when the user resizes an image on IE. + */ + function removePreSerializedStylesWhenSelectingControls() { + dom.bind(editor.getBody(), 'mouseup', function () { + var value, node = selection.getNode(); + + // Moved styles to attributes on IMG eements + if (node.nodeName == 'IMG') { + // Convert style width to width attribute + if ((value = dom.getStyle(node, 'width'))) { + dom.setAttrib(node, 'width', value.replace(/[^0-9%]+/g, '')); + dom.setStyle(node, 'width', ''); + } + + // Convert style height to height attribute + if ((value = dom.getStyle(node, 'height'))) { + dom.setAttrib(node, 'height', value.replace(/[^0-9%]+/g, '')); + dom.setStyle(node, 'height', ''); + } + } + }); + } + + /** + * Removes a blockquote when backspace is pressed at the beginning of it. + * + * For example: + *+ * + * Becomes: + *|x
|x
+ */ + function removeBlockQuoteOnBackSpace() { + // Add block quote deletion handler + editor.on('keydown', function (e) { + var rng, container, offset, root, parent; + + if (isDefaultPrevented(e) || e.keyCode != VK.BACKSPACE) { + return; + } + + rng = selection.getRng(); + container = rng.startContainer; + offset = rng.startOffset; + root = dom.getRoot(); + parent = container; + + if (!rng.collapsed || offset !== 0) { + return; + } + + while (parent && parent.parentNode && parent.parentNode.firstChild == parent && parent.parentNode != root) { + parent = parent.parentNode; + } + + // Is the cursor at the beginning of a blockquote? + if (parent.tagName === 'BLOCKQUOTE') { + // Remove the blockquote + editor.formatter.toggle('blockquote', null, parent); + + // Move the caret to the beginning of container + rng = dom.createRng(); + rng.setStart(container, 0); + rng.setEnd(container, 0); + selection.setRng(rng); + } + }); + } + + /** + * Sets various Gecko editing options on mouse down and before a execCommand to disable inline table editing that is broken etc. + */ + function setGeckoEditingOptions() { + function setOpts() { + refreshContentEditable(); + + setEditorCommandState("StyleWithCSS", false); + setEditorCommandState("enableInlineTableEditing", false); + + if (!settings.object_resizing) { + setEditorCommandState("enableObjectResizing", false); + } + } + + if (!settings.readonly) { + editor.on('BeforeExecCommand MouseDown', setOpts); + } + } + + /** + * Fixes a gecko link bug, when a link is placed at the end of block elements there is + * no way to move the caret behind the link. This fix adds a bogus br element after the link. + * + * For example this: + * + * + * Becomes this: + * + */ + function addBrAfterLastLinks() { + function fixLinks() { + each(dom.select('a'), function (node) { + var parentNode = node.parentNode, root = dom.getRoot(); + + if (parentNode.lastChild === node) { + while (parentNode && !dom.isBlock(parentNode)) { + if (parentNode.parentNode.lastChild !== parentNode || parentNode === root) { + return; + } + + parentNode = parentNode.parentNode; + } + + dom.add(parentNode, 'br', { 'data-mce-bogus': 1 }); + } + }); + } + + editor.on('SetContent ExecCommand', function (e) { + if (e.type == "setcontent" || e.command === 'mceInsertLink') { + fixLinks(); + } + }); + } + + /** + * WebKit will produce DIV elements here and there by default. But since TinyMCE uses paragraphs by + * default we want to change that behavior. + */ + function setDefaultBlockType() { + if (settings.forced_root_block) { + editor.on('init', function () { + setEditorCommandState('DefaultParagraphSeparator', settings.forced_root_block); + }); + } + } + + /** + * Deletes the selected image on IE instead of navigating to previous page. + */ + function deleteControlItemOnBackSpace() { + editor.on('keydown', function (e) { + var rng; + + if (!isDefaultPrevented(e) && e.keyCode == BACKSPACE) { + rng = editor.getDoc().selection.createRange(); + if (rng && rng.item) { + e.preventDefault(); + editor.undoManager.beforeChange(); + dom.remove(rng.item(0)); + editor.undoManager.add(); + } + } + }); + } + + /** + * IE10 doesn't properly render block elements with the right height until you add contents to them. + * This fixes that by adding a padding-right to all empty text block elements. + * See: https://connect.microsoft.com/IE/feedback/details/743881 + */ + function renderEmptyBlocksFix() { + var emptyBlocksCSS; + + // IE10+ + if (getDocumentMode() >= 10) { + emptyBlocksCSS = ''; + each('p div h1 h2 h3 h4 h5 h6'.split(' '), function (name, i) { + emptyBlocksCSS += (i > 0 ? ',' : '') + name + ':empty'; + }); + + editor.contentStyles.push(emptyBlocksCSS + '{padding-right: 1px !important}'); + } + } + + /** + * Old IE versions can't retain contents within noscript elements so this logic will store the contents + * as a attribute and the insert that value as it's raw text when the DOM is serialized. + */ + function keepNoScriptContents() { + if (getDocumentMode() < 9) { + parser.addNodeFilter('noscript', function (nodes) { + var i = nodes.length, node, textNode; + + while (i--) { + node = nodes[i]; + textNode = node.firstChild; + + if (textNode) { + node.attr('data-mce-innertext', textNode.value); + } + } + }); + + serializer.addNodeFilter('noscript', function (nodes) { + var i = nodes.length, node, textNode, value; + + while (i--) { + node = nodes[i]; + textNode = nodes[i].firstChild; + + if (textNode) { + textNode.value = Entities.decode(textNode.value); + } else { + // Old IE can't retain noscript value so an attribute is used to store it + value = node.attributes.map['data-mce-innertext']; + if (value) { + node.attr('data-mce-innertext', null); + textNode = new Node('#text', 3); + textNode.value = value; + textNode.raw = true; + node.append(textNode); + } + } + } + }); + } + } + + /** + * IE has an issue where you can't select/move the caret by clicking outside the body if the document is in standards mode. + */ + function fixCaretSelectionOfDocumentElementOnIe() { + var doc = dom.doc, body = doc.body, started, startRng, htmlElm; + + // Return range from point or null if it failed + function rngFromPoint(x, y) { + var rng = body.createTextRange(); + + try { + rng.moveToPoint(x, y); + } catch (ex) { + // IE sometimes throws and exception, so lets just ignore it + rng = null; + } + + return rng; + } + + // Fires while the selection is changing + function selectionChange(e) { + var pointRng; + + // Check if the button is down or not + if (e.button) { + // Create range from mouse position + pointRng = rngFromPoint(e.x, e.y); + + if (pointRng) { + // Check if pointRange is before/after selection then change the endPoint + if (pointRng.compareEndPoints('StartToStart', startRng) > 0) { + pointRng.setEndPoint('StartToStart', startRng); + } else { + pointRng.setEndPoint('EndToEnd', startRng); + } + + pointRng.select(); + } + } else { + endSelection(); + } + } + + // Removes listeners + function endSelection() { + var rng = doc.selection.createRange(); + + // If the range is collapsed then use the last start range + if (startRng && !rng.item && rng.compareEndPoints('StartToEnd', rng) === 0) { + startRng.select(); + } + + dom.unbind(doc, 'mouseup', endSelection); + dom.unbind(doc, 'mousemove', selectionChange); + startRng = started = 0; + } + + // Make HTML element unselectable since we are going to handle selection by hand + doc.documentElement.unselectable = true; + + // Detect when user selects outside BODY + dom.bind(doc, 'mousedown contextmenu', function (e) { + if (e.target.nodeName === 'HTML') { + if (started) { + endSelection(); + } + + // Detect vertical scrollbar, since IE will fire a mousedown on the scrollbar and have target set as HTML + htmlElm = doc.documentElement; + if (htmlElm.scrollHeight > htmlElm.clientHeight) { + return; + } + + started = 1; + // Setup start position + startRng = rngFromPoint(e.x, e.y); + if (startRng) { + // Listen for selection change events + dom.bind(doc, 'mouseup', endSelection); + dom.bind(doc, 'mousemove', selectionChange); + + dom.getRoot().focus(); + startRng.select(); + } + } + }); + } + + /** + * Fixes selection issues where the caret can be placed between two inline elements like a|b + * this fix will lean the caret right into the closest inline element. + */ + function normalizeSelection() { + // Normalize selection for example a|a becomes a|a except for Ctrl+A since it selects everything + editor.on('keyup focusin mouseup', function (e) { + if (e.keyCode != 65 || !VK.metaKeyPressed(e)) { + selection.normalize(); + } + }, true); + } + + /** + * Forces Gecko to render a broken image icon if it fails to load an image. + */ + function showBrokenImageIcon() { + editor.contentStyles.push( + 'img:-moz-broken {' + + '-moz-force-broken-image-icon:1;' + + 'min-width:24px;' + + 'min-height:24px' + + '}' + ); + } + + /** + * iOS has a bug where it's impossible to type if the document has a touchstart event + * bound and the user touches the document while having the on screen keyboard visible. + * + * The touch event moves the focus to the parent document while having the caret inside the iframe + * this fix moves the focus back into the iframe document. + */ + function restoreFocusOnKeyDown() { + if (!editor.inline) { + editor.on('keydown', function () { + if (document.activeElement == document.body) { + editor.getWin().focus(); + } + }); + } + } + + /** + * IE 11 has an annoying issue where you can't move focus into the editor + * by clicking on the white area HTML element. We used to be able to to fix this with + * the fixCaretSelectionOfDocumentElementOnIe fix. But since M$ removed the selection + * object it's not possible anymore. So we need to hack in a ungly CSS to force the + * body to be at least 150px. If the user clicks the HTML element out side this 150px region + * we simply move the focus into the first paragraph. Not ideal since you loose the + * positioning of the caret but goot enough for most cases. + */ + function bodyHeight() { + if (!editor.inline) { + editor.contentStyles.push('body {min-height: 150px}'); + editor.on('click', function (e) { + var rng; + + if (e.target.nodeName == 'HTML') { + // Edge seems to only need focus if we set the range + // the caret will become invisible and moved out of the iframe!! + if (Env.ie > 11) { + editor.getBody().focus(); + return; + } + + // Need to store away non collapsed ranges since the focus call will mess that up see #7382 + rng = editor.selection.getRng(); + editor.getBody().focus(); + editor.selection.setRng(rng); + editor.selection.normalize(); + editor.nodeChanged(); + } + }); + } + } + + /** + * Firefox on Mac OS will move the browser back to the previous page if you press CMD+Left arrow. + * You might then loose all your work so we need to block that behavior and replace it with our own. + */ + function blockCmdArrowNavigation() { + if (Env.mac) { + editor.on('keydown', function (e) { + if (VK.metaKeyPressed(e) && !e.shiftKey && (e.keyCode == 37 || e.keyCode == 39)) { + e.preventDefault(); + editor.selection.getSel().modify('move', e.keyCode == 37 ? 'backward' : 'forward', 'lineboundary'); + } + }); + } + } + + /** + * Disables the autolinking in IE 9+ this is then re-enabled by the autolink plugin. + */ + function disableAutoUrlDetect() { + setEditorCommandState("AutoUrlDetect", false); + } + + /** + * iOS 7.1 introduced two new bugs: + * 1) It's possible to open links within a contentEditable area by clicking on them. + * 2) If you hold down the finger it will display the link/image touch callout menu. + */ + function tapLinksAndImages() { + editor.on('click', function (e) { + var elm = e.target; + + do { + if (elm.tagName === 'A') { + e.preventDefault(); + return; + } + } while ((elm = elm.parentNode)); + }); + + editor.contentStyles.push('.mce-content-body {-webkit-touch-callout: none}'); + } + + /** + * iOS Safari and possible other browsers have a bug where it won't fire + * a click event when a contentEditable is focused. This function fakes click events + * by using touchstart/touchend and measuring the time and distance travelled. + */ + /* + function touchClickEvent() { + editor.on('touchstart', function(e) { + var elm, time, startTouch, changedTouches; + + elm = e.target; + time = new Date().getTime(); + changedTouches = e.changedTouches; + + if (!changedTouches || changedTouches.length > 1) { + return; + } + + startTouch = changedTouches[0]; + + editor.once('touchend', function(e) { + var endTouch = e.changedTouches[0], args; + + if (new Date().getTime() - time > 500) { + return; + } + + if (Math.abs(startTouch.clientX - endTouch.clientX) > 5) { + return; + } + + if (Math.abs(startTouch.clientY - endTouch.clientY) > 5) { + return; + } + + args = { + target: elm + }; + + each('pageX pageY clientX clientY screenX screenY'.split(' '), function(key) { + args[key] = endTouch[key]; + }); + + args = editor.fire('click', args); + + if (!args.isDefaultPrevented()) { + // iOS WebKit can't place the caret properly once + // you bind touch events so we need to do this manually + // TODO: Expand to the closest word? Touble tap still works. + editor.selection.placeCaretAt(endTouch.clientX, endTouch.clientY); + editor.nodeChanged(); + } + }); + }); + } + */ + + /** + * WebKit has a bug where it will allow forms to be submitted if they are inside a contentEditable element. + * For example this: + */ + function blockFormSubmitInsideEditor() { + editor.on('init', function () { + editor.dom.bind(editor.getBody(), 'submit', function (e) { + e.preventDefault(); + }); + }); + } + + /** + * Sometimes WebKit/Blink generates BR elements with the Apple-interchange-newline class. + * + * Scenario: + * 1) Create a table 2x2. + * 2) Select and copy cells A2-B2. + * 3) Paste and it will add BR element to table cell. + */ + function removeAppleInterchangeBrs() { + parser.addNodeFilter('br', function (nodes) { + var i = nodes.length; + + while (i--) { + if (nodes[i].attr('class') == 'Apple-interchange-newline') { + nodes[i].remove(); + } + } + }); + } + + /** + * IE cannot set custom contentType's on drag events, and also does not properly drag/drop between + * editors. This uses a special data:text/mce-internal URL to pass data when drag/drop between editors. + */ + function ieInternalDragAndDrop() { + editor.on('dragstart', function (e) { + setMceInternalContent(e); + }); + + editor.on('drop', function (e) { + if (!isDefaultPrevented(e)) { + var internalContent = getMceInternalContent(e); + + if (internalContent && internalContent.id != editor.id) { + e.preventDefault(); + + var rng = RangeUtils.getCaretRangeFromPoint(e.x, e.y, editor.getDoc()); + selection.setRng(rng); + insertClipboardContents(internalContent.html, true); + } + } + }); + } + + function refreshContentEditable() { + // No-op since Mozilla seems to have fixed the caret repaint issues + } + + function isHidden() { + var sel; + + if (!isGecko) { + return 0; + } + + // Weird, wheres that cursor selection? + sel = editor.selection.getSel(); + return (!sel || !sel.rangeCount || sel.rangeCount === 0); + } + + /** + * Properly empties the editor if all contents is selected and deleted this to + * prevent empty paragraphs from being produced at beginning/end of contents. + */ + function emptyEditorOnDeleteEverything() { + function isEverythingSelected(editor) { + var caretWalker = new CaretWalker(editor.getBody()); + var rng = editor.selection.getRng(); + var startCaretPos = CaretPosition.fromRangeStart(rng); + var endCaretPos = CaretPosition.fromRangeEnd(rng); + var prev = caretWalker.prev(startCaretPos); + var next = caretWalker.next(endCaretPos); + + return !editor.selection.isCollapsed() && + (!prev || (prev.isAtStart() && startCaretPos.isEqual(prev))) && + (!next || (next.isAtEnd() && startCaretPos.isEqual(next))); + } + + // Type over case delete and insert this won't cover typeover with a IME but at least it covers the common case + editor.on('keypress', function (e) { + if (!isDefaultPrevented(e) && !selection.isCollapsed() && e.charCode > 31 && !VK.metaKeyPressed(e)) { + if (isEverythingSelected(editor)) { + e.preventDefault(); + editor.setContent(String.fromCharCode(e.charCode)); + editor.selection.select(editor.getBody(), true); + editor.selection.collapse(false); + editor.nodeChanged(); + } + } + }); + + editor.on('keydown', function (e) { + var keyCode = e.keyCode; + + if (!isDefaultPrevented(e) && (keyCode == DELETE || keyCode == BACKSPACE)) { + if (isEverythingSelected(editor)) { + e.preventDefault(); + editor.setContent(''); + editor.nodeChanged(); + } + } + }); + } + + // All browsers + removeBlockQuoteOnBackSpace(); + emptyEditorWhenDeleting(); + + // Windows phone will return a range like [body, 0] on mousedown so + // it will always normalize to the wrong location + if (!Env.windowsPhone) { + normalizeSelection(); + } + + // WebKit + if (isWebKit) { + emptyEditorOnDeleteEverything(); + inputMethodFocus(); + selectControlElements(); + setDefaultBlockType(); + blockFormSubmitInsideEditor(); + disableBackspaceIntoATable(); + removeAppleInterchangeBrs(); + + //touchClickEvent(); + + // iOS + if (Env.iOS) { + restoreFocusOnKeyDown(); + bodyHeight(); + tapLinksAndImages(); + } else { + selectAll(); + } + } + + // IE + if (isIE && Env.ie < 11) { + removeHrOnBackspace(); + ensureBodyHasRoleApplication(); + addNewLinesBeforeBrInPre(); + removePreSerializedStylesWhenSelectingControls(); + deleteControlItemOnBackSpace(); + renderEmptyBlocksFix(); + keepNoScriptContents(); + fixCaretSelectionOfDocumentElementOnIe(); + } + + if (Env.ie >= 11) { + bodyHeight(); + disableBackspaceIntoATable(); + } + + if (Env.ie) { + selectAll(); + disableAutoUrlDetect(); + ieInternalDragAndDrop(); + } + + // Gecko + if (isGecko) { + emptyEditorOnDeleteEverything(); + removeHrOnBackspace(); + focusBody(); + removeStylesWhenDeletingAcrossBlockElements(); + setGeckoEditingOptions(); + addBrAfterLastLinks(); + showBrokenImageIcon(); + blockCmdArrowNavigation(); + disableBackspaceIntoATable(); + } + + return { + refreshContentEditable: refreshContentEditable, + isHidden: isHidden + }; + }; + } +); + +/** + * InitContentBody.js + * + * Released under LGPL License. + * Copyright (c) 1999-2017 Ephox Corp. All rights reserved + * + * License: http://www.tinymce.com/license + * Contributing: http://www.tinymce.com/contributing + */ + +define( + 'tinymce.core.init.InitContentBody', + [ + 'global!document', + 'global!window', + 'tinymce.core.caret.CaretContainerInput', + 'tinymce.core.dom.DOMUtils', + 'tinymce.core.dom.Selection', + 'tinymce.core.dom.Serializer', + 'tinymce.core.EditorUpload', + 'tinymce.core.ErrorReporter', + 'tinymce.core.ForceBlocks', + 'tinymce.core.Formatter', + 'tinymce.core.html.DomParser', + 'tinymce.core.html.Node', + 'tinymce.core.html.Schema', + 'tinymce.core.keyboard.KeyboardOverrides', + 'tinymce.core.NodeChange', + 'tinymce.core.SelectionOverrides', + 'tinymce.core.UndoManager', + 'tinymce.core.util.Delay', + 'tinymce.core.util.Quirks', + 'tinymce.core.util.Tools' + ], + function ( + document, window, CaretContainerInput, DOMUtils, Selection, Serializer, EditorUpload, ErrorReporter, ForceBlocks, Formatter, DomParser, Node, Schema, KeyboardOverrides, + NodeChange, SelectionOverrides, UndoManager, Delay, Quirks, Tools + ) { + var DOM = DOMUtils.DOM; + + var createParser = function (editor) { + var parser = new DomParser(editor.settings, editor.schema); + + // Convert src and href into data-mce-src, data-mce-href and data-mce-style + parser.addAttributeFilter('src,href,style,tabindex', function (nodes, name) { + var i = nodes.length, node, dom = editor.dom, value, internalName; + + while (i--) { + node = nodes[i]; + value = node.attr(name); + internalName = 'data-mce-' + name; + + // Add internal attribute if we need to we don't on a refresh of the document + if (!node.attributes.map[internalName]) { + // Don't duplicate these since they won't get modified by any browser + if (value.indexOf('data:') === 0 || value.indexOf('blob:') === 0) { + continue; + } + + if (name === "style") { + value = dom.serializeStyle(dom.parseStyle(value), node.name); + + if (!value.length) { + value = null; + } + + node.attr(internalName, value); + node.attr(name, value); + } else if (name === "tabindex") { + node.attr(internalName, value); + node.attr(name, null); + } else { + node.attr(internalName, editor.convertURL(value, name, node.name)); + } + } + } + }); + + // Keep scripts from executing + parser.addNodeFilter('script', function (nodes) { + var i = nodes.length, node, type; + + while (i--) { + node = nodes[i]; + type = node.attr('type') || 'no/type'; + if (type.indexOf('mce-') !== 0) { + node.attr('type', 'mce-' + type); + } + } + }); + + parser.addNodeFilter('#cdata', function (nodes) { + var i = nodes.length, node; + + while (i--) { + node = nodes[i]; + node.type = 8; + node.name = '#comment'; + node.value = '[CDATA[' + node.value + ']]'; + } + }); + + parser.addNodeFilter('p,h1,h2,h3,h4,h5,h6,div', function (nodes) { + var i = nodes.length, node, nonEmptyElements = editor.schema.getNonEmptyElements(); + + while (i--) { + node = nodes[i]; + + if (node.isEmpty(nonEmptyElements) && node.getAll('br').length === 0) { + node.append(new Node('br', 1)).shortEnded = true; + } + } + }); + + return parser; + }; + + var autoFocus = function (editor) { + if (editor.settings.auto_focus) { + Delay.setEditorTimeout(editor, function () { + var focusEditor; + + if (editor.settings.auto_focus === true) { + focusEditor = editor; + } else { + focusEditor = editor.editorManager.get(editor.settings.auto_focus); + } + + if (!focusEditor.destroyed) { + focusEditor.focus(); + } + }, 100); + } + }; + + var initEditor = function (editor) { + editor.bindPendingEventDelegates(); + editor.initialized = true; + editor.fire('init'); + editor.focus(true); + editor.nodeChanged({ initial: true }); + editor.execCallback('init_instance_callback', editor); + autoFocus(editor); + }; + + var initContentBody = function (editor, skipWrite) { + var settings = editor.settings, targetElm = editor.getElement(), doc = editor.getDoc(), body, contentCssText; + + // Restore visibility on target element + if (!settings.inline) { + editor.getElement().style.visibility = editor.orgVisibility; + } + + // Setup iframe body + if (!skipWrite && !settings.content_editable) { + doc.open(); + doc.write(editor.iframeHTML); + doc.close(); + } + + if (settings.content_editable) { + editor.on('remove', function () { + var bodyEl = this.getBody(); + + DOM.removeClass(bodyEl, 'mce-content-body'); + DOM.removeClass(bodyEl, 'mce-edit-focus'); + DOM.setAttrib(bodyEl, 'contentEditable', null); + }); + + DOM.addClass(targetElm, 'mce-content-body'); + editor.contentDocument = doc = settings.content_document || document; + editor.contentWindow = settings.content_window || window; + editor.bodyElement = targetElm; + + // Prevent leak in IE + settings.content_document = settings.content_window = null; + + // TODO: Fix this + settings.root_name = targetElm.nodeName.toLowerCase(); + } + + // It will not steal focus while setting contentEditable + body = editor.getBody(); + body.disabled = true; + editor.readonly = settings.readonly; + + if (!editor.readonly) { + if (editor.inline && DOM.getStyle(body, 'position', true) === 'static') { + body.style.position = 'relative'; + } + + body.contentEditable = editor.getParam('content_editable_state', true); + } + + body.disabled = false; + + editor.editorUpload = new EditorUpload(editor); + editor.schema = new Schema(settings); + editor.dom = new DOMUtils(doc, { + keep_values: true, + url_converter: editor.convertURL, + url_converter_scope: editor, + hex_colors: settings.force_hex_style_colors, + class_filter: settings.class_filter, + update_styles: true, + root_element: editor.inline ? editor.getBody() : null, + collect: settings.content_editable, + schema: editor.schema, + onSetAttrib: function (e) { + editor.fire('SetAttrib', e); + } + }); + + editor.parser = createParser(editor); + editor.serializer = new Serializer(settings, editor); + editor.selection = new Selection(editor.dom, editor.getWin(), editor.serializer, editor); + editor.formatter = new Formatter(editor); + editor.undoManager = new UndoManager(editor); + editor._nodeChangeDispatcher = new NodeChange(editor); + editor._selectionOverrides = new SelectionOverrides(editor); + + CaretContainerInput.setup(editor); + KeyboardOverrides.setup(editor); + ForceBlocks.setup(editor); + + editor.fire('PreInit'); + + if (!settings.browser_spellcheck && !settings.gecko_spellcheck) { + doc.body.spellcheck = false; // Gecko + DOM.setAttrib(body, "spellcheck", "false"); + } + + editor.quirks = new Quirks(editor); + editor.fire('PostRender'); + + if (settings.directionality) { + body.dir = settings.directionality; + } + + if (settings.nowrap) { + body.style.whiteSpace = "nowrap"; + } + + if (settings.protect) { + editor.on('BeforeSetContent', function (e) { + Tools.each(settings.protect, function (pattern) { + e.content = e.content.replace(pattern, function (str) { + return ''; + }); + }); + }); + } + + editor.on('SetContent', function () { + editor.addVisual(editor.getBody()); + }); + + // Remove empty contents + if (settings.padd_empty_editor) { + editor.on('PostProcess', function (e) { + e.content = e.content.replace(/^(]*>( | |\s|\u00a0|
|)<\/p>[\r\n]*|
[\r\n]*)$/, '');
+ });
+ }
+
+ editor.load({ initial: true, format: 'html' });
+ editor.startContent = editor.getContent({ format: 'raw' });
+
+ editor.on('compositionstart compositionend', function (e) {
+ editor.composing = e.type === 'compositionstart';
+ });
+
+ // Add editor specific CSS styles
+ if (editor.contentStyles.length > 0) {
+ contentCssText = '';
+
+ Tools.each(editor.contentStyles, function (style) {
+ contentCssText += style + "\r\n";
+ });
+
+ editor.dom.addStyle(contentCssText);
+ }
+
+ editor.dom.styleSheetLoader.loadAll(
+ editor.contentCSS,
+ function (_) {
+ initEditor(editor);
+ },
+ function (urls) {
+ initEditor(editor);
+ ErrorReporter.contentCssError(editor, urls);
+ }
+ );
+ };
+
+ return {
+ initContentBody: initContentBody
+ };
+ }
+);
+
+/**
+ * PluginManager.js
+ *
+ * Released under LGPL License.
+ * Copyright (c) 1999-2017 Ephox Corp. All rights reserved
+ *
+ * License: http://www.tinymce.com/license
+ * Contributing: http://www.tinymce.com/contributing
+ */
+
+define(
+ 'tinymce.core.PluginManager',
+ [
+ 'tinymce.core.AddOnManager'
+ ],
+ function (AddOnManager) {
+ return AddOnManager.PluginManager;
+ }
+);
+
+/**
+ * ThemeManager.js
+ *
+ * Released under LGPL License.
+ * Copyright (c) 1999-2017 Ephox Corp. All rights reserved
+ *
+ * License: http://www.tinymce.com/license
+ * Contributing: http://www.tinymce.com/contributing
+ */
+
+define(
+ 'tinymce.core.ThemeManager',
+ [
+ 'tinymce.core.AddOnManager'
+ ],
+ function (AddOnManager) {
+ return AddOnManager.ThemeManager;
+ }
+);
+
+/**
+ * Init.js
+ *
+ * Released under LGPL License.
+ * Copyright (c) 1999-2017 Ephox Corp. All rights reserved
+ *
+ * License: http://www.tinymce.com/license
+ * Contributing: http://www.tinymce.com/contributing
+ */
+
+define(
+ 'tinymce.core.init.Init',
+ [
+ 'global!document',
+ 'global!window',
+ 'tinymce.core.dom.DOMUtils',
+ 'tinymce.core.Env',
+ 'tinymce.core.init.InitContentBody',
+ 'tinymce.core.PluginManager',
+ 'tinymce.core.ThemeManager',
+ 'tinymce.core.util.Tools',
+ 'tinymce.core.util.Uuid'
+ ],
+ function (document, window, DOMUtils, Env, InitContentBody, PluginManager, ThemeManager, Tools, Uuid) {
+ var DOM = DOMUtils.DOM;
+
+ var initPlugin = function (editor, initializedPlugins, plugin) {
+ var Plugin = PluginManager.get(plugin), pluginUrl, pluginInstance;
+
+ pluginUrl = PluginManager.urls[plugin] || editor.documentBaseUrl.replace(/\/$/, '');
+ plugin = Tools.trim(plugin);
+ if (Plugin && Tools.inArray(initializedPlugins, plugin) === -1) {
+ Tools.each(PluginManager.dependencies(plugin), function (dep) {
+ initPlugin(editor, initializedPlugins, dep);
+ });
+
+ if (editor.plugins[plugin]) {
+ return;
+ }
+
+ pluginInstance = new Plugin(editor, pluginUrl, editor.$);
+
+ editor.plugins[plugin] = pluginInstance;
+
+ if (pluginInstance.init) {
+ pluginInstance.init(editor, pluginUrl);
+ initializedPlugins.push(plugin);
+ }
+ }
+ };
+
+ var initPlugins = function (editor) {
+ var initializedPlugins = [];
+
+ Tools.each(editor.settings.plugins.replace(/\-/g, '').split(/[ ,]/), function (name) {
+ initPlugin(editor, initializedPlugins, name);
+ });
+ };
+
+ var initTheme = function (editor) {
+ var Theme, settings = editor.settings;
+
+ if (settings.theme) {
+ if (typeof settings.theme != "function") {
+ settings.theme = settings.theme.replace(/-/, '');
+ Theme = ThemeManager.get(settings.theme);
+ editor.theme = new Theme(editor, ThemeManager.urls[settings.theme]);
+
+ if (editor.theme.init) {
+ editor.theme.init(editor, ThemeManager.urls[settings.theme] || editor.documentBaseUrl.replace(/\/$/, ''), editor.$);
+ }
+ } else {
+ editor.theme = settings.theme;
+ }
+ }
+ };
+
+ var measueBox = function (editor) {
+ var w, h, minHeight, re, o, settings = editor.settings, elm = editor.getElement();
+
+ // Measure box
+ if (settings.render_ui && editor.theme) {
+ editor.orgDisplay = elm.style.display;
+
+ if (typeof settings.theme != "function") {
+ w = settings.width || DOM.getStyle(elm, 'width') || '100%';
+ h = settings.height || DOM.getStyle(elm, 'height') || elm.offsetHeight;
+ minHeight = settings.min_height || 100;
+ re = /^[0-9\.]+(|px)$/i;
+
+ if (re.test('' + w)) {
+ w = Math.max(parseInt(w, 10), 100);
+ }
+
+ if (re.test('' + h)) {
+ h = Math.max(parseInt(h, 10), minHeight);
+ }
+
+ // Render UI
+ o = editor.theme.renderUI({
+ targetNode: elm,
+ width: w,
+ height: h,
+ deltaWidth: settings.delta_width,
+ deltaHeight: settings.delta_height
+ });
+
+ // Resize editor
+ if (!settings.content_editable) {
+ h = (o.iframeHeight || h) + (typeof h === 'number' ? (o.deltaHeight || 0) : '');
+ if (h < minHeight) {
+ h = minHeight;
+ }
+ }
+ } else {
+ o = settings.theme(editor, elm);
+
+ if (o.editorContainer.nodeType) {
+ o.editorContainer.id = o.editorContainer.id || editor.id + "_parent";
+ }
+
+ if (o.iframeContainer.nodeType) {
+ o.iframeContainer.id = o.iframeContainer.id || editor.id + "_iframecontainer";
+ }
+
+ // Use specified iframe height or the targets offsetHeight
+ h = o.iframeHeight || elm.offsetHeight;
+ }
+
+ editor.editorContainer = o.editorContainer;
+ o.height = h;
+ }
+
+ return o;
+ };
+
+ var createIframe = function (editor, o) {
+ var settings = editor.settings, bodyId, bodyClass, url;
+
+ editor.iframeHTML = settings.doctype + '
new
'); - */ - self.$ = DomQuery.overrideDefaults(function() { - return { - context: self.inline ? self.getBody() : self.getDoc(), - element: self.getBody() - }; - }); - } - - Editor.prototype = { - /** - * Renders the editor/adds it to the page. - * - * @method render - */ - render: function() { - var self = this, settings = self.settings, id = self.id, suffix = self.suffix; - - function readyHandler() { - DOM.unbind(window, 'ready', readyHandler); - self.render(); - } - - // Page is not loaded yet, wait for it - if (!Event.domLoaded) { - DOM.bind(window, 'ready', readyHandler); - return; - } - - // Element not found, then skip initialization - if (!self.getElement()) { - return; - } - - // No editable support old iOS versions etc - if (!Env.contentEditable) { - return; - } - - // Hide target element early to prevent content flashing - if (!settings.inline) { - self.orgVisibility = self.getElement().style.visibility; - self.getElement().style.visibility = 'hidden'; - } else { - self.inline = true; - } - - var form = self.getElement().form || DOM.getParent(id, 'form'); - if (form) { - self.formElement = form; - - // Add hidden input for non input elements inside form elements - if (settings.hidden_input && !/TEXTAREA|INPUT/i.test(self.getElement().nodeName)) { - DOM.insertAfter(DOM.create('input', {type: 'hidden', name: id}), id); - self.hasHiddenInput = true; - } - - // Pass submit/reset from form to editor instance - self.formEventDelegate = function(e) { - self.fire(e.type, e); - }; - - DOM.bind(form, 'submit reset', self.formEventDelegate); - - // Reset contents in editor when the form is reset - self.on('reset', function() { - self.setContent(self.startContent, {format: 'raw'}); - }); - - // Check page uses id="submit" or name="submit" for it's submit button - if (settings.submit_patch && !form.submit.nodeType && !form.submit.length && !form._mceOldSubmit) { - form._mceOldSubmit = form.submit; - form.submit = function() { - self.editorManager.triggerSave(); - self.setDirty(false); - - return form._mceOldSubmit(form); - }; - } - } - - /** - * Window manager reference, use this to open new windows and dialogs. - * - * @property windowManager - * @type tinymce.WindowManager - * @example - * // Shows an alert message - * tinymce.activeEditor.windowManager.alert('Hello world!'); - * - * // Opens a new dialog with the file.htm file and the size 320x240 - * // It also adds a custom parameter this can be retrieved by using tinyMCEPopup.getWindowArg inside the dialog. - * tinymce.activeEditor.windowManager.open({ - * url: 'file.htm', - * width: 320, - * height: 240 - * }, { - * custom_param: 1 - * }); - */ - self.windowManager = new WindowManager(self); - - /** - * Notification manager reference, use this to open new windows and dialogs. - * - * @property notificationManager - * @type tinymce.NotificationManager - * @example - * // Shows a notification info message. - * tinymce.activeEditor.notificationManager.open({text: 'Hello world!', type: 'info'}); - */ - self.notificationManager = new NotificationManager(self); - - if (settings.encoding == 'xml') { - self.on('GetContent', function(e) { - if (e.save) { - e.content = DOM.encode(e.content); - } - }); - } - - if (settings.add_form_submit_trigger) { - self.on('submit', function() { - if (self.initialized) { - self.save(); - } - }); - } - - if (settings.add_unload_trigger) { - self._beforeUnload = function() { - if (self.initialized && !self.destroyed && !self.isHidden()) { - self.save({format: 'raw', no_events: true, set_dirty: false}); - } - }; - - self.editorManager.on('BeforeUnload', self._beforeUnload); - } - - // Load scripts - function loadScripts() { - var scriptLoader = ScriptLoader.ScriptLoader; - - if (settings.language && settings.language != 'en' && !settings.language_url) { - settings.language_url = self.editorManager.baseURL + '/langs/' + settings.language + '.js'; - } - - if (settings.language_url) { - scriptLoader.add(settings.language_url); - } - - if (settings.theme && typeof settings.theme != "function" && - settings.theme.charAt(0) != '-' && !ThemeManager.urls[settings.theme]) { - var themeUrl = settings.theme_url; - - if (themeUrl) { - themeUrl = self.documentBaseURI.toAbsolute(themeUrl); - } else { - themeUrl = 'themes/' + settings.theme + '/theme' + suffix + '.js'; - } - - ThemeManager.load(settings.theme, themeUrl); - } - - if (Tools.isArray(settings.plugins)) { - settings.plugins = settings.plugins.join(' '); - } - - each(settings.external_plugins, function(url, name) { - PluginManager.load(name, url); - settings.plugins += ' ' + name; - }); - - each(settings.plugins.split(/[ ,]/), function(plugin) { - plugin = trim(plugin); - - if (plugin && !PluginManager.urls[plugin]) { - if (plugin.charAt(0) == '-') { - plugin = plugin.substr(1, plugin.length); - - var dependencies = PluginManager.dependencies(plugin); - - each(dependencies, function(dep) { - var defaultSettings = { - prefix: 'plugins/', - resource: dep, - suffix: '/plugin' + suffix + '.js' - }; - - dep = PluginManager.createUrl(defaultSettings, dep); - PluginManager.load(dep.resource, dep); - }); - } else { - PluginManager.load(plugin, { - prefix: 'plugins/', - resource: plugin, - suffix: '/plugin' + suffix + '.js' - }); - } - } - }); - - scriptLoader.loadQueue(function() { - if (!self.removed) { - self.init(); - } - }, self, function (urls) { - ErrorReporter.pluginLoadError(self, urls[0]); - - if (!self.removed) { - self.init(); - } - }); - } - - self.editorManager.add(self); - loadScripts(); - }, - - /** - * Initializes the editor this will be called automatically when - * all plugins/themes and language packs are loaded by the rendered method. - * This method will setup the iframe and create the theme and plugin instances. - * - * @method init - */ - init: function() { - var self = this, settings = self.settings, elm = self.getElement(); - var w, h, minHeight, n, o, Theme, url, bodyId, bodyClass, re, i, initializedPlugins = []; - - self.rtl = settings.rtl_ui || self.editorManager.i18n.rtl; - self.editorManager.i18n.setCode(settings.language); - settings.aria_label = settings.aria_label || DOM.getAttrib(elm, 'aria-label', self.getLang('aria.rich_text_area')); - - self.fire('ScriptsLoaded'); - - /** - * Reference to the theme instance that was used to generate the UI. - * - * @property theme - * @type tinymce.Theme - * @example - * // Executes a method on the theme directly - * tinymce.activeEditor.theme.someMethod(); - */ - if (settings.theme) { - if (typeof settings.theme != "function") { - settings.theme = settings.theme.replace(/-/, ''); - Theme = ThemeManager.get(settings.theme); - self.theme = new Theme(self, ThemeManager.urls[settings.theme]); - - if (self.theme.init) { - self.theme.init(self, ThemeManager.urls[settings.theme] || self.documentBaseUrl.replace(/\/$/, ''), self.$); - } - } else { - self.theme = settings.theme; - } - } - - function initPlugin(plugin) { - var Plugin = PluginManager.get(plugin), pluginUrl, pluginInstance; - - pluginUrl = PluginManager.urls[plugin] || self.documentBaseUrl.replace(/\/$/, ''); - plugin = trim(plugin); - if (Plugin && inArray(initializedPlugins, plugin) === -1) { - each(PluginManager.dependencies(plugin), function(dep) { - initPlugin(dep); - }); - - if (self.plugins[plugin]) { - return; - } - - pluginInstance = new Plugin(self, pluginUrl, self.$); - - self.plugins[plugin] = pluginInstance; - - if (pluginInstance.init) { - pluginInstance.init(self, pluginUrl); - initializedPlugins.push(plugin); - } - } - } - - // Create all plugins - each(settings.plugins.replace(/\-/g, '').split(/[ ,]/), initPlugin); - - // Measure box - if (settings.render_ui && self.theme) { - self.orgDisplay = elm.style.display; - - if (typeof settings.theme != "function") { - w = settings.width || elm.style.width || elm.offsetWidth; - h = settings.height || elm.style.height || elm.offsetHeight; - minHeight = settings.min_height || 100; - re = /^[0-9\.]+(|px)$/i; - - if (re.test('' + w)) { - w = Math.max(parseInt(w, 10), 100); - } - - if (re.test('' + h)) { - h = Math.max(parseInt(h, 10), minHeight); - } - - // Render UI - o = self.theme.renderUI({ - targetNode: elm, - width: w, - height: h, - deltaWidth: settings.delta_width, - deltaHeight: settings.delta_height - }); - - // Resize editor - if (!settings.content_editable) { - h = (o.iframeHeight || h) + (typeof h == 'number' ? (o.deltaHeight || 0) : ''); - if (h < minHeight) { - h = minHeight; - } - } - } else { - o = settings.theme(self, elm); - - if (o.editorContainer.nodeType) { - o.editorContainer.id = o.editorContainer.id || self.id + "_parent"; - } - - if (o.iframeContainer.nodeType) { - o.iframeContainer.id = o.iframeContainer.id || self.id + "_iframecontainer"; - } - - // Use specified iframe height or the targets offsetHeight - h = o.iframeHeight || elm.offsetHeight; - } - - self.editorContainer = o.editorContainer; - } - - // Load specified content CSS last - if (settings.content_css) { - each(explode(settings.content_css), function(u) { - self.contentCSS.push(self.documentBaseURI.toAbsolute(u)); - }); - } - - // Load specified content CSS last - if (settings.content_style) { - self.contentStyles.push(settings.content_style); - } - - // Content editable mode ends here - if (settings.content_editable) { - elm = n = o = null; // Fix IE leak - return self.initContentBody(); - } - - self.iframeHTML = settings.doctype + ''; - - // We only need to override paths if we have to - // IE has a bug where it remove site absolute urls to relative ones if this is specified - if (settings.document_base_url != self.documentBaseUrl) { - self.iframeHTML += ']*>( | |\s|\u00a0|
|)<\/p>[\r\n]*|
[\r\n]*)$/, '');
- });
- }
-
- self.load({initial: true, format: 'html'});
- self.startContent = self.getContent({format: 'raw'});
-
- /**
- * Is set to true after the editor instance has been initialized
- *
- * @property initialized
- * @type Boolean
- * @example
- * function isEditorInitialized(editor) {
- * return editor && editor.initialized;
- * }
- */
- self.initialized = true;
- self.bindPendingEventDelegates();
-
- self.fire('init');
- self.focus(true);
- self.nodeChanged({initial: true});
- self.execCallback('init_instance_callback', self);
-
- self.on('compositionstart compositionend', function(e) {
- self.composing = e.type === 'compositionstart';
- });
-
- // Add editor specific CSS styles
- if (self.contentStyles.length > 0) {
- contentCssText = '';
-
- each(self.contentStyles, function(style) {
- contentCssText += style + "\r\n";
- });
-
- self.dom.addStyle(contentCssText);
- }
-
- // Load specified content CSS last
- each(self.contentCSS, function(cssUrl) {
- if (!self.loadedCSS[cssUrl]) {
- self.dom.loadCSS(cssUrl);
- self.loadedCSS[cssUrl] = true;
- }
- });
-
- // Handle auto focus
- if (settings.auto_focus) {
- Delay.setEditorTimeout(self, function() {
- var editor;
-
- if (settings.auto_focus === true) {
- editor = self;
- } else {
- editor = self.editorManager.get(settings.auto_focus);
- }
-
- if (!editor.destroyed) {
- editor.focus();
- }
- }, 100);
- }
-
- // Clean up references for IE
- targetElm = doc = body = null;
- },
-
- /**
- * Focuses/activates the editor. This will set this editor as the activeEditor in the tinymce collection
- * it will also place DOM focus inside the editor.
- *
- * @method focus
- * @param {Boolean} skipFocus Skip DOM focus. Just set is as the active editor.
- */
- focus: function(skipFocus) {
- var self = this, selection = self.selection, contentEditable = self.settings.content_editable, rng;
- var controlElm, doc = self.getDoc(), body = self.getBody(), contentEditableHost;
-
- function getContentEditableHost(node) {
- return self.dom.getParent(node, function(node) {
- return self.dom.getContentEditable(node) === "true";
- });
- }
-
- if (!skipFocus) {
- // Get selected control element
- rng = selection.getRng();
- if (rng.item) {
- controlElm = rng.item(0);
- }
-
- self.quirks.refreshContentEditable();
-
- // Move focus to contentEditable=true child if needed
- contentEditableHost = getContentEditableHost(selection.getNode());
- if (self.$.contains(body, contentEditableHost)) {
- contentEditableHost.focus();
- selection.normalize();
- self.editorManager.setActive(self);
- return;
- }
-
- // Focus the window iframe
- if (!contentEditable) {
- // WebKit needs this call to fire focusin event properly see #5948
- // But Opera pre Blink engine will produce an empty selection so skip Opera
- if (!Env.opera) {
- self.getBody().focus();
- }
-
- self.getWin().focus();
- }
-
- // Focus the body as well since it's contentEditable
- if (isGecko || contentEditable) {
- // Check for setActive since it doesn't scroll to the element
- if (body.setActive) {
- // IE 11 sometimes throws "Invalid function" then fallback to focus
- try {
- body.setActive();
- } catch (ex) {
- body.focus();
- }
- } else {
- body.focus();
- }
-
- if (contentEditable) {
- selection.normalize();
- }
- }
-
- // Restore selected control element
- // This is needed when for example an image is selected within a
- // layer a call to focus will then remove the control selection
- if (controlElm && controlElm.ownerDocument == doc) {
- rng = doc.body.createControlRange();
- rng.addElement(controlElm);
- rng.select();
- }
- }
-
- self.editorManager.setActive(self);
- },
-
- /**
- * Executes a legacy callback. This method is useful to call old 2.x option callbacks.
- * There new event model is a better way to add callback so this method might be removed in the future.
- *
- * @method execCallback
- * @param {String} name Name of the callback to execute.
- * @return {Object} Return value passed from callback function.
- */
- execCallback: function(name) {
- var self = this, callback = self.settings[name], scope;
-
- if (!callback) {
- return;
- }
-
- // Look through lookup
- if (self.callbackLookup && (scope = self.callbackLookup[name])) {
- callback = scope.func;
- scope = scope.scope;
- }
-
- if (typeof callback === 'string') {
- scope = callback.replace(/\.\w+$/, '');
- scope = scope ? resolve(scope) : 0;
- callback = resolve(callback);
- self.callbackLookup = self.callbackLookup || {};
- self.callbackLookup[name] = {func: callback, scope: scope};
- }
-
- return callback.apply(scope || self, Array.prototype.slice.call(arguments, 1));
- },
-
- /**
- * Translates the specified string by replacing variables with language pack items it will also check if there is
- * a key matching the input.
- *
- * @method translate
- * @param {String} text String to translate by the language pack data.
- * @return {String} Translated string.
- */
- translate: function(text) {
- var lang = this.settings.language || 'en', i18n = this.editorManager.i18n;
-
- if (!text) {
- return '';
- }
-
- text = i18n.data[lang + '.' + text] || text.replace(/\{\#([^\}]+)\}/g, function(a, b) {
- return i18n.data[lang + '.' + b] || '{#' + b + '}';
- });
-
- return this.editorManager.translate(text);
- },
-
- /**
- * Returns a language pack item by name/key.
- *
- * @method getLang
- * @param {String} name Name/key to get from the language pack.
- * @param {String} defaultVal Optional default value to retrieve.
- */
- getLang: function(name, defaultVal) {
- return (
- this.editorManager.i18n.data[(this.settings.language || 'en') + '.' + name] ||
- (defaultVal !== undefined ? defaultVal : '{#' + name + '}')
- );
- },
-
- /**
- * Returns a configuration parameter by name.
- *
- * @method getParam
- * @param {String} name Configruation parameter to retrieve.
- * @param {String} defaultVal Optional default value to return.
- * @param {String} type Optional type parameter.
- * @return {String} Configuration parameter value or default value.
- * @example
- * // Returns a specific config value from the currently active editor
- * var someval = tinymce.activeEditor.getParam('myvalue');
- *
- * // Returns a specific config value from a specific editor instance by id
- * var someval2 = tinymce.get('my_editor').getParam('myvalue');
- */
- getParam: function(name, defaultVal, type) {
- var value = name in this.settings ? this.settings[name] : defaultVal, output;
-
- if (type === 'hash') {
- output = {};
-
- if (typeof value === 'string') {
- each(value.indexOf('=') > 0 ? value.split(/[;,](?![^=;,]*(?:[;,]|$))/) : value.split(','), function(value) {
- value = value.split('=');
-
- if (value.length > 1) {
- output[trim(value[0])] = trim(value[1]);
- } else {
- output[trim(value[0])] = trim(value);
- }
- });
- } else {
- output = value;
- }
-
- return output;
- }
-
- return value;
- },
-
- /**
- * Dispatches out a onNodeChange event to all observers. This method should be called when you
- * need to update the UI states or element path etc.
- *
- * @method nodeChanged
- * @param {Object} args Optional args to pass to NodeChange event handlers.
- */
- nodeChanged: function(args) {
- this._nodeChangeDispatcher.nodeChanged(args);
- },
-
- /**
- * Adds a button that later gets created by the theme in the editors toolbars.
- *
- * @method addButton
- * @param {String} name Button name to add.
- * @param {Object} settings Settings object with title, cmd etc.
- * @example
- * // Adds a custom button to the editor that inserts contents when clicked
- * tinymce.init({
- * ...
- *
- * toolbar: 'example'
- *
- * setup: function(ed) {
- * ed.addButton('example', {
- * title: 'My title',
- * image: '../js/tinymce/plugins/example/img/example.gif',
- * onclick: function() {
- * ed.insertContent('Hello world!!');
- * }
- * });
- * }
- * });
- */
- addButton: function(name, settings) {
- var self = this;
-
- if (settings.cmd) {
- settings.onclick = function() {
- self.execCommand(settings.cmd);
- };
- }
-
- if (!settings.text && !settings.icon) {
- settings.icon = name;
- }
-
- self.buttons = self.buttons || {};
- settings.tooltip = settings.tooltip || settings.title;
- self.buttons[name] = settings;
- },
-
- /**
- * Adds a sidebar for the editor instance.
- *
- * @method addSidebar
- * @param {String} name Sidebar name to add.
- * @param {Object} settings Settings object with icon, onshow etc.
- * @example
- * // Adds a custom sidebar that when clicked logs the panel element
- * tinymce.init({
- * ...
- * setup: function(ed) {
- * ed.addSidebar('example', {
- * tooltip: 'My sidebar',
- * icon: 'my-side-bar',
- * onshow: function(api) {
- * console.log(api.element());
- * }
- * });
- * }
- * });
- */
- addSidebar: function (name, settings) {
- return Sidebar.add(this, name, settings);
- },
-
- /**
- * Adds a menu item to be used in the menus of the theme. There might be multiple instances
- * of this menu item for example it might be used in the main menus of the theme but also in
- * the context menu so make sure that it's self contained and supports multiple instances.
- *
- * @method addMenuItem
- * @param {String} name Menu item name to add.
- * @param {Object} settings Settings object with title, cmd etc.
- * @example
- * // Adds a custom menu item to the editor that inserts contents when clicked
- * // The context option allows you to add the menu item to an existing default menu
- * tinymce.init({
- * ...
- *
- * setup: function(ed) {
- * ed.addMenuItem('example', {
- * text: 'My menu item',
- * context: 'tools',
- * onclick: function() {
- * ed.insertContent('Hello world!!');
- * }
- * });
- * }
- * });
- */
- addMenuItem: function(name, settings) {
- var self = this;
-
- if (settings.cmd) {
- settings.onclick = function() {
- self.execCommand(settings.cmd);
- };
- }
-
- self.menuItems = self.menuItems || {};
- self.menuItems[name] = settings;
- },
-
- /**
- * Adds a contextual toolbar to be rendered when the selector matches.
- *
- * @method addContextToolbar
- * @param {function/string} predicate Predicate that needs to return true if provided strings get converted into CSS predicates.
- * @param {String/Array} items String or array with items to add to the context toolbar.
- */
- addContextToolbar: function(predicate, items) {
- var self = this, selector;
-
- self.contextToolbars = self.contextToolbars || [];
-
- // Convert selector to predicate
- if (typeof predicate == "string") {
- selector = predicate;
- predicate = function(elm) {
- return self.dom.is(elm, selector);
- };
- }
-
- self.contextToolbars.push({
- id: Uuid.uuid('mcet'),
- predicate: predicate,
- items: items
- });
- },
-
- /**
- * Adds a custom command to the editor, you can also override existing commands with this method.
- * The command that you add can be executed with execCommand.
- *
- * @method addCommand
- * @param {String} name Command name to add/override.
- * @param {addCommandCallback} callback Function to execute when the command occurs.
- * @param {Object} scope Optional scope to execute the function in.
- * @example
- * // Adds a custom command that later can be executed using execCommand
- * tinymce.init({
- * ...
- *
- * setup: function(ed) {
- * // Register example command
- * ed.addCommand('mycommand', function(ui, v) {
- * ed.windowManager.alert('Hello world!! Selection: ' + ed.selection.getContent({format: 'text'}));
- * });
- * }
- * });
- */
- addCommand: function(name, callback, scope) {
- /**
- * Callback function that gets called when a command is executed.
- *
- * @callback addCommandCallback
- * @param {Boolean} ui Display UI state true/false.
- * @param {Object} value Optional value for command.
- * @return {Boolean} True/false state if the command was handled or not.
- */
- this.editorCommands.addCommand(name, callback, scope);
- },
-
- /**
- * Adds a custom query state command to the editor, you can also override existing commands with this method.
- * The command that you add can be executed with queryCommandState function.
- *
- * @method addQueryStateHandler
- * @param {String} name Command name to add/override.
- * @param {addQueryStateHandlerCallback} callback Function to execute when the command state retrieval occurs.
- * @param {Object} scope Optional scope to execute the function in.
- */
- addQueryStateHandler: function(name, callback, scope) {
- /**
- * Callback function that gets called when a queryCommandState is executed.
- *
- * @callback addQueryStateHandlerCallback
- * @return {Boolean} True/false state if the command is enabled or not like is it bold.
- */
- this.editorCommands.addQueryStateHandler(name, callback, scope);
- },
-
- /**
- * Adds a custom query value command to the editor, you can also override existing commands with this method.
- * The command that you add can be executed with queryCommandValue function.
- *
- * @method addQueryValueHandler
- * @param {String} name Command name to add/override.
- * @param {addQueryValueHandlerCallback} callback Function to execute when the command value retrieval occurs.
- * @param {Object} scope Optional scope to execute the function in.
- */
- addQueryValueHandler: function(name, callback, scope) {
- /**
- * Callback function that gets called when a queryCommandValue is executed.
- *
- * @callback addQueryValueHandlerCallback
- * @return {Object} Value of the command or undefined.
- */
- this.editorCommands.addQueryValueHandler(name, callback, scope);
- },
-
- /**
- * Adds a keyboard shortcut for some command or function.
- *
- * @method addShortcut
- * @param {String} pattern Shortcut pattern. Like for example: ctrl+alt+o.
- * @param {String} desc Text description for the command.
- * @param {String/Function} cmdFunc Command name string or function to execute when the key is pressed.
- * @param {Object} sc Optional scope to execute the function in.
- * @return {Boolean} true/false state if the shortcut was added or not.
- */
- addShortcut: function(pattern, desc, cmdFunc, scope) {
- this.shortcuts.add(pattern, desc, cmdFunc, scope);
- },
-
- /**
- * Executes a command on the current instance. These commands can be TinyMCE internal commands prefixed with "mce" or
- * they can be build in browser commands such as "Bold". A compleate list of browser commands is available on MSDN or Mozilla.org.
- * This function will dispatch the execCommand function on each plugin, theme or the execcommand_callback option if none of these
- * return true it will handle the command as a internal browser command.
- *
- * @method execCommand
- * @param {String} cmd Command name to execute, for example mceLink or Bold.
- * @param {Boolean} ui True/false state if a UI (dialog) should be presented or not.
- * @param {mixed} value Optional command value, this can be anything.
- * @param {Object} args Optional arguments object.
- */
- execCommand: function(cmd, ui, value, args) {
- return this.editorCommands.execCommand(cmd, ui, value, args);
- },
-
- /**
- * Returns a command specific state, for example if bold is enabled or not.
- *
- * @method queryCommandState
- * @param {string} cmd Command to query state from.
- * @return {Boolean} Command specific state, for example if bold is enabled or not.
- */
- queryCommandState: function(cmd) {
- return this.editorCommands.queryCommandState(cmd);
- },
-
- /**
- * Returns a command specific value, for example the current font size.
- *
- * @method queryCommandValue
- * @param {string} cmd Command to query value from.
- * @return {Object} Command specific value, for example the current font size.
- */
- queryCommandValue: function(cmd) {
- return this.editorCommands.queryCommandValue(cmd);
- },
-
- /**
- * Returns true/false if the command is supported or not.
- *
- * @method queryCommandSupported
- * @param {String} cmd Command that we check support for.
- * @return {Boolean} true/false if the command is supported or not.
- */
- queryCommandSupported: function(cmd) {
- return this.editorCommands.queryCommandSupported(cmd);
- },
-
- /**
- * Shows the editor and hides any textarea/div that the editor is supposed to replace.
- *
- * @method show
- */
- show: function() {
- var self = this;
-
- if (self.hidden) {
- self.hidden = false;
-
- if (self.inline) {
- self.getBody().contentEditable = true;
- } else {
- DOM.show(self.getContainer());
- DOM.hide(self.id);
- }
-
- self.load();
- self.fire('show');
- }
- },
-
- /**
- * Hides the editor and shows any textarea/div that the editor is supposed to replace.
- *
- * @method hide
- */
- hide: function() {
- var self = this, doc = self.getDoc();
-
- if (!self.hidden) {
- // Fixed bug where IE has a blinking cursor left from the editor
- if (ie && doc && !self.inline) {
- doc.execCommand('SelectAll');
- }
-
- // We must save before we hide so Safari doesn't crash
- self.save();
-
- if (self.inline) {
- self.getBody().contentEditable = false;
-
- // Make sure the editor gets blurred
- if (self == self.editorManager.focusedEditor) {
- self.editorManager.focusedEditor = null;
- }
- } else {
- DOM.hide(self.getContainer());
- DOM.setStyle(self.id, 'display', self.orgDisplay);
- }
-
- self.hidden = true;
- self.fire('hide');
- }
- },
-
- /**
- * Returns true/false if the editor is hidden or not.
- *
- * @method isHidden
- * @return {Boolean} True/false if the editor is hidden or not.
- */
- isHidden: function() {
- return !!this.hidden;
- },
-
- /**
- * Sets the progress state, this will display a throbber/progess for the editor.
- * This is ideal for asynchronous operations like an AJAX save call.
- *
- * @method setProgressState
- * @param {Boolean} state Boolean state if the progress should be shown or hidden.
- * @param {Number} time Optional time to wait before the progress gets shown.
- * @return {Boolean} Same as the input state.
- * @example
- * // Show progress for the active editor
- * tinymce.activeEditor.setProgressState(true);
- *
- * // Hide progress for the active editor
- * tinymce.activeEditor.setProgressState(false);
- *
- * // Show progress after 3 seconds
- * tinymce.activeEditor.setProgressState(true, 3000);
- */
- setProgressState: function(state, time) {
- this.fire('ProgressState', {state: state, time: time});
- },
-
- /**
- * Loads contents from the textarea or div element that got converted into an editor instance.
- * This method will move the contents from that textarea or div into the editor by using setContent
- * so all events etc that method has will get dispatched as well.
- *
- * @method load
- * @param {Object} args Optional content object, this gets passed around through the whole load process.
- * @return {String} HTML string that got set into the editor.
- */
- load: function(args) {
- var self = this, elm = self.getElement(), html;
-
- if (elm) {
- args = args || {};
- args.load = true;
-
- html = self.setContent(elm.value !== undefined ? elm.value : elm.innerHTML, args);
- args.element = elm;
-
- if (!args.no_events) {
- self.fire('LoadContent', args);
- }
-
- args.element = elm = null;
-
- return html;
- }
- },
-
- /**
- * Saves the contents from a editor out to the textarea or div element that got converted into an editor instance.
- * This method will move the HTML contents from the editor into that textarea or div by getContent
- * so all events etc that method has will get dispatched as well.
- *
- * @method save
- * @param {Object} args Optional content object, this gets passed around through the whole save process.
- * @return {String} HTML string that got set into the textarea/div.
- */
- save: function(args) {
- var self = this, elm = self.getElement(), html, form;
-
- if (!elm || !self.initialized) {
- return;
- }
-
- args = args || {};
- args.save = true;
-
- args.element = elm;
- html = args.content = self.getContent(args);
-
- if (!args.no_events) {
- self.fire('SaveContent', args);
- }
-
- // Always run this internal event
- if (args.format == 'raw') {
- self.fire('RawSaveContent', args);
- }
-
- html = args.content;
-
- if (!/TEXTAREA|INPUT/i.test(elm.nodeName)) {
- // Update DIV element when not in inline mode
- if (!self.inline) {
- elm.innerHTML = html;
- }
-
- // Update hidden form element
- if ((form = DOM.getParent(self.id, 'form'))) {
- each(form.elements, function(elm) {
- if (elm.name == self.id) {
- elm.value = html;
- return false;
- }
- });
- }
- } else {
- elm.value = html;
- }
-
- args.element = elm = null;
-
- if (args.set_dirty !== false) {
- self.setDirty(false);
- }
-
- return html;
- },
-
- /**
- * Sets the specified content to the editor instance, this will cleanup the content before it gets set using
- * the different cleanup rules options.
- *
- * @method setContent
- * @param {String} content Content to set to editor, normally HTML contents but can be other formats as well.
- * @param {Object} args Optional content object, this gets passed around through the whole set process.
- * @return {String} HTML string that got set into the editor.
- * @example
- * // Sets the HTML contents of the activeEditor editor
- * tinymce.activeEditor.setContent('some html');
- *
- * // Sets the raw contents of the activeEditor editor
- * tinymce.activeEditor.setContent('some html', {format: 'raw'});
- *
- * // Sets the content of a specific editor (my_editor in this example)
- * tinymce.get('my_editor').setContent(data);
- *
- * // Sets the bbcode contents of the activeEditor editor if the bbcode plugin was added
- * tinymce.activeEditor.setContent('[b]some[/b] html', {format: 'bbcode'});
- */
- setContent: function(content, args) {
- var self = this, body = self.getBody(), forcedRootBlockName, padd;
-
- // Setup args object
- args = args || {};
- args.format = args.format || 'html';
- args.set = true;
- args.content = content;
-
- // Do preprocessing
- if (!args.no_events) {
- self.fire('BeforeSetContent', args);
- }
-
- content = args.content;
-
- // Padd empty content in Gecko and Safari. Commands will otherwise fail on the content
- // It will also be impossible to place the caret in the editor unless there is a BR element present
- if (content.length === 0 || /^\s+$/.test(content)) {
- padd = ie && ie < 11 ? '' : '
';
-
- // Todo: There is a lot more root elements that need special padding
- // so separate this and add all of them at some point.
- if (body.nodeName == 'TABLE') {
- content = '
new
'); + */ + self.$ = DomQuery.overrideDefaults(function () { + return { + context: self.inline ? self.getBody() : self.getDoc(), + element: self.getBody() + }; + }); + } + + Editor.prototype = { + /** + * Renders the editor/adds it to the page. + * + * @method render + */ + render: function () { + Render.render(this); + }, + + /** + * Focuses/activates the editor. This will set this editor as the activeEditor in the tinymce collection + * it will also place DOM focus inside the editor. + * + * @method focus + * @param {Boolean} skipFocus Skip DOM focus. Just set is as the active editor. + */ + focus: function (skipFocus) { + var self = this, selection = self.selection, contentEditable = self.settings.content_editable, rng; + var controlElm, doc = self.getDoc(), body = self.getBody(), contentEditableHost; + + function getContentEditableHost(node) { + return self.dom.getParent(node, function (node) { + return self.dom.getContentEditable(node) === "true"; + }); + } + + if (!skipFocus) { + // Get selected control element + rng = selection.getRng(); + if (rng.item) { + controlElm = rng.item(0); + } + + self.quirks.refreshContentEditable(); + + // Move focus to contentEditable=true child if needed + contentEditableHost = getContentEditableHost(selection.getNode()); + if (self.$.contains(body, contentEditableHost)) { + contentEditableHost.focus(); + selection.normalize(); + self.editorManager.setActive(self); + return; + } + + // Focus the window iframe + if (!contentEditable) { + // WebKit needs this call to fire focusin event properly see #5948 + // But Opera pre Blink engine will produce an empty selection so skip Opera + if (!Env.opera) { + self.getBody().focus(); + } + + self.getWin().focus(); + } + + // Focus the body as well since it's contentEditable + if (isGecko || contentEditable) { + // Check for setActive since it doesn't scroll to the element + if (body.setActive) { + // IE 11 sometimes throws "Invalid function" then fallback to focus + try { + body.setActive(); + } catch (ex) { + body.focus(); + } + } else { + body.focus(); + } + + if (contentEditable) { + selection.normalize(); + } + } + + // Restore selected control element + // This is needed when for example an image is selected within a + // layer a call to focus will then remove the control selection + if (controlElm && controlElm.ownerDocument == doc) { + rng = doc.body.createControlRange(); + rng.addElement(controlElm); + rng.select(); + } + } + + self.editorManager.setActive(self); + }, + + /** + * Executes a legacy callback. This method is useful to call old 2.x option callbacks. + * There new event model is a better way to add callback so this method might be removed in the future. + * + * @method execCallback + * @param {String} name Name of the callback to execute. + * @return {Object} Return value passed from callback function. + */ + execCallback: function (name) { + var self = this, callback = self.settings[name], scope; + + if (!callback) { + return; + } + + // Look through lookup + if (self.callbackLookup && (scope = self.callbackLookup[name])) { + callback = scope.func; + scope = scope.scope; + } + + if (typeof callback === 'string') { + scope = callback.replace(/\.\w+$/, ''); + scope = scope ? resolve(scope) : 0; + callback = resolve(callback); + self.callbackLookup = self.callbackLookup || {}; + self.callbackLookup[name] = { func: callback, scope: scope }; + } + + return callback.apply(scope || self, Array.prototype.slice.call(arguments, 1)); + }, + + /** + * Translates the specified string by replacing variables with language pack items it will also check if there is + * a key matching the input. + * + * @method translate + * @param {String} text String to translate by the language pack data. + * @return {String} Translated string. + */ + translate: function (text) { + var lang = this.settings.language || 'en', i18n = this.editorManager.i18n; + + if (!text) { + return ''; + } + + text = i18n.data[lang + '.' + text] || text.replace(/\{\#([^\}]+)\}/g, function (a, b) { + return i18n.data[lang + '.' + b] || '{#' + b + '}'; + }); + + return this.editorManager.translate(text); + }, + + /** + * Returns a language pack item by name/key. + * + * @method getLang + * @param {String} name Name/key to get from the language pack. + * @param {String} defaultVal Optional default value to retrieve. + */ + getLang: function (name, defaultVal) { + return ( + this.editorManager.i18n.data[(this.settings.language || 'en') + '.' + name] || + (defaultVal !== undefined ? defaultVal : '{#' + name + '}') + ); + }, + + /** + * Returns a configuration parameter by name. + * + * @method getParam + * @param {String} name Configruation parameter to retrieve. + * @param {String} defaultVal Optional default value to return. + * @param {String} type Optional type parameter. + * @return {String} Configuration parameter value or default value. + * @example + * // Returns a specific config value from the currently active editor + * var someval = tinymce.activeEditor.getParam('myvalue'); + * + * // Returns a specific config value from a specific editor instance by id + * var someval2 = tinymce.get('my_editor').getParam('myvalue'); + */ + getParam: function (name, defaultVal, type) { + var value = name in this.settings ? this.settings[name] : defaultVal, output; + + if (type === 'hash') { + output = {}; + + if (typeof value === 'string') { + each(value.indexOf('=') > 0 ? value.split(/[;,](?![^=;,]*(?:[;,]|$))/) : value.split(','), function (value) { + value = value.split('='); + + if (value.length > 1) { + output[trim(value[0])] = trim(value[1]); + } else { + output[trim(value[0])] = trim(value); + } + }); + } else { + output = value; + } + + return output; + } + + return value; + }, + + /** + * Dispatches out a onNodeChange event to all observers. This method should be called when you + * need to update the UI states or element path etc. + * + * @method nodeChanged + * @param {Object} args Optional args to pass to NodeChange event handlers. + */ + nodeChanged: function (args) { + this._nodeChangeDispatcher.nodeChanged(args); + }, + + /** + * Adds a button that later gets created by the theme in the editors toolbars. + * + * @method addButton + * @param {String} name Button name to add. + * @param {Object} settings Settings object with title, cmd etc. + * @example + * // Adds a custom button to the editor that inserts contents when clicked + * tinymce.init({ + * ... + * + * toolbar: 'example' + * + * setup: function(ed) { + * ed.addButton('example', { + * title: 'My title', + * image: '../js/tinymce/plugins/example/img/example.gif', + * onclick: function() { + * ed.insertContent('Hello world!!'); + * } + * }); + * } + * }); + */ + addButton: function (name, settings) { + var self = this; + + if (settings.cmd) { + settings.onclick = function () { + self.execCommand(settings.cmd); + }; + } + + if (!settings.text && !settings.icon) { + settings.icon = name; + } + + self.buttons = self.buttons || {}; + settings.tooltip = settings.tooltip || settings.title; + self.buttons[name] = settings; + }, + + /** + * Adds a sidebar for the editor instance. + * + * @method addSidebar + * @param {String} name Sidebar name to add. + * @param {Object} settings Settings object with icon, onshow etc. + * @example + * // Adds a custom sidebar that when clicked logs the panel element + * tinymce.init({ + * ... + * setup: function(ed) { + * ed.addSidebar('example', { + * tooltip: 'My sidebar', + * icon: 'my-side-bar', + * onshow: function(api) { + * console.log(api.element()); + * } + * }); + * } + * }); + */ + addSidebar: function (name, settings) { + return Sidebar.add(this, name, settings); + }, + + /** + * Adds a menu item to be used in the menus of the theme. There might be multiple instances + * of this menu item for example it might be used in the main menus of the theme but also in + * the context menu so make sure that it's self contained and supports multiple instances. + * + * @method addMenuItem + * @param {String} name Menu item name to add. + * @param {Object} settings Settings object with title, cmd etc. + * @example + * // Adds a custom menu item to the editor that inserts contents when clicked + * // The context option allows you to add the menu item to an existing default menu + * tinymce.init({ + * ... + * + * setup: function(ed) { + * ed.addMenuItem('example', { + * text: 'My menu item', + * context: 'tools', + * onclick: function() { + * ed.insertContent('Hello world!!'); + * } + * }); + * } + * }); + */ + addMenuItem: function (name, settings) { + var self = this; + + if (settings.cmd) { + settings.onclick = function () { + self.execCommand(settings.cmd); + }; + } + + self.menuItems = self.menuItems || {}; + self.menuItems[name] = settings; + }, + + /** + * Adds a contextual toolbar to be rendered when the selector matches. + * + * @method addContextToolbar + * @param {function/string} predicate Predicate that needs to return true if provided strings get converted into CSS predicates. + * @param {String/Array} items String or array with items to add to the context toolbar. + */ + addContextToolbar: function (predicate, items) { + var self = this, selector; + + self.contextToolbars = self.contextToolbars || []; + + // Convert selector to predicate + if (typeof predicate == "string") { + selector = predicate; + predicate = function (elm) { + return self.dom.is(elm, selector); + }; + } + + self.contextToolbars.push({ + id: Uuid.uuid('mcet'), + predicate: predicate, + items: items + }); + }, + + /** + * Adds a custom command to the editor, you can also override existing commands with this method. + * The command that you add can be executed with execCommand. + * + * @method addCommand + * @param {String} name Command name to add/override. + * @param {addCommandCallback} callback Function to execute when the command occurs. + * @param {Object} scope Optional scope to execute the function in. + * @example + * // Adds a custom command that later can be executed using execCommand + * tinymce.init({ + * ... + * + * setup: function(ed) { + * // Register example command + * ed.addCommand('mycommand', function(ui, v) { + * ed.windowManager.alert('Hello world!! Selection: ' + ed.selection.getContent({format: 'text'})); + * }); + * } + * }); + */ + addCommand: function (name, callback, scope) { + /** + * Callback function that gets called when a command is executed. + * + * @callback addCommandCallback + * @param {Boolean} ui Display UI state true/false. + * @param {Object} value Optional value for command. + * @return {Boolean} True/false state if the command was handled or not. + */ + this.editorCommands.addCommand(name, callback, scope); + }, + + /** + * Adds a custom query state command to the editor, you can also override existing commands with this method. + * The command that you add can be executed with queryCommandState function. + * + * @method addQueryStateHandler + * @param {String} name Command name to add/override. + * @param {addQueryStateHandlerCallback} callback Function to execute when the command state retrieval occurs. + * @param {Object} scope Optional scope to execute the function in. + */ + addQueryStateHandler: function (name, callback, scope) { + /** + * Callback function that gets called when a queryCommandState is executed. + * + * @callback addQueryStateHandlerCallback + * @return {Boolean} True/false state if the command is enabled or not like is it bold. + */ + this.editorCommands.addQueryStateHandler(name, callback, scope); + }, + + /** + * Adds a custom query value command to the editor, you can also override existing commands with this method. + * The command that you add can be executed with queryCommandValue function. + * + * @method addQueryValueHandler + * @param {String} name Command name to add/override. + * @param {addQueryValueHandlerCallback} callback Function to execute when the command value retrieval occurs. + * @param {Object} scope Optional scope to execute the function in. + */ + addQueryValueHandler: function (name, callback, scope) { + /** + * Callback function that gets called when a queryCommandValue is executed. + * + * @callback addQueryValueHandlerCallback + * @return {Object} Value of the command or undefined. + */ + this.editorCommands.addQueryValueHandler(name, callback, scope); + }, + + /** + * Adds a keyboard shortcut for some command or function. + * + * @method addShortcut + * @param {String} pattern Shortcut pattern. Like for example: ctrl+alt+o. + * @param {String} desc Text description for the command. + * @param {String/Function} cmdFunc Command name string or function to execute when the key is pressed. + * @param {Object} sc Optional scope to execute the function in. + * @return {Boolean} true/false state if the shortcut was added or not. + */ + addShortcut: function (pattern, desc, cmdFunc, scope) { + this.shortcuts.add(pattern, desc, cmdFunc, scope); + }, + + /** + * Executes a command on the current instance. These commands can be TinyMCE internal commands prefixed with "mce" or + * they can be build in browser commands such as "Bold". A compleate list of browser commands is available on MSDN or Mozilla.org. + * This function will dispatch the execCommand function on each plugin, theme or the execcommand_callback option if none of these + * return true it will handle the command as a internal browser command. + * + * @method execCommand + * @param {String} cmd Command name to execute, for example mceLink or Bold. + * @param {Boolean} ui True/false state if a UI (dialog) should be presented or not. + * @param {mixed} value Optional command value, this can be anything. + * @param {Object} args Optional arguments object. + */ + execCommand: function (cmd, ui, value, args) { + return this.editorCommands.execCommand(cmd, ui, value, args); + }, + + /** + * Returns a command specific state, for example if bold is enabled or not. + * + * @method queryCommandState + * @param {string} cmd Command to query state from. + * @return {Boolean} Command specific state, for example if bold is enabled or not. + */ + queryCommandState: function (cmd) { + return this.editorCommands.queryCommandState(cmd); + }, + + /** + * Returns a command specific value, for example the current font size. + * + * @method queryCommandValue + * @param {string} cmd Command to query value from. + * @return {Object} Command specific value, for example the current font size. + */ + queryCommandValue: function (cmd) { + return this.editorCommands.queryCommandValue(cmd); + }, + + /** + * Returns true/false if the command is supported or not. + * + * @method queryCommandSupported + * @param {String} cmd Command that we check support for. + * @return {Boolean} true/false if the command is supported or not. + */ + queryCommandSupported: function (cmd) { + return this.editorCommands.queryCommandSupported(cmd); + }, + + /** + * Shows the editor and hides any textarea/div that the editor is supposed to replace. + * + * @method show + */ + show: function () { + var self = this; + + if (self.hidden) { + self.hidden = false; + + if (self.inline) { + self.getBody().contentEditable = true; + } else { + DOM.show(self.getContainer()); + DOM.hide(self.id); + } + + self.load(); + self.fire('show'); + } + }, + + /** + * Hides the editor and shows any textarea/div that the editor is supposed to replace. + * + * @method hide + */ + hide: function () { + var self = this, doc = self.getDoc(); + + if (!self.hidden) { + // Fixed bug where IE has a blinking cursor left from the editor + if (ie && doc && !self.inline) { + doc.execCommand('SelectAll'); + } + + // We must save before we hide so Safari doesn't crash + self.save(); + + if (self.inline) { + self.getBody().contentEditable = false; + + // Make sure the editor gets blurred + if (self == self.editorManager.focusedEditor) { + self.editorManager.focusedEditor = null; + } + } else { + DOM.hide(self.getContainer()); + DOM.setStyle(self.id, 'display', self.orgDisplay); + } + + self.hidden = true; + self.fire('hide'); + } + }, + + /** + * Returns true/false if the editor is hidden or not. + * + * @method isHidden + * @return {Boolean} True/false if the editor is hidden or not. + */ + isHidden: function () { + return !!this.hidden; + }, + + /** + * Sets the progress state, this will display a throbber/progess for the editor. + * This is ideal for asynchronous operations like an AJAX save call. + * + * @method setProgressState + * @param {Boolean} state Boolean state if the progress should be shown or hidden. + * @param {Number} time Optional time to wait before the progress gets shown. + * @return {Boolean} Same as the input state. + * @example + * // Show progress for the active editor + * tinymce.activeEditor.setProgressState(true); + * + * // Hide progress for the active editor + * tinymce.activeEditor.setProgressState(false); + * + * // Show progress after 3 seconds + * tinymce.activeEditor.setProgressState(true, 3000); + */ + setProgressState: function (state, time) { + this.fire('ProgressState', { state: state, time: time }); + }, + + /** + * Loads contents from the textarea or div element that got converted into an editor instance. + * This method will move the contents from that textarea or div into the editor by using setContent + * so all events etc that method has will get dispatched as well. + * + * @method load + * @param {Object} args Optional content object, this gets passed around through the whole load process. + * @return {String} HTML string that got set into the editor. + */ + load: function (args) { + var self = this, elm = self.getElement(), html; + + if (elm) { + args = args || {}; + args.load = true; + + html = self.setContent(elm.value !== undefined ? elm.value : elm.innerHTML, args); + args.element = elm; + + if (!args.no_events) { + self.fire('LoadContent', args); + } + + args.element = elm = null; + + return html; + } + }, + + /** + * Saves the contents from a editor out to the textarea or div element that got converted into an editor instance. + * This method will move the HTML contents from the editor into that textarea or div by getContent + * so all events etc that method has will get dispatched as well. + * + * @method save + * @param {Object} args Optional content object, this gets passed around through the whole save process. + * @return {String} HTML string that got set into the textarea/div. + */ + save: function (args) { + var self = this, elm = self.getElement(), html, form; + + if (!elm || !self.initialized) { + return; + } + + args = args || {}; + args.save = true; + + args.element = elm; + html = args.content = self.getContent(args); + + if (!args.no_events) { + self.fire('SaveContent', args); + } + + // Always run this internal event + if (args.format == 'raw') { + self.fire('RawSaveContent', args); + } + + html = args.content; + + if (!/TEXTAREA|INPUT/i.test(elm.nodeName)) { + // Update DIV element when not in inline mode + if (!self.inline) { + elm.innerHTML = html; + } + + // Update hidden form element + if ((form = DOM.getParent(self.id, 'form'))) { + each(form.elements, function (elm) { + if (elm.name == self.id) { + elm.value = html; + return false; + } + }); + } + } else { + elm.value = html; + } + + args.element = elm = null; + + if (args.set_dirty !== false) { + self.setDirty(false); + } + + return html; + }, + + /** + * Sets the specified content to the editor instance, this will cleanup the content before it gets set using + * the different cleanup rules options. + * + * @method setContent + * @param {String} content Content to set to editor, normally HTML contents but can be other formats as well. + * @param {Object} args Optional content object, this gets passed around through the whole set process. + * @return {String} HTML string that got set into the editor. + * @example + * // Sets the HTML contents of the activeEditor editor + * tinymce.activeEditor.setContent('some html'); + * + * // Sets the raw contents of the activeEditor editor + * tinymce.activeEditor.setContent('some html', {format: 'raw'}); + * + * // Sets the content of a specific editor (my_editor in this example) + * tinymce.get('my_editor').setContent(data); + * + * // Sets the bbcode contents of the activeEditor editor if the bbcode plugin was added + * tinymce.activeEditor.setContent('[b]some[/b] html', {format: 'bbcode'}); + */ + setContent: function (content, args) { + var self = this, body = self.getBody(), forcedRootBlockName, padd; + + // Setup args object + args = args || {}; + args.format = args.format || 'html'; + args.set = true; + args.content = content; + + // Do preprocessing + if (!args.no_events) { + self.fire('BeforeSetContent', args); + } + + content = args.content; + + // Padd empty content in Gecko and Safari. Commands will otherwise fail on the content + // It will also be impossible to place the caret in the editor unless there is a BR element present + if (content.length === 0 || /^\s+$/.test(content)) { + padd = ie && ie < 11 ? '' : '\xa0
').append(p),t.setStartAfter(o[0].firstChild.firstChild),t.setEndAfter(p)):(o.empty().append("\xa0").append(p).append("\xa0"),t.setStart(o[0].firstChild,1),t.setEnd(o[0].lastChild,0)),o.css({top:i.getPos(n,u.getBody()).y}),o[0].focus(),a=u.selection.getSel(),a.removeAllRanges(),a.addRange(t),u.$("*[data-mce-selected]").removeAttr("data-mce-selected"),n.setAttribute("data-mce-selected",1),ue=n,ne(),t)):null)}function ee(){ue&&(ue.removeAttribute("data-mce-selected"),u.$("#"+le).remove(),ue=null)}function te(){se.destroy(),ue=null}function ne(){se.hide()}var re=u.getBody(),ie=new t(re),oe=y(g,ie.next),ae=y(g,ie.prev),se=new o(u.getBody(),S),le="sel-"+u.dom.uniqueId(),ue,ce=u.$;return e.ceFalse&&(G(),J()),{showBlockCaretContainer:W,hideFakeCaret:ne,destroy:te}}var y=f.curry,b=l.isContentEditableTrue,C=l.isContentEditableFalse,x=l.isElement,w=i.isAfterContentEditableFalse,E=i.isBeforeContentEditableFalse,N=u.getSelectedNode;return v}),r(it,[],function(){var e=0,t=function(){var e=function(){return Math.round(4294967295*Math.random()).toString(36)},t=(new Date).getTime();return"s"+t.toString(36)+e()+e()+e()},n=function(n){return n+e++ +t()};return{uuid:n}}),r(ot,[],function(){var e=function(e,t,n){var r=e.sidebars?e.sidebars:[];r.push({name:t,settings:n}),e.sidebars=r};return{add:e}}),r(at,[w,g,N,R,A,O,P,Y,J,te,ne,re,le,ue,E,f,Le,Ie,B,L,ze,d,m,c,Ue,We,Ve,Ge,rt,it,ot,Ke],function(e,n,r,i,o,a,s,l,u,c,d,f,p,h,m,g,v,y,b,C,x,w,E,N,_,S,k,T,R,A,B,D){function L(e,t,i){var o=this,a,s,l;a=o.documentBaseUrl=i.documentBaseURL,s=i.baseURI,l=i.defaultSettings,t=H({id:e,theme:"modern",delta_width:0,delta_height:0,popup_css:"",plugins:"",document_base_url:a,add_form_submit_trigger:!0,submit_patch:!0,add_unload_trigger:!0,convert_urls:!0,relative_urls:!0,remove_script_host:!0,object_resizing:!0,doctype:"",visual:!0,font_size_style_values:"xx-small,x-small,small,medium,large,x-large,xx-large",font_size_legacy_values:"xx-small,small,medium,large,x-large,xx-large,300%",forced_root_block:"p",hidden_input:!0,padd_empty_editor:!0,render_ui:!0,indentation:"30px",inline_styles:!0,convert_fonts_to_spans:!0,indent:"simple",indent_before:"p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,th,ul,ol,li,dl,dt,dd,area,table,thead,tfoot,tbody,tr,section,article,hgroup,aside,figure,figcaption,option,optgroup,datalist",indent_after:"p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,th,ul,ol,li,dl,dt,dd,area,table,thead,tfoot,tbody,tr,section,article,hgroup,aside,figure,figcaption,option,optgroup,datalist",validate:!0,entity_encoding:"named",url_converter:o.convertURL,url_converter_scope:o,ie7_compat:!0},l,t),l&&l.external_plugins&&t.external_plugins&&(t.external_plugins=H({},l.external_plugins,t.external_plugins)),o.settings=t,r.language=t.language||"en",r.languageLoad=t.language_load,r.baseURL=i.baseURL,o.id=t.id=e,o.setDirty(!1),o.plugins={},o.documentBaseURI=new h(t.document_base_url||a,{base_uri:s}),o.baseURI=s,o.contentCSS=[],o.contentStyles=[],o.shortcuts=new k(o),o.loadedCSS={},o.editorCommands=new p(o),o.suffix=i.suffix,o.editorManager=i,o.inline=t.inline,o.settings.content_editable=o.inline,t.cache_suffix&&(w.cacheSuffix=t.cache_suffix.replace(/^[\?\&]+/,"")),t.override_viewport===!1&&(w.overrideViewPort=!1),i.fire("SetupEditor",o),o.execCallback("setup",o),o.$=n.overrideDefaults(function(){return{context:o.inline?o.getBody():o.getDoc(),element:o.getBody()}})}var M=e.DOM,P=r.ThemeManager,O=r.PluginManager,H=E.extend,I=E.each,F=E.explode,z=E.inArray,U=E.trim,W=E.resolve,V=g.Event,$=w.gecko,q=w.ie;return L.prototype={render:function(){function e(){M.unbind(window,"ready",e),n.render()}function t(){var e=m.ScriptLoader;if(r.language&&"en"!=r.language&&!r.language_url&&(r.language_url=n.editorManager.baseURL+"/langs/"+r.language+".js"),r.language_url&&e.add(r.language_url),r.theme&&"function"!=typeof r.theme&&"-"!=r.theme.charAt(0)&&!P.urls[r.theme]){var t=r.theme_url;t=t?n.documentBaseURI.toAbsolute(t):"themes/"+r.theme+"/theme"+o+".js",P.load(r.theme,t)}E.isArray(r.plugins)&&(r.plugins=r.plugins.join(" ")),I(r.external_plugins,function(e,t){O.load(t,e),r.plugins+=" "+t}),I(r.plugins.split(/[ ,]/),function(e){if(e=U(e),e&&!O.urls[e])if("-"==e.charAt(0)){e=e.substr(1,e.length);var t=O.dependencies(e);I(t,function(e){var t={prefix:"plugins/",resource:e,suffix:"/plugin"+o+".js"};e=O.createUrl(t,e),O.load(e.resource,e)})}else O.load(e,{prefix:"plugins/",resource:e,suffix:"/plugin"+o+".js"})}),e.loadQueue(function(){n.removed||n.init()},n,function(e){D.pluginLoadError(n,e[0]),n.removed||n.init()})}var n=this,r=n.settings,i=n.id,o=n.suffix;if(!V.domLoaded)return void M.bind(window,"ready",e);if(n.getElement()&&w.contentEditable){r.inline?n.inline=!0:(n.orgVisibility=n.getElement().style.visibility,n.getElement().style.visibility="hidden");var a=n.getElement().form||M.getParent(i,"form");a&&(n.formElement=a,r.hidden_input&&!/TEXTAREA|INPUT/i.test(n.getElement().nodeName)&&(M.insertAfter(M.create("input",{type:"hidden",name:i}),i),n.hasHiddenInput=!0),n.formEventDelegate=function(e){n.fire(e.type,e)},M.bind(a,"submit reset",n.formEventDelegate),n.on("reset",function(){n.setContent(n.startContent,{format:"raw"})}),!r.submit_patch||a.submit.nodeType||a.submit.length||a._mceOldSubmit||(a._mceOldSubmit=a.submit,a.submit=function(){return n.editorManager.triggerSave(),n.setDirty(!1),a._mceOldSubmit(a)})),n.windowManager=new v(n),n.notificationManager=new y(n),"xml"==r.encoding&&n.on("GetContent",function(e){e.save&&(e.content=M.encode(e.content))}),r.add_form_submit_trigger&&n.on("submit",function(){n.initialized&&n.save()}),r.add_unload_trigger&&(n._beforeUnload=function(){!n.initialized||n.destroyed||n.isHidden()||n.save({format:"raw",no_events:!0,set_dirty:!1})},n.editorManager.on("BeforeUnload",n._beforeUnload)),n.editorManager.add(n),t()}},init:function(){function e(n){var r=O.get(n),i,o;if(i=O.urls[n]||t.documentBaseUrl.replace(/\/$/,""),n=U(n),r&&z(m,n)===-1){if(I(O.dependencies(n),function(t){e(t)}),t.plugins[n])return;o=new r(t,i,t.$),t.plugins[n]=o,o.init&&(o.init(t,i),m.push(n))}}var t=this,n=t.settings,r=t.getElement(),i,o,a,s,l,u,c,d,f,p,h,m=[];if(t.rtl=n.rtl_ui||t.editorManager.i18n.rtl,t.editorManager.i18n.setCode(n.language),n.aria_label=n.aria_label||M.getAttrib(r,"aria-label",t.getLang("aria.rich_text_area")),t.fire("ScriptsLoaded"),n.theme&&("function"!=typeof n.theme?(n.theme=n.theme.replace(/-/,""),u=P.get(n.theme),t.theme=new u(t,P.urls[n.theme]),t.theme.init&&t.theme.init(t,P.urls[n.theme]||t.documentBaseUrl.replace(/\/$/,""),t.$)):t.theme=n.theme),I(n.plugins.replace(/\-/g,"").split(/[ ,]/),e),n.render_ui&&t.theme&&(t.orgDisplay=r.style.display,"function"!=typeof n.theme?(i=n.width||r.style.width||r.offsetWidth,o=n.height||r.style.height||r.offsetHeight,a=n.min_height||100,p=/^[0-9\.]+(|px)$/i,p.test(""+i)&&(i=Math.max(parseInt(i,10),100)),p.test(""+o)&&(o=Math.max(parseInt(o,10),a)),l=t.theme.renderUI({targetNode:r,width:i,height:o,deltaWidth:n.delta_width,deltaHeight:n.delta_height}),n.content_editable||(o=(l.iframeHeight||o)+("number"==typeof o?l.deltaHeight||0:""),o",n.document_base_url!=t.documentBaseUrl&&(t.iframeHTML+=']*>( | |\s|\u00a0|
|)<\/p>[\r\n]*|
[\r\n]*)$/,"")}),n.load({initial:!0,format:"html"}),n.startContent=n.getContent({format:"raw"}),n.initialized=!0,n.bindPendingEventDelegates(),n.fire("init"),n.focus(!0),n.nodeChanged({initial:!0}),n.execCallback("init_instance_callback",n),n.on("compositionstart compositionend",function(e){n.composing="compositionstart"===e.type}),n.contentStyles.length>0&&(m="",I(n.contentStyles,function(e){m+=e+"\r\n"}),n.dom.addStyle(m)),I(n.contentCSS,function(e){n.loadedCSS[e]||(n.dom.loadCSS(e),n.loadedCSS[e]=!0)}),r.auto_focus&&N.setEditorTimeout(n,function(){var e;e=r.auto_focus===!0?n:n.editorManager.get(r.auto_focus),e.destroyed||e.focus()},100),s=p=h=null},focus:function(e){function t(e){return n.dom.getParent(e,function(e){return"true"===n.dom.getContentEditable(e)})}var n=this,r=n.selection,i=n.settings.content_editable,o,a,s=n.getDoc(),l=n.getBody(),u;if(!e){if(o=r.getRng(),o.item&&(a=o.item(0)),n.quirks.refreshContentEditable(),u=t(r.getNode()),n.$.contains(l,u))return u.focus(),r.normalize(),void n.editorManager.setActive(n);if(i||(w.opera||n.getBody().focus(),n.getWin().focus()),$||i){if(l.setActive)try{l.setActive()}catch(c){l.focus()}else l.focus();i&&r.normalize()}a&&a.ownerDocument==s&&(o=s.body.createControlRange(),o.addElement(a),o.select())}n.editorManager.setActive(n)},execCallback:function(e){var t=this,n=t.settings[e],r;if(n)return t.callbackLookup&&(r=t.callbackLookup[e])&&(n=r.func,r=r.scope),"string"==typeof n&&(r=n.replace(/\.\w+$/,""),r=r?W(r):0,n=W(n),t.callbackLookup=t.callbackLookup||{},t.callbackLookup[e]={func:n,scope:r}),n.apply(r||t,Array.prototype.slice.call(arguments,1))},translate:function(e){var t=this.settings.language||"en",n=this.editorManager.i18n;return e?(e=n.data[t+"."+e]||e.replace(/\{\#([^\}]+)\}/g,function(e,r){return n.data[t+"."+r]||"{#"+r+"}"}),this.editorManager.translate(e)):""},getLang:function(e,n){return this.editorManager.i18n.data[(this.settings.language||"en")+"."+e]||(n!==t?n:"{#"+e+"}")},getParam:function(e,t,n){var r=e in this.settings?this.settings[e]:t,i;return"hash"===n?(i={},"string"==typeof r?I(r.indexOf("=")>0?r.split(/[;,](?![^=;,]*(?:[;,]|$))/):r.split(","),function(e){e=e.split("="),e.length>1?i[U(e[0])]=U(e[1]):i[U(e[0])]=U(e)}):i=r,i):r},nodeChanged:function(e){this._nodeChangeDispatcher.nodeChanged(e)},addButton:function(e,t){var n=this;t.cmd&&(t.onclick=function(){n.execCommand(t.cmd)}),t.text||t.icon||(t.icon=e),n.buttons=n.buttons||{},t.tooltip=t.tooltip||t.title,n.buttons[e]=t},addSidebar:function(e,t){return B.add(this,e,t)},addMenuItem:function(e,t){var n=this;t.cmd&&(t.onclick=function(){n.execCommand(t.cmd)}),n.menuItems=n.menuItems||{},n.menuItems[e]=t},addContextToolbar:function(e,t){var n=this,r;n.contextToolbars=n.contextToolbars||[],"string"==typeof e&&(r=e,e=function(e){return n.dom.is(e,r)}),n.contextToolbars.push({id:A.uuid("mcet"),predicate:e,items:t})},addCommand:function(e,t,n){this.editorCommands.addCommand(e,t,n)},addQueryStateHandler:function(e,t,n){this.editorCommands.addQueryStateHandler(e,t,n)},addQueryValueHandler:function(e,t,n){this.editorCommands.addQueryValueHandler(e,t,n)},addShortcut:function(e,t,n,r){this.shortcuts.add(e,t,n,r)},execCommand:function(e,t,n,r){return this.editorCommands.execCommand(e,t,n,r)},queryCommandState:function(e){return this.editorCommands.queryCommandState(e)},queryCommandValue:function(e){return this.editorCommands.queryCommandValue(e)},queryCommandSupported:function(e){return this.editorCommands.queryCommandSupported(e)},show:function(){var e=this;e.hidden&&(e.hidden=!1,e.inline?e.getBody().contentEditable=!0:(M.show(e.getContainer()),M.hide(e.id)),e.load(),e.fire("show"))},hide:function(){var e=this,t=e.getDoc();e.hidden||(q&&t&&!e.inline&&t.execCommand("SelectAll"),e.save(),e.inline?(e.getBody().contentEditable=!1,e==e.editorManager.focusedEditor&&(e.editorManager.focusedEditor=null)):(M.hide(e.getContainer()),M.setStyle(e.id,"display",e.orgDisplay)),e.hidden=!0,e.fire("hide"))},isHidden:function(){return!!this.hidden},setProgressState:function(e,t){this.fire("ProgressState",{state:e,time:t})},load:function(e){var n=this,r=n.getElement(),i;if(r)return e=e||{},e.load=!0,i=n.setContent(r.value!==t?r.value:r.innerHTML,e),e.element=r,e.no_events||n.fire("LoadContent",e),e.element=r=null,i},save:function(e){var t=this,n=t.getElement(),r,i;if(n&&t.initialized)return e=e||{},e.save=!0,e.element=n,r=e.content=t.getContent(e),e.no_events||t.fire("SaveContent",e),"raw"==e.format&&t.fire("RawSaveContent",e),r=e.content,/TEXTAREA|INPUT/i.test(n.nodeName)?n.value=r:(t.inline||(n.innerHTML=r),(i=M.getParent(t.id,"form"))&&I(i.elements,function(e){if(e.name==t.id)return e.value=r,!1})),e.element=n=null,e.set_dirty!==!1&&t.setDirty(!1),r},setContent:function(e,t){var n=this,r=n.getBody(),i,o;return t=t||{},t.format=t.format||"html",t.set=!0,t.content=e,t.no_events||n.fire("BeforeSetContent",t),e=t.content,0===e.length||/^\s+$/.test(e)?(o=q&&q<11?"":'
',"TABLE"==r.nodeName?e="
\xa0
').append(l),b.setStartAfter(d[0].firstChild.firstChild),b.setEndAfter(l)):(d.empty().append("\xa0").append(l).append("\xa0"),b.setStart(d[0].firstChild,1),b.setEnd(d[0].lastChild,0)),d.css({top:o.getPos(c,e.getBody()).y}),d[0].focus(),f=e.selection.getSel(),f.removeAllRanges(),f.addRange(b),e.$("*[data-mce-selected]").removeAttr("data-mce-selected"),c.setAttribute("data-mce-selected",1),Z=c,Y(),b)):null)}function W(){Z&&(Z.removeAttribute("data-mce-selected"),e.$("#"+da).remove(),Z=null)}function X(){ca.destroy(),Z=null}function Y(){ca.hide()}var Z,$=e.getBody(),_=new b($),aa=t(r,_.next),ba=t(r,_.prev),ca=new g(e.getBody(),z),da="sel-"+e.dom.uniqueId();return a.ceFalse&&(R(),S()),{showBlockCaretContainer:O,hideFakeCaret:Y,destroy:X}}var t=n.curry,u=j.isContentEditableTrue,v=j.isContentEditableFalse,w=f.isAfterContentEditableFalse,x=f.isBeforeContentEditableFalse,y=k.getSelectedNode;return s}),g("6l",["e"],function(a){function b(b,c,d){for(var e=[];c&&c!=b;c=c.parentNode)e.push(a.nodeIndex(c,d));return e}function c(a,b){var c,d,e;for(d=a,c=b.length-1;c>=0;c--){if(e=d.childNodes,b[c]>e.length-1)return null;d=e[b[c]]}return d}return{create:b,resolve:c}}),g("5u",["p","h","c","6l","i","d","6","9","5","1k","1n","44"],function(a,b,c,d,e,f,g,h,i,j,k,l){return function(c){function d(a,b){try{c.getDoc().execCommand(a,!1,b)}catch(a){}}function m(){var a=c.getDoc().documentMode;return a?a:6}function n(a){return a.isDefaultPrevented()}function o(a){var b,d;a.dataTransfer&&(c.selection.isCollapsed()&&"IMG"==a.target.tagName&&_.select(a.target),b=c.selection.getContent(),b.length>0&&(d=ga+escape(c.id)+","+escape(b),a.dataTransfer.setData(ha,d)))}function p(a){var b;return a.dataTransfer&&(b=a.dataTransfer.getData(ha),b&&b.indexOf(ga)>=0)?(b=b.substr(ga.length).split(","),{id:unescape(b[0]),html:unescape(b[1])}):null}function q(a,b){c.queryCommandSupported("mceInsertClipboardContent")?c.execCommand("mceInsertClipboardContent",!1,{content:a,internal:b}):c.execCommand("mceInsertContent",!1,a)}function r(){function a(a){var b=$.create("body"),c=a.cloneContents();return b.appendChild(c),_.serializer.serialize(b,{format:"html"})}function d(d){if(!d.setStart){if(d.item)return!1;var e=d.duplicate();return e.moveToElementText(c.getBody()),b.compareRanges(d,e)}var f=a(d),g=$.createRng();g.selectNode(c.getBody());var h=a(g);return f===h}c.on("keydown",function(a){var b,e,f=a.keyCode;if(!n(a)&&(f==Z||f==Y)){if(b=c.selection.isCollapsed(),e=c.getBody(),b&&!$.isEmpty(e))return;if(!b&&!d(c.selection.getRng()))return;a.preventDefault(),c.setContent(""),e.firstChild&&$.isBlock(e.firstChild)?c.selection.setCursorLocation(e.firstChild,0):c.selection.setCursorLocation(e,0),c.nodeChanged()}})}function s(){c.shortcuts.add("meta+a",null,"SelectAll")}function t(){c.settings.content_editable||$.bind(c.getDoc(),"mousedown mouseup",function(a){var b;if(a.target==c.getDoc().documentElement)if(b=_.getRng(),c.getBody().focus(),"mousedown"==a.type){if(j.isCaretContainer(b.startContainer))return;_.placeCaretAt(a.clientX,a.clientY)}else _.setRng(b)})}function u(){c.on("keydown",function(a){if(!n(a)&&a.keyCode===Y){if(!c.getBody().getElementsByTagName("hr").length)return;if(_.isCollapsed()&&0===_.getRng(!0).startOffset){var b=_.getNode(),d=b.previousSibling;if("HR"==b.nodeName)return $.remove(b),void a.preventDefault();d&&d.nodeName&&"hr"===d.nodeName.toLowerCase()&&($.remove(d),a.preventDefault())}}})}function v(){window.Range.prototype.getClientRects||c.on("mousedown",function(a){if(!n(a)&&"HTML"===a.target.nodeName){var b=c.getBody();b.blur(),i.setEditorTimeout(c,function(){b.focus()})}})}function w(){c.on("click",function(a){var b=a.target;/^(IMG|HR)$/.test(b.nodeName)&&"false"!==$.getContentEditableParent(b)&&(a.preventDefault(),c.selection.select(b),c.nodeChanged()),"A"==b.nodeName&&$.hasClass(b,"mce-item-anchor")&&(a.preventDefault(),_.select(b))})}function x(){function a(){var a=$.getAttribs(_.getStart().cloneNode(!1));return function(){var b=_.getStart();b!==c.getBody()&&($.setAttrib(b,"style",null),X(a,function(a){b.setAttributeNode(a.cloneNode(!0))}))}}function b(){return!_.isCollapsed()&&$.getParent(_.getStart(),$.isBlock)!=$.getParent(_.getEnd(),$.isBlock)}c.on("keypress",function(d){var e;if(!n(d)&&(8==d.keyCode||46==d.keyCode)&&b())return e=a(),c.getDoc().execCommand("delete",!1,null),e(),d.preventDefault(),!1}),$.bind(c.getDoc(),"cut",function(d){var e;!n(d)&&b()&&(e=a(),i.setEditorTimeout(c,function(){e()}))})}function y(){document.body.setAttribute("role","application")}function z(){c.on("keydown",function(a){if(!n(a)&&a.keyCode===Y&&_.isCollapsed()&&0===_.getRng(!0).startOffset){var b=_.getNode().previousSibling;if(b&&b.nodeName&&"table"===b.nodeName.toLowerCase())return a.preventDefault(),!1}})}function A(){m()>7||(d("RespectVisibilityInDesign",!0),c.contentStyles.push(".mceHideBrInPre pre br {display: none}"),$.addClass(c.getBody(),"mceHideBrInPre"),ba.addNodeFilter("pre",function(a){for(var b,c,d,f,g=a.length;g--;)for(b=a[g].getAll("br"),c=b.length;c--;)d=b[c],f=d.prev,f&&3===f.type&&"\n"!=f.value.charAt(f.value-1)?f.value+="\n":d.parent.insert(new e("#text",3),d,!0).value="\n"}),ca.addNodeFilter("pre",function(a){for(var b,c,d,e,f=a.length;f--;)for(b=a[f].getAll("br"),c=b.length;c--;)d=b[c],e=d.prev,e&&3==e.type&&(e.value=e.value.replace(/\r?\n$/,""))}))}function B(){$.bind(c.getBody(),"mouseup",function(){var a,b=_.getNode();"IMG"==b.nodeName&&((a=$.getStyle(b,"width"))&&($.setAttrib(b,"width",a.replace(/[^0-9%]+/g,"")),$.setStyle(b,"width","")),(a=$.getStyle(b,"height"))&&($.setAttrib(b,"height",a.replace(/[^0-9%]+/g,"")),$.setStyle(b,"height","")))})}function C(){c.on("keydown",function(b){var d,e,f,g,h;if(!n(b)&&b.keyCode==a.BACKSPACE&&(d=_.getRng(),e=d.startContainer,f=d.startOffset,g=$.getRoot(),h=e,d.collapsed&&0===f)){for(;h&&h.parentNode&&h.parentNode.firstChild==h&&h.parentNode!=g;)h=h.parentNode;"BLOCKQUOTE"===h.tagName&&(c.formatter.toggle("blockquote",null,h),d=$.createRng(),d.setStart(e,0),d.setEnd(e,0),_.setRng(d))}})}function D(){function a(){U(),d("StyleWithCSS",!1),d("enableInlineTableEditing",!1),aa.object_resizing||d("enableObjectResizing",!1)}aa.readonly||c.on("BeforeExecCommand MouseDown",a)}function E(){function a(){X($.select("a"),function(a){var b=a.parentNode,c=$.getRoot();if(b.lastChild===a){for(;b&&!$.isBlock(b);){if(b.parentNode.lastChild!==b||b===c)return;b=b.parentNode}$.add(b,"br",{"data-mce-bogus":1})}})}c.on("SetContent ExecCommand",function(b){"setcontent"!=b.type&&"mceInsertLink"!==b.command||a()})}function F(){aa.forced_root_block&&c.on("init",function(){d("DefaultParagraphSeparator",aa.forced_root_block)})}function G(){c.on("keydown",function(a){var b;n(a)||a.keyCode!=Y||(b=c.getDoc().selection.createRange(),b&&b.item&&(a.preventDefault(),c.undoManager.beforeChange(),$.remove(b.item(0)),c.undoManager.add()))})}function H(){var a;m()>=10&&(a="",X("p div h1 h2 h3 h4 h5 h6".split(" "),function(b,c){a+=(c>0?",":"")+b+":empty"}),c.contentStyles.push(a+"{padding-right: 1px !important}"))}function I(){m()<9&&(ba.addNodeFilter("noscript",function(a){for(var b,c,d=a.length;d--;)b=a[d],c=b.firstChild,c&&b.attr("data-mce-innertext",c.value)}),ca.addNodeFilter("noscript",function(a){for(var b,c,d,g=a.length;g--;)b=a[g],c=a[g].firstChild,c?c.value=f.decode(c.value):(d=b.attributes.map["data-mce-innertext"],d&&(b.attr("data-mce-innertext",null),c=new e("#text",3),c.value=d,c.raw=!0,b.append(c)))}))}function J(){function a(a,b){var c=h.createTextRange();try{c.moveToPoint(a,b)}catch(a){c=null}return c}function b(b){var d;b.button?(d=a(b.x,b.y),d&&(d.compareEndPoints("StartToStart",e)>0?d.setEndPoint("StartToStart",e):d.setEndPoint("EndToEnd",e),d.select())):c()}function c(){var a=g.selection.createRange();e&&!a.item&&0===a.compareEndPoints("StartToEnd",a)&&e.select(),$.unbind(g,"mouseup",c),$.unbind(g,"mousemove",b),e=d=0}var d,e,f,g=$.doc,h=g.body;g.documentElement.unselectable=!0,$.bind(g,"mousedown contextmenu",function(h){if("HTML"===h.target.nodeName){if(d&&c(),f=g.documentElement,f.scrollHeight>f.clientHeight)return;d=1,e=a(h.x,h.y),e&&($.bind(g,"mouseup",c),$.bind(g,"mousemove",b),$.getRoot().focus(),e.select())}})}function K(){c.on("keyup focusin mouseup",function(b){65==b.keyCode&&a.metaKeyPressed(b)||_.normalize()},!0)}function L(){c.contentStyles.push("img:-moz-broken {-moz-force-broken-image-icon:1;min-width:24px;min-height:24px}")}function M(){c.inline||c.on("keydown",function(){document.activeElement==document.body&&c.getWin().focus()})}function N(){c.inline||(c.contentStyles.push("body {min-height: 150px}"),c.on("click",function(a){var b;if("HTML"==a.target.nodeName){if(g.ie>11)return void c.getBody().focus();b=c.selection.getRng(),c.getBody().focus(),c.selection.setRng(b),c.selection.normalize(),c.nodeChanged()}}))}function O(){g.mac&&c.on("keydown",function(b){!a.metaKeyPressed(b)||b.shiftKey||37!=b.keyCode&&39!=b.keyCode||(b.preventDefault(),c.selection.getSel().modify("move",37==b.keyCode?"backward":"forward","lineboundary"))})}function P(){d("AutoUrlDetect",!1)}function Q(){c.on("click",function(a){var b=a.target;do if("A"===b.tagName)return void a.preventDefault();while(b=b.parentNode)}),c.contentStyles.push(".mce-content-body {-webkit-touch-callout: none}")}function R(){c.on("init",function(){c.dom.bind(c.getBody(),"submit",function(a){a.preventDefault()})})}function S(){ba.addNodeFilter("br",function(a){for(var b=a.length;b--;)"Apple-interchange-newline"==a[b].attr("class")&&a[b].remove()})}function T(){c.on("dragstart",function(a){o(a)}),c.on("drop",function(a){if(!n(a)){var d=p(a);if(d&&d.id!=c.id){a.preventDefault();var e=b.getCaretRangeFromPoint(a.x,a.y,c.getDoc());_.setRng(e),q(d.html,!0)}}})}function U(){}function V(){var a;return da?(a=c.selection.getSel(),!a||!a.rangeCount||0===a.rangeCount):0}function W(){function b(a){var b=new l(a.getBody()),c=a.selection.getRng(),d=k.fromRangeStart(c),e=k.fromRangeEnd(c),f=b.prev(d),g=b.next(e);return!a.selection.isCollapsed()&&(!f||f.isAtStart()&&d.isEqual(f))&&(!g||g.isAtEnd()&&d.isEqual(g))}c.on("keypress",function(d){!n(d)&&!_.isCollapsed()&&d.charCode>31&&!a.metaKeyPressed(d)&&b(c)&&(d.preventDefault(),c.setContent(String.fromCharCode(d.charCode)),c.selection.select(c.getBody(),!0),c.selection.collapse(!1),c.nodeChanged())}),c.on("keydown",function(a){var d=a.keyCode;n(a)||d!=Z&&d!=Y||b(c)&&(a.preventDefault(),c.setContent(""),c.nodeChanged())})}var X=h.each,Y=a.BACKSPACE,Z=a.DELETE,$=c.dom,_=c.selection,aa=c.settings,ba=c.parser,ca=c.serializer,da=g.gecko,ea=g.ie,fa=g.webkit,ga="data:text/mce-internal,",ha=ea?"Text":"URL";return C(),r(),g.windowsPhone||K(),fa&&(W(),t(),w(),F(),R(),z(),S(),g.iOS?(M(),N(),Q()):s()),ea&&g.ie<11&&(u(),y(),A(),B(),G(),H(),I(),J()),g.ie>=11&&(N(),z()),g.ie&&(s(),P(),T()),da&&(W(),u(),v(),x(),D(),E(),L(),O(),z()),{refreshContentEditable:U,isHidden:V}}}),g("54",["1x","4b","5o","e","s","o","5p","25","5q","t","l","i","j","5r","5s","5t","u","5","5u","9"],function(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t){var u=d.DOM,v=function(a){var b=new k(a.settings,a.schema);return b.addAttributeFilter("src,href,style,tabindex",function(b,c){for(var d,e,f,g=b.length,h=a.dom;g--;)if(d=b[g],e=d.attr(c),f="data-mce-"+c,!d.attributes.map[f]){if(0===e.indexOf("data:")||0===e.indexOf("blob:"))continue;"style"===c?(e=h.serializeStyle(h.parseStyle(e),d.name),e.length||(e=null),d.attr(f,e),d.attr(c,e)):"tabindex"===c?(d.attr(f,e),d.attr(c,null)):d.attr(f,a.convertURL(e,c,d.name))}}),b.addNodeFilter("script",function(a){for(var b,c,d=a.length;d--;)b=a[d],c=b.attr("type")||"no/type",0!==c.indexOf("mce-")&&b.attr("type","mce-"+c)}),b.addNodeFilter("#cdata",function(a){for(var b,c=a.length;c--;)b=a[c],b.type=8,b.name="#comment",b.value="[CDATA["+b.value+"]]"}),b.addNodeFilter("p,h1,h2,h3,h4,h5,h6,div",function(b){for(var c,d=b.length,e=a.schema.getNonEmptyElements();d--;)c=b[d],c.isEmpty(e)&&0===c.getAll("br").length&&(c.append(new l("br",1)).shortEnded=!0)}),b},w=function(a){a.settings.auto_focus&&r.setEditorTimeout(a,function(){var b;b=a.settings.auto_focus===!0?a:a.editorManager.get(a.settings.auto_focus),b.destroyed||b.focus()},100)},x=function(a){a.bindPendingEventDelegates(),a.initialized=!0,a.fire("init"),a.focus(!0),a.nodeChanged({initial:!0}),a.execCallback("init_instance_callback",a),w(a)},y=function(k,l){var r,w,y=k.settings,z=k.getElement(),A=k.getDoc();y.inline||(k.getElement().style.visibility=k.orgVisibility),l||y.content_editable||(A.open(),A.write(k.iframeHTML),A.close()),y.content_editable&&(k.on("remove",function(){var a=this.getBody();u.removeClass(a,"mce-content-body"),u.removeClass(a,"mce-edit-focus"),u.setAttrib(a,"contentEditable",null)}),u.addClass(z,"mce-content-body"),k.contentDocument=A=y.content_document||a,k.contentWindow=y.content_window||b,k.bodyElement=z,y.content_document=y.content_window=null,y.root_name=z.nodeName.toLowerCase()),r=k.getBody(),r.disabled=!0,k.readonly=y.readonly,k.readonly||(k.inline&&"static"===u.getStyle(r,"position",!0)&&(r.style.position="relative"),r.contentEditable=k.getParam("content_editable_state",!0)),r.disabled=!1,k.editorUpload=new g(k),k.schema=new m(y),k.dom=new d(A,{keep_values:!0,url_converter:k.convertURL,url_converter_scope:k,hex_colors:y.force_hex_style_colors,class_filter:y.class_filter, +update_styles:!0,root_element:k.inline?k.getBody():null,collect:y.content_editable,schema:k.schema,onSetAttrib:function(a){k.fire("SetAttrib",a)}}),k.parser=v(k),k.serializer=new f(y,k),k.selection=new e(k.dom,k.getWin(),k.serializer,k),k.formatter=new j(k),k.undoManager=new q(k),k._nodeChangeDispatcher=new o(k),k._selectionOverrides=new p(k),c.setup(k),n.setup(k),i.setup(k),k.fire("PreInit"),y.browser_spellcheck||y.gecko_spellcheck||(A.body.spellcheck=!1,u.setAttrib(r,"spellcheck","false")),k.quirks=new s(k),k.fire("PostRender"),y.directionality&&(r.dir=y.directionality),y.nowrap&&(r.style.whiteSpace="nowrap"),y.protect&&k.on("BeforeSetContent",function(a){t.each(y.protect,function(b){a.content=a.content.replace(b,function(a){return""})})}),k.on("SetContent",function(){k.addVisual(k.getBody())}),y.padd_empty_editor&&k.on("PostProcess",function(a){a.content=a.content.replace(/^(]*>( | |\s|\u00a0|
|)<\/p>[\r\n]*|
[\r\n]*)$/,"")}),k.load({initial:!0,format:"html"}),k.startContent=k.getContent({format:"raw"}),k.on("compositionstart compositionend",function(a){k.composing="compositionstart"===a.type}),k.contentStyles.length>0&&(w="",t.each(k.contentStyles,function(a){w+=a+"\r\n"}),k.dom.addStyle(w)),k.dom.styleSheetLoader.loadAll(k.contentCSS,function(a){x(k)},function(a){x(k),h.contentCssError(k,a)})};return{initContentBody:y}}),g("4d",["g"],function(a){return a.PluginManager}),g("4e",["g"],function(a){return a.ThemeManager}),g("4c",["1x","4b","e","6","54","4d","4e","9","24"],function(a,b,c,d,e,f,g,h,i){var j=c.DOM,k=function(a,b,c){var d,e,g=f.get(c);if(d=f.urls[c]||a.documentBaseUrl.replace(/\/$/,""),c=h.trim(c),g&&h.inArray(b,c)===-1){if(h.each(f.dependencies(c),function(c){k(a,b,c)}),a.plugins[c])return;e=new g(a,d,a.$),a.plugins[c]=e,e.init&&(e.init(a,d),b.push(c))}},l=function(a){var b=[];h.each(a.settings.plugins.replace(/\-/g,"").split(/[ ,]/),function(c){k(a,b,c)})},m=function(a){var b,c=a.settings;c.theme&&("function"!=typeof c.theme?(c.theme=c.theme.replace(/-/,""),b=g.get(c.theme),a.theme=new b(a,g.urls[c.theme]),a.theme.init&&a.theme.init(a,g.urls[c.theme]||a.documentBaseUrl.replace(/\/$/,""),a.$)):a.theme=c.theme)},n=function(a){var b,c,d,e,f,g=a.settings,h=a.getElement();return g.render_ui&&a.theme&&(a.orgDisplay=h.style.display,"function"!=typeof g.theme?(b=g.width||j.getStyle(h,"width")||"100%",c=g.height||j.getStyle(h,"height")||h.offsetHeight,d=g.min_height||100,e=/^[0-9\.]+(|px)$/i,e.test(""+b)&&(b=Math.max(parseInt(b,10),100)),e.test(""+c)&&(c=Math.max(parseInt(c,10),d)),f=a.theme.renderUI({targetNode:h,width:b,height:c,deltaWidth:g.delta_width,deltaHeight:g.delta_height}),g.content_editable||(c=(f.iframeHeight||c)+("number"==typeof c?f.deltaHeight||0:""),c
';var m=i.uuid("mce");c[m]=function(){e.initContentBody(c)};var n='javascript:(function(){document.open();document.domain="'+a.domain+'";var ed = window.parent.tinymce.get("'+c.id+'");document.write(ed.iframeHTML);document.close();ed.'+m+"(true);})()";a.domain!=b.location.hostname&&d.ie&&d.ie<12&&(k=n);var o=j.create("iframe",{id:c.id+"_ifr",frameBorder:"0",allowTransparency:"true",title:c.editorManager.translate("Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help"),style:{width:"100%",height:f.height,display:"block"}});if(o.onload=function(){o.onload=null,c.fire("load")},j.setAttrib(o,"src",k||'javascript:""'),c.contentAreaContainer=f.iframeContainer,c.iframeElement=o,j.add(f.iframeContainer,o),d.ie)try{c.getDoc()}catch(a){o.src=k=n}return k},p=function(a){var b,c,d=a.settings,f=a.getElement();return a.rtl=d.rtl_ui||a.editorManager.i18n.rtl,a.editorManager.i18n.setCode(d.language),d.aria_label=d.aria_label||j.getAttrib(f,"aria-label",a.getLang("aria.rich_text_area")),a.fire("ScriptsLoaded"),m(a),l(a),b=n(a),d.content_css&&h.each(h.explode(d.content_css),function(b){a.contentCSS.push(a.documentBaseURI.toAbsolute(b))}),d.content_style&&a.contentStyles.push(d.content_style),d.content_editable?e.initContentBody(a):(c=o(a,b),b.editorContainer&&(j.get(b.editorContainer).style.display=a.orgDisplay,a.hidden=j.isHidden(b.editorContainer)),a.getElement().style.display="none",j.setAttrib(a.id,"aria-hidden",!0),void(c||e.initContentBody(a)))};return{init:p}}),g("21",["4b","e","7","f","6","25","4c","11","4d","4e","9","10"],function(a,b,c,d,e,f,g,h,i,j,k,l){var m=b.DOM,n=function(a,b){var c=a.settings,e=d.ScriptLoader;if(c.language&&"en"!=c.language&&!c.language_url&&(c.language_url=a.editorManager.baseURL+"/langs/"+c.language+".js"),c.language_url&&e.add(c.language_url),c.theme&&"function"!=typeof c.theme&&"-"!=c.theme.charAt(0)&&!j.urls[c.theme]){var h=c.theme_url;h=h?a.documentBaseURI.toAbsolute(h):"themes/"+c.theme+"/theme"+b+".js",j.load(c.theme,h)}k.isArray(c.plugins)&&(c.plugins=c.plugins.join(" ")),k.each(c.external_plugins,function(a,b){i.load(b,a),c.plugins+=" "+b}),k.each(c.plugins.split(/[ ,]/),function(a){if(a=k.trim(a),a&&!i.urls[a])if("-"===a.charAt(0)){a=a.substr(1,a.length);var c=i.dependencies(a);k.each(c,function(a){var c={prefix:"plugins/",resource:a,suffix:"/plugin"+b+".js"};a=i.createUrl(c,a),i.load(a.resource,a)})}else i.load(a,{prefix:"plugins/",resource:a,suffix:"/plugin"+b+".js"})}),e.loadQueue(function(){a.removed||g.init(a)},a,function(b){f.pluginLoadError(a,b[0]),a.removed||g.init(a)})},o=function(b){function d(){m.unbind(a,"ready",d),b.render()}var f=b.settings,g=b.id;if(!c.Event.domLoaded)return void m.bind(a,"ready",d);if(b.getElement()&&e.contentEditable){f.inline?b.inline=!0:(b.orgVisibility=b.getElement().style.visibility,b.getElement().style.visibility="hidden");var i=b.getElement().form||m.getParent(g,"form");i&&(b.formElement=i,f.hidden_input&&!/TEXTAREA|INPUT/i.test(b.getElement().nodeName)&&(m.insertAfter(m.create("input",{type:"hidden",name:g}),g),b.hasHiddenInput=!0),b.formEventDelegate=function(a){b.fire(a.type,a)},m.bind(i,"submit reset",b.formEventDelegate),b.on("reset",function(){b.setContent(b.startContent,{format:"raw"})}),!f.submit_patch||i.submit.nodeType||i.submit.length||i._mceOldSubmit||(i._mceOldSubmit=i.submit,i.submit=function(){return b.editorManager.triggerSave(),b.setDirty(!1),i._mceOldSubmit(i)})),b.windowManager=new l(b),b.notificationManager=new h(b),"xml"===f.encoding&&b.on("GetContent",function(a){a.save&&(a.content=m.encode(a.content))}),f.add_form_submit_trigger&&b.on("submit",function(){b.initialized&&b.save()}),f.add_unload_trigger&&(b._beforeUnload=function(){!b.initialized||b.destroyed||b.isHidden()||b.save({format:"raw",no_events:!0,set_dirty:!1})},b.editorManager.on("BeforeUnload",b._beforeUnload)),b.editorManager.add(b),n(b,b.suffix)}};return{render:o}}),g("22",[],function(){function a(a,b,c){try{a.getDoc().execCommand(b,!1,c)}catch(a){}}function b(a){var b,c;return b=a.getBody(),c=function(b){a.dom.getParents(b.target,"a").length>0&&b.preventDefault()},a.dom.bind(b,"click",c),{unbind:function(){a.dom.unbind(b,"click",c)}}}function c(c,d){c._clickBlocker&&(c._clickBlocker.unbind(),c._clickBlocker=null),d?(c._clickBlocker=b(c),c.selection.controlSelection.hideResizeRect(),c.readonly=!0,c.getBody().contentEditable=!1):(c.readonly=!1,c.getBody().contentEditable=!0,a(c,"StyleWithCSS",!1),a(c,"enableInlineTableEditing",!1),a(c,"enableObjectResizing",!1),c.focus(),c.nodeChanged())}function d(a,b){var d=a.readonly?"readonly":"design";b!=d&&(a.initialized?c(a,"readonly"==b):a.on("init",function(){c(a,"readonly"==b)}),a.fire("SwitchMode",{mode:b}))}return{setMode:d}});g("23",[],function(){var a=function(a,b,c){var d=a.sidebars?a.sidebars:[];d.push({name:b,settings:c}),a.sidebars=d};return{add:a}});g("14",["g","a","e","v","12","6","n","21","22","13","23","9","w","24"],function(a,b,c,d,e,f,g,h,i,j,k,l,m,n){function o(c,e,g){var h,i,k,l=this;h=l.documentBaseUrl=g.documentBaseURL,i=g.baseURI,k=g.defaultSettings,e=q({id:c,theme:"modern",delta_width:0,delta_height:0,popup_css:"",plugins:"",document_base_url:h,add_form_submit_trigger:!0,submit_patch:!0,add_unload_trigger:!0,convert_urls:!0,relative_urls:!0,remove_script_host:!0,object_resizing:!0,doctype:"",visual:!0,font_size_style_values:"xx-small,x-small,small,medium,large,x-large,xx-large",font_size_legacy_values:"xx-small,small,medium,large,x-large,xx-large,300%",forced_root_block:"p",hidden_input:!0,padd_empty_editor:!0,render_ui:!0,indentation:"30px",inline_styles:!0,convert_fonts_to_spans:!0,indent:"simple",indent_before:"p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,th,ul,ol,li,dl,dt,dd,area,table,thead,tfoot,tbody,tr,section,article,hgroup,aside,figure,figcaption,option,optgroup,datalist",indent_after:"p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,th,ul,ol,li,dl,dt,dd,area,table,thead,tfoot,tbody,tr,section,article,hgroup,aside,figure,figcaption,option,optgroup,datalist",validate:!0,entity_encoding:"named",url_converter:l.convertURL,url_converter_scope:l,ie7_compat:!0},k,e),k&&k.external_plugins&&e.external_plugins&&(e.external_plugins=q({},k.external_plugins,e.external_plugins)),l.settings=e,a.language=e.language||"en",a.languageLoad=e.language_load,a.baseURL=g.baseURL,l.id=e.id=c,l.setDirty(!1),l.plugins={},l.documentBaseURI=new m(e.document_base_url||h,{base_uri:i}),l.baseURI=i,l.contentCSS=[],l.contentStyles=[],l.shortcuts=new j(l),l.loadedCSS={},l.editorCommands=new d(l),l.suffix=g.suffix,l.editorManager=g,l.inline=e.inline,l.settings.content_editable=l.inline,e.cache_suffix&&(f.cacheSuffix=e.cache_suffix.replace(/^[\?\&]+/,"")),e.override_viewport===!1&&(f.overrideViewPort=!1),g.fire("SetupEditor",l),l.execCallback("setup",l),l.$=b.overrideDefaults(function(){return{context:l.inline?l.getBody():l.getDoc(),element:l.getBody()}})}var p=c.DOM,q=l.extend,r=l.each,s=l.trim,t=l.resolve,u=f.gecko,v=f.ie;return o.prototype={render:function(){h.render(this)},focus:function(a){function b(a){return g.dom.getParent(a,function(a){return"true"===g.dom.getContentEditable(a)})}var c,d,e,g=this,h=g.selection,i=g.settings.content_editable,j=g.getDoc(),k=g.getBody();if(!a){if(c=h.getRng(),c.item&&(d=c.item(0)),g.quirks.refreshContentEditable(),e=b(h.getNode()),g.$.contains(k,e))return e.focus(),h.normalize(),void g.editorManager.setActive(g);if(i||(f.opera||g.getBody().focus(),g.getWin().focus()),u||i){if(k.setActive)try{k.setActive()}catch(a){k.focus()}else k.focus();i&&h.normalize()}d&&d.ownerDocument==j&&(c=j.body.createControlRange(),c.addElement(d),c.select())}g.editorManager.setActive(g)},execCallback:function(a){var b,c=this,d=c.settings[a];if(d)return c.callbackLookup&&(b=c.callbackLookup[a])&&(d=b.func,b=b.scope),"string"==typeof d&&(b=d.replace(/\.\w+$/,""),b=b?t(b):0,d=t(d),c.callbackLookup=c.callbackLookup||{},c.callbackLookup[a]={func:d,scope:b}),d.apply(b||c,Array.prototype.slice.call(arguments,1))},translate:function(a){var b=this.settings.language||"en",c=this.editorManager.i18n;return a?(a=c.data[b+"."+a]||a.replace(/\{\#([^\}]+)\}/g,function(a,d){return c.data[b+"."+d]||"{#"+d+"}"}),this.editorManager.translate(a)):""},getLang:function(a,b){return this.editorManager.i18n.data[(this.settings.language||"en")+"."+a]||(void 0!==b?b:"{#"+a+"}")},getParam:function(a,b,c){var d,e=a in this.settings?this.settings[a]:b;return"hash"===c?(d={},"string"==typeof e?r(e.indexOf("=")>0?e.split(/[;,](?![^=;,]*(?:[;,]|$))/):e.split(","),function(a){a=a.split("="),a.length>1?d[s(a[0])]=s(a[1]):d[s(a[0])]=s(a)}):d=e,d):e},nodeChanged:function(a){this._nodeChangeDispatcher.nodeChanged(a)},addButton:function(a,b){var c=this;b.cmd&&(b.onclick=function(){c.execCommand(b.cmd)}),b.text||b.icon||(b.icon=a),c.buttons=c.buttons||{},b.tooltip=b.tooltip||b.title,c.buttons[a]=b},addSidebar:function(a,b){return k.add(this,a,b)},addMenuItem:function(a,b){var c=this;b.cmd&&(b.onclick=function(){c.execCommand(b.cmd)}),c.menuItems=c.menuItems||{},c.menuItems[a]=b},addContextToolbar:function(a,b){var c,d=this;d.contextToolbars=d.contextToolbars||[],"string"==typeof a&&(c=a,a=function(a){return d.dom.is(a,c)}),d.contextToolbars.push({id:n.uuid("mcet"),predicate:a,items:b})},addCommand:function(a,b,c){this.editorCommands.addCommand(a,b,c)},addQueryStateHandler:function(a,b,c){this.editorCommands.addQueryStateHandler(a,b,c)},addQueryValueHandler:function(a,b,c){this.editorCommands.addQueryValueHandler(a,b,c)},addShortcut:function(a,b,c,d){this.shortcuts.add(a,b,c,d)},execCommand:function(a,b,c,d){return this.editorCommands.execCommand(a,b,c,d)},queryCommandState:function(a){return this.editorCommands.queryCommandState(a)},queryCommandValue:function(a){return this.editorCommands.queryCommandValue(a)},queryCommandSupported:function(a){return this.editorCommands.queryCommandSupported(a)},show:function(){var a=this;a.hidden&&(a.hidden=!1,a.inline?a.getBody().contentEditable=!0:(p.show(a.getContainer()),p.hide(a.id)),a.load(),a.fire("show"))},hide:function(){var a=this,b=a.getDoc();a.hidden||(v&&b&&!a.inline&&b.execCommand("SelectAll"),a.save(),a.inline?(a.getBody().contentEditable=!1,a==a.editorManager.focusedEditor&&(a.editorManager.focusedEditor=null)):(p.hide(a.getContainer()),p.setStyle(a.id,"display",a.orgDisplay)),a.hidden=!0,a.fire("hide"))},isHidden:function(){return!!this.hidden},setProgressState:function(a,b){this.fire("ProgressState",{state:a,time:b})},load:function(a){var b,c=this,d=c.getElement();if(d)return a=a||{},a.load=!0,b=c.setContent(void 0!==d.value?d.value:d.innerHTML,a),a.element=d,a.no_events||c.fire("LoadContent",a),a.element=d=null,b},save:function(a){var b,c,d=this,e=d.getElement();if(e&&d.initialized)return a=a||{},a.save=!0,a.element=e,b=a.content=d.getContent(a),a.no_events||d.fire("SaveContent",a),"raw"==a.format&&d.fire("RawSaveContent",a),b=a.content,/TEXTAREA|INPUT/i.test(e.nodeName)?e.value=b:(d.inline||(e.innerHTML=b),(c=p.getParent(d.id,"form"))&&r(c.elements,function(a){if(a.name==d.id)return a.value=b,!1})),a.element=e=null,a.set_dirty!==!1&&d.setDirty(!1),b},setContent:function(a,b){var c,d,e=this,f=e.getBody();return b=b||{},b.format=b.format||"html",b.set=!0,b.content=a,b.no_events||e.fire("BeforeSetContent",b),a=b.content,0===a.length||/^\s+$/.test(a)?(d=v&&v<11?"":'
',"TABLE"==f.nodeName?a=" ":/^(UL|OL)$/.test(f.nodeName)&&(a=""+d+"
'),e.dom.setHTML(f,a),e.fire("SetContent",b)):("raw"!==b.format&&(a=new g({validate:e.validate},e.schema).serialize(e.parser.parse(a,{isRootContent:!0}))),b.content=s(a),e.dom.setHTML(f,b.content),b.no_events||e.fire("SetContent",b)),b.content},getContent:function(a){var b,c=this,d=c.getBody();return a=a||{},a.format=a.format||"html",a.get=!0,a.getInner=!0,a.no_events||c.fire("BeforeGetContent",a),b="raw"==a.format?l.trim(c.serializer.getTrimmedContent()):"text"==a.format?d.innerText||d.textContent:c.serializer.serialize(d,a),"text"!=a.format?a.content=s(b):a.content=b,a.no_events||c.fire("GetContent",a),a.content},insertContent:function(a,b){b&&(a=q({content:a},b)),this.execCommand("mceInsertContent",!1,a)},isDirty:function(){return!this.isNotDirty},setDirty:function(a){var b=!this.isNotDirty;this.isNotDirty=!a,a&&a!=b&&this.fire("dirty")},setMode:function(a){i.setMode(this,a)},getContainer:function(){var a=this;return a.container||(a.container=p.get(a.editorContainer||a.id+"_parent")),a.container},getContentAreaContainer:function(){return this.contentAreaContainer},getElement:function(){return this.targetElm||(this.targetElm=p.get(this.id)),this.targetElm},getWin:function(){var a,b=this;return b.contentWindow||(a=b.iframeElement,a&&(b.contentWindow=a.contentWindow)),b.contentWindow},getDoc:function(){var a,b=this;return b.contentDocument||(a=b.getWin(),a&&(b.contentDocument=a.document)),b.contentDocument},getBody:function(){var a=this.getDoc();return this.bodyElement||(a?a.body:null)},convertURL:function(a,b,c){var d=this,e=d.settings;return e.urlconverter_callback?d.execCallback("urlconverter_callback",a,c,!0,b):!e.convert_urls||c&&"LINK"==c.nodeName||0===a.indexOf("file:")||0===a.length?a:e.relative_urls?d.documentBaseURI.toRelative(a):a=d.documentBaseURI.toAbsolute(a,e.remove_script_host)},addVisual:function(a){var b,c=this,d=c.settings,e=c.dom;a=a||c.getBody(),void 0===c.hasVisual&&(c.hasVisual=d.visual),r(e.select("table,a",a),function(a){var f;switch(a.nodeName){case"TABLE":return b=d.visual_table_class||"mce-item-table",f=e.getAttrib(a,"border"),void(f&&"0"!=f||!c.hasVisual?e.removeClass(a,b):e.addClass(a,b));case"A":return void(e.getAttrib(a,"href",!1)||(f=e.getAttrib(a,"name")||a.id,b=d.visual_anchor_class||"mce-item-anchor",f&&c.hasVisual?e.addClass(a,b):e.removeClass(a,b)))}}),c.fire("VisualAid",{element:a,hasVisual:c.hasVisual})},remove:function(){var a=this;a.removed||(a.save(),a.removed=1,a.unbindAllNativeEvents(),a.hasHiddenInput&&p.remove(a.getElement().nextSibling),a.inline||(v&&v<10&&a.getDoc().execCommand("SelectAll",!1,null),p.setStyle(a.id,"display",a.orgDisplay),a.getBody().onload=null),a.fire("remove"),a.editorManager.remove(a),p.remove(a.getContainer()),a._selectionOverrides.destroy(),a.editorUpload.destroy(),a.destroy())},destroy:function(a){var b,c=this;if(!c.destroyed){if(!a&&!c.removed)return void c.remove();a||(c.editorManager.off("beforeunload",c._beforeUnload),c.theme&&c.theme.destroy&&c.theme.destroy(),c.selection.destroy(),c.dom.destroy()),b=c.formElement,b&&(b._mceOldSubmit&&(b.submit=b._mceOldSubmit,b._mceOldSubmit=null),p.unbind(b,"submit reset",c.formEventDelegate)),c.contentAreaContainer=c.formElement=c.container=c.editorContainer=null,c.bodyElement=c.contentDocument=c.contentWindow=null,c.iframeElement=c.targetElm=null,c.selection&&(c.selection=c.selection.win=c.selection.dom=c.selection.dom.doc=null),c.destroyed=1}},uploadImages:function(a){return this.editorUpload.uploadImages(a)},_scanForImages:function(){return this.editorUpload.scanForImages()}},q(o.prototype,e),o}),g("15",["9"],function(a){"use strict";var b={},c="en";return{setCode:function(a){a&&(c=a,this.rtl=!!this.data[a]&&"rtl"===this.data[a]._dir)},getCode:function(){return c},rtl:!1,add:function(a,c){var d=b[a];d||(b[a]=d={});for(var e in c)d[e]=c[e];this.setCode(a)},translate:function(d){function e(b){return a.is(b,"function")?Object.prototype.toString.call(b):f(b)?"":""+b}function f(b){return""===b||null===b||a.is(b,"undefined")}function g(b){return b=e(b),a.hasOwn(h,b)?e(h[b]):b}var h=b[c]||{};if(f(d))return"";if(a.is(d,"object")&&a.hasOwn(d,"raw"))return e(d.raw);if(a.is(d,"array")){var i=d.slice(1);d=g(d[0]).replace(/\{([0-9]+)\}/g,function(b,c){return a.hasOwn(i,c)?e(i[c]):b})}return g(d).replace(/{context:\w+}$/,"")},data:b}}),g("16",["e","5","6"],function(a,b,c){function d(a){function d(){try{return document.activeElement}catch(a){return document.body}}function j(a,b){if(b&&b.startContainer){if(!a.isChildOf(b.startContainer,a.getRoot())||!a.isChildOf(b.endContainer,a.getRoot()))return;return{startContainer:b.startContainer,startOffset:b.startOffset,endContainer:b.endContainer,endOffset:b.endOffset}}return b}function l(a,b){var c;return b.startContainer?(c=a.getDoc().createRange(),c.setStart(b.startContainer,b.startOffset),c.setEnd(b.endContainer,b.endOffset)):c=b,c}function m(m){var n=m.editor;n.on("init",function(){(n.inline||c.ie)&&("onbeforedeactivate"in document&&c.ie<9?n.dom.bind(n.getBody(),"beforedeactivate",function(a){if(a.target==n.getBody())try{n.lastRng=n.selection.getRng()}catch(a){}}):n.on("nodechange mouseup keyup",function(a){var b=d();"nodechange"==a.type&&a.selectionChange||(b&&b.id==n.id+"_ifr"&&(b=n.getBody()),n.dom.isChildOf(b,n.getBody())&&(n.lastRng=n.selection.getRng()))}),c.webkit&&!e&&(e=function(){var b=a.activeEditor;if(b&&b.selection){var c=b.selection.getRng();c&&!c.collapsed&&(n.lastRng=c)}},h.bind(document,"selectionchange",e)))}),n.on("setcontent",function(){n.lastRng=null}),n.on("mousedown",function(){n.selection.lastFocusBookmark=null}),n.on("focusin",function(){var b,c=a.focusedEditor;n.selection.lastFocusBookmark&&(b=l(n,n.selection.lastFocusBookmark),n.selection.lastFocusBookmark=null,n.selection.setRng(b)),c!=n&&(c&&c.fire("blur",{focusedEditor:n}),a.setActive(n),a.focusedEditor=n,n.fire("focus",{blurredEditor:c}),n.focus(!0)),n.lastRng=null}),n.on("focusout",function(){b.setEditorTimeout(n,function(){var b=a.focusedEditor;i(n,d())||b!=n||(n.fire("blur",{focusedEditor:null}),a.focusedEditor=null,n.selection&&(n.selection.lastFocusBookmark=null))})}),f||(f=function(b){var c,d=a.activeEditor;c=b.target,d&&c.ownerDocument===document&&(d.selection&&c!==d.getBody()&&k(n,c)&&(d.selection.lastFocusBookmark=j(d.dom,d.lastRng)),c===document.body||i(d,c)||a.focusedEditor!==d||(d.fire("blur",{focusedEditor:null}),a.focusedEditor=null))},h.bind(document,"focusin",f)),n.inline&&!g&&(g=function(b){var c=a.activeEditor,d=c.dom;if(c.inline&&d&&!d.isChildOf(b.target,c.getBody())){var e=c.selection.getRng();e.collapsed||(c.lastRng=e)}},h.bind(document,"mouseup",g))}function n(b){a.focusedEditor==b.editor&&(a.focusedEditor=null),a.activeEditor||(h.unbind(document,"selectionchange",e),h.unbind(document,"focusin",f),h.unbind(document,"mouseup",g),e=f=g=null)}a.on("AddEditor",m),a.on("RemoveEditor",n)}var e,f,g,h=a.DOM,i=function(a,b){var c=a?a.settings.custom_ui_selector:"",e=h.getParent(b,function(b){return d.isEditorUIElement(b)||!!c&&a.dom.is(b,c)});return null!==e},j=function(a){return a.inline===!0},k=function(a,b){return j(a)===!1||a.dom.isChildOf(b,a.getBody())===!1};return d.isEditorUIElement=function(a){return a.className.toString().indexOf("mce-")!==-1},d._isUIElement=i,d}),g("26",["9"],function(a){var b=a.each,c=a.explode,d=function(a){a.on("AddEditor",function(a){var d=a.editor;d.on("preInit",function(){function a(a,c){b(c,function(b,c){b&&h.setStyle(a,c,b)}),h.rename(a,"span")}function e(a){h=d.dom,i.convert_fonts_to_spans&&b(h.select("font,u,strike",a.node),function(a){f[a.nodeName.toLowerCase()](h,a)})}var f,g,h,i=d.settings;i.inline_styles&&(g=c(i.font_size_legacy_values),f={font:function(b,c){a(c,{backgroundColor:c.style.backgroundColor,color:c.color,fontFamily:c.face,fontSize:g[parseInt(c.size,10)-1]})},u:function(b,c){"html4"===d.settings.schema&&a(c,{textDecoration:"underline"})},strike:function(b,c){a(c,{textDecoration:"line-through"})}},d.on("PreProcess SetContent",e))})})};return{register:d}}),g("17",["g","a","e","14","6","25","16","26","15","z","4","9","w"],function(a,b,c,d,e,f,g,h,i,j,k,l,m){function n(a){v(s.editors,function(b){"scroll"===a.type?b.fire("ScrollWindow",a):b.fire("ResizeWindow",a)})}function o(a,c){c!==y&&(c?b(window).on("resize scroll",n):b(window).off("resize scroll",n),y=c)}function p(a){var b,c=s.editors;delete c[a.id];for(var d=0;d