v1.0
只读模式 group, 分享 评论更多问题 博客标签总是存在一个
This commit is contained in:
1 parent
7e458bb433
commit
6987a38820
1453 files changed
+114558
-91533
No files matched your search
+389
@@ -0,0 +1,389 @@
|
||||
/**
|
||||
* BookmarkManager.js
|
||||
*
|
||||
* Copyright, Moxiecode Systems AB
|
||||
* Released under LGPL License.
|
||||
*
|
||||
* License: http://www.tinymce.com/license
|
||||
* Contributing: http://www.tinymce.com/contributing
|
||||
*/
|
||||
|
||||
/**
|
||||
* This class handles selection bookmarks.
|
||||
*
|
||||
* @class tinymce.dom.BookmarkManager
|
||||
*/
|
||||
define("tinymce/dom/BookmarkManager", [
|
||||
"tinymce/Env",
|
||||
"tinymce/util/Tools"
|
||||
], function(Env, Tools) {
|
||||
/**
|
||||
* Constructs a new BookmarkManager instance for a specific selection instance.
|
||||
*
|
||||
* @constructor
|
||||
* @method BookmarkManager
|
||||
* @param {tinymce.dom.Selection} selection Selection instance to handle bookmarks for.
|
||||
*/
|
||||
function BookmarkManager(selection) {
|
||||
var dom = selection.dom;
|
||||
|
||||
/**
|
||||
* Returns a bookmark location for the current selection. This bookmark object
|
||||
* can then be used to restore the selection after some content modification to the document.
|
||||
*
|
||||
* @method getBookmark
|
||||
* @param {Number} type Optional state if the bookmark should be simple or not. Default is complex.
|
||||
* @param {Boolean} normalized Optional state that enables you to get a position that it would be after normalization.
|
||||
* @return {Object} Bookmark object, use moveToBookmark with this object to restore the selection.
|
||||
* @example
|
||||
* // Stores a bookmark of the current selection
|
||||
* var bm = tinymce.activeEditor.selection.getBookmark();
|
||||
*
|
||||
* tinymce.activeEditor.setContent(tinymce.activeEditor.getContent() + 'Some new content');
|
||||
*
|
||||
* // Restore the selection bookmark
|
||||
* tinymce.activeEditor.selection.moveToBookmark(bm);
|
||||
*/
|
||||
this.getBookmark = function(type, normalized) {
|
||||
var rng, rng2, id, collapsed, name, element, chr = '', styles;
|
||||
|
||||
function findIndex(name, element) {
|
||||
var index = 0;
|
||||
|
||||
Tools.each(dom.select(name), function(node, i) {
|
||||
if (node == element) {
|
||||
index = i;
|
||||
}
|
||||
});
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
function normalizeTableCellSelection(rng) {
|
||||
function moveEndPoint(start) {
|
||||
var container, offset, childNodes, prefix = start ? 'start' : 'end';
|
||||
|
||||
container = rng[prefix + 'Container'];
|
||||
offset = rng[prefix + 'Offset'];
|
||||
|
||||
if (container.nodeType == 1 && container.nodeName == "TR") {
|
||||
childNodes = container.childNodes;
|
||||
container = childNodes[Math.min(start ? offset : offset - 1, childNodes.length - 1)];
|
||||
if (container) {
|
||||
offset = start ? 0 : container.childNodes.length;
|
||||
rng['set' + (start ? 'Start' : 'End')](container, offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
moveEndPoint(true);
|
||||
moveEndPoint();
|
||||
|
||||
return rng;
|
||||
}
|
||||
|
||||
function getLocation() {
|
||||
var rng = selection.getRng(true), root = dom.getRoot(), bookmark = {};
|
||||
|
||||
function getPoint(rng, start) {
|
||||
var container = rng[start ? 'startContainer' : 'endContainer'],
|
||||
offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0;
|
||||
|
||||
if (container.nodeType == 3) {
|
||||
if (normalized) {
|
||||
for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling) {
|
||||
offset += node.nodeValue.length;
|
||||
}
|
||||
}
|
||||
|
||||
point.push(offset);
|
||||
} else {
|
||||
childNodes = container.childNodes;
|
||||
|
||||
if (offset >= childNodes.length && childNodes.length) {
|
||||
after = 1;
|
||||
offset = Math.max(0, childNodes.length - 1);
|
||||
}
|
||||
|
||||
point.push(dom.nodeIndex(childNodes[offset], normalized) + after);
|
||||
}
|
||||
|
||||
for (; container && container != root; container = container.parentNode) {
|
||||
point.push(dom.nodeIndex(container, normalized));
|
||||
}
|
||||
|
||||
return point;
|
||||
}
|
||||
|
||||
bookmark.start = getPoint(rng, true);
|
||||
|
||||
if (!selection.isCollapsed()) {
|
||||
bookmark.end = getPoint(rng);
|
||||
}
|
||||
|
||||
return bookmark;
|
||||
}
|
||||
|
||||
if (type == 2) {
|
||||
element = selection.getNode();
|
||||
name = element ? element.nodeName : null;
|
||||
|
||||
if (name == 'IMG') {
|
||||
return {name: name, index: findIndex(name, element)};
|
||||
}
|
||||
|
||||
if (selection.tridentSel) {
|
||||
return selection.tridentSel.getBookmark(type);
|
||||
}
|
||||
|
||||
return getLocation();
|
||||
}
|
||||
|
||||
// Handle simple range
|
||||
if (type) {
|
||||
return {rng: selection.getRng()};
|
||||
}
|
||||
|
||||
rng = selection.getRng();
|
||||
id = dom.uniqueId();
|
||||
collapsed = selection.isCollapsed();
|
||||
styles = 'overflow:hidden;line-height:0px';
|
||||
|
||||
// Explorer method
|
||||
if (rng.duplicate || rng.item) {
|
||||
// Text selection
|
||||
if (!rng.item) {
|
||||
rng2 = rng.duplicate();
|
||||
|
||||
try {
|
||||
// Insert start marker
|
||||
rng.collapse();
|
||||
rng.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_start" style="' + styles + '">' + chr + '</span>');
|
||||
|
||||
// Insert end marker
|
||||
if (!collapsed) {
|
||||
rng2.collapse(false);
|
||||
|
||||
// Detect the empty space after block elements in IE and move the
|
||||
// end back one character <p></p>] becomes <p>]</p>
|
||||
rng.moveToElementText(rng2.parentElement());
|
||||
if (rng.compareEndPoints('StartToEnd', rng2) === 0) {
|
||||
rng2.move('character', -1);
|
||||
}
|
||||
|
||||
rng2.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_end" style="' + styles + '">' + chr + '</span>');
|
||||
}
|
||||
} catch (ex) {
|
||||
// IE might throw unspecified error so lets ignore it
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
// Control selection
|
||||
element = rng.item(0);
|
||||
name = element.nodeName;
|
||||
|
||||
return {name: name, index: findIndex(name, element)};
|
||||
}
|
||||
} else {
|
||||
element = selection.getNode();
|
||||
name = element.nodeName;
|
||||
if (name == 'IMG') {
|
||||
return {name: name, index: findIndex(name, element)};
|
||||
}
|
||||
|
||||
// W3C method
|
||||
rng2 = normalizeTableCellSelection(rng.cloneRange());
|
||||
|
||||
// Insert end marker
|
||||
if (!collapsed) {
|
||||
rng2.collapse(false);
|
||||
rng2.insertNode(dom.create('span', {'data-mce-type': "bookmark", id: id + '_end', style: styles}, chr));
|
||||
}
|
||||
|
||||
rng = normalizeTableCellSelection(rng);
|
||||
rng.collapse(true);
|
||||
rng.insertNode(dom.create('span', {'data-mce-type': "bookmark", id: id + '_start', style: styles}, chr));
|
||||
}
|
||||
|
||||
selection.moveToBookmark({id: id, keep: 1});
|
||||
|
||||
return {id: id};
|
||||
};
|
||||
|
||||
/**
|
||||
* Restores the selection to the specified bookmark.
|
||||
*
|
||||
* @method moveToBookmark
|
||||
* @param {Object} bookmark Bookmark to restore selection from.
|
||||
* @return {Boolean} true/false if it was successful or not.
|
||||
* @example
|
||||
* // Stores a bookmark of the current selection
|
||||
* var bm = tinymce.activeEditor.selection.getBookmark();
|
||||
*
|
||||
* tinymce.activeEditor.setContent(tinymce.activeEditor.getContent() + 'Some new content');
|
||||
*
|
||||
* // Restore the selection bookmark
|
||||
* tinymce.activeEditor.selection.moveToBookmark(bm);
|
||||
*/
|
||||
this.moveToBookmark = function(bookmark) {
|
||||
var rng, root, startContainer, endContainer, startOffset, endOffset;
|
||||
|
||||
function setEndPoint(start) {
|
||||
var point = bookmark[start ? 'start' : 'end'], i, node, offset, children;
|
||||
|
||||
if (point) {
|
||||
offset = point[0];
|
||||
|
||||
// Find container node
|
||||
for (node = root, i = point.length - 1; i >= 1; i--) {
|
||||
children = node.childNodes;
|
||||
|
||||
if (point[i] > children.length - 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
node = children[point[i]];
|
||||
}
|
||||
|
||||
// Move text offset to best suitable location
|
||||
if (node.nodeType === 3) {
|
||||
offset = Math.min(point[0], node.nodeValue.length);
|
||||
}
|
||||
|
||||
// Move element offset to best suitable location
|
||||
if (node.nodeType === 1) {
|
||||
offset = Math.min(point[0], node.childNodes.length);
|
||||
}
|
||||
|
||||
// Set offset within container node
|
||||
if (start) {
|
||||
rng.setStart(node, offset);
|
||||
} else {
|
||||
rng.setEnd(node, offset);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function restoreEndPoint(suffix) {
|
||||
var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep;
|
||||
|
||||
if (marker) {
|
||||
node = marker.parentNode;
|
||||
|
||||
if (suffix == 'start') {
|
||||
if (!keep) {
|
||||
idx = dom.nodeIndex(marker);
|
||||
} else {
|
||||
node = marker.firstChild;
|
||||
idx = 1;
|
||||
}
|
||||
|
||||
startContainer = endContainer = node;
|
||||
startOffset = endOffset = idx;
|
||||
} else {
|
||||
if (!keep) {
|
||||
idx = dom.nodeIndex(marker);
|
||||
} else {
|
||||
node = marker.firstChild;
|
||||
idx = 1;
|
||||
}
|
||||
|
||||
endContainer = node;
|
||||
endOffset = idx;
|
||||
}
|
||||
|
||||
if (!keep) {
|
||||
prev = marker.previousSibling;
|
||||
next = marker.nextSibling;
|
||||
|
||||
// Remove all marker text nodes
|
||||
Tools.each(Tools.grep(marker.childNodes), function(node) {
|
||||
if (node.nodeType == 3) {
|
||||
node.nodeValue = node.nodeValue.replace(/\uFEFF/g, '');
|
||||
}
|
||||
});
|
||||
|
||||
// Remove marker but keep children if for example contents where inserted into the marker
|
||||
// Also remove duplicated instances of the marker for example by a
|
||||
// split operation or by WebKit auto split on paste feature
|
||||
while ((marker = dom.get(bookmark.id + '_' + suffix))) {
|
||||
dom.remove(marker, 1);
|
||||
}
|
||||
|
||||
// If siblings are text nodes then merge them unless it's Opera since it some how removes the node
|
||||
// and we are sniffing since adding a lot of detection code for a browser with 3% of the market
|
||||
// isn't worth the effort. Sorry, Opera but it's just a fact
|
||||
if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3 && !Env.opera) {
|
||||
idx = prev.nodeValue.length;
|
||||
prev.appendData(next.nodeValue);
|
||||
dom.remove(next);
|
||||
|
||||
if (suffix == 'start') {
|
||||
startContainer = endContainer = prev;
|
||||
startOffset = endOffset = idx;
|
||||
} else {
|
||||
endContainer = prev;
|
||||
endOffset = idx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function addBogus(node) {
|
||||
// Adds a bogus BR element for empty block elements
|
||||
if (dom.isBlock(node) && !node.innerHTML && !Env.ie) {
|
||||
node.innerHTML = '<br data-mce-bogus="1" />';
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
if (bookmark) {
|
||||
if (bookmark.start) {
|
||||
rng = dom.createRng();
|
||||
root = dom.getRoot();
|
||||
|
||||
if (selection.tridentSel) {
|
||||
return selection.tridentSel.moveToBookmark(bookmark);
|
||||
}
|
||||
|
||||
if (setEndPoint(true) && setEndPoint()) {
|
||||
selection.setRng(rng);
|
||||
}
|
||||
} else if (bookmark.id) {
|
||||
// Restore start/end points
|
||||
restoreEndPoint('start');
|
||||
restoreEndPoint('end');
|
||||
|
||||
if (startContainer) {
|
||||
rng = dom.createRng();
|
||||
rng.setStart(addBogus(startContainer), startOffset);
|
||||
rng.setEnd(addBogus(endContainer), endOffset);
|
||||
selection.setRng(rng);
|
||||
}
|
||||
} else if (bookmark.name) {
|
||||
selection.select(dom.select(bookmark.name)[bookmark.index]);
|
||||
} else if (bookmark.rng) {
|
||||
selection.setRng(bookmark.rng);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true/false if the specified node is a bookmark node or not.
|
||||
*
|
||||
* @static
|
||||
* @method isBookmarkNode
|
||||
* @param {DOMNode} node DOM Node to check if it's a bookmark node or not.
|
||||
* @return {Boolean} true/false if the node is a bookmark node or not.
|
||||
*/
|
||||
BookmarkManager.isBookmarkNode = function(node) {
|
||||
return node && node.tagName === 'SPAN' && node.getAttribute('data-mce-type') === 'bookmark';
|
||||
};
|
||||
|
||||
return BookmarkManager;
|
||||
});
|
||||
Reference in new issue
Block a user