| | |
| | | var tinymce = {
|
| | | majorVersion : '3',
|
| | |
|
| | | minorVersion : '5',
|
| | | minorVersion : '5.6',
|
| | |
|
| | | releaseDate : '2012-05-03',
|
| | | releaseDate : '2012-07-26',
|
| | |
|
| | | _init : function() {
|
| | | var t = this, d = document, na = navigator, ua = na.userAgent, i, nl, n, base, p, v;
|
| | |
| | | ((s) ? "; secure" : "");
|
| | | },
|
| | |
|
| | | remove : function(n, p) {
|
| | | var d = new Date();
|
| | | remove : function(name, path, domain) {
|
| | | var date = new Date();
|
| | |
|
| | | d.setTime(d.getTime() - 1000);
|
| | | date.setTime(date.getTime() - 1000);
|
| | |
|
| | | this.set(n, '', d, p, d);
|
| | | this.set(name, '', date, path, domain);
|
| | | }
|
| | | });
|
| | | })();
|
| | |
| | |
|
| | | modifierPressed: function (e) {
|
| | | return e.shiftKey || e.ctrlKey || e.altKey;
|
| | | },
|
| | |
|
| | | metaKeyPressed: function(e) {
|
| | | // Check if ctrl or meta key is pressed also check if alt is false for Polish users
|
| | | return tinymce.isMac ? e.metaKey : e.ctrlKey && !e.altKey;
|
| | | }
|
| | | };
|
| | | })(tinymce);
|
| | |
| | | // Ignore
|
| | | }
|
| | | }
|
| | |
|
| | | function getDocumentMode() {
|
| | | var documentMode = editor.getDoc().documentMode;
|
| | |
|
| | | return documentMode ? documentMode : 6;
|
| | | };
|
| | |
|
| | | function cleanupStylesWhenDeleting() {
|
| | | function removeMergedFormatSpans(isDelete) {
|
| | |
| | | };
|
| | |
|
| | | function emptyEditorWhenDeleting() {
|
| | | function getEndPointNode(rng, start) {
|
| | | var container, offset, prefix = start ? 'start' : 'end';
|
| | | function serializeRng(rng) {
|
| | | var body = dom.create("body");
|
| | | var contents = rng.cloneContents();
|
| | | body.appendChild(contents);
|
| | | return selection.serializer.serialize(body, {format: 'html'});
|
| | | }
|
| | |
|
| | | container = rng[prefix + 'Container'];
|
| | | offset = rng[prefix + 'Offset'];
|
| | | function allContentsSelected(rng) {
|
| | | var selection = serializeRng(rng);
|
| | |
|
| | | // Resolve indexed container
|
| | | if (container.nodeType == 1 && container.hasChildNodes()) {
|
| | | container = container.childNodes[Math.min(start ? offset : (offset > 0 ? offset - 1 : 0), container.childNodes.length - 1)]
|
| | | }
|
| | | var allRng = dom.createRng();
|
| | | allRng.selectNode(editor.getBody());
|
| | |
|
| | | return container;
|
| | | };
|
| | | var allSelection = serializeRng(allRng);
|
| | | return selection === allSelection;
|
| | | }
|
| | |
|
| | | function isAtStartEndOfBody(rng, start) {
|
| | | var container, offset, root, childNode, prefix = start ? 'start' : 'end', isAfter;
|
| | | editor.onKeyDown.add(function(editor, e) {
|
| | | var keyCode = e.keyCode, isCollapsed;
|
| | |
|
| | | container = rng[prefix + 'Container'];
|
| | | offset = rng[prefix + 'Offset'];
|
| | | root = dom.getRoot();
|
| | |
|
| | | // Resolve indexed container
|
| | | if (container.nodeType == 1) {
|
| | | isAfter = offset >= container.childNodes.length;
|
| | | container = getEndPointNode(rng, start);
|
| | |
|
| | | if (container.nodeType == 3) {
|
| | | offset = start && !isAfter ? 0 : container.nodeValue.length;
|
| | | }
|
| | | }
|
| | |
|
| | | // Check if start/end is in the middle of text
|
| | | if (container.nodeType == 3 && ((start && offset > 0) || (!start && offset < container.nodeValue.length))) {
|
| | | return false;
|
| | | }
|
| | |
|
| | | // Walk up the DOM tree to see if the endpoint is at the beginning/end of body
|
| | | while (container !== root) {
|
| | | childNode = container.parentNode[start ? 'firstChild' : 'lastChild'];
|
| | |
|
| | | // If first/last element is a BR then jump to it's sibling in case: <p>x<br></p>
|
| | | if (childNode.nodeName == "BR") {
|
| | | childNode = childNode[start ? 'nextSibling' : 'previousSibling'] || childNode;
|
| | | }
|
| | |
|
| | | // If the childNode isn't the container node then break in case <p><span>A</span>[X]</p>
|
| | | if (childNode !== container) {
|
| | | return false;
|
| | | }
|
| | |
|
| | | container = container.parentNode;
|
| | | }
|
| | |
|
| | | return true;
|
| | | };
|
| | |
|
| | | editor.onKeyDown.addToTop(function(editor, e) {
|
| | | var rng, keyCode = e.keyCode;
|
| | |
|
| | | // Empty the editor if it's needed for example backspace at <p><b>|</b></p>
|
| | | if (!e.isDefaultPrevented() && (keyCode == DELETE || keyCode == BACKSPACE)) {
|
| | | rng = selection.getRng(true);
|
| | | isCollapsed = editor.selection.isCollapsed();
|
| | |
|
| | | if (isAtStartEndOfBody(rng, true) && isAtStartEndOfBody(rng, false) &&
|
| | | (rng.collapsed || dom.findCommonAncestor(getEndPointNode(rng, true), getEndPointNode(rng)) === dom.getRoot())) {
|
| | | editor.setContent('');
|
| | | editor.nodeChanged();
|
| | | e.preventDefault();
|
| | | // Selection is collapsed but the editor isn't empty
|
| | | if (isCollapsed && !dom.isEmpty(editor.getBody())) {
|
| | | return;
|
| | | }
|
| | |
|
| | | // IE deletes all contents correctly when everything is selected
|
| | | if (tinymce.isIE && !isCollapsed) {
|
| | | return;
|
| | | }
|
| | |
|
| | | // Selection isn't collapsed but not all the contents is selected
|
| | | if (!isCollapsed && !allContentsSelected(editor.selection.getRng())) {
|
| | | return;
|
| | | }
|
| | |
|
| | | // Manually empty the editor
|
| | | editor.setContent('');
|
| | | editor.selection.setCursorLocation(editor.getBody(), 0);
|
| | | editor.nodeChanged();
|
| | | }
|
| | | });
|
| | | };
|
| | |
|
| | | function selectAll() {
|
| | | editor.onKeyDown.add(function(editor, e) {
|
| | | if (e.keyCode == 65 && VK.metaKeyPressed(e)) {
|
| | | e.preventDefault();
|
| | | editor.execCommand('SelectAll');
|
| | | }
|
| | | });
|
| | | };
|
| | |
| | | }
|
| | |
|
| | | function isSelectionAcrossElements() {
|
| | | return !selection.isCollapsed() && selection.getStart() != selection.getEnd();
|
| | | return !selection.isCollapsed() && dom.getParent(selection.getStart(), dom.isBlock) != dom.getParent(selection.getEnd(), dom.isBlock);
|
| | | }
|
| | |
|
| | | function blockEvent(editor, e) {
|
| | |
| | | }
|
| | |
|
| | | function addNewLinesBeforeBrInPre() {
|
| | | var documentMode = editor.getDoc().documentMode;
|
| | |
|
| | | // IE8+ rendering mode does the right thing with BR in PRE
|
| | | if (documentMode && documentMode > 7) {
|
| | | 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
|
| | |
| | | editor.onSetContent.add(selection.onSetContent.add(fixLinks));
|
| | | };
|
| | |
|
| | | function setDefaultBlockType() {
|
| | | if (settings.forced_root_block) {
|
| | | editor.onInit.add(function() {
|
| | | setEditorCommandState('DefaultParagraphSeparator', settings.forced_root_block);
|
| | | });
|
| | | }
|
| | | }
|
| | |
|
| | | function removeGhostSelection() {
|
| | | function repaint(sender, args) {
|
| | | if (!sender || !args.initial) {
|
| | |
| | | editor.onSetContent.add(repaint);
|
| | | };
|
| | |
|
| | | function deleteImageOnBackSpace() {
|
| | | function deleteControlItemOnBackSpace() {
|
| | | editor.onKeyDown.add(function(editor, e) {
|
| | | if (!e.isDefaultPrevented() && e.keyCode == 8 && selection.getNode().nodeName == 'IMG') {
|
| | | e.preventDefault();
|
| | | editor.undoManager.beforeChange();
|
| | | dom.remove(selection.getNode());
|
| | | editor.undoManager.add();
|
| | | var rng;
|
| | |
|
| | | if (!e.isDefaultPrevented() && 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();
|
| | | }
|
| | | }
|
| | | });
|
| | | };
|
| | |
|
| | | function renderEmptyBlocksFix() {
|
| | | var emptyBlocksCSS;
|
| | |
|
| | | // IE10+
|
| | | if (getDocumentMode() >= 10) {
|
| | | emptyBlocksCSS = '';
|
| | | tinymce.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}');
|
| | | }
|
| | | };
|
| | |
|
| | | function fakeImageResize() {
|
| | | var selectedElmX, selectedElmY, selectedElm, selectedElmGhost, selectedHandle, startX, startY, startW, startH, ratio,
|
| | | resizeHandles, width, height, rootDocument = document, editableDoc = editor.getDoc();
|
| | |
|
| | | if (!settings.object_resizing || settings.webkit_fake_resize === false) {
|
| | | return;
|
| | | }
|
| | |
|
| | | // Try disabling object resizing if WebKit implements resizing in the future
|
| | | setEditorCommandState("enableObjectResizing", false);
|
| | |
|
| | | // Details about each resize handle how to scale etc
|
| | | resizeHandles = {
|
| | | // Name: x multiplier, y multiplier, delta size x, delta size y
|
| | | n: [.5, 0, 0, -1],
|
| | | e: [1, .5, 1, 0],
|
| | | s: [.5, 1, 0, 1],
|
| | | w: [0, .5, -1, 0],
|
| | | nw: [0, 0, -1, -1],
|
| | | ne: [1, 0, 1, -1],
|
| | | se: [1, 1, 1, 1],
|
| | | sw : [0, 1, -1, 1]
|
| | | };
|
| | |
|
| | | function resizeElement(e) {
|
| | | var deltaX, deltaY;
|
| | |
|
| | | // Calc new width/height
|
| | | deltaX = e.screenX - startX;
|
| | | deltaY = e.screenY - startY;
|
| | |
|
| | | // Calc new size
|
| | | width = deltaX * selectedHandle[2] + startW;
|
| | | height = deltaY * selectedHandle[3] + startH;
|
| | |
|
| | | // Never scale down lower than 5 pixels
|
| | | width = width < 5 ? 5 : width;
|
| | | height = height < 5 ? 5 : height;
|
| | |
|
| | | // Constrain proportions when modifier key is pressed or if the nw, ne, sw, se corners are moved on an image
|
| | | if (VK.modifierPressed(e) || (selectedElm.nodeName == "IMG" && selectedHandle[2] * selectedHandle[3] !== 0)) {
|
| | | width = Math.round(height / ratio);
|
| | | height = Math.round(width * ratio);
|
| | | }
|
| | |
|
| | | // Update ghost size
|
| | | dom.setStyles(selectedElmGhost, {
|
| | | width: width,
|
| | | height: height
|
| | | });
|
| | |
|
| | | // Update ghost X position if needed
|
| | | if (selectedHandle[2] < 0 && selectedElmGhost.clientWidth <= width) {
|
| | | dom.setStyle(selectedElmGhost, 'left', selectedElmX + (startW - width));
|
| | | }
|
| | |
|
| | | // Update ghost Y position if needed
|
| | | if (selectedHandle[3] < 0 && selectedElmGhost.clientHeight <= height) {
|
| | | dom.setStyle(selectedElmGhost, 'top', selectedElmY + (startH - height));
|
| | | }
|
| | | }
|
| | |
|
| | | function endResize() {
|
| | | function setSizeProp(name, value) {
|
| | | if (value) {
|
| | | // Resize by using style or attribute
|
| | | if (selectedElm.style[name] || !editor.schema.isValid(selectedElm.nodeName.toLowerCase(), name)) {
|
| | | dom.setStyle(selectedElm, name, value);
|
| | | } else {
|
| | | dom.setAttrib(selectedElm, name, value);
|
| | | }
|
| | | }
|
| | | }
|
| | |
|
| | | // Set width/height properties
|
| | | setSizeProp('width', width);
|
| | | setSizeProp('height', height);
|
| | |
|
| | | dom.unbind(editableDoc, 'mousemove', resizeElement);
|
| | | dom.unbind(editableDoc, 'mouseup', endResize);
|
| | |
|
| | | if (rootDocument != editableDoc) {
|
| | | dom.unbind(rootDocument, 'mousemove', resizeElement);
|
| | | dom.unbind(rootDocument, 'mouseup', endResize);
|
| | | }
|
| | |
|
| | | // Remove ghost and update resize handle positions
|
| | | dom.remove(selectedElmGhost);
|
| | | showResizeRect(selectedElm);
|
| | | }
|
| | |
|
| | | function showResizeRect(targetElm) {
|
| | | var position, targetWidth, targetHeight;
|
| | |
|
| | | hideResizeRect();
|
| | |
|
| | | // Get position and size of target
|
| | | position = dom.getPos(targetElm);
|
| | | selectedElmX = position.x;
|
| | | selectedElmY = position.y;
|
| | | targetWidth = targetElm.offsetWidth;
|
| | | targetHeight = targetElm.offsetHeight;
|
| | |
|
| | | // Reset width/height if user selects a new image/table
|
| | | if (selectedElm != targetElm) {
|
| | | selectedElm = targetElm;
|
| | | width = height = 0;
|
| | | }
|
| | |
|
| | | tinymce.each(resizeHandles, function(handle, name) {
|
| | | var handleElm;
|
| | |
|
| | | // Get existing or render resize handle
|
| | | handleElm = dom.get('mceResizeHandle' + name);
|
| | | if (!handleElm) {
|
| | | handleElm = dom.add(editableDoc.documentElement, 'div', {
|
| | | id: 'mceResizeHandle' + name,
|
| | | 'class': 'mceResizeHandle',
|
| | | style: 'cursor:' + name + '-resize; margin:0; padding:0'
|
| | | });
|
| | |
|
| | | dom.bind(handleElm, 'mousedown', function(e) {
|
| | | e.preventDefault();
|
| | |
|
| | | endResize();
|
| | |
|
| | | startX = e.screenX;
|
| | | startY = e.screenY;
|
| | | startW = selectedElm.clientWidth;
|
| | | startH = selectedElm.clientHeight;
|
| | | ratio = startH / startW;
|
| | | selectedHandle = handle;
|
| | |
|
| | | selectedElmGhost = selectedElm.cloneNode(true);
|
| | | dom.addClass(selectedElmGhost, 'mceClonedResizable');
|
| | | dom.setStyles(selectedElmGhost, {
|
| | | left: selectedElmX,
|
| | | top: selectedElmY,
|
| | | margin: 0
|
| | | });
|
| | |
|
| | | editableDoc.documentElement.appendChild(selectedElmGhost);
|
| | |
|
| | | dom.bind(editableDoc, 'mousemove', resizeElement);
|
| | | dom.bind(editableDoc, 'mouseup', endResize);
|
| | |
|
| | | if (rootDocument != editableDoc) {
|
| | | dom.bind(rootDocument, 'mousemove', resizeElement);
|
| | | dom.bind(rootDocument, 'mouseup', endResize);
|
| | | }
|
| | | });
|
| | | } else {
|
| | | dom.show(handleElm);
|
| | | }
|
| | |
|
| | | // Position element
|
| | | dom.setStyles(handleElm, {
|
| | | left: (targetWidth * handle[0] + selectedElmX) - (handleElm.offsetWidth / 2),
|
| | | top: (targetHeight * handle[1] + selectedElmY) - (handleElm.offsetHeight / 2)
|
| | | });
|
| | | });
|
| | |
|
| | | // Only add resize rectangle on WebKit and only on images
|
| | | if (!tinymce.isOpera && selectedElm.nodeName == "IMG") {
|
| | | selectedElm.setAttribute('data-mce-selected', '1');
|
| | | }
|
| | | }
|
| | |
|
| | | function hideResizeRect() {
|
| | | if (selectedElm) {
|
| | | selectedElm.removeAttribute('data-mce-selected');
|
| | | }
|
| | |
|
| | | for (var name in resizeHandles) {
|
| | | dom.hide('mceResizeHandle' + name);
|
| | | }
|
| | | }
|
| | |
|
| | | // Add CSS for resize handles, cloned element and selected
|
| | | editor.contentStyles.push(
|
| | | '.mceResizeHandle {' +
|
| | | 'position: absolute;' +
|
| | | 'border: 1px solid black;' +
|
| | | 'background: #FFF;' +
|
| | | 'width: 5px;' +
|
| | | 'height: 5px;' +
|
| | | 'z-index: 10000' +
|
| | | '}' +
|
| | | '.mceResizeHandle:hover {' +
|
| | | 'background: #000' +
|
| | | '}' +
|
| | | 'img[data-mce-selected] {' +
|
| | | 'outline: 1px solid black' +
|
| | | '}' +
|
| | | 'img.mceClonedResizable, table.mceClonedResizable {' +
|
| | | 'position: absolute;' +
|
| | | 'outline: 1px dashed black;' +
|
| | | 'opacity: .5;' +
|
| | | 'z-index: 10000' +
|
| | | '}'
|
| | | );
|
| | |
|
| | | function updateResizeRect() {
|
| | | var controlElm = dom.getParent(selection.getNode(), 'table,img');
|
| | |
|
| | | // Remove data-mce-selected from all elements since they might have been copied using Ctrl+c/v
|
| | | tinymce.each(dom.select('img[data-mce-selected]'), function(img) {
|
| | | img.removeAttribute('data-mce-selected');
|
| | | });
|
| | |
|
| | | if (controlElm) {
|
| | | showResizeRect(controlElm);
|
| | | } else {
|
| | | hideResizeRect();
|
| | | }
|
| | | }
|
| | |
|
| | | // Show/hide resize rect when image is selected
|
| | | editor.onNodeChange.add(updateResizeRect);
|
| | |
|
| | | // Fixes WebKit quirk where it returns IMG on getNode if caret is after last image in container
|
| | | dom.bind(editableDoc, 'selectionchange', updateResizeRect);
|
| | |
|
| | | // Remove the internal attribute when serializing the DOM
|
| | | editor.serializer.addAttributeFilter('data-mce-selected', function(nodes, name) {
|
| | | var i = nodes.length;
|
| | |
|
| | | while (i--) {
|
| | | nodes[i].attr(name, null);
|
| | | }
|
| | | });
|
| | | }
|
| | |
|
| | | // All browsers
|
| | | disableBackspaceIntoATable();
|
| | |
| | | cleanupStylesWhenDeleting();
|
| | | inputMethodFocus();
|
| | | selectControlElements();
|
| | | setDefaultBlockType();
|
| | |
|
| | | // iOS
|
| | | if (tinymce.isIDevice) {
|
| | | selectionChangeNodeChanged();
|
| | | } else {
|
| | | fakeImageResize();
|
| | | selectAll();
|
| | | }
|
| | | }
|
| | |
|
| | |
| | | ensureBodyHasRoleApplication();
|
| | | addNewLinesBeforeBrInPre();
|
| | | removePreSerializedStylesWhenSelectingControls();
|
| | | deleteImageOnBackSpace();
|
| | | deleteControlItemOnBackSpace();
|
| | | renderEmptyBlocksFix();
|
| | | }
|
| | |
|
| | | // Gecko
|
| | |
| | | setGeckoEditingOptions();
|
| | | addBrAfterLastLinks();
|
| | | removeGhostSelection();
|
| | | }
|
| | |
|
| | | // Opera
|
| | | if (tinymce.isOpera) {
|
| | | fakeImageResize();
|
| | | }
|
| | | };
|
| | | (function(tinymce) {
|
| | |
| | |
|
| | | if (!html5) {
|
| | | html5 = mapCache.html5 = unpack({
|
| | | A : 'id|accesskey|class|dir|draggable|item|hidden|itemprop|role|spellcheck|style|subject|title',
|
| | | B : '#|a|abbr|area|audio|b|bdo|br|button|canvas|cite|code|command|datalist|del|dfn|em|embed|i|iframe|img|input|ins|kbd|keygen|label|link|map|mark|meta|meter|noscript|object|output|progress|q|ruby|samp|script|select|small|span|strong|sub|sup|svg|textarea|time|var|video',
|
| | | C : '#|a|abbr|area|address|article|aside|audio|b|bdo|blockquote|br|button|canvas|cite|code|command|datalist|del|details|dfn|dialog|div|dl|em|embed|fieldset|figure|footer|form|h1|h2|h3|h4|h5|h6|header|hgroup|hr|i|iframe|img|input|ins|kbd|keygen|label|link|map|mark|menu|meta|meter|nav|noscript|ol|object|output|p|pre|progress|q|ruby|samp|script|section|select|small|span|strong|style|sub|sup|svg|table|textarea|time|ul|var|video'
|
| | | A : 'id|accesskey|class|dir|draggable|item|hidden|itemprop|role|spellcheck|style|subject|title|onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup',
|
| | | B : '#|a|abbr|area|audio|b|bdo|br|button|canvas|cite|code|command|datalist|del|dfn|em|embed|i|iframe|img|input|ins|kbd|keygen|label|link|map|mark|meta|' +
|
| | | 'meter|noscript|object|output|progress|q|ruby|samp|script|select|small|span|strong|sub|sup|svg|textarea|time|var|video|wbr',
|
| | | C : '#|a|abbr|area|address|article|aside|audio|b|bdo|blockquote|br|button|canvas|cite|code|command|datalist|del|details|dfn|dialog|div|dl|em|embed|fieldset|' +
|
| | | 'figure|footer|form|h1|h2|h3|h4|h5|h6|header|hgroup|hr|i|iframe|img|input|ins|kbd|keygen|label|link|map|mark|menu|meta|meter|nav|noscript|ol|object|output|' +
|
| | | 'p|pre|progress|q|ruby|samp|script|section|select|small|span|strong|style|sub|sup|svg|table|textarea|time|ul|var|video'
|
| | | }, 'html[A|manifest][body|head]' +
|
| | | 'head[A][base|command|link|meta|noscript|script|style|title]' +
|
| | | 'title[A][#]' +
|
| | |
| | | 'dl[A][dd|dt]' +
|
| | | 'dt[A][B]' +
|
| | | 'dd[A][C]' +
|
| | | 'a[A|href|target|ping|rel|media|type][C]' +
|
| | | 'a[A|href|target|ping|rel|media|type][B]' +
|
| | | 'em[A][B]' +
|
| | | 'strong[A][B]' +
|
| | | 'small[A][B]' +
|
| | |
| | | 'form[A|accept-charset|action|autocomplete|enctype|method|name|novalidate|target][C]' +
|
| | | 'fieldset[A|disabled|form|name][C|legend]' +
|
| | | 'label[A|form|for][B]' +
|
| | | 'input[A|type|accept|alt|autocomplete|checked|disabled|form|formaction|formenctype|formmethod|formnovalidate|formtarget|height|list|max|maxlength|min|multiple|pattern|placeholder|readonly|required|size|src|step|width|files|value][]' +
|
| | | 'input[A|type|accept|alt|autocomplete|checked|disabled|form|formaction|formenctype|formmethod|formnovalidate|formtarget|height|list|max|maxlength|min|' +
|
| | | 'multiple|pattern|placeholder|readonly|required|size|src|step|width|files|value|name][]' +
|
| | | 'button[A|autofocus|disabled|form|formaction|formenctype|formmethod|formnovalidate|formtarget|name|value|type][B]' +
|
| | | 'select[A|autofocus|disabled|form|multiple|name|size][option|optgroup]' +
|
| | | 'datalist[A][B|option]' +
|
| | |
| | | 'area[A|shape|coords|href|alt|target|media|rel|ping|type][]' +
|
| | | 'mathml[A][]' +
|
| | | 'svg[A][]' +
|
| | | 'table[A|summary][caption|colgroup|thead|tfoot|tbody|tr]' +
|
| | | 'table[A|border][caption|colgroup|thead|tfoot|tbody|tr]' +
|
| | | 'caption[A][C]' +
|
| | | 'colgroup[A|span][col]' +
|
| | | 'col[A|span][]' +
|
| | |
| | | 'tbody[A][tr]' +
|
| | | 'tr[A][th|td]' +
|
| | | 'th[A|headers|rowspan|colspan|scope][B]' +
|
| | | 'td[A|headers|rowspan|colspan][C]'
|
| | | 'td[A|headers|rowspan|colspan][C]' +
|
| | | 'wbr[A][]'
|
| | | );
|
| | | }
|
| | |
|
| | |
| | |
|
| | | // Setup map objects
|
| | | whiteSpaceElementsMap = createLookupTable('whitespace_elements', 'pre script style textarea');
|
| | | selfClosingElementsMap = createLookupTable('self_closing_elements', 'colgroup dd dt li options p td tfoot th thead tr');
|
| | | shortEndedElementsMap = createLookupTable('short_ended_elements', 'area base basefont br col frame hr img input isindex link meta param embed source');
|
| | | selfClosingElementsMap = createLookupTable('self_closing_elements', 'colgroup dd dt li option p td tfoot th thead tr');
|
| | | shortEndedElementsMap = createLookupTable('short_ended_elements', 'area base basefont br col frame hr img input isindex link meta param embed source wbr');
|
| | | boolAttrMap = createLookupTable('boolean_attributes', 'checked compact declare defer disabled ismap multiple nohref noresize noshade nowrap readonly selected autoplay loop controls');
|
| | | nonEmptyElementsMap = createLookupTable('non_empty_elements', 'td th iframe video audio object', shortEndedElementsMap);
|
| | | blockElementsMap = createLookupTable('block_elements', 'h1 h2 h3 h4 h5 h6 hr p div address pre form table tbody thead tfoot ' +
|
| | | 'th tr td li ol ul caption blockquote center dl dt dd dir fieldset ' +
|
| | | 'noscript menu isindex samp header footer article section hgroup aside nav figure');
|
| | | 'noscript menu isindex samp header footer article section hgroup aside nav figure option datalist select optgroup');
|
| | |
|
| | | // Converts a wildcard expression string to a regexp for example *a will become /.*a/.
|
| | | function patternToRegExp(str) {
|
| | |
| | | return !!(parent && parent[child]);
|
| | | };
|
| | |
|
| | | self.isValid = function(name, attr) {
|
| | | var attrPatterns, i, rule = getElementRule(name);
|
| | |
|
| | | // Check if it's a valid element
|
| | | if (rule) {
|
| | | if (attr) {
|
| | | // Check if attribute name exists
|
| | | if (rule.attributes[attr]) {
|
| | | return true;
|
| | | }
|
| | |
|
| | | // Check if attribute matches a regexp pattern
|
| | | attrPatterns = rule.attributePatterns;
|
| | | if (attrPatterns) {
|
| | | i = attrPatterns.length;
|
| | | while (i--) {
|
| | | if (attrPatterns[i].pattern.test(name)) {
|
| | | return true;
|
| | | }
|
| | | }
|
| | | }
|
| | | } else {
|
| | | return true;
|
| | | }
|
| | | }
|
| | |
|
| | | // No match
|
| | | return false;
|
| | | };
|
| | | |
| | | self.getElementRule = getElementRule;
|
| | |
|
| | | self.getCustomElements = function() {
|
| | |
| | |
|
| | | // Setup lookup tables for empty elements and boolean attributes
|
| | | shortEndedElements = schema.getShortEndedElements();
|
| | | selfClosing = schema.getSelfClosingElements();
|
| | | selfClosing = settings.self_closing_elements || schema.getSelfClosingElements();
|
| | | fillAttrsMap = schema.getBoolAttrs();
|
| | | validate = settings.validate;
|
| | | removeInternalElements = settings.remove_internals;
|
| | |
| | | }
|
| | | };
|
| | |
|
| | | function cloneAndExcludeBlocks(input) {
|
| | | var name, output = {};
|
| | |
|
| | | for (name in input) {
|
| | | if (name !== 'li' && name != 'p') {
|
| | | output[name] = input[name];
|
| | | }
|
| | | }
|
| | |
|
| | | return output;
|
| | | };
|
| | |
|
| | | parser = new tinymce.html.SaxParser({
|
| | | validate : validate,
|
| | | fix_self_closing : !validate, // Let the DOM parser handle <li> in <li> or <p> in <p> for better results
|
| | |
|
| | | // Exclude P and LI from DOM parsing since it's treated better by the DOM parser
|
| | | self_closing_elements: cloneAndExcludeBlocks(schema.getSelfClosingElements()),
|
| | |
|
| | | cdata: function(text) {
|
| | | node.append(createNode('#cdata', 4)).value = text;
|
| | |
| | | node.empty().append(new Node('#text', '3')).value = '\u00a0';
|
| | | else {
|
| | | // Leave nodes that have a name like <a name="name">
|
| | | if (!node.attributes.map.name) {
|
| | | if (!node.attributes.map.name && !node.attributes.map.id) {
|
| | | tempNode = node.parent;
|
| | | node.empty().remove();
|
| | | node = tempNode;
|
| | |
| | |
|
| | | // Force anchor names closed, unless the setting "allow_html_in_named_anchor" is explicitly included.
|
| | | if (!settings.allow_html_in_named_anchor) {
|
| | | self.addAttributeFilter('name', function(nodes, name) {
|
| | | self.addAttributeFilter('id,name', function(nodes, name) {
|
| | | var i = nodes.length, sibling, prevSibling, parent, node;
|
| | |
|
| | | while (i--) {
|
| | | node = nodes[i];
|
| | | if (node.name === 'a' && node.firstChild) {
|
| | | if (node.name === 'a' && node.firstChild && !node.attr('href')) {
|
| | | parent = node.parent;
|
| | |
|
| | | // Move children after current node
|
| | |
| | |
|
| | | // Old API supported multiple targets
|
| | | if (target && target instanceof Array) {
|
| | | var i = target;
|
| | | var i = target.length;
|
| | |
|
| | | while (i--) {
|
| | | self.add(target[i], events, func, scope);
|
| | |
| | | };
|
| | |
|
| | | self.prevent = function(e) {
|
| | | if (!e.preventDefault) {
|
| | | e = fix(e);
|
| | | }
|
| | |
|
| | | e.preventDefault();
|
| | |
|
| | | return false;
|
| | | };
|
| | |
|
| | | self.stop = function(e) {
|
| | | if (!e.stopPropagation) {
|
| | | e = fix(e);
|
| | | }
|
| | |
|
| | | e.stopPropagation();
|
| | |
|
| | | return false;
|
| | |
| | | return this.styles.serialize(o, name);
|
| | | },
|
| | |
|
| | | addStyle: function(cssText) {
|
| | | var doc = this.doc, head;
|
| | |
|
| | | // Create style element if needed
|
| | | styleElm = doc.getElementById('mceDefaultStyles');
|
| | | if (!styleElm) {
|
| | | styleElm = doc.createElement('style'),
|
| | | styleElm.id = 'mceDefaultStyles';
|
| | | styleElm.type = 'text/css';
|
| | |
|
| | | head = doc.getElementsByTagName('head')[0];
|
| | | if (head.firstChild) {
|
| | | head.insertBefore(styleElm, head.firstChild);
|
| | | } else {
|
| | | head.appendChild(styleElm);
|
| | | }
|
| | | }
|
| | |
|
| | | // Append style data to old or new style element
|
| | | if (styleElm.styleSheet) {
|
| | | styleElm.styleSheet.cssText += cssText;
|
| | | } else {
|
| | | styleElm.appendChild(doc.createTextNode(cssText));
|
| | | }
|
| | | },
|
| | |
|
| | | loadCSS : function(u) {
|
| | | var t = this, d = t.doc, head;
|
| | |
|
| | |
| | | // This seems to fix this problem
|
| | |
|
| | | // Create new div with HTML contents and a BR infront to keep comments
|
| | | element = self.create('div');
|
| | | element.innerHTML = '<br />' + html;
|
| | | var newElement = self.create('div');
|
| | | newElement.innerHTML = '<br />' + html;
|
| | |
|
| | | // Add all children from div to target
|
| | | each (element.childNodes, function(node, i) {
|
| | | each (tinymce.grep(newElement.childNodes), function(node, i) {
|
| | | // Skip br element
|
| | | if (i)
|
| | | if (i && element.canHaveHTML)
|
| | | element.appendChild(node);
|
| | | });
|
| | | }
|
| | |
| | | cloneContents : cloneContents,
|
| | | insertNode : insertNode,
|
| | | surroundContents : surroundContents,
|
| | | cloneRange : cloneRange
|
| | | cloneRange : cloneRange,
|
| | | toStringIE : toStringIE
|
| | | });
|
| | |
|
| | | function createDocumentFragment() {
|
| | |
| | |
|
| | | n.parentNode.removeChild(n);
|
| | | };
|
| | |
|
| | | function toStringIE() {
|
| | | return dom.create('body', null, cloneContents()).outerText;
|
| | | }
|
| | | |
| | | return t;
|
| | | };
|
| | |
|
| | | ns.Range = Range;
|
| | |
|
| | | // Older IE versions doesn't let you override toString by it's constructor so we have to stick it in the prototype
|
| | | Range.prototype.toString = function() {
|
| | | return this.toStringIE();
|
| | | };
|
| | | })(tinymce.dom);
|
| | |
|
| | | (function() {
|
| | |
| | | };
|
| | |
|
| | | this.addRange = function(rng) {
|
| | | var ieRng, ctrlRng, startContainer, startOffset, endContainer, endOffset, doc = selection.dom.doc, body = doc.body;
|
| | | var ieRng, ctrlRng, startContainer, startOffset, endContainer, endOffset, sibling,
|
| | | doc = selection.dom.doc, body = doc.body, nativeRng, ctrlElm;
|
| | |
|
| | | function setEndPoint(start) {
|
| | | var container, offset, marker, tmpRng, nodes;
|
| | |
| | | // Trick to place the caret inside an empty block element like <p></p>
|
| | | if (startOffset == endOffset && !startContainer.hasChildNodes()) {
|
| | | if (startContainer.canHaveHTML) {
|
| | | // Check if previous sibling is an empty block if it is then we need to render it
|
| | | // IE would otherwise move the caret into the sibling instead of the empty startContainer see: #5236
|
| | | // Example this: <p></p><p>|</p> would become this: <p>|</p><p></p>
|
| | | sibling = startContainer.previousSibling;
|
| | | if (sibling && !sibling.hasChildNodes() && dom.isBlock(sibling)) {
|
| | | sibling.innerHTML = '\uFEFF';
|
| | | } else {
|
| | | sibling = null;
|
| | | }
|
| | |
|
| | | startContainer.innerHTML = '<span>\uFEFF</span><span>\uFEFF</span>';
|
| | | ieRng.moveToElementText(startContainer.lastChild);
|
| | | ieRng.select();
|
| | | dom.doc.selection.clear();
|
| | | startContainer.innerHTML = '';
|
| | |
|
| | | if (sibling) {
|
| | | sibling.innerHTML = '';
|
| | | }
|
| | | return;
|
| | | } else {
|
| | | startOffset = dom.nodeIndex(startContainer);
|
| | |
| | |
|
| | | if (startOffset == endOffset - 1) {
|
| | | try {
|
| | | ctrlElm = startContainer.childNodes[startOffset];
|
| | | ctrlRng = body.createControlRange();
|
| | | ctrlRng.addElement(startContainer.childNodes[startOffset]);
|
| | | ctrlRng.addElement(ctrlElm);
|
| | | ctrlRng.select();
|
| | | return;
|
| | |
|
| | | // 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
|
| | | }
|
| | |
| | | var is = tinymce.is, isIE = tinymce.isIE, each = tinymce.each, TreeWalker = tinymce.dom.TreeWalker;
|
| | |
|
| | | tinymce.create('tinymce.dom.Selection', {
|
| | | Selection : function(dom, win, serializer) {
|
| | | Selection : function(dom, win, serializer, editor) {
|
| | | var t = this;
|
| | |
|
| | | t.dom = dom;
|
| | | t.win = win;
|
| | | t.serializer = serializer;
|
| | | t.editor = editor;
|
| | |
|
| | | // Add events
|
| | | each([
|
| | |
| | | },
|
| | |
|
| | | getStart : function() {
|
| | | var rng = this.getRng(), startElement, parentElement, checkRng, node;
|
| | | var self = this, rng = self.getRng(), startElement, parentElement, checkRng, node;
|
| | |
|
| | | if (rng.duplicate || rng.item) {
|
| | | // Control selection, return first item
|
| | |
| | | checkRng = rng.duplicate();
|
| | | checkRng.collapse(1);
|
| | | startElement = checkRng.parentElement();
|
| | | if (startElement.ownerDocument !== self.dom.doc) {
|
| | | startElement = self.dom.getRoot();
|
| | | }
|
| | |
|
| | | // Check if range parent is inside the start element, then return the inner parent element
|
| | | // This will fix issues when a single element is selected, IE would otherwise return the wrong start element
|
| | |
| | | },
|
| | |
|
| | | getEnd : function() {
|
| | | var t = this, r = t.getRng(), e, eo;
|
| | | var self = this, rng = self.getRng(), endElement, endOffset;
|
| | |
|
| | | if (r.duplicate || r.item) {
|
| | | if (r.item)
|
| | | return r.item(0);
|
| | | if (rng.duplicate || rng.item) {
|
| | | if (rng.item)
|
| | | return rng.item(0);
|
| | |
|
| | | r = r.duplicate();
|
| | | r.collapse(0);
|
| | | e = r.parentElement();
|
| | | rng = rng.duplicate();
|
| | | rng.collapse(0);
|
| | | endElement = rng.parentElement();
|
| | | if (endElement.ownerDocument !== self.dom.doc) {
|
| | | endElement = self.dom.getRoot();
|
| | | }
|
| | |
|
| | | if (e && e.nodeName == 'BODY')
|
| | | return e.lastChild || e;
|
| | | if (endElement && endElement.nodeName == 'BODY')
|
| | | return endElement.lastChild || endElement;
|
| | |
|
| | | return e;
|
| | | return endElement;
|
| | | } else {
|
| | | e = r.endContainer;
|
| | | eo = r.endOffset;
|
| | | endElement = rng.endContainer;
|
| | | endOffset = rng.endOffset;
|
| | |
|
| | | if (e.nodeType == 1 && e.hasChildNodes())
|
| | | e = e.childNodes[eo > 0 ? eo - 1 : eo];
|
| | | if (endElement.nodeType == 1 && endElement.hasChildNodes())
|
| | | endElement = endElement.childNodes[endOffset > 0 ? endOffset - 1 : endOffset];
|
| | |
|
| | | if (e && e.nodeType == 3)
|
| | | return e.parentNode;
|
| | | if (endElement && endElement.nodeType == 3)
|
| | | return endElement.parentNode;
|
| | |
|
| | | return e;
|
| | | return endElement;
|
| | | }
|
| | | },
|
| | |
|
| | |
| | | }
|
| | | },
|
| | |
|
| | | destroy : function(s) {
|
| | | var t = this;
|
| | | selectorChanged: function(selector, callback) {
|
| | | var self = this, currentSelectors;
|
| | |
|
| | | t.win = null;
|
| | | if (!self.selectorChangedData) {
|
| | | self.selectorChangedData = {};
|
| | | currentSelectors = {};
|
| | |
|
| | | self.editor.onNodeChange.addToTop(function(ed, cm, node) {
|
| | | var dom = self.dom, parents = dom.getParents(node, null, dom.getRoot()), matchedSelectors = {};
|
| | |
|
| | | // Check for new matching selectors
|
| | | each(self.selectorChangedData, function(callbacks, selector) {
|
| | | each(parents, function(node) {
|
| | | if (dom.is(node, selector)) {
|
| | | if (!currentSelectors[selector]) {
|
| | | // Execute callbacks
|
| | | each(callbacks, function(callback) {
|
| | | callback(true, {node: node, selector: selector, parents: parents});
|
| | | });
|
| | |
|
| | | currentSelectors[selector] = callbacks;
|
| | | }
|
| | |
|
| | | matchedSelectors[selector] = callbacks;
|
| | | return false;
|
| | | }
|
| | | });
|
| | | });
|
| | |
|
| | | // Check if current selectors still match
|
| | | each(currentSelectors, function(callbacks, selector) {
|
| | | if (!matchedSelectors[selector]) {
|
| | | delete currentSelectors[selector];
|
| | |
|
| | | each(callbacks, function(callback) {
|
| | | callback(false, {node: node, selector: selector, parents: parents});
|
| | | });
|
| | | }
|
| | | });
|
| | | });
|
| | | }
|
| | |
|
| | | // Add selector listeners
|
| | | if (!self.selectorChangedData[selector]) {
|
| | | self.selectorChangedData[selector] = [];
|
| | | }
|
| | |
|
| | | self.selectorChangedData[selector].push(callback);
|
| | |
|
| | | return self;
|
| | | },
|
| | |
|
| | | destroy : function(manual) {
|
| | | var self = this;
|
| | |
|
| | | self.win = null;
|
| | |
|
| | | // Manual destroy then remove unload handler
|
| | | if (!s)
|
| | | tinymce.removeUnload(t.destroy);
|
| | | if (!manual)
|
| | | tinymce.removeUnload(self.destroy);
|
| | | },
|
| | |
|
| | | // IE has an issue where you can't select/move the caret by clicking outside the body if the document is in standards mode
|
| | |
| | |
|
| | | // Replace all BOM characters for now until we can find a better solution
|
| | | if (!args.cleanup)
|
| | | args.content = args.content.replace(/\uFEFF|\u200B/g, '');
|
| | | args.content = args.content.replace(/\uFEFF/g, '');
|
| | |
|
| | | // Post process
|
| | | if (!args.no_events)
|
| | |
| | | }
|
| | |
|
| | | // Create new script element
|
| | | elm = dom.create('script', {
|
| | | id : id,
|
| | | type : 'text/javascript',
|
| | | src : tinymce._addVer(url)
|
| | | });
|
| | | elm = document.createElement('script');
|
| | | elm.id = id;
|
| | | elm.type = 'text/javascript';
|
| | | elm.src = tinymce._addVer(url);
|
| | |
|
| | | // Add onload listener for non IE browsers since IE9
|
| | | // fires onload event before the script is parsed and executed
|
| | |
| | |
|
| | | t.destroy = function() {
|
| | | each(items, function(item) {
|
| | | dom.unbind(dom.get(item.id), 'focus', itemFocussed);
|
| | | dom.unbind(dom.get(item.id), 'blur', itemBlurred);
|
| | | var elm = dom.get(item.id);
|
| | |
|
| | | dom.unbind(elm, 'focus', itemFocussed);
|
| | | dom.unbind(elm, 'blur', itemBlurred);
|
| | | });
|
| | |
|
| | | dom.unbind(dom.get(root), 'focus', rootFocussed);
|
| | | dom.unbind(dom.get(root), 'keydown', rootKeydown);
|
| | | var rootElm = dom.get(root);
|
| | | dom.unbind(rootElm, 'focus', rootFocussed);
|
| | | dom.unbind(rootElm, 'keydown', rootKeydown);
|
| | |
|
| | | items = dom = root = t.focus = itemFocussed = itemBlurred = rootKeydown = rootFocussed = null;
|
| | | t.destroy = function() {};
|
| | |
| | |
|
| | | // Set up state and listeners for each item.
|
| | | each(items, function(item, idx) {
|
| | | var tabindex;
|
| | | var tabindex, elm;
|
| | |
|
| | | if (!item.id) {
|
| | | item.id = dom.uniqueId('_mce_item_');
|
| | | }
|
| | |
|
| | | elm = dom.get(item.id);
|
| | |
|
| | | if (excludeFromTabOrder) {
|
| | | dom.bind(item.id, 'blur', itemBlurred);
|
| | | dom.bind(elm, 'blur', itemBlurred);
|
| | | tabindex = '-1';
|
| | | } else {
|
| | | tabindex = (idx === 0 ? '0' : '-1');
|
| | | }
|
| | |
|
| | | dom.setAttrib(item.id, 'tabindex', tabindex);
|
| | | dom.bind(dom.get(item.id), 'focus', itemFocussed);
|
| | | elm.setAttribute('tabindex', tabindex);
|
| | | dom.bind(elm, 'focus', itemFocussed);
|
| | | });
|
| | |
|
| | | // Setup initial state for root element.
|
| | |
| | | }
|
| | |
|
| | | dom.setAttrib(root, 'tabindex', '-1');
|
| | | |
| | |
|
| | | // Setup listeners for root element.
|
| | | dom.bind(dom.get(root), 'focus', rootFocussed);
|
| | | dom.bind(dom.get(root), 'keydown', rootKeydown);
|
| | | var rootElm = dom.get(root);
|
| | | dom.bind(rootElm, 'focus', rootFocussed);
|
| | | dom.bind(rootElm, 'keydown', rootKeydown);
|
| | | }
|
| | | });
|
| | | })(tinymce);
|
| | |
| | | l = DOM.encode(s.label || '');
|
| | | h = '<a role="button" id="' + this.id + '" href="javascript:;" class="' + cp + ' ' + cp + 'Enabled ' + s['class'] + (l ? ' ' + cp + 'Labeled' : '') +'" onmousedown="return false;" onclick="return false;" aria-labelledby="' + this.id + '_voice" title="' + DOM.encode(s.title) + '">';
|
| | | if (s.image && !(this.editor &&this.editor.forcedHighContrastMode) )
|
| | | h += '<img class="mceIcon" src="' + s.image + '" alt="' + DOM.encode(s.title) + '" />' + l;
|
| | | h += '<span class="mceIcon ' + s['class'] + '"><img class="mceIcon" src="' + s.image + '" alt="' + DOM.encode(s.title) + '" /></span>' + (l ? '<span class="' + cp + 'Label">' + l + '</span>' : '');
|
| | | else
|
| | | h += '<span class="mceIcon ' + s['class'] + '"></span>' + (l ? '<span class="' + cp + 'Label">' + l + '</span>' : '');
|
| | |
|
| | |
| | | DOM.select('a', t.id + '_menu')[0].focus(); // Select first link
|
| | | }
|
| | |
|
| | | t.keyboardNav = new tinymce.ui.KeyboardNavigation({
|
| | | root: t.id + '_menu',
|
| | | items: DOM.select('a', t.id + '_menu'),
|
| | | onCancel: function() {
|
| | | t.hideMenu();
|
| | | t.focus();
|
| | | }
|
| | | });
|
| | |
|
| | | t.keyboardNav.focus();
|
| | | t.isMenuVisible = 1;
|
| | | },
|
| | |
|
| | |
| | |
|
| | | t.isMenuVisible = 0;
|
| | | t.onHideMenu.dispatch();
|
| | | t.keyboardNav.destroy();
|
| | | }
|
| | | },
|
| | |
|
| | |
| | | }
|
| | |
|
| | | DOM.addClass(m, 'mceColorSplitMenu');
|
| | | |
| | | new tinymce.ui.KeyboardNavigation({
|
| | | root: t.id + '_menu',
|
| | | items: DOM.select('a', t.id + '_menu'),
|
| | | onCancel: function() {
|
| | | t.hideMenu();
|
| | | t.focus();
|
| | | }
|
| | | });
|
| | |
|
| | | // Prevent IE from scrolling and hindering click to occur #4019
|
| | | Event.add(t.id + '_menu', 'mousedown', function(e) {return Event.cancel(e);});
|
| | |
| | | },
|
| | |
|
| | | destroy : function() {
|
| | | this.parent();
|
| | | var self = this;
|
| | |
|
| | | Event.clear(this.id + '_menu');
|
| | | Event.clear(this.id + '_more');
|
| | | DOM.remove(this.id + '_menu');
|
| | | self.parent();
|
| | |
|
| | | Event.clear(self.id + '_menu');
|
| | | Event.clear(self.id + '_more');
|
| | | DOM.remove(self.id + '_menu');
|
| | |
|
| | | if (self.keyboardNav) {
|
| | | self.keyboardNav.destroy();
|
| | | }
|
| | | }
|
| | | });
|
| | | })(tinymce);
|
| | |
| | | return c.constructor === RegExp ? c.test(n.className) : DOM.hasClass(n, c);
|
| | | };
|
| | |
|
| | | s = extend({
|
| | | theme : "simple",
|
| | | language : "en"
|
| | | }, s);
|
| | |
|
| | | t.settings = s;
|
| | |
|
| | | // Legacy call
|
| | |
| | | self.settings = settings = extend({
|
| | | id : id,
|
| | | language : 'en',
|
| | | theme : 'simple',
|
| | | theme : 'advanced',
|
| | | skin : 'default',
|
| | | delta_width : 0,
|
| | | delta_height : 0,
|
| | |
| | | inline_styles : TRUE,
|
| | | convert_fonts_to_spans : TRUE,
|
| | | indent : 'simple',
|
| | | indent_before : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr,section,article,hgroup,aside,figure',
|
| | | indent_after : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr,section,article,hgroup,aside,figure',
|
| | | indent_before : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist',
|
| | | indent_after : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist',
|
| | | validate : TRUE,
|
| | | entity_encoding : 'named',
|
| | | url_converter : self.convertURL,
|
| | |
| | | self.baseURI = tinymce.baseURI;
|
| | |
|
| | | self.contentCSS = [];
|
| | |
|
| | | self.contentStyles = [];
|
| | |
|
| | | // Creates all events like onClick, onSetContent etc see Editor.Events.js for the actual logic
|
| | | self.setupEvents();
|
| | |
| | | // Add hidden input for non input elements inside form elements
|
| | | if (!/TEXTAREA|INPUT/i.test(t.getElement().nodeName) && s.hidden_input && DOM.getParent(id, 'form'))
|
| | | DOM.insertAfter(DOM.create('input', {type : 'hidden', name : id}), id);
|
| | |
|
| | | // Hide target element early to prevent content flashing
|
| | | if (!s.content_editable) {
|
| | | t.orgVisibility = t.getElement().style.visibility;
|
| | | t.getElement().style.visibility = 'hidden';
|
| | | }
|
| | |
|
| | | if (tinymce.WindowManager)
|
| | | t.windowManager = new tinymce.WindowManager(t);
|
| | |
| | | if (s.language && s.language_load !== false)
|
| | | sl.add(tinymce.baseURL + '/langs/' + s.language + '.js');
|
| | |
|
| | | if (s.theme && s.theme.charAt(0) != '-' && !ThemeManager.urls[s.theme])
|
| | | if (s.theme && typeof s.theme != "function" && s.theme.charAt(0) != '-' && !ThemeManager.urls[s.theme])
|
| | | ThemeManager.load(s.theme, 'themes/' + s.theme + '/editor_template' + tinymce.suffix + '.js');
|
| | |
|
| | | each(explode(s.plugins), function(p) {
|
| | |
| | | },
|
| | |
|
| | | init : function() {
|
| | | var n, t = this, s = t.settings, w, h, e = t.getElement(), o, ti, u, bi, bc, re, i, initializedPlugins = [];
|
| | | var n, t = this, s = t.settings, w, h, mh, e = t.getElement(), o, ti, u, bi, bc, re, i, initializedPlugins = [];
|
| | |
|
| | | tinymce.add(t);
|
| | |
|
| | | s.aria_label = s.aria_label || DOM.getAttrib(e, 'aria-label', t.getLang('aria.rich_text_area'));
|
| | |
|
| | | if (s.theme) {
|
| | | s.theme = s.theme.replace(/-/, '');
|
| | | o = ThemeManager.get(s.theme);
|
| | | t.theme = new o();
|
| | | if (typeof s.theme != "function") {
|
| | | s.theme = s.theme.replace(/-/, '');
|
| | | o = ThemeManager.get(s.theme);
|
| | | t.theme = new o();
|
| | |
|
| | | if (t.theme.init)
|
| | | t.theme.init(t, ThemeManager.urls[s.theme] || tinymce.documentBaseURL.replace(/\/$/, ''));
|
| | | if (t.theme.init)
|
| | | t.theme.init(t, ThemeManager.urls[s.theme] || tinymce.documentBaseURL.replace(/\/$/, ''));
|
| | | } else {
|
| | | t.theme = s.theme;
|
| | | }
|
| | | }
|
| | |
|
| | | function initPlugin(p) {
|
| | | var c = PluginManager.get(p), u = PluginManager.urls[p] || tinymce.documentBaseURL.replace(/\/$/, ''), po;
|
| | | if (c && tinymce.inArray(initializedPlugins,p) === -1) {
|
| | |
| | |
|
| | | t.controlManager = new tinymce.ControlManager(t);
|
| | |
|
| | | t.onExecCommand.add(function(ed, c) {
|
| | | // Don't refresh the select lists until caret move
|
| | | if (!/^(FontName|FontSize)$/.test(c))
|
| | | t.nodeChanged();
|
| | | });
|
| | |
|
| | | // Enables users to override the control factory
|
| | | t.onBeforeRenderUI.dispatch(t, t.controlManager);
|
| | |
|
| | | // Measure box
|
| | | if (s.render_ui && t.theme) {
|
| | | w = s.width || e.style.width || e.offsetWidth;
|
| | | h = s.height || e.style.height || e.offsetHeight;
|
| | | t.orgDisplay = e.style.display;
|
| | | re = /^[0-9\.]+(|px)$/i;
|
| | |
|
| | | if (re.test('' + w))
|
| | | w = Math.max(parseInt(w, 10) + (o.deltaWidth || 0), 100);
|
| | | if (typeof s.theme != "function") {
|
| | | w = s.width || e.style.width || e.offsetWidth;
|
| | | h = s.height || e.style.height || e.offsetHeight;
|
| | | mh = s.min_height || 100;
|
| | | re = /^[0-9\.]+(|px)$/i;
|
| | |
|
| | | if (re.test('' + h))
|
| | | h = Math.max(parseInt(h, 10) + (o.deltaHeight || 0), 100);
|
| | | if (re.test('' + w))
|
| | | w = Math.max(parseInt(w, 10) + (o.deltaWidth || 0), 100);
|
| | |
|
| | | // Render UI
|
| | | o = t.theme.renderUI({
|
| | | targetNode : e,
|
| | | width : w,
|
| | | height : h,
|
| | | deltaWidth : s.delta_width,
|
| | | deltaHeight : s.delta_height
|
| | | });
|
| | | if (re.test('' + h))
|
| | | h = Math.max(parseInt(h, 10) + (o.deltaHeight || 0), mh);
|
| | |
|
| | | // Render UI
|
| | | o = t.theme.renderUI({
|
| | | targetNode : e,
|
| | | width : w,
|
| | | height : h,
|
| | | deltaWidth : s.delta_width,
|
| | | deltaHeight : s.delta_height
|
| | | });
|
| | |
|
| | | // Resize editor
|
| | | DOM.setStyles(o.sizeContainer || o.editorContainer, {
|
| | | width : w,
|
| | | height : h
|
| | | });
|
| | |
|
| | | h = (o.iframeHeight || h) + (typeof(h) == 'number' ? (o.deltaHeight || 0) : '');
|
| | | if (h < mh)
|
| | | h = mh;
|
| | | } else {
|
| | | o = s.theme(t, e);
|
| | |
|
| | | // Convert element type to id:s
|
| | | if (o.editorContainer.nodeType) {
|
| | | o.editorContainer = o.editorContainer.id = o.editorContainer.id || t.id + "_parent";
|
| | | }
|
| | |
|
| | | // Convert element type to id:s
|
| | | if (o.iframeContainer.nodeType) {
|
| | | o.iframeContainer = o.iframeContainer.id = o.iframeContainer.id || t.id + "_iframecontainer";
|
| | | }
|
| | |
|
| | | // Use specified iframe height or the targets offsetHeight
|
| | | h = o.iframeHeight || e.offsetHeight;
|
| | |
|
| | | // Store away the selection when it's changed to it can be restored later with a editor.focus() call
|
| | | if (isIE) {
|
| | | t.onInit.add(function(ed) {
|
| | | ed.dom.bind(ed.getBody(), 'beforedeactivate keydown', function() {
|
| | | ed.lastIERng = ed.selection.getRng();
|
| | | });
|
| | | });
|
| | | }
|
| | | }
|
| | |
|
| | | t.editorContainer = o.editorContainer;
|
| | | }
|
| | |
| | | });
|
| | | }
|
| | |
|
| | | // Load specified content CSS last
|
| | | if (s.content_style) {
|
| | | t.contentStyles.push(s.content_style);
|
| | | }
|
| | |
|
| | | // Content editable mode ends here
|
| | | if (s.content_editable) {
|
| | | e = n = o = null; // Fix IE leak
|
| | |
| | | // User specified a document.domain value
|
| | | if (document.domain && location.hostname != document.domain)
|
| | | tinymce.relaxedDomain = document.domain;
|
| | |
|
| | | // Resize editor
|
| | | DOM.setStyles(o.sizeContainer || o.editorContainer, {
|
| | | width : w,
|
| | | height : h
|
| | | });
|
| | |
|
| | | h = (o.iframeHeight || h) + (typeof(h) == 'number' ? (o.deltaHeight || 0) : '');
|
| | | if (h < 100)
|
| | | h = 100;
|
| | |
|
| | | t.iframeHTML = s.doctype + '<html><head xmlns="http://www.w3.org/1999/xhtml">';
|
| | |
|
| | |
| | | });
|
| | |
|
| | | t.contentAreaContainer = o.iframeContainer;
|
| | | DOM.get(o.editorContainer).style.display = t.orgDisplay;
|
| | |
|
| | | if (o.editorContainer) {
|
| | | DOM.get(o.editorContainer).style.display = t.orgDisplay;
|
| | | }
|
| | |
|
| | | // Restore visibility on target element
|
| | | e.style.visibility = t.orgVisibility;
|
| | |
|
| | | DOM.get(t.id).style.display = 'none';
|
| | | DOM.setAttrib(t.id, 'aria-hidden', true);
|
| | |
|
| | |
| | | },
|
| | |
|
| | | initContentBody : function() {
|
| | | var self = this, settings = self.settings, targetElm = DOM.get(self.id), doc = self.getDoc(), html, body;
|
| | | var self = this, settings = self.settings, targetElm = DOM.get(self.id), doc = self.getDoc(), html, body, contentCssText;
|
| | |
|
| | | // Setup iframe body
|
| | | if ((!isIE || !tinymce.relaxedDomain) && !settings.content_editable) {
|
| | |
| | |
|
| | | self.serializer = new tinymce.dom.Serializer(settings, self.dom, self.schema);
|
| | |
|
| | | self.selection = new tinymce.dom.Selection(self.dom, self.getWin(), self.serializer);
|
| | | self.selection = new tinymce.dom.Selection(self.dom, self.getWin(), self.serializer, self);
|
| | |
|
| | | self.formatter = new tinymce.Formatter(self);
|
| | |
|
| | |
| | | self.forceBlocks = new tinymce.ForceBlocks(self);
|
| | | self.enterKey = new tinymce.EnterKey(self);
|
| | | self.editorCommands = new tinymce.EditorCommands(self);
|
| | |
|
| | | self.onExecCommand.add(function(editor, command) {
|
| | | // Don't refresh the select lists until caret move
|
| | | if (!/^(FontName|FontSize)$/.test(command))
|
| | | self.nodeChanged();
|
| | | });
|
| | |
|
| | | // Pass through
|
| | | self.serializer.onPreProcess.add(function(se, o) {
|
| | |
| | |
|
| | | self.onPreInit.dispatch(self);
|
| | |
|
| | | if (!settings.gecko_spellcheck)
|
| | | if (!settings.browser_spellcheck && !settings.gecko_spellcheck)
|
| | | doc.body.spellcheck = false;
|
| | |
|
| | | if (!settings.readonly) {
|
| | |
| | | self.focus(true);
|
| | | self.nodeChanged({initial : true});
|
| | |
|
| | | // 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(url) {
|
| | | self.dom.loadCSS(url);
|
| | |
| | | var oed, self = this, selection = self.selection, contentEditable = self.settings.content_editable, ieRng, controlElm, doc = self.getDoc(), body;
|
| | |
|
| | | if (!skip_focus) {
|
| | | if (self.lastIERng) {
|
| | | selection.setRng(self.lastIERng);
|
| | | }
|
| | |
|
| | | // Get selected control element
|
| | | ieRng = selection.getRng();
|
| | | if (ieRng.item) {
|
| | |
| | | // Fix for bug #1896577 it seems that this can not be fired while the editor is loading
|
| | | if (self.initialized) {
|
| | | o = o || {};
|
| | |
|
| | | // Normalize selection for example <b>a</b><i>|a</i> becomes <b>a|</b><i>a</i>
|
| | | selection.normalize();
|
| | |
|
| | | // Get start node
|
| | | node = selection.getStart() || self.getBody();
|
| | |
| | | if (!args.no_events)
|
| | | self.onSetContent.dispatch(self, args);
|
| | |
|
| | | self.selection.normalize();
|
| | | // Don't normalize selection if the focused element isn't the body in content editable mode since it will steal focus otherwise
|
| | | if (!self.settings.content_editable || document.activeElement === self.getBody()) {
|
| | | self.selection.normalize();
|
| | | }
|
| | |
|
| | | return args.content;
|
| | | },
|
| | |
|
| | | getContent : function(args) {
|
| | | var self = this, content;
|
| | | var self = this, content, body = self.getBody();
|
| | |
|
| | | // Setup args object
|
| | | args = args || {};
|
| | |
| | |
|
| | | // Get raw contents or by default the cleaned contents
|
| | | if (args.format == 'raw')
|
| | | content = self.getBody().innerHTML;
|
| | | content = body.innerHTML;
|
| | | else if (args.format == 'text')
|
| | | content = body.innerText || body.textContent;
|
| | | else
|
| | | content = self.serializer.serialize(self.getBody(), args);
|
| | | content = self.serializer.serialize(body, args);
|
| | |
|
| | | args.content = tinymce.trim(content);
|
| | | // Trim whitespace in beginning/end of HTML
|
| | | if (args.format != 'text') {
|
| | | args.content = tinymce.trim(content);
|
| | | } else {
|
| | | args.content = content;
|
| | | }
|
| | |
|
| | | // Do post processing
|
| | | if (!args.no_events)
|
| | |
| | | return;
|
| | |
|
| | | case 'A':
|
| | | value = dom.getAttrib(elm, 'name');
|
| | | cls = 'mceItemAnchor';
|
| | | if (!dom.getAttrib(elm, 'href', false)) {
|
| | | value = dom.getAttrib(elm, 'name') || elm.id;
|
| | | cls = 'mceItemAnchor';
|
| | |
|
| | | if (value) {
|
| | | if (self.hasVisual)
|
| | | dom.addClass(elm, cls);
|
| | | else
|
| | | dom.removeClass(elm, cls);
|
| | | if (value) {
|
| | | if (self.hasVisual)
|
| | | dom.addClass(elm, cls);
|
| | | else
|
| | | dom.removeClass(elm, cls);
|
| | | }
|
| | | }
|
| | |
|
| | | return;
|
| | |
| | | // Don't clear the window or document if content editable
|
| | | // is enabled since other instances might still be present
|
| | | if (!self.settings.content_editable) {
|
| | | Event.clear(self.getWin());
|
| | | Event.clear(self.getDoc());
|
| | | Event.unbind(self.getWin());
|
| | | Event.unbind(self.getDoc());
|
| | | }
|
| | |
|
| | | Event.clear(self.getBody());
|
| | | Event.clear(self.formElement);
|
| | | Event.unbind(elm);
|
| | | Event.unbind(self.getBody());
|
| | | Event.clear(elm);
|
| | |
|
| | | self.execCallback('remove_instance_callback', self);
|
| | | self.onRemove.dispatch(self);
|
| | |
| | | // Handle legacy handle_event_callback option
|
| | | if (settings.handle_event_callback) {
|
| | | self.onEvent.add(function(ed, e, o) {
|
| | | if (self.execCallback('handle_event_callback', e, ed, o) === false)
|
| | | Event.cancel(e);
|
| | | if (self.execCallback('handle_event_callback', e, ed, o) === false) {
|
| | | e.preventDefault();
|
| | | e.stopPropagation();
|
| | | }
|
| | | });
|
| | | }
|
| | |
|
| | |
| | | self.focus(true);
|
| | | };
|
| | |
|
| | | function nodeChanged(ed, e) {
|
| | | // 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
|
| | | if (e.keyCode != 65 || !tinymce.VK.metaKeyPressed(e)) {
|
| | | self.selection.normalize();
|
| | | }
|
| | |
|
| | | self.nodeChanged();
|
| | | }
|
| | |
|
| | | // Add DOM events
|
| | | each(nativeToDispatcherMap, function(dispatcherName, nativeName) {
|
| | | var root = settings.content_editable ? self.getBody() : self.getDoc();
|
| | |
| | | }
|
| | |
|
| | | // Add node change handler
|
| | | self.onMouseUp.add(self.nodeChanged);
|
| | | self.onMouseUp.add(nodeChanged);
|
| | |
|
| | | self.onKeyUp.add(function(ed, e) {
|
| | | var keyCode = e.keyCode;
|
| | |
|
| | | if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 13 || keyCode == 45 || keyCode == 46 || keyCode == 8 || (tinymce.isMac && (keyCode == 91 || keyCode == 93)) || e.ctrlKey)
|
| | | self.nodeChanged();
|
| | | nodeChanged(ed, e);
|
| | | });
|
| | |
|
| | | // Add reset handler
|
| | |
| | |
|
| | | // Insert bookmark node and get the parent
|
| | | selection.setContent(bookmarkHtml);
|
| | | parentNode = editor.selection.getNode();
|
| | | parentNode = selection.getNode();
|
| | | rootNode = editor.getBody();
|
| | |
|
| | | // Opera will return the document node when selection is in root
|
| | |
| | | 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) {
|
| | |
| | | selectAll : function() {
|
| | | var root = dom.getRoot(), rng = dom.createRng();
|
| | |
|
| | | rng.setStart(root, 0);
|
| | | rng.setEnd(root, root.childNodes.length);
|
| | | // Old IE does a better job with selectall than new versions
|
| | | if (selection.getRng().setStart) {
|
| | | rng.setStart(root, 0);
|
| | | rng.setEnd(root, root.childNodes.length);
|
| | |
|
| | | editor.selection.setRng(rng);
|
| | | selection.setRng(rng);
|
| | | } else {
|
| | | execNativeCommand('SelectAll');
|
| | | }
|
| | | }
|
| | | });
|
| | |
|
| | |
| | | },
|
| | |
|
| | | 'InsertUnorderedList,InsertOrderedList' : function(command) {
|
| | | return dom.getParent(selection.getNode(), command == 'insertunorderedlist' ? 'UL' : 'OL');
|
| | | var list = dom.getParent(selection.getNode(), 'ul,ol');
|
| | | return list && |
| | | (command === 'insertunorderedlist' && list.tagName === 'UL'
|
| | | || command === 'insertorderedlist' && list.tagName === 'OL');
|
| | | }
|
| | | }, 'state');
|
| | |
|
| | |
| | | };
|
| | |
|
| | | // Create event instances
|
| | | onAdd = new Dispatcher(self);
|
| | | onUndo = new Dispatcher(self);
|
| | | onRedo = new Dispatcher(self);
|
| | | onBeforeAdd = new Dispatcher(self);
|
| | | onAdd = new Dispatcher(self);
|
| | | onUndo = new Dispatcher(self);
|
| | | onRedo = new Dispatcher(self);
|
| | |
|
| | | // Pass though onAdd event from UndoManager to Editor as onChange
|
| | | onAdd.add(function(undoman, level) {
|
| | |
| | | data : data,
|
| | |
|
| | | typing : false,
|
| | | |
| | | onBeforeAdd: onBeforeAdd,
|
| | |
|
| | | onAdd : onAdd,
|
| | |
|
| | |
| | |
|
| | | level = level || {};
|
| | | level.content = getContent();
|
| | | |
| | | self.onBeforeAdd.dispatch(self, level);
|
| | |
|
| | | // Add undo level if needed
|
| | | lastLevel = data[index];
|
| | |
| | | var settings = editor.settings, dom = editor.dom, selection = editor.selection, blockElements = editor.schema.getBlockElements();
|
| | |
|
| | | function addRootBlocks() {
|
| | | var node = selection.getStart(), rootNode = editor.getBody(), rng, startContainer, startOffset, endContainer, endOffset, rootBlockNode, tempNode, offset = -0xFFFFFF, wrapped;
|
| | | var node = selection.getStart(), rootNode = editor.getBody(), rng, startContainer, startOffset, endContainer, endOffset, rootBlockNode, tempNode, offset = -0xFFFFFF, wrapped, isInEditorDocument;
|
| | |
|
| | | if (!node || node.nodeType !== 1 || !settings.forced_root_block)
|
| | | return;
|
| | |
|
| | | // Check if node is wrapped in block
|
| | | while (node != rootNode) {
|
| | | while (node && node != rootNode) {
|
| | | if (blockElements[node.nodeName])
|
| | | return;
|
| | |
|
| | |
| | | rng.moveToElementText(node);
|
| | | }
|
| | |
|
| | | isInEditorDocument = rng.parentElement().ownerDocument === editor.getDoc();
|
| | | tmpRng = rng.duplicate();
|
| | | tmpRng.collapse(true);
|
| | | startOffset = tmpRng.move('character', offset) * -1;
|
| | |
| | | node = rootNode.firstChild;
|
| | | while (node) {
|
| | | if (node.nodeType === 3 || (node.nodeType == 1 && !blockElements[node.nodeName])) {
|
| | | // 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(settings.forced_root_block);
|
| | | node.parentNode.insertBefore(rootBlockNode, node);
|
| | |
| | | }
|
| | | }
|
| | |
|
| | | if (rng.setStart) {
|
| | | rng.setStart(startContainer, startOffset);
|
| | | rng.setEnd(endContainer, endOffset);
|
| | | selection.setRng(rng);
|
| | | } else {
|
| | | 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
|
| | | }
|
| | | }
|
| | |
|
| | | // Only trigger nodeChange when we wrapped nodes to prevent a forever loop
|
| | | if (wrapped) {
|
| | | 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
|
| | | if (isInEditorDocument) {
|
| | | 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();
|
| | | }
|
| | | };
|
| | |
| | | return c;
|
| | | },
|
| | |
|
| | | createControl : function(n) {
|
| | | var c, t = this, ed = t.editor;
|
| | | createControl : function(name) {
|
| | | var ctrl, i, l, self = this, editor = self.editor, factories, ctrlName;
|
| | |
|
| | | each(ed.plugins, function(p) {
|
| | | if (p.createControl) {
|
| | | c = p.createControl(n, t);
|
| | |
|
| | | if (c)
|
| | | return false;
|
| | | }
|
| | | });
|
| | |
|
| | | switch (n) {
|
| | | case "|":
|
| | | case "separator":
|
| | | return t.createSeparator();
|
| | | // Build control factory cache
|
| | | if (!self.controlFactories) {
|
| | | self.controlFactories = [];
|
| | | each(editor.plugins, function(plugin) {
|
| | | if (plugin.createControl) {
|
| | | self.controlFactories.push(plugin);
|
| | | }
|
| | | });
|
| | | }
|
| | |
|
| | | if (!c && ed.buttons && (c = ed.buttons[n]))
|
| | | return t.createButton(n, c);
|
| | | // Create controls by asking cached factories
|
| | | factories = self.controlFactories;
|
| | | for (i = 0, l = factories.length; i < l; i++) {
|
| | | ctrl = factories[i].createControl(name, self);
|
| | |
|
| | | return t.add(c);
|
| | | if (ctrl) {
|
| | | return self.add(ctrl);
|
| | | }
|
| | | }
|
| | |
|
| | | // Create sepearator
|
| | | if (name === "|" || name === "separator") {
|
| | | return self.createSeparator();
|
| | | }
|
| | |
|
| | | // Create control from button collection
|
| | | if (editor.buttons && (ctrl = editor.buttons[name])) {
|
| | | return self.createButton(name, ctrl);
|
| | | }
|
| | |
|
| | | return self.add(ctrl);
|
| | | },
|
| | |
|
| | | createDropMenu : function(id, s, cc) {
|
| | |
| | | isBlock = dom.isBlock,
|
| | | forcedRootBlock = ed.settings.forced_root_block,
|
| | | nodeIndex = dom.nodeIndex,
|
| | | INVISIBLE_CHAR = tinymce.isGecko ? '\u200B' : '\uFEFF',
|
| | | INVISIBLE_CHAR = '\uFEFF',
|
| | | MCE_ATTR_RE = /^(src|href|style)$/,
|
| | | FALSE = false,
|
| | | TRUE = true,
|
| | | formatChangeData,
|
| | | undef,
|
| | | getContentEditable = dom.getContentEditable;
|
| | |
|
| | |
| | | matchedFormatNames.push(name);
|
| | | }
|
| | | }
|
| | | });
|
| | | }, dom.getRoot());
|
| | |
|
| | | return matchedFormatNames;
|
| | | };
|
| | |
| | | return FALSE;
|
| | | };
|
| | |
|
| | | function formatChanged(formats, callback, similar) {
|
| | | var currentFormats;
|
| | |
|
| | | // Setup format node change logic
|
| | | if (!formatChangeData) {
|
| | | formatChangeData = {};
|
| | | currentFormats = {};
|
| | |
|
| | | ed.onNodeChange.addToTop(function(ed, cm, node) {
|
| | | var parents = getParents(node), matchedFormats = {};
|
| | |
|
| | | // 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;
|
| | | }
|
| | | });
|
| | | });
|
| | |
|
| | | // Check if current formats still match
|
| | | each(currentFormats, function(callbacks, format) {
|
| | | if (!matchedFormats[format]) {
|
| | | delete currentFormats[format];
|
| | |
|
| | | each(callbacks, function(callback) {
|
| | | callback(false, {node: node, 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;
|
| | | };
|
| | |
|
| | | // Expose to public
|
| | | tinymce.extend(this, {
|
| | | get : get,
|
| | |
| | | match : match,
|
| | | matchAll : matchAll,
|
| | | matchNode : matchNode,
|
| | | canApply : canApply
|
| | | canApply : canApply,
|
| | | formatChanged: formatChanged
|
| | | });
|
| | |
|
| | | // Initialize
|
| | |
| | | }
|
| | | };
|
| | |
|
| | | // 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 i, caretContainer, node;
|
| | |
|
| | | caretContainer = getParentCaretContainer(selection.getStart());
|
| | | if (caretContainer && !dom.isEmpty(caretContainer)) {
|
| | | tinymce.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 (!self._hasCaretEvents) {
|
| | | // Mark current caret container elements as bogus when getting the contents so we don't end up with empty elements
|
| | |
| | | tinymce.each('onMouseUp onKeyUp'.split(' '), function(name) {
|
| | | ed[name].addToTop(function() {
|
| | | removeCaretContainer();
|
| | | unmarkBogusCaretParents();
|
| | | });
|
| | | });
|
| | |
|
| | |
| | | if (keyCode == 8 || keyCode == 37 || keyCode == 39) {
|
| | | removeCaretContainer(getParentCaretContainer(selection.getStart()));
|
| | | }
|
| | |
|
| | | unmarkBogusCaretParents();
|
| | | });
|
| | |
|
| | | // Remove bogus state if they got filled by contents using editor.selection.setContent
|
| | | selection.onSetContent.add(function() {
|
| | | dom.getParent(selection.getStart(), function(node) {
|
| | | if (node.id !== caretContainerId && dom.getAttrib(node, 'data-mce-bogus') && !dom.isEmpty(node)) {
|
| | | dom.setAttrib(node, 'data-mce-bogus', null);
|
| | | }
|
| | | });
|
| | | });
|
| | | selection.onSetContent.add(unmarkBogusCaretParents);
|
| | |
|
| | | self._hasCaretEvents = true;
|
| | | }
|
| | |
| | | var TreeWalker = tinymce.dom.TreeWalker;
|
| | |
|
| | | tinymce.EnterKey = function(editor) {
|
| | | var dom = editor.dom, selection = editor.selection, settings = editor.settings, undoManager = editor.undoManager;
|
| | | var dom = editor.dom, selection = editor.selection, settings = editor.settings, undoManager = editor.undoManager, nonEmptyElementsMap = editor.schema.getNonEmptyElements();
|
| | |
|
| | | function handleEnterKey(evt) {
|
| | | var rng = selection.getRng(true), tmpRng, editableRoot, container, offset, parentBlock, documentMode,
|
| | | var rng = selection.getRng(true), 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)$/.test(node.nodeName) &&
|
| | | !/^(TD|TH|CAPTION|FORM)$/.test(node.nodeName) &&
|
| | | !/^(fixed|absolute)/i.test(node.style.position) &&
|
| | | dom.getContentEditable(node) !== "true";
|
| | | };
|
| | |
|
| | | // Renders empty block on IE
|
| | | function renderBlockOnIE(block) {
|
| | | var oldRng;
|
| | |
|
| | | if (tinymce.isIE && 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: <p><b><em></em></b>x</p> becomes this: <p>x</p>
|
| | | function trimInlineElementsOnLeftSideOfBlock(block) {
|
| | | var node = block, firstChilds = [], i;
|
| | |
|
| | | // Find inner most first child ex: <p><i><b>*</b></i></p>
|
| | | 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 <a> </a> 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, y, viewPort, lastNode = root, tempElm;
|
| | |
| | | break;
|
| | | }
|
| | |
|
| | | if (/^(BR|IMG)$/.test(node.nodeName)) {
|
| | | if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
|
| | | rng.setStartBefore(node);
|
| | | rng.setEndBefore(node);
|
| | | break;
|
| | |
| | | return true;
|
| | | }
|
| | |
|
| | | // If the caret if before the first element in parentBlock
|
| | | if (start && container.nodeType == 1 && container == parentBlock.firstChild) {
|
| | | return true;
|
| | | }
|
| | |
|
| | | // Caret can be before/after a table
|
| | | if (container.nodeName === "TABLE" || (container.previousSibling && container.previousSibling.nodeName == "TABLE")) {
|
| | | return (isAfterLastNodeInContainer && !start) || (!isAfterLastNodeInContainer && start);
|
| | |
| | |
|
| | | // Walk the DOM and look for text nodes or non empty elements
|
| | | walker = new TreeWalker(container, parentBlock);
|
| | | while (node = (start ? walker.prev() : walker.next())) {
|
| | | |
| | | // If caret is in beginning or end of a text block then jump to the next/previous node
|
| | | if (container.nodeType == 3) {
|
| | | if (start && offset == 0) {
|
| | | walker.prev();
|
| | | } else if (!start && offset == container.nodeValue.length) {
|
| | | walker.next();
|
| | | }
|
| | | }
|
| | |
|
| | | while (node = walker.current()) {
|
| | | if (node.nodeType === 1) {
|
| | | // Ignore bogus elements
|
| | | if (node.getAttribute('data-mce-bogus')) {
|
| | | continue;
|
| | | }
|
| | |
|
| | | // Keep empty elements like <img />
|
| | | name = node.nodeName.toLowerCase();
|
| | | if (name === 'IMG') {
|
| | | return false;
|
| | | if (!node.getAttribute('data-mce-bogus')) {
|
| | | // Keep empty elements like <img /> <input /> but not trailing br:s like <p>text|<br></p>
|
| | | name = node.nodeName.toLowerCase();
|
| | | if (nonEmptyElementsMap[name] && name !== 'br') {
|
| | | return false;
|
| | | }
|
| | | }
|
| | | } else if (node.nodeType === 3 && !/^[ \t\r\n]*$/.test(node.nodeValue)) {
|
| | | return false;
|
| | | }
|
| | |
|
| | | if (start) {
|
| | | walker.prev();
|
| | | } else {
|
| | | walker.next();
|
| | | }
|
| | | }
|
| | |
|
| | |
| | | } else if (isFirstOrLastLi()) {
|
| | | // Last LI in list then temove LI and add text block after list
|
| | | dom.insertAfter(newBlock, containerBlock);
|
| | | renderBlockOnIE(newBlock);
|
| | | } else {
|
| | | // Middle LI in list the split the list and insert a text block in the middle
|
| | | // Extract after fragment and insert it after the current block
|
| | |
| | | if (container && container.nodeType == 3 && offset >= container.nodeValue.length) {
|
| | | // Insert extra BR element at the end block elements
|
| | | if (!tinymce.isIE && !hasRightSideBr()) {
|
| | | brElm = dom.create('br')
|
| | | brElm = dom.create('br');
|
| | | rng.insertNode(brElm);
|
| | | rng.setStartAfter(brElm);
|
| | | rng.setEndAfter(brElm);
|
| | |
| | | return parent !== root ? editableRoot : root;
|
| | | };
|
| | |
|
| | | // Adds a BR at the end of blocks that only contains an IMG or INPUT since these might be floated and then they won't expand the block
|
| | | function addBrToBlockIfNeeded(block) {
|
| | | var lastChild;
|
| | |
|
| | | // IE will render the blocks correctly other browsers needs a BR
|
| | | if (!tinymce.isIE) {
|
| | | block.normalize(); // Remove empty text nodes that got left behind by the extract
|
| | |
|
| | | // Check if the block is empty or contains a floated last child
|
| | | lastChild = block.lastChild;
|
| | | if (!lastChild || (/^(left|right)$/gi.test(dom.getStyle(lastChild, 'float', true)))) {
|
| | | dom.add(block, 'br');
|
| | | }
|
| | | }
|
| | | };
|
| | |
|
| | | // Delete any selected contents
|
| | | if (!rng.collapsed) {
|
| | | editor.execCommand('Delete');
|
| | |
| | | // Setup range items and newBlockName
|
| | | container = rng.startContainer;
|
| | | offset = rng.startOffset;
|
| | | newBlockName = settings.forced_root_block;
|
| | | newBlockName = (settings.force_p_newlines ? 'p' : '') || settings.forced_root_block;
|
| | | newBlockName = newBlockName ? newBlockName.toUpperCase() : '';
|
| | | documentMode = dom.doc.documentMode;
|
| | | shiftKey = evt.shiftKey;
|
| | |
|
| | | // Resolve node index
|
| | | if (container.nodeType == 1 && container.hasChildNodes()) {
|
| | | isAfterLastNodeInContainer = offset > container.childNodes.length - 1;
|
| | | container = container.childNodes[Math.min(offset, container.childNodes.length - 1)] || container;
|
| | | offset = 0;
|
| | | if (isAfterLastNodeInContainer && container.nodeType == 3) {
|
| | | offset = container.nodeValue.length;
|
| | | } else {
|
| | | offset = 0;
|
| | | }
|
| | | }
|
| | |
|
| | | // Get editable root node normaly the body element but sometimes a div or span
|
| | |
| | |
|
| | | // If editable root isn't block nor the root of the editor
|
| | | if (!dom.isBlock(editableRoot) && editableRoot != dom.getRoot()) {
|
| | | if (!newBlockName || evt.shiftKey) {
|
| | | if (!newBlockName || shiftKey) {
|
| | | insertBr();
|
| | | }
|
| | |
|
| | |
| | | // Wrap the current node and it's sibling in a default block if it's needed.
|
| | | // for example this <td>text|<b>text2</b></td> will become this <td><p>text|<b>text2</p></b></td>
|
| | | // This won't happen if root blocks are disabled or the shiftKey is pressed
|
| | | if ((newBlockName && !evt.shiftKey) || (!newBlockName && evt.shiftKey)) {
|
| | | if ((newBlockName && !shiftKey) || (!newBlockName && shiftKey)) {
|
| | | container = wrapSelfAndSiblingsInDefaultBlock(container, offset);
|
| | | }
|
| | |
|
| | |
| | | parentBlockName = parentBlock ? parentBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5
|
| | | containerBlockName = containerBlock ? containerBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5
|
| | |
|
| | | // Handle enter inside an empty list item
|
| | | if (parentBlockName == 'LI' && dom.isEmpty(parentBlock)) {
|
| | | // Let the list plugin or browser handle nested lists for now
|
| | | if (/^(UL|OL|LI)$/.test(containerBlock.parentNode.nodeName)) {
|
| | | return false;
|
| | | // Handle enter in LI
|
| | | if (parentBlockName == 'LI') {
|
| | | if (!newBlockName && shiftKey) {
|
| | | insertBr();
|
| | | return;
|
| | | }
|
| | |
|
| | | handleEmptyListItem();
|
| | | return;
|
| | | // Handle enter inside an empty list item
|
| | | if (dom.isEmpty(parentBlock)) {
|
| | | // Let the list plugin or browser handle nested lists for now
|
| | | if (/^(UL|OL|LI)$/.test(containerBlock.parentNode.nodeName)) {
|
| | | return false;
|
| | | }
|
| | |
|
| | | handleEmptyListItem();
|
| | | return;
|
| | | }
|
| | | }
|
| | |
|
| | | // Don't split PRE tags but insert a BR instead easier when writing code samples etc
|
| | | if (parentBlockName == 'PRE' && settings.br_in_pre !== false) {
|
| | | if (!evt.shiftKey) {
|
| | | if (!shiftKey) {
|
| | | insertBr();
|
| | | return;
|
| | | }
|
| | | } else {
|
| | | // If no root block is configured then insert a BR by default or if the shiftKey is pressed
|
| | | if ((!newBlockName && !evt.shiftKey && parentBlockName != 'LI') || (newBlockName && evt.shiftKey)) {
|
| | | if ((!newBlockName && !shiftKey && parentBlockName != 'LI') || (newBlockName && shiftKey)) {
|
| | | insertBr();
|
| | | return;
|
| | | }
|
| | |
| | | } else {
|
| | | dom.insertAfter(newBlock, parentBlock);
|
| | | }
|
| | |
|
| | | moveToCaretPosition(newBlock);
|
| | | } else if (isCaretAtStartOrEndOfBlock(true)) {
|
| | | // Insert new block before
|
| | | newBlock = parentBlock.parentNode.insertBefore(createNewBlock(), parentBlock);
|
| | | renderBlockOnIE(newBlock);
|
| | | } else {
|
| | | // Extract after fragment and insert it after the current block
|
| | | tmpRng = rng.cloneRange();
|
| | |
| | | trimLeadingLineBreaks(fragment);
|
| | | newBlock = fragment.firstChild;
|
| | | dom.insertAfter(fragment, parentBlock);
|
| | | trimInlineElementsOnLeftSideOfBlock(newBlock);
|
| | | addBrToBlockIfNeeded(parentBlock);
|
| | | moveToCaretPosition(newBlock);
|
| | | }
|
| | |
|
| | | dom.setAttrib(newBlock, 'id', ''); // Remove ID since it needs to be document unique
|
| | | moveToCaretPosition(newBlock);
|
| | | undoManager.add();
|
| | | }
|
| | |
|