You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@corinthia.apache.org by ja...@apache.org on 2015/08/17 10:50:18 UTC
[26/28] incubator-corinthia git commit: included MOC compiler for Qt
implementation
http://git-wip-us.apache.org/repos/asf/incubator-corinthia/blob/9bf02bb2/experiments/editorFramework/src/Javascript_Layer_0/Clipboard.js
----------------------------------------------------------------------
diff --git a/experiments/editorFramework/src/Javascript_Layer_0/Clipboard.js b/experiments/editorFramework/src/Javascript_Layer_0/Clipboard.js
new file mode 100644
index 0000000..03efdcc
--- /dev/null
+++ b/experiments/editorFramework/src/Javascript_Layer_0/Clipboard.js
@@ -0,0 +1,757 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+var Markdown_htmlToMarkdown;
+var Clipboard_htmlToText;
+var Clipboard_cut;
+var Clipboard_copy;
+var Clipboard_pasteText;
+var Clipboard_pasteHTML;
+var Clipboard_pasteNodes;
+
+(function() {
+
+ // private
+ function blockToText(md,node,indent,nextIndent,listType,listNo)
+ {
+ var linesBetweenChildren = 1;
+ var childIndent = indent;
+ switch (node._type) {
+ case HTML_LI:
+ if (listType == "OL") {
+ var listMarker;
+ if (listNo.value < 10)
+ listMarker = listNo.value+". ";
+ else
+ listMarker = listNo.value+". ";
+ beginParagraph(md,0,indent,nextIndent,listMarker);
+ nextIndent += " ";
+ }
+ else {
+ beginParagraph(md,0,indent,nextIndent," - ");
+ nextIndent += " ";
+ }
+ listNo.value++;
+ break;
+ case HTML_UL:
+ listType = "UL";
+ listNo = { value: 1 };
+ beginParagraph(md,1,indent,nextIndent);
+ linesBetweenChildren = 0;
+ break;
+ case HTML_OL:
+ listType = "OL";
+ listNo = { value: 1 };
+ beginParagraph(md,1,indent,nextIndent);
+ linesBetweenChildren = 0;
+ break;
+ case HTML_H1:
+ beginParagraph(md,1,indent,nextIndent,"# "," #");
+ break;
+ case HTML_H2:
+ beginParagraph(md,1,indent,nextIndent,"## "," ##");
+ break;
+ case HTML_H3:
+ beginParagraph(md,1,indent,nextIndent,"### "," ###");
+ break;
+ case HTML_H4:
+ beginParagraph(md,1,indent,nextIndent,"#### "," ####");
+ break;
+ case HTML_H5:
+ beginParagraph(md,1,indent,nextIndent,"##### "," #####");
+ break;
+ case HTML_H6:
+ beginParagraph(md,1,indent,nextIndent,"###### "," ######");
+ break;
+ case HTML_BLOCKQUOTE:
+ beginParagraph(md,1,indent,nextIndent,"> ");
+ nextIndent += "> ";
+ break;
+ case HTML_PRE:
+ md.preDepth++;
+ break;
+ }
+
+ var foundNonWhitespaceChild = false;
+ for (var child = node.firstChild; child != null; child = child.nextSibling) {
+ if (isContainerNode(child) || isParagraphNode(child)) {
+ beginParagraph(md,linesBetweenChildren,indent,nextIndent);
+ blockToText(md,child,indent,nextIndent,listType,listNo);
+ beginParagraph(md,linesBetweenChildren);
+ indent = nextIndent;
+ foundNonWhitespaceChild = false;
+ }
+ else {
+ if (!foundNonWhitespaceChild) {
+ if (isWhitespaceTextNode(child))
+ continue;
+ beginParagraph(md,0,indent,nextIndent);
+ indent = nextIndent;
+ foundNonWhitespaceChild = true;
+ }
+
+ inlineToText(md,child);
+ }
+ }
+
+ if (node._type == HTML_PRE)
+ md.preDepth--;
+ }
+
+ // private
+ function shipOutParagraph(md)
+ {
+ var text = md.buildParagraph.join("");
+ if (md.buildPre) {
+ text = text.replace(/\n$/,"");
+ text = " "+text.replace(/\n/g,"\n"+md.nextIndent+" ");
+ }
+ else {
+ text = normalizeWhitespace(text);
+ }
+ if (md.allText.length > 0) {
+ for (var i = 0; i < md.buildLines; i++)
+ md.allText.push("\n");
+ }
+ md.allText.push(md.indent+md.buildPrefix+text+md.buildSuffix+"\n");
+ resetBuild(md);
+ }
+
+ // private
+ function beginParagraph(md,blankLines,indent,nextIndent,paraPrefix,paraSuffix)
+ {
+ if (blankLines == null)
+ blankLines = 1;
+ if (indent == null)
+ indent = "";
+ if (nextIndent == null)
+ nextIndent = "";
+ if (paraPrefix == null)
+ paraPrefix = "";
+ if (paraSuffix == null)
+ paraSuffix = "";
+
+ if (md == null)
+ throw new Error("beginParagraph: md is null");
+ if (md.buildParagraph == null)
+ throw new Error("beginParagraph: md.buildParagraph is null");
+
+ if (md.buildParagraph.length > 0) {
+ shipOutParagraph(md);
+ }
+
+ if (md.buildLines < blankLines)
+ md.buildLines = blankLines;
+ if (md.indent.length < indent.length)
+ md.indent = indent;
+ if (md.nextIndent.length < nextIndent.length)
+ md.nextIndent = nextIndent;
+ md.buildPrefix += paraPrefix;
+ md.buildSuffix = paraSuffix + md.buildSuffix;
+ if (md.preDepth > 0)
+ md.buildPre = true;
+ }
+
+ // private
+ function inlineToText(md,node)
+ {
+ switch (node._type) {
+ case HTML_TEXT: {
+ var text = node.nodeValue;
+ if (md.preDepth == 0) {
+ text = text.replace(/\\/g,"\\\\");
+ text = text.replace(/\*/g,"\\*");
+ text = text.replace(/\[/g,"\\[");
+ text = text.replace(/\]/g,"\\]");
+ }
+ md.buildParagraph.push(text);
+ break;
+ }
+ case HTML_I:
+ case HTML_EM:
+ md.buildParagraph.push("*");
+ processChildren();
+ md.buildParagraph.push("*");
+ break;
+ case HTML_B:
+ case HTML_STRONG:
+ md.buildParagraph.push("**");
+ processChildren();
+ md.buildParagraph.push("**");
+ break;
+ case HTML_A:
+ if (node.hasAttribute("href")) {
+ md.buildParagraph.push("[");
+ processChildren();
+ md.buildParagraph.push("]("+node.getAttribute("href")+")");
+ }
+ break;
+ default:
+ processChildren();
+ break;
+ }
+
+ function processChildren()
+ {
+ for (var child = node.firstChild; child != null; child = child.nextSibling) {
+ inlineToText(md,child);
+ }
+ }
+ }
+
+ // private
+ function resetBuild(md)
+ {
+ md.buildParagraph = new Array();
+ md.buildLines = 0;
+ md.buildPrefix = "";
+ md.buildSuffix = "";
+ md.buildPre = false;
+ md.indent = "";
+ md.nextIndent = "";
+ }
+
+ // private
+ function MarkdownBuilder()
+ {
+ }
+
+ // public
+ Markdown_htmlToMarkdown = function(node)
+ {
+ var md = new MarkdownBuilder();
+ md.allText = new Array();
+ md.preDepth = 0;
+ resetBuild(md);
+
+ if (isContainerNode(node) || isParagraphNode(node)) {
+ blockToText(md,node,"","","UL",{value: 1});
+ beginParagraph(md);
+ return md.allText.join("");
+ }
+ else {
+ inlineToText(md,node);
+ return normalizeWhitespace(md.buildParagraph.join(""));
+ }
+ }
+
+})();
+
+(function() {
+
+ function expandRangeForCopy(range)
+ {
+ if (range == null)
+ return range;
+
+ var startInLI = null;
+ for (var node = range.start.node; node != null; node = node.parentNode) {
+ if (node._type == HTML_LI)
+ startInLI = node;
+ }
+
+ var endInLI = null;
+ for (var node = range.end.node; node != null; node = node.parentNode) {
+ if (node._type == HTML_LI)
+ endInLI = node;
+ }
+
+ if ((startInLI != null) && (startInLI == endInLI)) {
+ var beforeRange = new Range(startInLI,0,
+ range.start.node,range.start.offset);
+ var afterRange = new Range(range.end.node,range.end.offset,
+ endInLI,DOM_maxChildOffset(endInLI));
+ var contentBefore = Range_hasContent(beforeRange);
+ var contentAfter = Range_hasContent(afterRange);
+
+ if (!contentBefore && !contentAfter) {
+ var li = startInLI;
+ var offset = DOM_nodeOffset(li);
+ range = new Range(li.parentNode,offset,li.parentNode,offset+1);
+ }
+ }
+ return range;
+ }
+
+ function copyRange(range)
+ {
+ var html = "";
+ var text = "";
+
+ if (range != null) {
+ var nodes;
+ var region = Tables_regionFromRange(range);
+ if (region != null) {
+ nodes = [Tables_cloneRegion(region)];
+ }
+ else {
+ nodes = Range_cloneContents(range);
+ };
+
+ var div = DOM_createElement(document,"DIV");
+ for (var i = 0; i < nodes.length; i++)
+ DOM_appendChild(div,nodes[i]);
+ Main_removeSpecial(div);
+
+ html = div.innerHTML;
+ text = Clipboard_htmlToText(div);
+ }
+
+ return { "text/html": html,
+ "text/plain": text };
+ }
+
+ // public (FIXME: temp: for testing)
+ Clipboard_htmlToText = function(node)
+ {
+ return Markdown_htmlToMarkdown(node);
+ }
+
+ // public
+ Clipboard_cut = function()
+ {
+ UndoManager_newGroup("Cut");
+ var content;
+
+ var range = Selection_get();
+ range = expandRangeForCopy(range);
+ content = copyRange(range);
+
+ Selection_set(range.start.node,range.start.offset,range.end.node,range.end.offset);
+ Selection_deleteContents(false);
+ var selRange = Selection_get();
+ if (selRange != null) {
+ Range_trackWhileExecuting(selRange,function() {
+ var node = Position_closestActualNode(selRange.start);
+ while (node != null) {
+ var parent = node.parentNode;
+ switch (node._type) {
+ case HTML_LI:
+ if (!nodeHasContent(node))
+ DOM_deleteNode(node);
+ break;
+ case HTML_UL:
+ case HTML_OL: {
+ var haveLI = false;
+ for (var c = node.firstChild; c != null; c = c.nextSibling) {
+ if (c._type == HTML_LI) {
+ haveLI = true;
+ break;
+ }
+ }
+ if (!haveLI)
+ DOM_deleteNode(node);
+ break;
+ }
+ }
+ node = parent;
+ }
+ });
+
+ var pos = Position_closestMatchForwards(selRange.start,Position_okForMovement);
+ Selection_set(pos.node,pos.offset,pos.node,pos.offset);
+ }
+
+ Cursor_ensureCursorVisible();
+
+ PostponedActions_perform(UndoManager_newGroup);
+ return content;
+ }
+
+ // public
+ Clipboard_copy = function()
+ {
+ var range = Selection_get();
+ range = expandRangeForCopy(range);
+ return copyRange(range);
+ }
+
+ // public
+ Clipboard_pasteText = function(text)
+ {
+ var converter = new Showdown.converter();
+ var html = converter.makeHtml(text);
+ UndoManager_newGroup("Paste");
+ Clipboard_pasteHTML(html);
+ UndoManager_newGroup();
+ }
+
+ // public
+ Clipboard_pasteHTML = function(html)
+ {
+ if (html.match(/^\s*<thead/i))
+ html = "<table>" + html + "</table>";
+ else if (html.match(/^\s*<tbody/i))
+ html = "<table>" + html + "</table>";
+ else if (html.match(/^\s*<tfoot/i))
+ html = "<table>" + html + "</table>";
+ else if (html.match(/^\s*<tr/i))
+ html = "<table>" + html + "</table>";
+ else if (html.match(/^\s*<td/i))
+ html = "<table><tr>" + html + "</tr></table>";
+ else if (html.match(/^\s*<th/i))
+ html = "<table><tr>" + html + "</tr></table>";
+ else if (html.match(/^\s*<li/i))
+ html = "<ul>" + html + "</ul>";
+
+ var div = DOM_createElement(document,"DIV");
+ div.innerHTML = html;
+ for (var child = div.firstChild; child != null; child = child.nextSibling)
+ DOM_assignNodeIds(child);
+
+ var nodes = new Array();
+ for (var child = div.firstChild; child != null; child = child.nextSibling)
+ nodes.push(child);
+
+ UndoManager_newGroup("Paste");
+ var region = Tables_regionFromRange(Selection_get(),true);
+ if ((region != null) && (nodes.length == 1) && (nodes[0]._type == HTML_TABLE))
+ pasteTable(nodes[0],region);
+ else
+ Clipboard_pasteNodes(nodes);
+ UndoManager_newGroup();
+ }
+
+ function pasteTable(srcTable,dest)
+ {
+ var src = Tables_analyseStructure(srcTable);
+
+ // In the destination table, the region into which we will paste the cells will the
+ // the same size as that of the source table, regardless of how many rows and columns
+ // were selected - i.e. we only pay attention to the top-left most cell, ignoring
+ // whatever the bottom-right is set to
+ dest.bottom = dest.top + src.numRows - 1;
+ dest.right = dest.left + src.numCols - 1;
+
+ // Make sure the destination table is big enough to hold all the cells we want to paste.
+ // This will add rows and columns as appropriate, with empty cells that only contain a
+ // <p><br></p> (to ensure they have non-zero height)
+ if (dest.structure.numRows < dest.bottom + 1)
+ dest.structure.numRows = dest.bottom + 1;
+ if (dest.structure.numCols < dest.right + 1)
+ dest.structure.numCols = dest.right + 1;
+ dest.structure = Table_fix(dest.structure);
+
+ // To simplify the paste, split any merged cells that are in the region of the destination
+ // table we're pasting into. We have to re-analyse the table structure after this to
+ // get the correct cell array.
+ TableRegion_splitCells(dest);
+ dest.structure = Tables_analyseStructure(dest.structure.element);
+
+ // Do the actual paste
+ Selection_preserveWhileExecuting(function() {
+ replaceCells(src,dest.structure,dest.top,dest.left);
+ });
+
+ // If any new columns were added, calculate a width for them
+ Table_fixColumnWidths(dest.structure);
+
+ // Remove duplicate ids
+ var found = new Object();
+ removeDuplicateIds(dest.structure.element,found);
+
+ // Place the cursor in the bottom-right cell that was pasted
+ var bottomRightCell = Table_get(dest.structure,dest.bottom,dest.right);
+ var node = bottomRightCell.element;
+ Selection_set(node,node.childNodes.length,node,node.childNodes.length);
+ }
+
+ function replaceCells(src,dest,destRow,destCol)
+ {
+ // By this point, all of the cells have been split. So it is guaranteed that every cell
+ // in dest will have rowspan = 1 and colspan = 1.
+ for (var srcRow = 0; srcRow < src.numRows; srcRow++) {
+ for (var srcCol = 0; srcCol < src.numCols; srcCol++) {
+ var srcCell = Table_get(src,srcRow,srcCol);
+ var destCell = Table_get(dest,srcRow+destRow,srcCol+destCol);
+
+ if ((srcRow != srcCell.row) || (srcCol != srcCell.col))
+ continue;
+
+ if (destCell.rowspan != 1)
+ throw new Error("unexpected rowspan: "+destCell.rowspan);
+ if (destCell.colspan != 1)
+ throw new Error("unexpected colspan: "+destCell.colspan);
+
+ DOM_insertBefore(destCell.element.parentNode,srcCell.element,destCell.element);
+
+ var destTop = destRow + srcRow;
+ var destLeft = destCol + srcCol;
+ var destBottom = destTop + srcCell.rowspan - 1;
+ var destRight = destLeft + srcCell.colspan - 1;
+ Table_setRegion(dest,destTop,destLeft,destBottom,destRight,srcCell);
+ }
+ }
+ }
+
+ function insertChildrenBefore(parent,child,nextSibling,pastedNodes)
+ {
+ var next;
+ for (var grandChild = child.firstChild; grandChild != null; grandChild = next) {
+ next = grandChild.nextSibling;
+ pastedNodes.push(grandChild);
+ DOM_insertBefore(parent,grandChild,nextSibling);
+ }
+ }
+
+ function fixParagraphStyles(node,paragraphClass)
+ {
+ if (isParagraphNode(node)) {
+ if (node._type == HTML_P) {
+ var className = DOM_getAttribute(node,"class");
+ if ((className == null) || (className == "")) {
+ debug("Setting paragraph class to "+paragraphClass);
+ DOM_setAttribute(node,"class",paragraphClass);
+ }
+ }
+ }
+ else {
+ for (var child = node.firstChild; child != null; child = child.nextSibling) {
+ fixParagraphStyles(child,paragraphClass);
+ }
+ }
+ }
+
+ // public
+ Clipboard_pasteNodes = function(nodes)
+ {
+ if (nodes.length == 0)
+ return;
+
+ var paragraphClass = Styles_getParagraphClass();
+ if (paragraphClass != null) {
+ for (var i = 0; i < nodes.length; i++) {
+ fixParagraphStyles(nodes[i],paragraphClass);
+ }
+ }
+
+ // Remove any elements which don't belong in the document body (in case an entire
+ // HTML document is being pasted in)
+ var i = 0;
+ while (i < nodes.length) {
+ switch (nodes[i]._type) {
+ case HTML_HTML:
+ case HTML_BODY:
+ case HTML_META:
+ case HTML_TITLE:
+ case HTML_SCRIPT:
+ case HTML_STYLE:
+ nodes.splice(i,1);
+ break;
+ default:
+ i++;
+ }
+ }
+
+ var found = new Object();
+ for (var i = 0; i < nodes.length; i++)
+ removeDuplicateIds(nodes[i],found);
+
+// if ((nodes.length == 0) && (nodes[0]._type == HTML_TABLE)) {
+// // FIXME: this won't work; selectionRange is not defined
+// var fromRegion = Tables_getTableRegionFromTable(nodes[0]);
+// var toRegion = Tables_regionFromRange(selectionRange);
+// if (toRegion != null) {
+// return;
+// }
+// }
+
+ Selection_deleteContents(true);
+ var range = Selection_get();
+ if (range == null)
+ throw new Error("No current selection");
+
+ var parent;
+ var previousSibling;
+ var nextSibling;
+
+ var start = range.start;
+ start = Position_preferElementPosition(start);
+ if (start.node.nodeType == Node.ELEMENT_NODE) {
+ parent = start.node;
+ nextSibling = start.node.childNodes[start.offset];
+ previousSibling = start.node.childNodes[start.offset-1];
+ }
+ else {
+ Formatting_splitTextAfter(start);
+ parent = start.node.parentNode;
+ nextSibling = start.node.nextSibling;
+ previousSibling = start.node;
+ }
+
+ var prevLI = null;
+ var inItem = null;
+ var inList = null;
+ var containerParent = null;
+
+ for (var temp = parent; temp != null; temp = temp.parentNode) {
+ if (isContainerNode(temp)) {
+ switch (temp._type) {
+ case HTML_LI:
+ inItem = temp;
+ break;
+ case HTML_UL:
+ case HTML_OL:
+ inList = temp;
+ break;
+ }
+ containerParent = temp.parentNode;
+ break;
+ }
+ }
+
+ var pastedNodes;
+ if (inItem) {
+ pastedNodes = new Array();
+ for (var i = 0; i < nodes.length; i++) {
+ var child = nodes[i];
+
+ var offset = DOM_nodeOffset(nextSibling,parent);
+
+ switch (child._type) {
+ case HTML_UL:
+ case HTML_OL:
+ Formatting_movePreceding(new Position(parent,offset),
+ function(x) { return (x == containerParent); });
+ insertChildrenBefore(inItem.parentNode,child,inItem,pastedNodes);
+ break;
+ case HTML_LI:
+ Formatting_movePreceding(new Position(parent,offset),
+ function(x) { return (x == containerParent); });
+ DOM_insertBefore(inItem.parentNode,child,inItem);
+ pastedNodes.push(child);
+ break;
+ default:
+ DOM_insertBefore(parent,child,nextSibling);
+ pastedNodes.push(child);
+ break;
+ }
+ }
+ }
+ else if (inList) {
+ pastedNodes = new Array();
+ for (var i = 0; i < nodes.length; i++) {
+ var child = nodes[i];
+
+ var offset = DOM_nodeOffset(nextSibling,parent);
+
+ switch (child._type) {
+ case HTML_UL:
+ case HTML_OL:
+ insertChildrenBefore(parent,child,nextSibling,pastedNodes);
+ prevLI = null;
+ break;
+ case HTML_LI:
+ DOM_insertBefore(parent,child,nextSibling);
+ pastedNodes.push(child);
+ prevLI = null;
+ break;
+ default:
+ if (!isWhitespaceTextNode(child)) {
+ if (prevLI == null)
+ prevLI = DOM_createElement(document,"LI");
+ DOM_appendChild(prevLI,child);
+ DOM_insertBefore(parent,prevLI,nextSibling);
+ pastedNodes.push(child);
+ }
+ }
+ }
+ }
+ else {
+ pastedNodes = nodes;
+ for (var i = 0; i < nodes.length; i++) {
+ var child = nodes[i];
+ DOM_insertBefore(parent,child,nextSibling);
+ }
+ }
+
+ var prevOffset;
+ if (previousSibling == null)
+ prevOffset = 0;
+ else
+ prevOffset = DOM_nodeOffset(previousSibling);
+ var nextOffset = DOM_nodeOffset(nextSibling,parent);
+
+ var origRange = new Range(parent,prevOffset,parent,nextOffset);
+
+ var firstPasted = pastedNodes[0];
+ var lastPasted = pastedNodes[pastedNodes.length-1];
+ var pastedRange = new Range(firstPasted,0,lastPasted,DOM_maxChildOffset(lastPasted));
+ Range_trackWhileExecuting(origRange,function() {
+ Range_trackWhileExecuting(pastedRange,function() {
+ if (previousSibling != null)
+ Formatting_mergeWithNeighbours(previousSibling,Formatting_MERGEABLE_INLINE);
+ if (nextSibling != null)
+ Formatting_mergeWithNeighbours(nextSibling,Formatting_MERGEABLE_INLINE);
+
+ Cursor_updateBRAtEndOfParagraph(parent);
+
+ Range_ensureValidHierarchy(pastedRange,true);
+ })});
+
+ var pos = new Position(origRange.end.node,origRange.end.offset);
+ Range_trackWhileExecuting(pastedRange,function() {
+ Position_trackWhileExecuting(pos,function() {
+ while (true) {
+ if (pos.node == document.body)
+ break;
+ if (isContainerNode(pos.node) && (pos.node._type != HTML_LI))
+ break;
+ if (!nodeHasContent(pos.node)) {
+ var oldNode = pos.node;
+ pos = new Position(pos.node.parentNode,DOM_nodeOffset(pos.node));
+ DOM_deleteNode(oldNode);
+ }
+ else
+ break;
+ }
+ });
+ });
+
+ pos = new Position(pastedRange.end.node,pastedRange.end.offset);
+ while (isOpaqueNode(pos.node))
+ pos = new Position(pos.node.parentNode,DOM_nodeOffset(pos.node)+1);
+ pos = Position_closestMatchBackwards(pos,Position_okForInsertion);
+
+ Selection_set(pos.node,pos.offset,pos.node,pos.offset);
+ Cursor_ensureCursorVisible();
+ }
+
+ function removeDuplicateIds(node,found)
+ {
+ if ((node.nodeType == Node.ELEMENT_NODE) && node.hasAttribute("id")) {
+ var id = node.getAttribute("id");
+
+ var existing = document.getElementById(id);
+ if (existing == null)
+ existing = found[id];
+
+ if ((existing != null) && (existing != node))
+ DOM_removeAttribute(node,"id");
+ else
+ found[id] = node;
+ }
+ for (var child = node.firstChild; child != null; child = child.nextSibling)
+ removeDuplicateIds(child,found);
+ }
+
+ function pasteImage(href)
+ {
+ // FIXME
+ }
+
+})();
http://git-wip-us.apache.org/repos/asf/incubator-corinthia/blob/9bf02bb2/experiments/editorFramework/src/Javascript_Layer_0/Cursor.js
----------------------------------------------------------------------
diff --git a/experiments/editorFramework/src/Javascript_Layer_0/Cursor.js b/experiments/editorFramework/src/Javascript_Layer_0/Cursor.js
new file mode 100644
index 0000000..7362a9e
--- /dev/null
+++ b/experiments/editorFramework/src/Javascript_Layer_0/Cursor.js
@@ -0,0 +1,1050 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+var Cursor_ensurePositionVisible;
+var Cursor_ensureCursorVisible;
+var Cursor_scrollDocumentForY;
+var Cursor_positionCursor;
+var Cursor_getCursorPosition;
+var Cursor_moveLeft;
+var Cursor_moveRight;
+var Cursor_moveToStartOfDocument;
+var Cursor_moveToEndOfDocument;
+var Cursor_updateBRAtEndOfParagraph;
+var Cursor_insertReference;
+var Cursor_insertLink;
+var Cursor_insertCharacter;
+var Cursor_deleteCharacter;
+var Cursor_enterPressed;
+var Cursor_getPrecedingWord;
+var Cursor_getAdjacentNodeWithType;
+var Cursor_getLinkProperties;
+var Cursor_setLinkProperties;
+var Cursor_setReferenceTarget;
+var Cursor_makeContainerInsertionPoint;
+var Cursor_set;
+var Cursor_insertFootnote;
+var Cursor_insertEndnote;
+
+(function() {
+
+ var cursorX = null;
+
+ Cursor_ensurePositionVisible = function(pos,center)
+ {
+ // If we can't find the cursor rect for some reason, just don't do anything.
+ // This is better than using an incorrect position or throwing an exception.
+ var rect = Position_displayRectAtPos(pos)
+ if (rect != null) {
+ var extraSpace = 4;
+
+ var cursorTop = rect.top + window.scrollY - extraSpace;
+ var cursorBottom = rect.top + rect.height + window.scrollY + extraSpace;
+
+ var windowTop = window.scrollY;
+ var windowBottom = window.scrollY + window.innerHeight;
+
+ if (center) {
+ var newY = Math.floor(cursorTop + rect.height/2 - window.innerHeight/2);
+ window.scrollTo(window.scrollX,newY);
+ }
+ else if (cursorTop < windowTop) {
+ window.scrollTo(window.scrollX,cursorTop);
+ }
+ else if (cursorBottom > windowBottom) {
+ window.scrollTo(window.scrollX,cursorBottom - window.innerHeight);
+ }
+ }
+ }
+
+ // public
+ Cursor_ensureCursorVisible = function(center)
+ {
+ var selRange = Selection_get();
+ if (selRange != null)
+ Cursor_ensurePositionVisible(selRange.end,center);
+ }
+
+ Cursor_scrollDocumentForY = function(y)
+ {
+ var absY = window.scrollY + y;
+ if (absY-44 < window.scrollY) {
+ window.scrollTo(window.scrollX,absY-44);
+ y = absY - window.scrollY;
+ }
+ else if (absY+44 >= window.scrollY + window.innerHeight) {
+ window.scrollTo(window.scrollX,absY+44 - window.innerHeight);
+ y = absY - window.scrollY;
+ }
+ return y;
+ }
+
+ // public
+ Cursor_positionCursor = function(x,y,wordBoundary)
+ {
+ if (UndoManager_groupType() != "Cursor movement")
+ UndoManager_newGroup("Cursor movement");
+
+ y = Cursor_scrollDocumentForY(y);
+
+ var result = null;
+ var position = Position_atPoint(x,y);
+ if (position == null)
+ return null;
+
+ var node = Position_closestActualNode(position);
+ for (; node != null; node = node.parentNode) {
+ var type = node._type;
+ if ((type == HTML_A) &&
+ (node.hasAttribute("href")) &&
+ (result == null)) {
+
+ var arange = new Range(node,0,node,node.childNodes.length);
+ var rects = Range_getClientRects(arange);
+ var insideLink = false;
+ for (var i = 0; i < rects.length; i++) {
+ if (rectContainsPoint(rects[i],x,y))
+ insideLink = true;
+ }
+
+ if (insideLink) {
+ var href = node.getAttribute("href");
+ if ((href != null) && (href.charAt(0) == "#")) {
+ if (isInTOC(node))
+ result = "intocreference-"+href.substring(1);
+ else
+ result = "inreference";
+ }
+ else {
+ result = "inlink";
+ }
+ }
+ }
+ else if ((type == HTML_IMG) && (result == null)) {
+ for (var anc = node; anc != null; anc = anc.parentNode) {
+ if (anc._type == HTML_FIGURE) {
+ result = "infigure";
+ break;
+ }
+ }
+ }
+ else if (isAutoCorrectNode(node) && (result == null)) {
+ result = "incorrection";
+ }
+ else if (isTOCNode(node)) {
+ var rect = node.getBoundingClientRect();
+ if (x >= rect.left + rect.width/2)
+ position = new Position(node.parentNode,DOM_nodeOffset(node)+1);
+ else
+ position = new Position(node.parentNode,DOM_nodeOffset(node));
+ break;
+ }
+ }
+
+ var position = Position_closestMatchForwards(position,Position_okForMovement);
+ if ((position != null) && isOpaqueNode(position.node))
+ position = Position_nextMatch(position,Position_okForMovement);
+ if (position == null)
+ return false;
+
+ var selectionRange = Selection_get();
+ var samePosition = ((selectionRange != null) && Range_isEmpty(selectionRange) &&
+ (position.node == selectionRange.start.node) &&
+ (position.offset == selectionRange.start.offset));
+ if (samePosition && (result == null))
+ result = "same";
+
+ if (wordBoundary) {
+ var startOfWord = Selection_posAtStartOfWord(position);
+ var endOfWord = Selection_posAtEndOfWord(position);
+ if ((startOfWord.node != position.node) || (startOfWord.node != position.node))
+ throw new Error("Word boundary in different node");
+ var distanceBefore = position.offset - startOfWord.offset;
+ var distanceAfter = endOfWord.offset - position.offset;
+ if (distanceBefore <= distanceAfter)
+ position = startOfWord;
+ else
+ position = endOfWord;
+ }
+
+ Cursor_set(position.node,position.offset);
+ return result;
+ }
+
+ // public
+ Cursor_getCursorPosition = function()
+ {
+ var selRange = Selection_get();
+ if (selRange == null)
+ return null;
+
+ // FIXME: in the cases where this is called from Objective C, test what happens if we
+ // return a null rect
+ var rect = Position_displayRectAtPos(selRange.end);
+ if (rect == null)
+ return null;
+
+ var left = rect.left + window.scrollX;
+ var top = rect.top + window.scrollY;
+ var height = rect.height;
+ return { x: left, y: top, width: 0, height: height };
+ }
+
+ // public
+ Cursor_moveLeft = function()
+ {
+ var range = Selection_get();
+ if (range == null)
+ return;
+
+ var pos = Position_prevMatch(range.start,Position_okForMovement);
+ if (pos != null)
+ Cursor_set(pos.node,pos.offset);
+ Cursor_ensureCursorVisible();
+ }
+
+ // public
+ Cursor_moveRight = function()
+ {
+ var range = Selection_get();
+ if (range == null)
+ return;
+
+ var pos = Position_nextMatch(range.start,Position_okForMovement);
+ if (pos != null)
+ Cursor_set(pos.node,pos.offset);
+ Cursor_ensureCursorVisible();
+ }
+
+ // public
+ Cursor_moveToStartOfDocument = function()
+ {
+ var pos = new Position(document.body,0);
+ pos = Position_closestMatchBackwards(pos,Position_okForMovement);
+ Cursor_set(pos.node,pos.offset);
+ Cursor_ensureCursorVisible();
+ }
+
+ // public
+ Cursor_moveToEndOfDocument = function()
+ {
+ var pos = new Position(document.body,document.body.childNodes.length);
+ pos = Position_closestMatchForwards(pos,Position_okForMovement);
+ Cursor_set(pos.node,pos.offset);
+ Cursor_ensureCursorVisible();
+ }
+
+ // An empty paragraph does not get shown and cannot be edited. We can fix this by adding
+ // a BR element as a child
+ // public
+ Cursor_updateBRAtEndOfParagraph = function(node)
+ {
+ var paragraph = node;
+ while ((paragraph != null) && !isParagraphNode(paragraph))
+ paragraph = paragraph.parentNode;
+ if (paragraph != null) {
+
+ var br = null;
+ var last = paragraph;
+ do {
+
+ var child = last;
+ while ((child != null) && isWhitespaceTextNode(child))
+ child = child.previousSibling;
+
+ if ((child != null) && (child._type == HTML_BR))
+ br = child;
+
+ last = last.lastChild;
+
+ } while ((last != null) && isInlineNode(last));
+
+ if (nodeHasContent(paragraph)) {
+ // Paragraph has content: don't want BR at end
+ if (br != null) {
+ DOM_deleteNode(br);
+ }
+ }
+ else {
+ // Paragraph consists only of whitespace: must have BR at end
+ if (br == null) {
+ br = DOM_createElement(document,"BR");
+ DOM_appendChild(paragraph,br);
+ }
+ }
+ }
+ }
+
+ // public
+ Cursor_insertReference = function(itemId)
+ {
+ var a = DOM_createElement(document,"A");
+ DOM_setAttribute(a,"href","#"+itemId);
+ Clipboard_pasteNodes([a]);
+ }
+
+ // public
+ Cursor_insertLink = function(text,url)
+ {
+ var a = DOM_createElement(document,"A");
+ DOM_setAttribute(a,"href",url);
+ DOM_appendChild(a,DOM_createTextNode(document,text));
+ Clipboard_pasteNodes([a]);
+ }
+
+ var nbsp = String.fromCharCode(160);
+
+ function spaceToNbsp(pos)
+ {
+ var node = pos.node;
+ var offset = pos.offset;
+
+ if ((node.nodeType == Node.TEXT_NODE) && (offset > 0) &&
+ (isWhitespaceString(node.nodeValue.charAt(offset-1)))) {
+ // Insert first, to preserve any tracked positions
+ DOM_insertCharacters(node,offset-1,nbsp);
+ DOM_deleteCharacters(node,offset,offset+1);
+ }
+ }
+
+ function nbspToSpace(pos)
+ {
+ var node = pos.node;
+ var offset = pos.offset;
+
+ if ((node.nodeType == Node.TEXT_NODE) && (offset > 0) &&
+ (node.nodeValue.charAt(offset-1) == nbsp)) {
+ // Insert first, to preserve any tracked positions
+ DOM_insertCharacters(node,offset-1," ");
+ DOM_deleteCharacters(node,offset,offset+1);
+ }
+ }
+
+ function checkNbsp()
+ {
+ Selection_preserveWhileExecuting(function() {
+ var selRange = Selection_get();
+ if (selRange != null)
+ nbspToSpace(selRange.end);
+ });
+ }
+
+ function isPosAtStartOfParagraph(pos)
+ {
+ if ((pos.node.nodeType == Node.ELEMENT_NODE) && (pos.offset == 0) &&
+ !isInlineNode(pos.node)) {
+ return true;
+ }
+
+
+
+ while (pos != null) {
+ if (pos.node.nodeType == Node.ELEMENT_NODE) {
+ if ((pos.offset == 0) && !isInlineNode(pos.node))
+ return true;
+ else
+ pos = Position_prev(pos);
+ }
+ else if (pos.node.nodeType == Node.TEXT_NODE) {
+ if (pos.offset > 0)
+ return false;
+ else
+ pos = Position_prev(pos);
+ }
+ else {
+ return false;
+ }
+ }
+
+ return false;
+ }
+
+ // public
+ Cursor_insertCharacter = function(str,allowInvalidPos,allowNoParagraph)
+ {
+ var firstInsertion = (UndoManager_groupType() != "Insert text");
+
+ if (firstInsertion)
+ UndoManager_newGroup("Insert text",checkNbsp);
+
+ if (str == "-") {
+ var preceding = Cursor_getPrecedingWord();
+ if (preceding.match(/[0-9]\s*$/))
+ str = String.fromCharCode(0x2013); // en dash
+ else if (preceding.match(/\s+$/))
+ str = String.fromCharCode(0x2014); // em dash
+ }
+
+ var selRange = Selection_get();
+ if (selRange == null)
+ return;
+
+ if (!Range_isEmpty(selRange)) {
+ Selection_deleteContents(true);
+ selRange = Selection_get();
+ }
+ var pos = selRange.start;
+ pos = Position_preferTextPosition(pos);
+ if ((str == " ") && isPosAtStartOfParagraph(pos))
+ return;
+ if (!allowInvalidPos && !Position_okForInsertion(pos)) {
+ var elemPos = Position_preferElementPosition(pos);
+ if (Position_okForInsertion(elemPos)) {
+ pos = elemPos;
+ }
+ else {
+ var oldPos = pos;
+ pos = Position_closestMatchForwards(selRange.start,Position_okForInsertion);
+ var difference = new Range(oldPos.node,oldPos.offset,pos.node,pos.offset);
+ difference = Range_forwards(difference);
+ Position_trackWhileExecuting([pos],function() {
+ if (!Range_hasContent(difference)) {
+ Selection_deleteRangeContents(difference,true);
+ }
+ });
+ }
+ }
+ var node = pos.node;
+ var offset = pos.offset;
+
+ if ((str == " ") &&
+ !firstInsertion &&
+ (node.nodeType == Node.TEXT_NODE) &&
+ (offset > 0) &&
+ (node.nodeValue.charAt(offset-1) == nbsp)) {
+
+ if (!node.nodeValue.substring(0,offset).match(/\.\s+$/)) {
+ DOM_deleteCharacters(node,offset-1,offset);
+ DOM_insertCharacters(node,offset-1,".");
+ }
+ }
+
+ if (isWhitespaceString(str) && (node.nodeType == Node.TEXT_NODE) && (offset > 0)) {
+ var prevChar = node.nodeValue.charAt(offset-1);
+ if (isWhitespaceString(prevChar) || (prevChar == nbsp)) {
+ Selection_update();
+ Cursor_ensureCursorVisible();
+ return;
+ }
+ }
+
+ nbspToSpace(pos);
+
+ // If the user enters two double quotes in succession (open and close), replace them with
+ // just one plain double quote character
+ if ((str == "”") && (node.nodeType == Node.TEXT_NODE) &&
+ (offset > 0) && (node.nodeValue.charAt(offset-1) == "“")) {
+ DOM_deleteCharacters(node,offset-1,offset);
+ offset--;
+ str = "\"";
+ }
+
+ if (node.nodeType == Node.ELEMENT_NODE) {
+ var emptyTextNode = DOM_createTextNode(document,"");
+ if (offset >= node.childNodes.length)
+ DOM_appendChild(node,emptyTextNode);
+ else
+ DOM_insertBefore(node,emptyTextNode,node.childNodes[offset]);
+ node = emptyTextNode;
+ offset = 0;
+ }
+
+ if (str == " ")
+ DOM_insertCharacters(node,offset,nbsp);
+ else
+ DOM_insertCharacters(node,offset,str);
+
+ // must be done *after* inserting the text
+ if (!allowNoParagraph) {
+ switch (node.parentNode._type) {
+ case HTML_CAPTION:
+ case HTML_FIGCAPTION:
+ // Do nothing
+ break;
+ default:
+ Hierarchy_ensureInlineNodesInParagraph(node,true);
+ break;
+ }
+ }
+
+ offset += str.length;
+
+ pos = new Position(node,offset);
+ Position_trackWhileExecuting([pos],function() {
+ Formatting_mergeWithNeighbours(pos.node,Formatting_MERGEABLE_INLINE);
+ });
+
+ Cursor_set(pos.node,pos.offset);
+ Range_trackWhileExecuting(Selection_get(),function() {
+ Cursor_updateBRAtEndOfParagraph(pos.node);
+ });
+
+ Selection_update();
+ Cursor_ensureCursorVisible();
+ }
+
+ function tryDeleteEmptyCaption(pos)
+ {
+ var caption = Position_captionAncestor(pos);
+ if ((caption == null) || nodeHasContent(caption))
+ return false;
+
+ var container = Position_figureOrTableAncestor(pos);
+ if (container == null)
+ return false;
+
+ Cursor_set(container.parentNode,DOM_nodeOffset(container)+1);
+ Selection_preserveWhileExecuting(function() {
+ DOM_deleteNode(caption);
+ });
+
+ return true;
+ }
+
+ function tryDeleteEmptyNote(pos)
+ {
+ var note = Position_noteAncestor(pos);
+ if ((note == null) || nodeHasContent(note))
+ return false;
+
+ var parent = note.parentNode;
+ Cursor_set(note.parentNode,DOM_nodeOffset(note)+1);
+ Selection_preserveWhileExecuting(function() {
+ DOM_deleteNode(note);
+ });
+
+ return true;
+ }
+
+ // public
+ Cursor_deleteCharacter = function()
+ {
+ if (UndoManager_groupType() != "Delete text")
+ UndoManager_newGroup("Delete text",checkNbsp);
+
+ Selection_preferElementPositions();
+ var selRange = Selection_get();
+ if (selRange == null)
+ return;
+
+ if (!Range_isEmpty(selRange)) {
+ Selection_deleteContents(true);
+ }
+ else {
+ var currentPos = selRange.start;
+
+ // Special cases of pressing backspace after a table, figure, TOC, hyperlink,
+ // footnote, or endnote. For each of these we delete the whole thing.
+ var back = Position_closestMatchBackwards(currentPos,Position_okForMovement);
+ if ((back != null) && (back.node.nodeType == Node.ELEMENT_NODE) && (back.offset > 0)) {
+ var prevNode = back.node.childNodes[back.offset-1];
+ if (isSpecialBlockNode(prevNode)) {
+ var p = DOM_createElement(document,"P");
+ DOM_insertBefore(prevNode.parentNode,p,prevNode);
+ DOM_deleteNode(prevNode);
+ Cursor_updateBRAtEndOfParagraph(p);
+ Cursor_set(p,0);
+ Cursor_ensureCursorVisible();
+ return;
+ }
+ if ((prevNode._type == HTML_A) || isNoteNode(prevNode)) {
+ Cursor_set(back.node,back.offset-1);
+ Selection_preserveWhileExecuting(function() {
+ DOM_deleteNode(prevNode);
+ });
+ return;
+ }
+ }
+
+ // Backspace inside an empty figure or table caption
+ if (tryDeleteEmptyCaption(currentPos))
+ return;
+
+ currentPos = Position_preferTextPosition(currentPos);
+ var prevPos = Position_prevMatch(currentPos,Position_okForMovement);
+
+ // Backspace inside or just after a footnote or endnote
+ if (tryDeleteEmptyNote(currentPos))
+ return;
+ if ((prevPos != null) && tryDeleteEmptyNote(prevPos))
+ return;
+
+ if (prevPos != null) {
+ var startBlock = firstBlockAncestor(Position_closestActualNode(prevPos));
+ var endBlock = firstBlockAncestor(Position_closestActualNode(selRange.end));
+ if ((startBlock != endBlock) &&
+ isParagraphNode(startBlock) && !nodeHasContent(startBlock)) {
+ DOM_deleteNode(startBlock);
+ Cursor_set(selRange.end.node,selRange.end.offset)
+ }
+ else {
+ var range = new Range(prevPos.node,prevPos.offset,
+ selRange.end.node,selRange.end.offset);
+ Selection_deleteRangeContents(range,true);
+ }
+ }
+ }
+
+ selRange = Selection_get();
+ if (selRange != null)
+ spaceToNbsp(selRange.end);
+ Selection_update();
+ Cursor_ensureCursorVisible();
+
+ function firstBlockAncestor(node)
+ {
+ while (isInlineNode(node))
+ node = node.parentNode;
+ return node;
+ }
+ }
+
+ // public
+ Cursor_enterPressed = function()
+ {
+ UndoManager_newGroup("New paragraph");
+
+ Selection_preferElementPositions();
+ var selRange = Selection_get();
+ if (selRange == null)
+ return;
+
+ Range_trackWhileExecuting(selRange,function() {
+ if (!Range_isEmpty(selRange))
+ Selection_deleteContents(true);
+ });
+
+ // Are we inside a figure or table caption? If so, put an empty paragraph directly after it
+ var inCaption = false;
+ var inFigCaption = false;
+ var closestNode = Position_closestActualNode(selRange.start);
+ for (var ancestor = closestNode; ancestor != null; ancestor = ancestor.parentNode) {
+ switch (ancestor._type) {
+ case HTML_CAPTION:
+ inCaption = true;
+ break;
+ case HTML_FIGCAPTION:
+ inFigCaption = true;
+ break;
+ case HTML_TABLE:
+ case HTML_FIGURE:
+ if ((inCaption && (ancestor._type == HTML_TABLE)) ||
+ (inFigCaption && (ancestor._type == HTML_FIGURE))) {
+ var p = DOM_createElement(document,"P");
+ DOM_insertBefore(ancestor.parentNode,p,ancestor.nextSibling);
+ Cursor_updateBRAtEndOfParagraph(p);
+ Selection_set(p,0,p,0);
+ return;
+ }
+ break;
+ }
+ }
+
+ // Are we inside a footnote or endnote? If so, move the cursor immediately after it
+ var note = null;
+ if (selRange.start.node.nodeType == Node.TEXT_NODE) {
+ note = Position_noteAncestor(selRange.start);
+ }
+ else {
+ // We can't use Position_noteAncestor in this case, because we want to to break
+ // the paragraph *before* the note, not after
+ var checkNode = selRange.start.node;
+ for (var anc = checkNode; anc != null; anc = anc.parentNode) {
+ if (isNoteNode(anc)) {
+ note = anc;
+ break;
+ }
+ }
+ }
+ if (note != null) {
+ var noteOffset = DOM_nodeOffset(note);
+ selRange = new Range(note.parentNode,noteOffset+1,note.parentNode,noteOffset+1);
+ }
+
+ var check = Position_preferElementPosition(selRange.start);
+ if (check.node.nodeType == Node.ELEMENT_NODE) {
+ var before = check.node.childNodes[check.offset-1];
+ var after = check.node.childNodes[check.offset];
+ if (((before != null) && isSpecialBlockNode(before)) ||
+ ((after != null) && isSpecialBlockNode(after))) {
+ var p = DOM_createElement(document,"P");
+ DOM_insertBefore(check.node,p,check.node.childNodes[check.offset]);
+ Cursor_updateBRAtEndOfParagraph(p);
+ Cursor_set(p,0);
+ Cursor_ensureCursorVisible();
+ return;
+ }
+ }
+
+ Range_trackWhileExecuting(selRange,function() {
+ Range_ensureInlineNodesInParagraph(selRange);
+ Range_ensureValidHierarchy(selRange);
+ });
+
+ var pos = selRange.start;
+
+ var detail = Range_detail(selRange);
+ switch (detail.startParent._type) {
+ case HTML_OL:
+ case HTML_UL: {
+ var li = DOM_createElement(document,"LI");
+ DOM_insertBefore(detail.startParent,li,detail.startChild);
+
+ Cursor_set(li,0);
+ Cursor_ensureCursorVisible();
+ return;
+ }
+ }
+
+ if (isAutoCorrectNode(pos.node)) {
+ pos = Position_preferTextPosition(pos);
+ selRange.start = selRange.end = pos;
+ }
+
+ Range_trackWhileExecuting(selRange,function() {
+
+ // If we're directly in a container node, add a paragraph, so we have something to
+ // split.
+ if (isContainerNode(pos.node) && (pos.node._type != HTML_LI)) {
+ var p = DOM_createElement(document,"P");
+ DOM_insertBefore(pos.node,p,pos.node.childNodes[pos.offset]);
+ pos = new Position(p,0);
+ }
+
+ var blockToSplit = getBlockToSplit(pos);
+ var stopAt = blockToSplit.parentNode;
+
+ if (positionAtStartOfHeading(pos)) {
+ var container = getContainerOrParagraph(pos.node);
+ pos = new Position(container,0);
+ pos = Formatting_movePreceding(pos,function(n) { return (n == stopAt); },true);
+ }
+ else if (pos.node.nodeType == Node.TEXT_NODE) {
+ pos = Formatting_splitTextAfter(pos,function(n) { return (n == stopAt); },true);
+ }
+ else {
+ pos = Formatting_moveFollowing(pos,function(n) { return (n == stopAt); },true);
+ }
+ });
+
+ Cursor_set(pos.node,pos.offset);
+ selRange = Selection_get();
+
+ Range_trackWhileExecuting(selRange,function() {
+ if ((pos.node.nodeType == Node.TEXT_NODE) && (pos.node.nodeValue.length == 0)) {
+ DOM_deleteNode(pos.node);
+ }
+
+ var detail = Range_detail(selRange);
+
+ // If a preceding paragraph has become empty as a result of enter being pressed
+ // while the cursor was in it, then update the BR at the end of the paragraph
+ var start = detail.startChild ? detail.startChild : detail.startParent;
+ for (var ancestor = start; ancestor != null; ancestor = ancestor.parentNode) {
+ var prev = ancestor.previousSibling;
+ if ((prev != null) && isParagraphNode(prev) && !nodeHasContent(prev)) {
+ DOM_deleteAllChildren(prev);
+ Cursor_updateBRAtEndOfParagraph(prev);
+ break;
+ }
+ else if ((prev != null) && (prev._type == HTML_LI) && !nodeHasContent(prev)) {
+ var next;
+ for (var child = prev.firstChild; child != null; child = next) {
+ next = child.nextSibling;
+ if (isWhitespaceTextNode(child))
+ DOM_deleteNode(child);
+ else
+ Cursor_updateBRAtEndOfParagraph(child);
+ }
+ break;
+ }
+ }
+
+ for (var ancestor = start; ancestor != null; ancestor = ancestor.parentNode) {
+
+ if (isParagraphNode(ancestor)) {
+ var nextSelector = Styles_nextSelectorAfter(ancestor);
+ if (nextSelector != null) {
+ var nextElementName = null;
+ var nextClassName = null;
+
+
+ var dotIndex = nextSelector.indexOf(".");
+ if (dotIndex >= 0) {
+ nextElementName = nextSelector.substring(0,dotIndex);
+ nextClassName = nextSelector.substring(dotIndex+1);
+ }
+ else {
+ nextElementName = nextSelector;
+ }
+
+ ancestor = DOM_replaceElement(ancestor,nextElementName);
+ DOM_removeAttribute(ancestor,"id");
+ DOM_setAttribute(ancestor,"class",nextClassName);
+ }
+ }
+
+ if (isParagraphNode(ancestor) && !nodeHasContent(ancestor)) {
+ Cursor_updateBRAtEndOfParagraph(prev);
+ break;
+ }
+ else if ((ancestor._type == HTML_LI) && !nodeHasContent(ancestor)) {
+ DOM_deleteAllChildren(ancestor);
+ break;
+ }
+ }
+
+ Cursor_updateBRAtEndOfParagraph(Range_singleNode(selRange));
+ });
+
+ Selection_set(selRange.start.node,selRange.start.offset,
+ selRange.end.node,selRange.end.offset);
+ cursorX = null;
+ Cursor_ensureCursorVisible();
+
+ function getBlockToSplit(pos)
+ {
+ var blockToSplit = null;
+ for (var n = pos.node; n != null; n = n.parentNode) {
+ if (n._type == HTML_LI) {
+ blockToSplit = n;
+ break;
+ }
+ }
+ if (blockToSplit == null) {
+ blockToSplit = pos.node;
+ while (isInlineNode(blockToSplit))
+ blockToSplit = blockToSplit.parentNode;
+ }
+ return blockToSplit;
+ }
+
+ function getContainerOrParagraph(node)
+ {
+ while ((node != null) && isInlineNode(node))
+ node = node.parentNode;
+ return node;
+ }
+
+ function positionAtStartOfHeading(pos)
+ {
+ var container = getContainerOrParagraph(pos.node);
+ if (isHeadingNode(container)) {
+ var startOffset = 0;
+ if (isOpaqueNode(container.firstChild))
+ startOffset = 1;
+ var range = new Range(container,startOffset,pos.node,pos.offset);
+ return !Range_hasContent(range);
+ }
+ else
+ return false;
+ }
+ }
+
+ Cursor_getPrecedingWord = function() {
+ var selRange = Selection_get();
+ if ((selRange == null) && !Range_isEmpty(selRange))
+ return "";
+
+ var node = selRange.start.node;
+ var offset = selRange.start.offset;
+ if (node.nodeType != Node.TEXT_NODE)
+ return "";
+
+ return node.nodeValue.substring(0,offset);
+ }
+
+ Cursor_getAdjacentNodeWithType = function(type)
+ {
+ var selRange = Selection_get();
+ var pos = Position_preferElementPosition(selRange.start);
+ var node = pos.node;
+ var offset = pos.offset;
+
+ while (true) {
+
+ if (node._type == type)
+ return node;
+
+ if (node.nodeType == Node.ELEMENT_NODE) {
+ var before = node.childNodes[offset-1];
+ if ((before != null) && (before._type == type))
+ return before;
+
+ var after = node.childNodes[offset];
+ if ((after != null) && (after._type == type))
+ return after;
+ }
+
+ if (node.parentNode == null)
+ return null;
+
+ offset = DOM_nodeOffset(node);
+ node = node.parentNode;
+ }
+ }
+
+ Cursor_getLinkProperties = function()
+ {
+ var a = Cursor_getAdjacentNodeWithType(HTML_A);
+ if (a == null)
+ return null;
+
+ return { href: a.getAttribute("href"),
+ text: getNodeText(a) };
+ }
+
+ Cursor_setLinkProperties = function(properties)
+ {
+ var a = Cursor_getAdjacentNodeWithType(HTML_A);
+ if (a == null)
+ return null;
+
+ Selection_preserveWhileExecuting(function() {
+ DOM_setAttribute(a,"href",properties.href);
+ DOM_deleteAllChildren(a);
+ DOM_appendChild(a,DOM_createTextNode(document,properties.text));
+ });
+ }
+
+ Cursor_setReferenceTarget = function(itemId)
+ {
+ var a = Cursor_getAdjacentNodeWithType(HTML_A);
+ if (a != null)
+ Outline_setReferenceTarget(a,itemId);
+ }
+
+ // Deletes the current selection contents and ensures that the cursor is located directly
+ // inside the nearest container element, i.e. not inside a paragraph or inline node. This
+ // is intended for preventing things like inserting a table of contants inside a heading
+ Cursor_makeContainerInsertionPoint = function()
+ {
+ var selRange = Selection_get();
+ if (selRange == null)
+ return;
+
+ if (!Range_isEmpty(selRange)) {
+ Selection_deleteContents();
+ selRange = Selection_get();
+ }
+
+ var parent;
+ var previousSibling;
+ var nextSibling;
+
+ if (selRange.start.node.nodeType == Node.ELEMENT_NODE) {
+ parent = selRange.start.node;
+ nextSibling = selRange.start.node.childNodes[selRange.start.offset];
+ }
+ else {
+ if (selRange.start.offset > 0)
+ Formatting_splitTextBefore(selRange.start);
+ parent = selRange.start.node.parentNode;
+ nextSibling = selRange.start.node;
+ }
+
+ var offset = DOM_nodeOffset(nextSibling,parent);
+
+ if (isContainerNode(parent)) {
+ Cursor_set(parent,offset);
+ return;
+ }
+
+ if ((offset > 0) && isItemNumber(parent.childNodes[offset-1]))
+ offset--;
+
+ Formatting_moveFollowing(new Position(parent,offset),isContainerNode);
+ Formatting_movePreceding(new Position(parent,offset),isContainerNode);
+
+ offset = 0;
+ while (!isContainerNode(parent)) {
+ var old = parent;
+ offset = DOM_nodeOffset(parent);
+ parent = parent.parentNode;
+ DOM_deleteNode(old);
+ }
+
+ Cursor_set(parent,offset);
+ cursorX = null;
+ }
+
+ Cursor_set = function(node,offset,keepCursorX)
+ {
+ Selection_set(node,offset,node,offset);
+ if (!keepCursorX)
+ cursorX = null;
+ }
+
+ function moveRangeOutsideOfNote(range)
+ {
+ var node = range.start.node;
+ var offset = range.start.offset;
+
+ for (var anc = node; anc != null; anc = anc.parentNode) {
+ if (isNoteNode(anc) && (anc.parentNode != null)) {
+ node = anc.parentNode;
+ offset = DOM_nodeOffset(anc)+1;
+ return new Range(node,offset,node,offset);
+ }
+ }
+
+ return range;
+ }
+
+ function insertNote(className,content)
+ {
+ var footnote = DOM_createElement(document,"span");
+ DOM_setAttribute(footnote,"class",className);
+ DOM_appendChild(footnote,DOM_createTextNode(document,content));
+
+ var range = Selection_get();
+ range = moveRangeOutsideOfNote(range);
+ Formatting_splitAroundSelection(range,false);
+
+ // If we're part-way through a text node, splitAroundSelection will give us an
+ // empty text node between the before and after text. For formatting purposes that's
+ // fine (not sure if necessary), but when inserting a footnote or endnote we want
+ // to avoid this as it causes problems with cursor movement - specifically, the cursor
+ // is allowed to go inside the empty text node, and this doesn't show up in the correct
+ // position on screen.
+ var pos = range.start;
+ if ((pos.node._type == HTML_TEXT) &&
+ (pos.node.nodeValue.length == 0)) {
+ var empty = pos.node;
+ pos = new Position(empty.parentNode,DOM_nodeOffset(empty));
+ DOM_deleteNode(empty);
+ }
+ else {
+ pos = Position_preferElementPosition(pos);
+ }
+
+ DOM_insertBefore(pos.node,footnote,pos.node.childNodes[pos.offset]);
+ Selection_set(footnote,0,footnote,footnote.childNodes.length);
+ Cursor_updateBRAtEndOfParagraph(footnote);
+ }
+
+ Cursor_insertFootnote = function(content)
+ {
+ insertNote("footnote",content);
+ }
+
+ Cursor_insertEndnote = function(content)
+ {
+ insertNote("endnote",content);
+ }
+
+})();