(function() { function fontFace(face) { if (tinymce.isOpera) { return "'" + face + "'"; } return face; } function findContainer(selector) { var container; if (tinymce.is(selector, 'string')) { container = editor.dom.select(selector)[0]; } else { container = selector; } if (container.firstChild) { container = container.firstChild; } return container; } function setSelection(startSelector, startOffset, endSelector, endOffset) { if (!endSelector) { endSelector = startSelector; endOffset = startOffset; } var startContainer = findContainer(startSelector); var endContainer = findContainer(endSelector); var rng = editor.dom.createRng(); function setRange(container, offset, start) { offset = offset || 0; if (offset === 'after') { if (start) { rng.setStartAfter(container); } else { rng.setEndAfter(container); } return; } else if (offset === 'afterNextCharacter') { container = container.nextSibling; offset = 1; } if (start) { rng.setStart(container, offset); } else { rng.setEnd(container, offset); } } setRange(startContainer, startOffset, true); setRange(endContainer, endOffset, false); editor.selection.setRng(rng); } function assertRange(actual, expected) { deepEqual({ startContainer: actual.startContainer, startOffset: actual.startOffset, endContainer: actual.endContainer, endOffset: actual.endOffset }, { startContainer: expected.startContainer, startOffset: expected.startOffset, endContainer: expected.endContainer, endOffset: expected.endOffset }); } function createRange(startContainer, startOffset, endContainer, endOffset) { var rng = tinymce.DOM.createRng(); rng.setStart(startContainer, startOffset); if (endContainer) { rng.setEnd(endContainer, endOffset); } return rng; } function assertCaretPosition(actual, expected, message) { if (expected === null) { strictEqual(actual, expected, message || 'Expected null.'); return; } if (actual === null) { strictEqual(actual, expected, message || 'Didn\'t expect null.'); return; } deepEqual({ container: actual.container(), offset: actual.offset() }, { container: expected.container(), offset: expected.offset() }, message); } function trimContent(content) { return content.replace(/^
<\/p>\n?/, '').replace(/\n?
<\/p>$/, ''); } /** * Fakes a key event. * * @param {Element/String} e DOM element object or element id to send fake event to. * @param {String} na Event name to fake like "keydown". * @param {Object} o Optional object with data to send with the event like keyCode and charCode. */ function fakeKeyEvent(e, na, o) { var ev; o = tinymce.extend({ keyCode : 13, charCode : 0 }, o); e = tinymce.DOM.get(e); if (e.fireEvent) { ev = document.createEventObject(); tinymce.extend(ev, o); e.fireEvent('on' + na, ev); return; } if (document.createEvent) { try { // Fails in Safari ev = document.createEvent('KeyEvents'); ev.initKeyEvent(na, true, true, window, false, false, false, false, o.keyCode, o.charCode); } catch (ex) { ev = document.createEvent('Events'); ev.initEvent(na, true, true); ev.keyCode = o.keyCode; ev.charCode = o.charCode; } } else { ev = document.createEvent('UIEvents'); if (ev.initUIEvent) { ev.initUIEvent(na, true, true, window, 1); } ev.keyCode = o.keyCode; ev.charCode = o.charCode; } e.dispatchEvent(ev); } function normalizeRng(rng) { if (rng.startContainer.nodeType == 3) { if (rng.startOffset === 0) { rng.setStartBefore(rng.startContainer); } else if (rng.startOffset >= rng.startContainer.nodeValue.length - 1) { rng.setStartAfter(rng.startContainer); } } if (rng.endContainer.nodeType == 3) { if (rng.endOffset === 0) { rng.setEndBefore(rng.endContainer); } else if (rng.endOffset >= rng.endContainer.nodeValue.length - 1) { rng.setEndAfter(rng.endContainer); } } return rng; } // TODO: Replace this with the new event logic in 3.5 function type(chr) { var editor = tinymce.activeEditor, keyCode, charCode, evt, startElm, rng, offset; function charCodeToKeyCode(charCode) { var lookup = { '0': 48, '1': 49, '2': 50, '3': 51, '4': 52, '5': 53, '6': 54, '7': 55, '8': 56, '9': 57, 'a': 65, 'b': 66, 'c': 67, 'd': 68, 'e': 69, 'f': 70, 'g': 71, 'h': 72, 'i': 73, 'j': 74, 'k': 75, 'l': 76, 'm': 77, 'n': 78, 'o': 79, 'p': 80, 'q': 81, 'r': 82, 's': 83, 't': 84, 'u': 85, 'v': 86, 'w': 87, 'x': 88, 'y': 89, ' ': 32, ',': 188, '-': 189, '.': 190, '/': 191, '\\': 220, '[': 219, ']': 221, '\'': 222, ';': 186, '=': 187, ')': 41 }; return lookup[String.fromCharCode(charCode)]; } function fakeEvent(target, type, evt) { editor.dom.fire(target, type, evt); } // Numeric keyCode if (typeof chr == "number") { charCode = chr; keyCode = charCodeToKeyCode(charCode); } else if (typeof chr == "string") { // String value if (chr == '\b') { keyCode = 8; charCode = chr.charCodeAt(0); } else if (chr == '\n') { keyCode = 13; charCode = chr.charCodeAt(0); } else { charCode = chr.charCodeAt(0); keyCode = charCodeToKeyCode(charCode); } } else { evt = chr; if (evt.charCode) { chr = String.fromCharCode(evt.charCode); } if (evt.keyCode) { keyCode = evt.keyCode; } } evt = evt || {keyCode: keyCode, charCode: charCode}; startElm = editor.selection.getStart(); fakeEvent(startElm, 'keydown', evt); fakeEvent(startElm, 'keypress', evt); if (!evt.isDefaultPrevented()) { if (keyCode == 8) { if (editor.getDoc().selection) { rng = editor.getDoc().selection.createRange(); if (rng.text.length === 0) { rng.moveStart('character', -1); rng.select(); } rng.execCommand('Delete', false, null); } else { rng = editor.selection.getRng(); if (rng.collapsed) { if (rng.startContainer.nodeType == 1) { var nodes = rng.startContainer.childNodes, lastNode = nodes[nodes.length - 1]; // If caret is at
abc|
and after the abc text node then move it to the end of the text node // Expand the range to include the last charab[c]
since IE 11 doesn't delete otherwise if (rng.startOffset >= nodes.length - 1 && lastNode && lastNode.nodeType == 3 && lastNode.data.length > 0) { rng.setStart(lastNode, lastNode.data.length - 1); rng.setEnd(lastNode, lastNode.data.length); editor.selection.setRng(rng); } } else if (rng.startContainer.nodeType == 3) { // If caret is atabc|
and after the abc text node then move it to the end of the text node // Expand the range to include the last charab[c]
since IE 11 doesn't delete otherwise offset = rng.startOffset; if (offset > 0) { rng.setStart(rng.startContainer, offset - 1); rng.setEnd(rng.startContainer, offset); editor.selection.setRng(rng); } } } editor.getDoc().execCommand('Delete', false, null); } } else if (typeof chr == 'string') { rng = editor.selection.getRng(true); if (rng.startContainer.nodeType == 3 && rng.collapsed) { rng.startContainer.insertData(rng.startOffset, chr); rng.setStart(rng.startContainer, rng.startOffset + 1); rng.collapse(true); editor.selection.setRng(rng); } else { rng.deleteContents(); rng.insertNode(editor.getDoc().createTextNode(chr)); } } } fakeEvent(startElm, 'keyup', evt); } function cleanHtml(html) { html = html.toLowerCase().replace(/[\r\n]+/gi, ''); html = html.replace(/ (sizcache[0-9]+|sizcache|nodeindex|sizset[0-9]+|sizset|data\-mce\-expando|data\-mce\-selected)="[^"]*"/gi, ''); html = html.replace(/]+data-mce-bogus[^>]+>[\u200B\uFEFF]+<\/span>|