| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184 |
- /**
- * Quirks.js
- *
- * Copyright, Moxiecode Systems AB
- * Released under LGPL License.
- *
- * 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.
- *
- * @class tinymce.util.Quirks
- */
- define("tinymce/util/Quirks", [
- "tinymce/util/VK",
- "tinymce/dom/RangeUtils",
- "tinymce/html/Node",
- "tinymce/html/Entities",
- "tinymce/Env",
- "tinymce/util/Tools"
- ], function(VK, RangeUtils, Node, Entities, Env, Tools) {
- 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;
- /**
- * 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();
- }
- /**
- * Fixes a WebKit bug when deleting contents using backspace or delete key.
- * WebKit will produce a span element if you delete across two block elements.
- *
- * Example:
- * <h1>a</h1><p>|b</p>
- *
- * Will produce this on backspace:
- * <h1>a<span style="<all runtime styles>">b</span></p>
- *
- * This fixes the backspace to produce:
- * <h1>a|b</p>
- *
- * See bug: https://bugs.webkit.org/show_bug.cgi?id=45784
- *
- * This fixes the following delete scenarios:
- * 1. Delete by pressing backspace key.
- * 2. Delete by pressing delete key.
- * 3. Delete by pressing backspace key with ctrl/cmd (Word delete).
- * 4. Delete by pressing delete key with ctrl/cmd (Word delete).
- * 5. Delete by drag/dropping contents inside the editor.
- * 6. Delete by using Cut Ctrl+X/Cmd+X.
- * 7. Delete by selecting contents and writing a character.'
- *
- * This code is a ugly hack since writing full custom delete logic for just this bug
- * fix seemed like a huge task. I hope we can remove this before the year 2030.
- */
- function cleanupStylesWhenDeleting() {
- var doc = editor.getDoc(), urlPrefix = 'data:text/mce-internal,';
- var MutationObserver = window.MutationObserver, olderWebKit, dragStartRng;
- // Add mini polyfill for older WebKits
- // TODO: Remove this when old Safari versions gets updated
- if (!MutationObserver) {
- olderWebKit = true;
- MutationObserver = function() {
- var records = [], target;
- function nodeInsert(e) {
- var target = e.relatedNode || e.target;
- records.push({target: target, addedNodes: [target]});
- }
- function attrModified(e) {
- var target = e.relatedNode || e.target;
- records.push({target: target, attributeName: e.attrName});
- }
- this.observe = function(node) {
- target = node;
- target.addEventListener('DOMSubtreeModified', nodeInsert, false);
- target.addEventListener('DOMNodeInsertedIntoDocument', nodeInsert, false);
- target.addEventListener('DOMNodeInserted', nodeInsert, false);
- target.addEventListener('DOMAttrModified', attrModified, false);
- };
- this.disconnect = function() {
- target.removeEventListener('DOMNodeInserted', nodeInsert);
- target.removeEventListener('DOMAttrModified', attrModified);
- target.removeEventListener('DOMSubtreeModified', nodeInsert, false);
- };
- this.takeRecords = function() {
- return records;
- };
- };
- }
- function customDelete(isForward) {
- var mutationObserver = new MutationObserver(function() {});
- Tools.each(editor.getBody().getElementsByTagName('*'), function(elm) {
- // Mark existing spans
- if (elm.tagName == 'SPAN') {
- elm.setAttribute('mce-data-marked', 1);
- }
- // Make sure all elements has a data-mce-style attribute
- if (!elm.hasAttribute('data-mce-style') && elm.hasAttribute('style')) {
- editor.dom.setAttrib(elm, 'style', elm.getAttribute('style'));
- }
- });
- // Observe added nodes and style attribute changes
- mutationObserver.observe(editor.getDoc(), {
- childList: true,
- attributes: true,
- subtree: true,
- attributeFilter: ['style']
- });
- editor.getDoc().execCommand(isForward ? 'ForwardDelete' : 'Delete', false, null);
- var rng = editor.selection.getRng();
- var caretElement = rng.startContainer.parentNode;
- Tools.each(mutationObserver.takeRecords(), function(record) {
- // Restore style attribute to previous value
- if (record.attributeName == "style") {
- var oldValue = record.target.getAttribute('data-mce-style');
- if (oldValue) {
- record.target.setAttribute("style", oldValue);
- } else {
- record.target.removeAttribute("style");
- }
- }
- // Remove all spans that isn't maked and retain selection
- Tools.each(record.addedNodes, function(node) {
- if (node.nodeName == "SPAN" && !node.getAttribute('mce-data-marked')) {
- var offset, container;
- if (node == caretElement) {
- offset = rng.startOffset;
- container = node.firstChild;
- }
- dom.remove(node, true);
- if (container) {
- rng.setStart(container, offset);
- rng.setEnd(container, offset);
- editor.selection.setRng(rng);
- }
- }
- });
- });
- mutationObserver.disconnect();
- // Remove any left over marks
- Tools.each(editor.dom.select('span[mce-data-marked]'), function(span) {
- span.removeAttribute('mce-data-marked');
- });
- }
- editor.on('keydown', function(e) {
- var isForward = e.keyCode == DELETE, isMeta = VK.metaKeyPressed(e);
- if (!isDefaultPrevented(e) && (isForward || e.keyCode == BACKSPACE)) {
- var rng = editor.selection.getRng(), container = rng.startContainer, offset = rng.startOffset;
- // Ignore non meta delete in the where there is text before/after the caret
- if (!isMeta && rng.collapsed && container.nodeType == 3) {
- if (isForward ? offset < container.data.length : offset > 0) {
- return;
- }
- }
- e.preventDefault();
- if (isMeta) {
- editor.selection.getSel().modify("extend", isForward ? "forward" : "backward", "word");
- }
- customDelete(isForward);
- }
- });
- editor.on('keypress', function(e) {
- if (!isDefaultPrevented(e) && !selection.isCollapsed() && e.charCode && !VK.metaKeyPressed(e)) {
- e.preventDefault();
- customDelete(true);
- editor.selection.setContent(String.fromCharCode(e.charCode));
- }
- });
- editor.addCommand('Delete', function() {
- customDelete();
- });
- editor.addCommand('ForwardDelete', function() {
- customDelete(true);
- });
- // Older WebKits doesn't properly handle the clipboard so we can't add the rest
- if (olderWebKit) {
- return;
- }
- editor.on('dragstart', function(e) {
- var selectionHtml;
- if (editor.selection.isCollapsed() && e.target.tagName == 'IMG') {
- selection.select(e.target);
- }
- dragStartRng = selection.getRng();
- selectionHtml = editor.selection.getContent();
- // Safari doesn't support custom dataTransfer items so we can only use URL and Text
- if (selectionHtml.length > 0) {
- e.dataTransfer.setData('URL', 'data:text/mce-internal,' + escape(selectionHtml));
- }
- });
- editor.on('drop', function(e) {
- if (!isDefaultPrevented(e)) {
- var internalContent = e.dataTransfer.getData('URL');
- if (!internalContent || internalContent.indexOf(urlPrefix) == -1 || !doc.caretRangeFromPoint) {
- return;
- }
- internalContent = unescape(internalContent.substr(urlPrefix.length));
- if (doc.caretRangeFromPoint) {
- e.preventDefault();
- // Safari has a weird issue where drag/dropping images sometimes
- // produces a green plus icon. When this happens the caretRangeFromPoint
- // will return "null" even though the x, y coordinate is correct.
- // But if we detach the insert from the drop event we will get a proper range
- window.setTimeout(function() {
- var pointRng = doc.caretRangeFromPoint(e.x, e.y);
- if (dragStartRng) {
- selection.setRng(dragStartRng);
- dragStartRng = null;
- }
- customDelete();
- selection.setRng(pointRng);
- editor.insertContent(internalContent);
- }, 0);
- }
- }
- });
- editor.on('cut', function(e) {
- if (!isDefaultPrevented(e) && e.clipboardData) {
- e.preventDefault();
- e.clipboardData.clearData();
- e.clipboardData.setData('text/html', editor.selection.getContent());
- e.clipboardData.setData('text/plain', editor.selection.getContent({format: 'text'}));
- customDelete(true);
- }
- });
- }
- /**
- * Makes sure that the editor body becomes empty when backspace or delete is pressed in empty editors.
- *
- * For example:
- * <p><b>|</b></p>
- *
- * Or:
- * <h1>|</h1>
- *
- * Or:
- * [<h1></h1>]
- */
- function emptyEditorWhenDeleting() {
- function serializeRng(rng) {
- var body = dom.create("body");
- var contents = rng.cloneContents();
- body.appendChild(contents);
- return selection.serializer.serialize(body, {format: 'html'});
- }
- function allContentsSelected(rng) {
- if (!rng.setStart) {
- if (rng.item) {
- return false;
- }
- var bodyRng = rng.duplicate();
- bodyRng.moveToElementText(editor.getBody());
- return RangeUtils.compareRanges(rng, bodyRng);
- }
- var selection = serializeRng(rng);
- var allRng = dom.createRng();
- allRng.selectNode(editor.getBody());
- var allSelection = serializeRng(allRng);
- return selection === allSelection;
- }
- editor.on('keydown', function(e) {
- var keyCode = e.keyCode, isCollapsed, body;
- // Empty the editor if it's needed for example backspace at <p><b>|</b></p>
- 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 <body>[<p>a</p>]</body> instead of <body><p>[a]</p]</body> see bug #6438
- * This selects the whole body so that backspace/delete logic will delete everything
- */
- function selectAll() {
- editor.on('keydown', function(e) {
- if (!isDefaultPrevented(e) && e.keyCode == 65 && VK.metaKeyPressed(e)) {
- e.preventDefault();
- editor.execCommand('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
- 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
- dom.bind(editor.getDoc(), 'mousedown', function(e) {
- if (e.target == editor.getDoc().documentElement) {
- editor.getBody().focus();
- selection.setRng(selection.getRng());
- }
- });
- }
- }
- /**
- * 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) {
- 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
- setTimeout(function() {
- body.focus();
- }, 0);
- }
- });
- }
- }
- /**
- * 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) {
- e = 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 tobe the setBaseAndExtend or it will fail to select floated images
- if (/^(IMG|HR)$/.test(e.nodeName)) {
- selection.getSel().setBaseAndExtent(e, 0, e, 1);
- }
- if (e.nodeName == 'A' && dom.hasClass(e, 'mce-item-anchor')) {
- selection.select(e);
- }
- editor.nodeChanged();
- });
- }
- /**
- * 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:
- * <p>bla[ck</p><p style="color:red">r]ed</p>
- *
- * Would become:
- * <p>bla|ed</p>
- *
- * Instead of:
- * <p style="color:red">bla|ed</p>
- */
- 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();
- setTimeout(function() {
- applyAttributes();
- }, 0);
- }
- });
- }
- /**
- * Fire a nodeChanged when the selection is changed on WebKit this fixes selection issues on iOS5. It only fires the nodeChange
- * event every 50ms since it would other wise update the UI when you type and it hogs the CPU.
- */
- function selectionChangeNodeChanged() {
- var lastRng, selectionTimer;
- editor.on('selectionchange', function() {
- if (selectionTimer) {
- clearTimeout(selectionTimer);
- selectionTimer = 0;
- }
- selectionTimer = window.setTimeout(function() {
- if (editor.removed) {
- return;
- }
- var rng = selection.getRng();
- // Compare the ranges to see if it was a real change or not
- if (!lastRng || !RangeUtils.compareRanges(rng, lastRng)) {
- editor.nodeChanged();
- lastRng = rng;
- }
- }, 50);
- });
- }
- /**
- * 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:
- * <blockquote><p>|x</p></blockquote>
- *
- * Becomes:
- * <p>|x</p>
- */
- 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() {
- editor._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:
- * <p><b><a href="#">x</a></b></p>
- *
- * Becomes this:
- * <p><b><a href="#">x</a></b><br></p>
- */
- 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);
- });
- }
- }
- /**
- * Removes ghost selections from images/tables on Gecko.
- */
- function removeGhostSelection() {
- editor.on('Undo Redo SetContent', function(e) {
- if (!e.initial) {
- editor.execCommand('mceRepaint');
- }
- });
- }
- /**
- * 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 <b>a</b>|<b>b</b>
- * this fix will lean the caret right into the closest inline element.
- */
- function normalizeSelection() {
- // Normalize selection for example <b>a</b><i>|a</i> becomes <b>a|</b><i>a</i> 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) {
- if (e.target.nodeName == 'HTML') {
- editor.getBody().focus();
- 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.keyCode == 37 || e.keyCode == 39)) {
- e.preventDefault();
- editor.selection.getSel().modify('move', e.keyCode == 37 ? 'backward' : 'forward', 'word');
- }
- });
- }
- }
- /**
- * Disables the autolinking in IE 9+ this is then re-enabled by the autolink plugin.
- */
- function disableAutoUrlDetect() {
- setEditorCommandState("AutoUrlDetect", false);
- }
- /**
- * 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. However is we add a BR element to the body then remove it
- * it doesn't seem to add these BR elements makes sence right?!
- *
- * Example of what happens: <body>text</body> becomes <body>text<br><br></body>
- */
- function doubleTrailingBrElements() {
- if (!editor.inline) {
- editor.on('focus blur beforegetcontent', function() {
- var br = editor.dom.create('br');
- editor.getBody().appendChild(br);
- br.parentNode.removeChild(br);
- }, true);
- }
- }
- /**
- * 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}');
- }
- /**
- * WebKit has a bug where it will allow forms to be submitted if they are inside a contentEditable element.
- * For example this: <form><button></form>
- */
- function blockFormSubmitInsideEditor() {
- editor.on('init', function() {
- editor.dom.bind(editor.getBody(), 'submit', function(e) {
- e.preventDefault();
- });
- });
- }
- // All browsers
- disableBackspaceIntoATable();
- removeBlockQuoteOnBackSpace();
- emptyEditorWhenDeleting();
- normalizeSelection();
- // WebKit
- if (isWebKit) {
- cleanupStylesWhenDeleting();
- inputMethodFocus();
- selectControlElements();
- setDefaultBlockType();
- blockFormSubmitInsideEditor();
- // iOS
- if (Env.iOS) {
- selectionChangeNodeChanged();
- restoreFocusOnKeyDown();
- bodyHeight();
- tapLinksAndImages();
- } else {
- selectAll();
- }
- }
- // IE
- if (isIE && Env.ie < 11) {
- removeHrOnBackspace();
- ensureBodyHasRoleApplication();
- addNewLinesBeforeBrInPre();
- removePreSerializedStylesWhenSelectingControls();
- deleteControlItemOnBackSpace();
- renderEmptyBlocksFix();
- keepNoScriptContents();
- fixCaretSelectionOfDocumentElementOnIe();
- }
- if (Env.ie >= 11) {
- bodyHeight();
- doubleTrailingBrElements();
- }
- if (Env.ie) {
- selectAll();
- disableAutoUrlDetect();
- }
- // Gecko
- if (isGecko) {
- removeHrOnBackspace();
- focusBody();
- removeStylesWhenDeletingAcrossBlockElements();
- setGeckoEditingOptions();
- addBrAfterLastLinks();
- removeGhostSelection();
- showBrokenImageIcon();
- blockCmdArrowNavigation();
- }
- };
- });
|