You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@shindig.apache.org by li...@apache.org on 2010/10/07 17:54:40 UTC
svn commit: r1005506 [3/45] - in /shindig/branches/2.0.x: ./ content/editor/
content/editor/CodeMirror-0.8/ content/editor/CodeMirror-0.8/css/
content/editor/CodeMirror-0.8/js/ content/samplecontainer/examples/media/
etc/ etc/checkstyle/ extras/src/mai...
Added: shindig/branches/2.0.x/content/editor/CodeMirror-0.8/js/mirrorframe.js
URL: http://svn.apache.org/viewvc/shindig/branches/2.0.x/content/editor/CodeMirror-0.8/js/mirrorframe.js?rev=1005506&view=auto
==============================================================================
--- shindig/branches/2.0.x/content/editor/CodeMirror-0.8/js/mirrorframe.js (added)
+++ shindig/branches/2.0.x/content/editor/CodeMirror-0.8/js/mirrorframe.js Thu Oct 7 15:54:09 2010
@@ -0,0 +1,81 @@
+/* Demonstration of embedding CodeMirror in a bigger application. The
+ * interface defined here is a mess of prompts and confirms, and
+ * should probably not be used in a real project.
+ */
+
+function MirrorFrame(place, options) {
+ this.home = document.createElement("DIV");
+ if (place.appendChild)
+ place.appendChild(this.home);
+ else
+ place(this.home);
+
+ var self = this;
+ function makeButton(name, action) {
+ var button = document.createElement("INPUT");
+ button.type = "button";
+ button.value = name;
+ self.home.appendChild(button);
+ button.onclick = function(){self[action].call(self);};
+ }
+
+ makeButton("Search", "search");
+ makeButton("Replace", "replace");
+ makeButton("Current line", "line");
+ makeButton("Jump to line", "jump");
+ makeButton("Insert constructor", "macro");
+ makeButton("Indent all", "reindent");
+
+ this.mirror = new CodeMirror(this.home, options);
+}
+
+MirrorFrame.prototype = {
+ search: function() {
+ var text = prompt("Enter search term:", "");
+ if (!text) return;
+
+ var first = true;
+ do {
+ var cursor = this.mirror.getSearchCursor(text, first);
+ first = false;
+ while (cursor.findNext()) {
+ cursor.select();
+ if (!confirm("Search again?"))
+ return;
+ }
+ } while (confirm("End of document reached. Start over?"));
+ },
+
+ replace: function() {
+ // This is a replace-all, but it is possible to implement a
+ // prompting replace.
+ var from = prompt("Enter search string:", ""), to;
+ if (from) to = prompt("What should it be replaced with?", "");
+ if (to == null) return;
+
+ var cursor = this.mirror.getSearchCursor(from, false);
+ while (cursor.findNext())
+ cursor.replace(to);
+ },
+
+ jump: function() {
+ var line = prompt("Jump to line:", "");
+ if (line && !isNaN(Number(line)))
+ this.mirror.jumpToLine(Number(line));
+ },
+
+ line: function() {
+ alert("The cursor is currently at line " + this.mirror.currentLine());
+ this.mirror.focus();
+ },
+
+ macro: function() {
+ var name = prompt("Name your constructor:", "");
+ if (name)
+ this.mirror.replaceSelection("function " + name + "() {\n \n}\n\n" + name + ".prototype = {\n \n};\n");
+ },
+
+ reindent: function() {
+ this.mirror.reindent();
+ }
+};
Added: shindig/branches/2.0.x/content/editor/CodeMirror-0.8/js/parsexml.js
URL: http://svn.apache.org/viewvc/shindig/branches/2.0.x/content/editor/CodeMirror-0.8/js/parsexml.js?rev=1005506&view=auto
==============================================================================
--- shindig/branches/2.0.x/content/editor/CodeMirror-0.8/js/parsexml.js (added)
+++ shindig/branches/2.0.x/content/editor/CodeMirror-0.8/js/parsexml.js Thu Oct 7 15:54:09 2010
@@ -0,0 +1,286 @@
+/* This file defines an XML parser, with a few kludges to make it
+ * useable for HTML. autoSelfClosers defines a set of tag names that
+ * are expected to not have a closing tag, and doNotIndent specifies
+ * the tags inside of which no indentation should happen (see Config
+ * object). These can be disabled by passing the editor an object like
+ * {useHTMLKludges: false} as parserConfig option.
+ */
+
+var XMLParser = Editor.Parser = (function() {
+ var Kludges = {
+ autoSelfClosers: {"br": true, "img": true, "hr": true, "link": true, "input": true,
+ "meta": true, "col": true, "frame": true, "base": true, "area": true},
+ doNotIndent: {"pre": true, "!cdata": true}
+ };
+ var NoKludges = {autoSelfClosers: {}, doNotIndent: {"!cdata": true}};
+ var UseKludges = Kludges;
+ var alignCDATA = false;
+
+ // Simple stateful tokenizer for XML documents. Returns a
+ // MochiKit-style iterator, with a state property that contains a
+ // function encapsulating the current state. See tokenize.js.
+ var tokenizeXML = (function() {
+ function inText(source, setState) {
+ var ch = source.next();
+ if (ch == "<") {
+ if (source.equals("!")) {
+ source.next();
+ if (source.equals("[")) {
+ if (source.lookAhead("[CDATA[", true)) {
+ setState(inBlock("xml-cdata", "]]>"));
+ return null;
+ }
+ else {
+ return "xml-text";
+ }
+ }
+ else if (source.lookAhead("--", true)) {
+ setState(inBlock("xml-comment", "-->"));
+ return null;
+ }
+ else {
+ return "xml-text";
+ }
+ }
+ else if (source.equals("?")) {
+ source.next();
+ source.nextWhileMatches(/[\w\._\-]/);
+ setState(inBlock("xml-processing", "?>"));
+ return "xml-processing";
+ }
+ else {
+ if (source.equals("/")) source.next();
+ setState(inTag);
+ return "xml-punctuation";
+ }
+ }
+ else if (ch == "&") {
+ while (!source.endOfLine()) {
+ if (source.next() == ";")
+ break;
+ }
+ return "xml-entity";
+ }
+ else {
+ source.nextWhileMatches(/[^&<\n]/);
+ return "xml-text";
+ }
+ }
+
+ function inTag(source, setState) {
+ var ch = source.next();
+ if (ch == ">") {
+ setState(inText);
+ return "xml-punctuation";
+ }
+ else if (/[?\/]/.test(ch) && source.equals(">")) {
+ source.next();
+ setState(inText);
+ return "xml-punctuation";
+ }
+ else if (ch == "=") {
+ return "xml-punctuation";
+ }
+ else if (/[\'\"]/.test(ch)) {
+ setState(inAttribute(ch));
+ return null;
+ }
+ else {
+ source.nextWhileMatches(/[^\s\u00a0=<>\"\'\/?]/);
+ return "xml-name";
+ }
+ }
+
+ function inAttribute(quote) {
+ return function(source, setState) {
+ while (!source.endOfLine()) {
+ if (source.next() == quote) {
+ setState(inTag);
+ break;
+ }
+ }
+ return "xml-attribute";
+ };
+ }
+
+ function inBlock(style, terminator) {
+ return function(source, setState) {
+ while (!source.endOfLine()) {
+ if (source.lookAhead(terminator, true)) {
+ setState(inText);
+ break;
+ }
+ source.next();
+ }
+ return style;
+ };
+ }
+
+ return function(source, startState) {
+ return tokenizer(source, startState || inText);
+ };
+ })();
+
+ // The parser. The structure of this function largely follows that of
+ // parseJavaScript in parsejavascript.js (there is actually a bit more
+ // shared code than I'd like), but it is quite a bit simpler.
+ function parseXML(source) {
+ var tokens = tokenizeXML(source), token;
+ var cc = [base];
+ var tokenNr = 0, indented = 0;
+ var currentTag = null, context = null;
+ var consume;
+
+ function push(fs) {
+ for (var i = fs.length - 1; i >= 0; i--)
+ cc.push(fs[i]);
+ }
+ function cont() {
+ push(arguments);
+ consume = true;
+ }
+ function pass() {
+ push(arguments);
+ consume = false;
+ }
+
+ function markErr() {
+ token.style += " xml-error";
+ }
+ function expect(text) {
+ return function(style, content) {
+ if (content == text) cont();
+ else {markErr(); cont(arguments.callee);}
+ };
+ }
+
+ function pushContext(tagname, startOfLine) {
+ var noIndent = UseKludges.doNotIndent.hasOwnProperty(tagname) || (context && context.noIndent);
+ context = {prev: context, name: tagname, indent: indented, startOfLine: startOfLine, noIndent: noIndent};
+ }
+ function popContext() {
+ context = context.prev;
+ }
+ function computeIndentation(baseContext) {
+ return function(nextChars, current) {
+ var context = baseContext;
+ if (context && context.noIndent)
+ return current;
+ if (alignCDATA && /<!\[CDATA\[/.test(nextChars))
+ return 0;
+ if (context && /^<\//.test(nextChars))
+ context = context.prev;
+ while (context && !context.startOfLine)
+ context = context.prev;
+ if (context)
+ return context.indent + indentUnit;
+ else
+ return 0;
+ };
+ }
+
+ function base() {
+ return pass(element, base);
+ }
+ var harmlessTokens = {"xml-text": true, "xml-entity": true, "xml-comment": true, "xml-processing": true};
+ function element(style, content) {
+ if (content == "<") cont(tagname, attributes, endtag(tokenNr == 1));
+ else if (content == "</") cont(closetagname, expect(">"));
+ else if (style == "xml-cdata") {
+ if (!context || context.name != "!cdata") pushContext("!cdata");
+ if (/\]\]>$/.test(content)) popContext();
+ cont();
+ }
+ else if (harmlessTokens.hasOwnProperty(style)) cont();
+ else {markErr(); cont();}
+ }
+ function tagname(style, content) {
+ if (style == "xml-name") {
+ currentTag = content.toLowerCase();
+ token.style = "xml-tagname";
+ cont();
+ }
+ else {
+ currentTag = null;
+ pass();
+ }
+ }
+ function closetagname(style, content) {
+ if (style == "xml-name") {
+ token.style = "xml-tagname";
+ if (context && content.toLowerCase() == context.name) popContext();
+ else markErr();
+ }
+ cont();
+ }
+ function endtag(startOfLine) {
+ return function(style, content) {
+ if (content == "/>" || (content == ">" && UseKludges.autoSelfClosers.hasOwnProperty(currentTag))) cont();
+ else if (content == ">") {pushContext(currentTag, startOfLine); cont();}
+ else {markErr(); cont(arguments.callee);}
+ };
+ }
+ function attributes(style) {
+ if (style == "xml-name") {token.style = "xml-attname"; cont(attribute, attributes);}
+ else pass();
+ }
+ function attribute(style, content) {
+ if (content == "=") cont(value);
+ else if (content == ">" || content == "/>") pass(endtag);
+ else pass();
+ }
+ function value(style) {
+ if (style == "xml-attribute") cont(value);
+ else pass();
+ }
+
+ return {
+ indentation: function() {return indented;},
+
+ next: function(){
+ token = tokens.next();
+ if (token.style == "whitespace" && tokenNr == 0)
+ indented = token.value.length;
+ else
+ tokenNr++;
+ if (token.content == "\n") {
+ indented = tokenNr = 0;
+ token.indentation = computeIndentation(context);
+ }
+
+ if (token.style == "whitespace" || token.type == "xml-comment")
+ return token;
+
+ while(true){
+ consume = false;
+ cc.pop()(token.style, token.content);
+ if (consume) return token;
+ }
+ },
+
+ copy: function(){
+ var _cc = cc.concat([]), _tokenState = tokens.state, _context = context;
+ var parser = this;
+
+ return function(input){
+ cc = _cc.concat([]);
+ tokenNr = indented = 0;
+ context = _context;
+ tokens = tokenizeXML(input, _tokenState);
+ return parser;
+ };
+ }
+ };
+ }
+
+ return {
+ make: parseXML,
+ electricChars: "/",
+ configure: function(config) {
+ if (config.useHTMLKludges != null)
+ UseKludges = config.useHTMLKludges ? Kludges : NoKludges;
+ if (config.alignCDATA)
+ alignCDATA = config.alignCDATA;
+ }
+ };
+})();
Added: shindig/branches/2.0.x/content/editor/CodeMirror-0.8/js/select.js
URL: http://svn.apache.org/viewvc/shindig/branches/2.0.x/content/editor/CodeMirror-0.8/js/select.js?rev=1005506&view=auto
==============================================================================
--- shindig/branches/2.0.x/content/editor/CodeMirror-0.8/js/select.js (added)
+++ shindig/branches/2.0.x/content/editor/CodeMirror-0.8/js/select.js Thu Oct 7 15:54:09 2010
@@ -0,0 +1,672 @@
+/* Functionality for finding, storing, and restoring selections
+ *
+ * This does not provide a generic API, just the minimal functionality
+ * required by the CodeMirror system.
+ */
+
+// Namespace object.
+var select = {};
+
+(function() {
+ select.ie_selection = document.selection && document.selection.createRangeCollection;
+
+ // Find the 'top-level' (defined as 'a direct child of the node
+ // passed as the top argument') node that the given node is
+ // contained in. Return null if the given node is not inside the top
+ // node.
+ function topLevelNodeAt(node, top) {
+ while (node && node.parentNode != top)
+ node = node.parentNode;
+ return node;
+ }
+
+ // Find the top-level node that contains the node before this one.
+ function topLevelNodeBefore(node, top) {
+ while (!node.previousSibling && node.parentNode != top)
+ node = node.parentNode;
+ return topLevelNodeAt(node.previousSibling, top);
+ }
+
+ var fourSpaces = "\u00a0\u00a0\u00a0\u00a0";
+
+ select.scrollToNode = function(node, cursor) {
+ if (!node) return;
+ var element = node,
+ doc = element.ownerDocument, body = doc.body,
+ win = (doc.defaultView || doc.parentWindow),
+ html = doc.documentElement,
+ atEnd = !element.nextSibling || !element.nextSibling.nextSibling
+ || !element.nextSibling.nextSibling.nextSibling;
+ // In Opera (and recent Webkit versions), BR elements *always*
+ // have a offsetTop property of zero.
+ var compensateHack = 0;
+ while (element && !element.offsetTop) {
+ compensateHack++;
+ element = element.previousSibling;
+ }
+ // atEnd is another kludge for these browsers -- if the cursor is
+ // at the end of the document, and the node doesn't have an
+ // offset, just scroll to the end.
+ if (compensateHack == 0) atEnd = false;
+
+ // WebKit has a bad habit of (sometimes) happily returning bogus
+ // offsets when the document has just been changed. This seems to
+ // always be 5/5, so we don't use those.
+ if (webkit && element && element.offsetTop == 5 && element.offsetLeft == 5)
+ return;
+
+ var y = compensateHack * (element ? element.offsetHeight : 0), x = 0,
+ width = (node ? node.offsetWidth : 0), pos = element;
+ while (pos && pos.offsetParent) {
+ y += pos.offsetTop;
+ // Don't count X offset for <br> nodes
+ if (!isBR(pos))
+ x += pos.offsetLeft;
+ pos = pos.offsetParent;
+ }
+
+ var scroll_x = body.scrollLeft || html.scrollLeft || 0,
+ scroll_y = body.scrollTop || html.scrollTop || 0,
+ scroll = false, screen_width = win.innerWidth || html.clientWidth || 0;
+
+ if (cursor || width < screen_width) {
+ if (cursor) {
+ var off = select.offsetInNode(win, node), size = nodeText(node).length;
+ if (size) x += width * (off / size);
+ }
+ var screen_x = x - scroll_x;
+ if (screen_x < 0 || screen_x > screen_width) {
+ scroll_x = x;
+ scroll = true;
+ }
+ }
+ var screen_y = y - scroll_y;
+ if (screen_y < 0 || atEnd || screen_y > (win.innerHeight || html.clientHeight || 0) - 50) {
+ scroll_y = atEnd ? 1e6 : y;
+ scroll = true;
+ }
+ if (scroll) win.scrollTo(scroll_x, scroll_y);
+ };
+
+ select.scrollToCursor = function(container) {
+ select.scrollToNode(select.selectionTopNode(container, true) || container.firstChild, true);
+ };
+
+ // Used to prevent restoring a selection when we do not need to.
+ var currentSelection = null;
+
+ select.snapshotChanged = function() {
+ if (currentSelection) currentSelection.changed = true;
+ };
+
+ // This is called by the code in editor.js whenever it is replacing
+ // a text node. The function sees whether the given oldNode is part
+ // of the current selection, and updates this selection if it is.
+ // Because nodes are often only partially replaced, the length of
+ // the part that gets replaced has to be taken into account -- the
+ // selection might stay in the oldNode if the newNode is smaller
+ // than the selection's offset. The offset argument is needed in
+ // case the selection does move to the new object, and the given
+ // length is not the whole length of the new node (part of it might
+ // have been used to replace another node).
+ select.snapshotReplaceNode = function(from, to, length, offset) {
+ if (!currentSelection) return;
+
+ function replace(point) {
+ if (from == point.node) {
+ currentSelection.changed = true;
+ if (length && point.offset > length) {
+ point.offset -= length;
+ }
+ else {
+ point.node = to;
+ point.offset += (offset || 0);
+ }
+ }
+ }
+ replace(currentSelection.start);
+ replace(currentSelection.end);
+ };
+
+ select.snapshotMove = function(from, to, distance, relative, ifAtStart) {
+ if (!currentSelection) return;
+
+ function move(point) {
+ if (from == point.node && (!ifAtStart || point.offset == 0)) {
+ currentSelection.changed = true;
+ point.node = to;
+ if (relative) point.offset = Math.max(0, point.offset + distance);
+ else point.offset = distance;
+ }
+ }
+ move(currentSelection.start);
+ move(currentSelection.end);
+ };
+
+ // Most functions are defined in two ways, one for the IE selection
+ // model, one for the W3C one.
+ if (select.ie_selection) {
+ function selectionNode(win, start) {
+ var range = win.document.selection.createRange();
+ range.collapse(start);
+
+ function nodeAfter(node) {
+ var found = null;
+ while (!found && node) {
+ found = node.nextSibling;
+ node = node.parentNode;
+ }
+ return nodeAtStartOf(found);
+ }
+
+ function nodeAtStartOf(node) {
+ while (node && node.firstChild) node = node.firstChild;
+ return {node: node, offset: 0};
+ }
+
+ var containing = range.parentElement();
+ if (!isAncestor(win.document.body, containing)) return null;
+ if (!containing.firstChild) return nodeAtStartOf(containing);
+
+ var working = range.duplicate();
+ working.moveToElementText(containing);
+ working.collapse(true);
+ for (var cur = containing.firstChild; cur; cur = cur.nextSibling) {
+ if (cur.nodeType == 3) {
+ var size = cur.nodeValue.length;
+ working.move("character", size);
+ }
+ else {
+ working.moveToElementText(cur);
+ working.collapse(false);
+ }
+
+ var dir = range.compareEndPoints("StartToStart", working);
+ if (dir == 0) return nodeAfter(cur);
+ if (dir == 1) continue;
+ if (cur.nodeType != 3) return nodeAtStartOf(cur);
+
+ working.setEndPoint("StartToEnd", range);
+ return {node: cur, offset: size - working.text.length};
+ }
+ return nodeAfter(containing);
+ }
+
+ select.markSelection = function(win) {
+ currentSelection = null;
+ var sel = win.document.selection;
+ if (!sel) return;
+ var start = selectionNode(win, true),
+ end = selectionNode(win, false);
+ if (!start || !end) return;
+ currentSelection = {start: start, end: end, window: win, changed: false};
+ };
+
+ select.selectMarked = function() {
+ if (!currentSelection || !currentSelection.changed) return;
+ var win = currentSelection.window, doc = win.document;
+
+ function makeRange(point) {
+ var range = doc.body.createTextRange(),
+ node = point.node;
+ if (!node) {
+ range.moveToElementText(currentSelection.window.document.body);
+ range.collapse(false);
+ }
+ else if (node.nodeType == 3) {
+ range.moveToElementText(node.parentNode);
+ var offset = point.offset;
+ while (node.previousSibling) {
+ node = node.previousSibling;
+ offset += (node.innerText || "").length;
+ }
+ range.move("character", offset);
+ }
+ else {
+ range.moveToElementText(node);
+ range.collapse(true);
+ }
+ return range;
+ }
+
+ var start = makeRange(currentSelection.start), end = makeRange(currentSelection.end);
+ start.setEndPoint("StartToEnd", end);
+ start.select();
+ };
+
+ select.offsetInNode = function(win, node) {
+ var sel = win.document.selection;
+ if (!sel) return 0;
+ var range = sel.createRange(), range2 = range.duplicate();
+ try {range2.moveToElementText(node);} catch(e){return 0;}
+ range.setEndPoint("StartToStart", range2);
+ return range.text.length;
+ };
+
+ // Get the top-level node that one end of the cursor is inside or
+ // after. Note that this returns false for 'no cursor', and null
+ // for 'start of document'.
+ select.selectionTopNode = function(container, start) {
+ var selection = container.ownerDocument.selection;
+ if (!selection) return false;
+
+ var range = selection.createRange(), range2 = range.duplicate();
+ range.collapse(start);
+ var around = range.parentElement();
+ if (around && isAncestor(container, around)) {
+ // Only use this node if the selection is not at its start.
+ range2.moveToElementText(around);
+ if (range.compareEndPoints("StartToStart", range2) == 1)
+ return topLevelNodeAt(around, container);
+ }
+
+ // Move the start of a range to the start of a node,
+ // compensating for the fact that you can't call
+ // moveToElementText with text nodes.
+ function moveToNodeStart(range, node) {
+ if (node.nodeType == 3) {
+ var count = 0, cur = node.previousSibling;
+ while (cur && cur.nodeType == 3) {
+ count += cur.nodeValue.length;
+ cur = cur.previousSibling;
+ }
+ if (cur) {
+ try{range.moveToElementText(cur);}
+ catch(e){return false;}
+ range.collapse(false);
+ }
+ else range.moveToElementText(node.parentNode);
+ if (count) range.move("character", count);
+ }
+ else {
+ try{range.moveToElementText(node);}
+ catch(e){return false;}
+ }
+ return true;
+ }
+
+ // Do a binary search through the container object, comparing
+ // the start of each node to the selection
+ var start = 0, end = container.childNodes.length - 1;
+ while (start < end) {
+ var middle = Math.ceil((end + start) / 2), node = container.childNodes[middle];
+ if (!node) return false; // Don't ask. IE6 manages this sometimes.
+ if (!moveToNodeStart(range2, node)) return false;
+ if (range.compareEndPoints("StartToStart", range2) == 1)
+ start = middle;
+ else
+ end = middle - 1;
+ }
+ return container.childNodes[start] || null;
+ };
+
+ // Place the cursor after this.start. This is only useful when
+ // manually moving the cursor instead of restoring it to its old
+ // position.
+ select.focusAfterNode = function(node, container) {
+ var range = container.ownerDocument.body.createTextRange();
+ range.moveToElementText(node || container);
+ range.collapse(!node);
+ range.select();
+ };
+
+ select.somethingSelected = function(win) {
+ var sel = win.document.selection;
+ return sel && (sel.createRange().text != "");
+ };
+
+ function insertAtCursor(window, html) {
+ var selection = window.document.selection;
+ if (selection) {
+ var range = selection.createRange();
+ range.pasteHTML(html);
+ range.collapse(false);
+ range.select();
+ }
+ }
+
+ // Used to normalize the effect of the enter key, since browsers
+ // do widely different things when pressing enter in designMode.
+ select.insertNewlineAtCursor = function(window) {
+ insertAtCursor(window, "<br>");
+ };
+
+ select.insertTabAtCursor = function(window) {
+ insertAtCursor(window, fourSpaces);
+ };
+
+ // Get the BR node at the start of the line on which the cursor
+ // currently is, and the offset into the line. Returns null as
+ // node if cursor is on first line.
+ select.cursorPos = function(container, start) {
+ var selection = container.ownerDocument.selection;
+ if (!selection) return null;
+
+ var topNode = select.selectionTopNode(container, start);
+ while (topNode && !isBR(topNode))
+ topNode = topNode.previousSibling;
+
+ var range = selection.createRange(), range2 = range.duplicate();
+ range.collapse(start);
+ if (topNode) {
+ range2.moveToElementText(topNode);
+ range2.collapse(false);
+ }
+ else {
+ // When nothing is selected, we can get all kinds of funky errors here.
+ try { range2.moveToElementText(container); }
+ catch (e) { return null; }
+ range2.collapse(true);
+ }
+ range.setEndPoint("StartToStart", range2);
+
+ return {node: topNode, offset: range.text.length};
+ };
+
+ select.setCursorPos = function(container, from, to) {
+ function rangeAt(pos) {
+ var range = container.ownerDocument.body.createTextRange();
+ if (!pos.node) {
+ range.moveToElementText(container);
+ range.collapse(true);
+ }
+ else {
+ range.moveToElementText(pos.node);
+ range.collapse(false);
+ }
+ range.move("character", pos.offset);
+ return range;
+ }
+
+ var range = rangeAt(from);
+ if (to && to != from)
+ range.setEndPoint("EndToEnd", rangeAt(to));
+ range.select();
+ }
+
+ // Some hacks for storing and re-storing the selection when the editor loses and regains focus.
+ select.getBookmark = function (container) {
+ var from = select.cursorPos(container, true), to = select.cursorPos(container, false);
+ if (from && to) return {from: from, to: to};
+ };
+
+ // Restore a stored selection.
+ select.setBookmark = function(container, mark) {
+ if (!mark) return;
+ select.setCursorPos(container, mark.from, mark.to);
+ };
+ }
+ // W3C model
+ else {
+ // Find the node right at the cursor, not one of its
+ // ancestors with a suitable offset. This goes down the DOM tree
+ // until a 'leaf' is reached (or is it *up* the DOM tree?).
+ function innerNode(node, offset) {
+ while (node.nodeType != 3 && !isBR(node)) {
+ var newNode = node.childNodes[offset] || node.nextSibling;
+ offset = 0;
+ while (!newNode && node.parentNode) {
+ node = node.parentNode;
+ newNode = node.nextSibling;
+ }
+ node = newNode;
+ if (!newNode) break;
+ }
+ return {node: node, offset: offset};
+ }
+
+ // Store start and end nodes, and offsets within these, and refer
+ // back to the selection object from those nodes, so that this
+ // object can be updated when the nodes are replaced before the
+ // selection is restored.
+ select.markSelection = function (win) {
+ var selection = win.getSelection();
+ if (!selection || selection.rangeCount == 0)
+ return (currentSelection = null);
+ var range = selection.getRangeAt(0);
+
+ currentSelection = {
+ start: innerNode(range.startContainer, range.startOffset),
+ end: innerNode(range.endContainer, range.endOffset),
+ window: win,
+ changed: false
+ };
+ };
+
+ select.selectMarked = function () {
+ var cs = currentSelection;
+ // on webkit-based browsers, it is apparently possible that the
+ // selection gets reset even when a node that is not one of the
+ // endpoints get messed with. the most common situation where
+ // this occurs is when a selection is deleted or overwitten. we
+ // check for that here.
+ function focusIssue() {
+ if (cs.start.node == cs.end.node && cs.start.offset == cs.end.offset) {
+ var selection = cs.window.getSelection();
+ if (!selection || selection.rangeCount == 0) return true;
+ var range = selection.getRangeAt(0), point = innerNode(range.startContainer, range.startOffset);
+ return cs.start.node != point.node || cs.start.offset != point.offset;
+ }
+ }
+ if (!cs || !(cs.changed || (webkit && focusIssue()))) return;
+ var win = cs.window, range = win.document.createRange();
+
+ function setPoint(point, which) {
+ if (point.node) {
+ // Some magic to generalize the setting of the start and end
+ // of a range.
+ if (point.offset == 0)
+ range["set" + which + "Before"](point.node);
+ else
+ range["set" + which](point.node, point.offset);
+ }
+ else {
+ range.setStartAfter(win.document.body.lastChild || win.document.body);
+ }
+ }
+
+ setPoint(cs.end, "End");
+ setPoint(cs.start, "Start");
+ selectRange(range, win);
+ };
+
+ // Helper for selecting a range object.
+ function selectRange(range, window) {
+ var selection = window.getSelection();
+ if (!selection) return;
+ selection.removeAllRanges();
+ selection.addRange(range);
+ }
+ function selectionRange(window) {
+ var selection = window.getSelection();
+ if (!selection || selection.rangeCount == 0)
+ return false;
+ else
+ return selection.getRangeAt(0);
+ }
+
+ // Finding the top-level node at the cursor in the W3C is, as you
+ // can see, quite an involved process.
+ select.selectionTopNode = function(container, start) {
+ var range = selectionRange(container.ownerDocument.defaultView);
+ if (!range) return false;
+
+ var node = start ? range.startContainer : range.endContainer;
+ var offset = start ? range.startOffset : range.endOffset;
+ // Work around (yet another) bug in Opera's selection model.
+ if (window.opera && !start && range.endContainer == container && range.endOffset == range.startOffset + 1 &&
+ container.childNodes[range.startOffset] && isBR(container.childNodes[range.startOffset]))
+ offset--;
+
+ // For text nodes, we look at the node itself if the cursor is
+ // inside, or at the node before it if the cursor is at the
+ // start.
+ if (node.nodeType == 3){
+ if (offset > 0)
+ return topLevelNodeAt(node, container);
+ else
+ return topLevelNodeBefore(node, container);
+ }
+ // Occasionally, browsers will return the HTML node as
+ // selection. If the offset is 0, we take the start of the frame
+ // ('after null'), otherwise, we take the last node.
+ else if (node.nodeName.toUpperCase() == "HTML") {
+ return (offset == 1 ? null : container.lastChild);
+ }
+ // If the given node is our 'container', we just look up the
+ // correct node by using the offset.
+ else if (node == container) {
+ return (offset == 0) ? null : node.childNodes[offset - 1];
+ }
+ // In any other case, we have a regular node. If the cursor is
+ // at the end of the node, we use the node itself, if it is at
+ // the start, we use the node before it, and in any other
+ // case, we look up the child before the cursor and use that.
+ else {
+ if (offset == node.childNodes.length)
+ return topLevelNodeAt(node, container);
+ else if (offset == 0)
+ return topLevelNodeBefore(node, container);
+ else
+ return topLevelNodeAt(node.childNodes[offset - 1], container);
+ }
+ };
+
+ select.focusAfterNode = function(node, container) {
+ var win = container.ownerDocument.defaultView,
+ range = win.document.createRange();
+ range.setStartBefore(container.firstChild || container);
+ // In Opera, setting the end of a range at the end of a line
+ // (before a BR) will cause the cursor to appear on the next
+ // line, so we set the end inside of the start node when
+ // possible.
+ if (node && !node.firstChild)
+ range.setEndAfter(node);
+ else if (node)
+ range.setEnd(node, node.childNodes.length);
+ else
+ range.setEndBefore(container.firstChild || container);
+ range.collapse(false);
+ selectRange(range, win);
+ };
+
+ select.somethingSelected = function(win) {
+ var range = selectionRange(win);
+ return range && !range.collapsed;
+ };
+
+ select.offsetInNode = function(win, node) {
+ var range = selectionRange(win);
+ if (!range) return 0;
+ range = range.cloneRange();
+ range.setStartBefore(node);
+ return range.toString().length;
+ };
+
+ function insertNodeAtCursor(window, node) {
+ var range = selectionRange(window);
+ if (!range) return;
+
+ range.deleteContents();
+ range.insertNode(node);
+
+ // work around weirdness where Opera will magically insert a new
+ // BR node when a BR node inside a span is moved around. makes
+ // sure the BR ends up outside of spans.
+ if (window.opera && isBR(node) && isSpan(node.parentNode)) {
+ var next = node.nextSibling, p = node.parentNode, outer = p.parentNode;
+ outer.insertBefore(node, p.nextSibling);
+ var textAfter = "";
+ for (; next && next.nodeType == 3; next = next.nextSibling) {
+ textAfter += next.nodeValue;
+ removeElement(next);
+ }
+ outer.insertBefore(makePartSpan(textAfter, window.document), node.nextSibling);
+ }
+ range = window.document.createRange();
+ range.selectNode(node);
+ range.collapse(false);
+ selectRange(range, window);
+ }
+
+ select.insertNewlineAtCursor = function(window) {
+ if (webkit)
+ document.execCommand('insertLineBreak');
+ else
+ insertNodeAtCursor(window, window.document.createElement("BR"));
+ };
+
+ select.insertTabAtCursor = function(window) {
+ insertNodeAtCursor(window, window.document.createTextNode(fourSpaces));
+ };
+
+ select.cursorPos = function(container, start) {
+ var range = selectionRange(window);
+ if (!range) return;
+
+ var topNode = select.selectionTopNode(container, start);
+ while (topNode && !isBR(topNode))
+ topNode = topNode.previousSibling;
+
+ range = range.cloneRange();
+ range.collapse(start);
+ if (topNode)
+ range.setStartAfter(topNode);
+ else
+ range.setStartBefore(container);
+
+ return {node: topNode, offset: range.toString().length};
+ };
+
+ select.setCursorPos = function(container, from, to) {
+ var win = container.ownerDocument.defaultView,
+ range = win.document.createRange();
+
+ function setPoint(node, offset, side) {
+ if (offset == 0 && node && !node.nextSibling) {
+ range["set" + side + "After"](node);
+ return true;
+ }
+
+ if (!node)
+ node = container.firstChild;
+ else
+ node = node.nextSibling;
+
+ if (!node) return;
+
+ if (offset == 0) {
+ range["set" + side + "Before"](node);
+ return true;
+ }
+
+ var backlog = []
+ function decompose(node) {
+ if (node.nodeType == 3)
+ backlog.push(node);
+ else
+ forEach(node.childNodes, decompose);
+ }
+ while (true) {
+ while (node && !backlog.length) {
+ decompose(node);
+ node = node.nextSibling;
+ }
+ var cur = backlog.shift();
+ if (!cur) return false;
+
+ var length = cur.nodeValue.length;
+ if (length >= offset) {
+ range["set" + side](cur, offset);
+ return true;
+ }
+ offset -= length;
+ }
+ }
+
+ to = to || from;
+ if (setPoint(to.node, to.offset, "End") && setPoint(from.node, from.offset, "Start"))
+ selectRange(range, win);
+ };
+ }
+})();
Added: shindig/branches/2.0.x/content/editor/CodeMirror-0.8/js/stringstream.js
URL: http://svn.apache.org/viewvc/shindig/branches/2.0.x/content/editor/CodeMirror-0.8/js/stringstream.js?rev=1005506&view=auto
==============================================================================
--- shindig/branches/2.0.x/content/editor/CodeMirror-0.8/js/stringstream.js (added)
+++ shindig/branches/2.0.x/content/editor/CodeMirror-0.8/js/stringstream.js Thu Oct 7 15:54:09 2010
@@ -0,0 +1,140 @@
+/* String streams are the things fed to parsers (which can feed them
+ * to a tokenizer if they want). They provide peek and next methods
+ * for looking at the current character (next 'consumes' this
+ * character, peek does not), and a get method for retrieving all the
+ * text that was consumed since the last time get was called.
+ *
+ * An easy mistake to make is to let a StopIteration exception finish
+ * the token stream while there are still characters pending in the
+ * string stream (hitting the end of the buffer while parsing a
+ * token). To make it easier to detect such errors, the stringstreams
+ * throw an exception when this happens.
+ */
+
+// Make a stringstream stream out of an iterator that returns strings.
+// This is applied to the result of traverseDOM (see codemirror.js),
+// and the resulting stream is fed to the parser.
+var stringStream = function(source){
+ // String that's currently being iterated over.
+ var current = "";
+ // Position in that string.
+ var pos = 0;
+ // Accumulator for strings that have been iterated over but not
+ // get()-ed yet.
+ var accum = "";
+ // Make sure there are more characters ready, or throw
+ // StopIteration.
+ function ensureChars() {
+ while (pos == current.length) {
+ accum += current;
+ current = ""; // In case source.next() throws
+ pos = 0;
+ try {current = source.next();}
+ catch (e) {
+ if (e != StopIteration) throw e;
+ else return false;
+ }
+ }
+ return true;
+ }
+
+ return {
+ // Return the next character in the stream.
+ peek: function() {
+ if (!ensureChars()) return null;
+ return current.charAt(pos);
+ },
+ // Get the next character, throw StopIteration if at end, check
+ // for unused content.
+ next: function() {
+ if (!ensureChars()) {
+ if (accum.length > 0)
+ throw "End of stringstream reached without emptying buffer ('" + accum + "').";
+ else
+ throw StopIteration;
+ }
+ return current.charAt(pos++);
+ },
+ // Return the characters iterated over since the last call to
+ // .get().
+ get: function() {
+ var temp = accum;
+ accum = "";
+ if (pos > 0){
+ temp += current.slice(0, pos);
+ current = current.slice(pos);
+ pos = 0;
+ }
+ return temp;
+ },
+ // Push a string back into the stream.
+ push: function(str) {
+ current = current.slice(0, pos) + str + current.slice(pos);
+ },
+ lookAhead: function(str, consume, skipSpaces, caseInsensitive) {
+ function cased(str) {return caseInsensitive ? str.toLowerCase() : str;}
+ str = cased(str);
+ var found = false;
+
+ var _accum = accum, _pos = pos;
+ if (skipSpaces) this.nextWhileMatches(/[\s\u00a0]/);
+
+ while (true) {
+ var end = pos + str.length, left = current.length - pos;
+ if (end <= current.length) {
+ found = str == cased(current.slice(pos, end));
+ pos = end;
+ break;
+ }
+ else if (str.slice(0, left) == cased(current.slice(pos))) {
+ accum += current; current = "";
+ try {current = source.next();}
+ catch (e) {break;}
+ pos = 0;
+ str = str.slice(left);
+ }
+ else {
+ break;
+ }
+ }
+
+ if (!(found && consume)) {
+ current = accum.slice(_accum.length) + current;
+ pos = _pos;
+ accum = _accum;
+ }
+
+ return found;
+ },
+
+ // Utils built on top of the above
+ more: function() {
+ return this.peek() !== null;
+ },
+ applies: function(test) {
+ var next = this.peek();
+ return (next !== null && test(next));
+ },
+ nextWhile: function(test) {
+ var next;
+ while ((next = this.peek()) !== null && test(next))
+ this.next();
+ },
+ matches: function(re) {
+ var next = this.peek();
+ return (next !== null && re.test(next));
+ },
+ nextWhileMatches: function(re) {
+ var next;
+ while ((next = this.peek()) !== null && re.test(next))
+ this.next();
+ },
+ equals: function(ch) {
+ return ch === this.peek();
+ },
+ endOfLine: function() {
+ var next = this.peek();
+ return next == null || next == "\n";
+ }
+ };
+};
Added: shindig/branches/2.0.x/content/editor/CodeMirror-0.8/js/tokenize.js
URL: http://svn.apache.org/viewvc/shindig/branches/2.0.x/content/editor/CodeMirror-0.8/js/tokenize.js?rev=1005506&view=auto
==============================================================================
--- shindig/branches/2.0.x/content/editor/CodeMirror-0.8/js/tokenize.js (added)
+++ shindig/branches/2.0.x/content/editor/CodeMirror-0.8/js/tokenize.js Thu Oct 7 15:54:09 2010
@@ -0,0 +1,57 @@
+// A framework for simple tokenizers. Takes care of newlines and
+// white-space, and of getting the text from the source stream into
+// the token object. A state is a function of two arguments -- a
+// string stream and a setState function. The second can be used to
+// change the tokenizer's state, and can be ignored for stateless
+// tokenizers. This function should advance the stream over a token
+// and return a string or object containing information about the next
+// token, or null to pass and have the (new) state be called to finish
+// the token. When a string is given, it is wrapped in a {style, type}
+// object. In the resulting object, the characters consumed are stored
+// under the content property. Any whitespace following them is also
+// automatically consumed, and added to the value property. (Thus,
+// content is the actual meaningful part of the token, while value
+// contains all the text it spans.)
+
+function tokenizer(source, state) {
+ // Newlines are always a separate token.
+ function isWhiteSpace(ch) {
+ // The messy regexp is because IE's regexp matcher is of the
+ // opinion that non-breaking spaces are no whitespace.
+ return ch != "\n" && /^[\s\u00a0]*$/.test(ch);
+ }
+
+ var tokenizer = {
+ state: state,
+
+ take: function(type) {
+ if (typeof(type) == "string")
+ type = {style: type, type: type};
+
+ type.content = (type.content || "") + source.get();
+ if (!/\n$/.test(type.content))
+ source.nextWhile(isWhiteSpace);
+ type.value = type.content + source.get();
+ return type;
+ },
+
+ next: function () {
+ if (!source.more()) throw StopIteration;
+
+ var type;
+ if (source.equals("\n")) {
+ source.next();
+ return this.take("whitespace");
+ }
+
+ if (source.applies(isWhiteSpace))
+ type = "whitespace";
+ else
+ while (!type)
+ type = this.state(source, function(s) {tokenizer.state = s;});
+
+ return this.take(type);
+ }
+ };
+ return tokenizer;
+}
Added: shindig/branches/2.0.x/content/editor/CodeMirror-0.8/js/tokenizejavascript.js
URL: http://svn.apache.org/viewvc/shindig/branches/2.0.x/content/editor/CodeMirror-0.8/js/tokenizejavascript.js?rev=1005506&view=auto
==============================================================================
--- shindig/branches/2.0.x/content/editor/CodeMirror-0.8/js/tokenizejavascript.js (added)
+++ shindig/branches/2.0.x/content/editor/CodeMirror-0.8/js/tokenizejavascript.js Thu Oct 7 15:54:09 2010
@@ -0,0 +1,174 @@
+/* Tokenizer for JavaScript code */
+
+var tokenizeJavaScript = (function() {
+ // Advance the stream until the given character (not preceded by a
+ // backslash) is encountered, or the end of the line is reached.
+ function nextUntilUnescaped(source, end) {
+ var escaped = false;
+ while (!source.endOfLine()) {
+ var next = source.next();
+ if (next == end && !escaped)
+ return false;
+ escaped = !escaped && next == "\\";
+ }
+ return escaped;
+ }
+
+ // A map of JavaScript's keywords. The a/b/c keyword distinction is
+ // very rough, but it gives the parser enough information to parse
+ // correct code correctly (we don't care that much how we parse
+ // incorrect code). The style information included in these objects
+ // is used by the highlighter to pick the correct CSS style for a
+ // token.
+ var keywords = function(){
+ function result(type, style){
+ return {type: type, style: "js-" + style};
+ }
+ // keywords that take a parenthised expression, and then a
+ // statement (if)
+ var keywordA = result("keyword a", "keyword");
+ // keywords that take just a statement (else)
+ var keywordB = result("keyword b", "keyword");
+ // keywords that optionally take an expression, and form a
+ // statement (return)
+ var keywordC = result("keyword c", "keyword");
+ var operator = result("operator", "keyword");
+ var atom = result("atom", "atom");
+ return {
+ "if": keywordA, "while": keywordA, "with": keywordA,
+ "else": keywordB, "do": keywordB, "try": keywordB, "finally": keywordB,
+ "return": keywordC, "break": keywordC, "continue": keywordC, "new": keywordC, "delete": keywordC, "throw": keywordC,
+ "in": operator, "typeof": operator, "instanceof": operator,
+ "var": result("var", "keyword"), "function": result("function", "keyword"), "catch": result("catch", "keyword"),
+ "for": result("for", "keyword"), "switch": result("switch", "keyword"),
+ "case": result("case", "keyword"), "default": result("default", "keyword"),
+ "true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom
+ };
+ }();
+
+ // Some helper regexps
+ var isOperatorChar = /[+\-*&%=<>!?|]/;
+ var isHexDigit = /[0-9A-Fa-f]/;
+ var isWordChar = /[\w\$_]/;
+
+ // Wrapper around jsToken that helps maintain parser state (whether
+ // we are inside of a multi-line comment and whether the next token
+ // could be a regular expression).
+ function jsTokenState(inside, regexp) {
+ return function(source, setState) {
+ var newInside = inside;
+ var type = jsToken(inside, regexp, source, function(c) {newInside = c;});
+ var newRegexp = type.type == "operator" || type.type == "keyword c" || type.type.match(/^[\[{}\(,;:]$/);
+ if (newRegexp != regexp || newInside != inside)
+ setState(jsTokenState(newInside, newRegexp));
+ return type;
+ };
+ }
+
+ // The token reader, intended to be used by the tokenizer from
+ // tokenize.js (through jsTokenState). Advances the source stream
+ // over a token, and returns an object containing the type and style
+ // of that token.
+ function jsToken(inside, regexp, source, setInside) {
+ function readHexNumber(){
+ source.next(); // skip the 'x'
+ source.nextWhileMatches(isHexDigit);
+ return {type: "number", style: "js-atom"};
+ }
+
+ function readNumber() {
+ source.nextWhileMatches(/[0-9]/);
+ if (source.equals(".")){
+ source.next();
+ source.nextWhileMatches(/[0-9]/);
+ }
+ if (source.equals("e") || source.equals("E")){
+ source.next();
+ if (source.equals("-"))
+ source.next();
+ source.nextWhileMatches(/[0-9]/);
+ }
+ return {type: "number", style: "js-atom"};
+ }
+ // Read a word, look it up in keywords. If not found, it is a
+ // variable, otherwise it is a keyword of the type found.
+ function readWord() {
+ source.nextWhileMatches(isWordChar);
+ var word = source.get();
+ var known = keywords.hasOwnProperty(word) && keywords.propertyIsEnumerable(word) && keywords[word];
+ return known ? {type: known.type, style: known.style, content: word} :
+ {type: "variable", style: "js-variable", content: word};
+ }
+ function readRegexp() {
+ nextUntilUnescaped(source, "/");
+ source.nextWhileMatches(/[gi]/);
+ return {type: "regexp", style: "js-string"};
+ }
+ // Mutli-line comments are tricky. We want to return the newlines
+ // embedded in them as regular newline tokens, and then continue
+ // returning a comment token for every line of the comment. So
+ // some state has to be saved (inside) to indicate whether we are
+ // inside a /* */ sequence.
+ function readMultilineComment(start){
+ var newInside = "/*";
+ var maybeEnd = (start == "*");
+ while (true) {
+ if (source.endOfLine())
+ break;
+ var next = source.next();
+ if (next == "/" && maybeEnd){
+ newInside = null;
+ break;
+ }
+ maybeEnd = (next == "*");
+ }
+ setInside(newInside);
+ return {type: "comment", style: "js-comment"};
+ }
+ function readOperator() {
+ source.nextWhileMatches(isOperatorChar);
+ return {type: "operator", style: "js-operator"};
+ }
+ function readString(quote) {
+ var endBackSlash = nextUntilUnescaped(source, quote);
+ setInside(endBackSlash ? quote : null);
+ return {type: "string", style: "js-string"};
+ }
+
+ // Fetch the next token. Dispatches on first character in the
+ // stream, or first two characters when the first is a slash.
+ if (inside == "\"" || inside == "'")
+ return readString(inside);
+ var ch = source.next();
+ if (inside == "/*")
+ return readMultilineComment(ch);
+ else if (ch == "\"" || ch == "'")
+ return readString(ch);
+ // with punctuation, the type of the token is the symbol itself
+ else if (/[\[\]{}\(\),;\:\.]/.test(ch))
+ return {type: ch, style: "js-punctuation"};
+ else if (ch == "0" && (source.equals("x") || source.equals("X")))
+ return readHexNumber();
+ else if (/[0-9]/.test(ch))
+ return readNumber();
+ else if (ch == "/"){
+ if (source.equals("*"))
+ { source.next(); return readMultilineComment(ch); }
+ else if (source.equals("/"))
+ { nextUntilUnescaped(source, null); return {type: "comment", style: "js-comment"};}
+ else if (regexp)
+ return readRegexp();
+ else
+ return readOperator();
+ }
+ else if (isOperatorChar.test(ch))
+ return readOperator();
+ else
+ return readWord();
+ }
+
+ // The external interface to the tokenizer.
+ return function(source, startState) {
+ return tokenizer(source, startState || jsTokenState(false, true));
+ };
+})();
Added: shindig/branches/2.0.x/content/editor/CodeMirror-0.8/js/undo.js
URL: http://svn.apache.org/viewvc/shindig/branches/2.0.x/content/editor/CodeMirror-0.8/js/undo.js?rev=1005506&view=auto
==============================================================================
--- shindig/branches/2.0.x/content/editor/CodeMirror-0.8/js/undo.js (added)
+++ shindig/branches/2.0.x/content/editor/CodeMirror-0.8/js/undo.js Thu Oct 7 15:54:09 2010
@@ -0,0 +1,410 @@
+/**
+ * Storage and control for undo information within a CodeMirror
+ * editor. 'Why on earth is such a complicated mess required for
+ * that?', I hear you ask. The goal, in implementing this, was to make
+ * the complexity of storing and reverting undo information depend
+ * only on the size of the edited or restored content, not on the size
+ * of the whole document. This makes it necessary to use a kind of
+ * 'diff' system, which, when applied to a DOM tree, causes some
+ * complexity and hackery.
+ *
+ * In short, the editor 'touches' BR elements as it parses them, and
+ * the UndoHistory stores these. When nothing is touched in commitDelay
+ * milliseconds, the changes are committed: It goes over all touched
+ * nodes, throws out the ones that did not change since last commit or
+ * are no longer in the document, and assembles the rest into zero or
+ * more 'chains' -- arrays of adjacent lines. Links back to these
+ * chains are added to the BR nodes, while the chain that previously
+ * spanned these nodes is added to the undo history. Undoing a change
+ * means taking such a chain off the undo history, restoring its
+ * content (text is saved per line) and linking it back into the
+ * document.
+ */
+
+// A history object needs to know about the DOM container holding the
+// document, the maximum amount of undo levels it should store, the
+// delay (of no input) after which it commits a set of changes, and,
+// unfortunately, the 'parent' window -- a window that is not in
+// designMode, and on which setTimeout works in every browser.
+function UndoHistory(container, maxDepth, commitDelay, editor) {
+ this.container = container;
+ this.maxDepth = maxDepth; this.commitDelay = commitDelay;
+ this.editor = editor; this.parent = editor.parent;
+ // This line object represents the initial, empty editor.
+ var initial = {text: "", from: null, to: null};
+ // As the borders between lines are represented by BR elements, the
+ // start of the first line and the end of the last one are
+ // represented by null. Since you can not store any properties
+ // (links to line objects) in null, these properties are used in
+ // those cases.
+ this.first = initial; this.last = initial;
+ // Similarly, a 'historyTouched' property is added to the BR in
+ // front of lines that have already been touched, and 'firstTouched'
+ // is used for the first line.
+ this.firstTouched = false;
+ // History is the set of committed changes, touched is the set of
+ // nodes touched since the last commit.
+ this.history = []; this.redoHistory = []; this.touched = [];
+}
+
+UndoHistory.prototype = {
+ // Schedule a commit (if no other touches come in for commitDelay
+ // milliseconds).
+ scheduleCommit: function() {
+ var self = this;
+ this.parent.clearTimeout(this.commitTimeout);
+ this.commitTimeout = this.parent.setTimeout(function(){self.tryCommit();}, this.commitDelay);
+ },
+
+ // Mark a node as touched. Null is a valid argument.
+ touch: function(node) {
+ this.setTouched(node);
+ this.scheduleCommit();
+ },
+
+ // Undo the last change.
+ undo: function() {
+ // Make sure pending changes have been committed.
+ this.commit();
+
+ if (this.history.length) {
+ // Take the top diff from the history, apply it, and store its
+ // shadow in the redo history.
+ var item = this.history.pop();
+ this.redoHistory.push(this.updateTo(item, "applyChain"));
+ this.notifyEnvironment();
+ return this.chainNode(item);
+ }
+ },
+
+ // Redo the last undone change.
+ redo: function() {
+ this.commit();
+ if (this.redoHistory.length) {
+ // The inverse of undo, basically.
+ var item = this.redoHistory.pop();
+ this.addUndoLevel(this.updateTo(item, "applyChain"));
+ this.notifyEnvironment();
+ return this.chainNode(item);
+ }
+ },
+
+ clear: function() {
+ this.history = [];
+ this.redoHistory = [];
+ },
+
+ // Ask for the size of the un/redo histories.
+ historySize: function() {
+ return {undo: this.history.length, redo: this.redoHistory.length};
+ },
+
+ // Push a changeset into the document.
+ push: function(from, to, lines) {
+ var chain = [];
+ for (var i = 0; i < lines.length; i++) {
+ var end = (i == lines.length - 1) ? to : this.container.ownerDocument.createElement("BR");
+ chain.push({from: from, to: end, text: cleanText(lines[i])});
+ from = end;
+ }
+ this.pushChains([chain], from == null && to == null);
+ this.notifyEnvironment();
+ },
+
+ pushChains: function(chains, doNotHighlight) {
+ this.commit(doNotHighlight);
+ this.addUndoLevel(this.updateTo(chains, "applyChain"));
+ this.redoHistory = [];
+ },
+
+ // Retrieve a DOM node from a chain (for scrolling to it after undo/redo).
+ chainNode: function(chains) {
+ for (var i = 0; i < chains.length; i++) {
+ var start = chains[i][0], node = start && (start.from || start.to);
+ if (node) return node;
+ }
+ },
+
+ // Clear the undo history, make the current document the start
+ // position.
+ reset: function() {
+ this.history = []; this.redoHistory = [];
+ },
+
+ textAfter: function(br) {
+ return this.after(br).text;
+ },
+
+ nodeAfter: function(br) {
+ return this.after(br).to;
+ },
+
+ nodeBefore: function(br) {
+ return this.before(br).from;
+ },
+
+ // Commit unless there are pending dirty nodes.
+ tryCommit: function() {
+ if (!window.UndoHistory) return; // Stop when frame has been unloaded
+ if (this.editor.highlightDirty()) this.commit(true);
+ else this.scheduleCommit();
+ },
+
+ // Check whether the touched nodes hold any changes, if so, commit
+ // them.
+ commit: function(doNotHighlight) {
+ this.parent.clearTimeout(this.commitTimeout);
+ // Make sure there are no pending dirty nodes.
+ if (!doNotHighlight) this.editor.highlightDirty(true);
+ // Build set of chains.
+ var chains = this.touchedChains(), self = this;
+
+ if (chains.length) {
+ this.addUndoLevel(this.updateTo(chains, "linkChain"));
+ this.redoHistory = [];
+ this.notifyEnvironment();
+ }
+ },
+
+ // [ end of public interface ]
+
+ // Update the document with a given set of chains, return its
+ // shadow. updateFunc should be "applyChain" or "linkChain". In the
+ // second case, the chains are taken to correspond the the current
+ // document, and only the state of the line data is updated. In the
+ // first case, the content of the chains is also pushed iinto the
+ // document.
+ updateTo: function(chains, updateFunc) {
+ var shadows = [], dirty = [];
+ for (var i = 0; i < chains.length; i++) {
+ shadows.push(this.shadowChain(chains[i]));
+ dirty.push(this[updateFunc](chains[i]));
+ }
+ if (updateFunc == "applyChain")
+ this.notifyDirty(dirty);
+ return shadows;
+ },
+
+ // Notify the editor that some nodes have changed.
+ notifyDirty: function(nodes) {
+ forEach(nodes, method(this.editor, "addDirtyNode"))
+ this.editor.scheduleHighlight();
+ },
+
+ notifyEnvironment: function() {
+ if (this.onChange) this.onChange();
+ // Used by the line-wrapping line-numbering code.
+ if (window.frameElement && window.frameElement.CodeMirror.updateNumbers)
+ window.frameElement.CodeMirror.updateNumbers();
+ },
+
+ // Link a chain into the DOM nodes (or the first/last links for null
+ // nodes).
+ linkChain: function(chain) {
+ for (var i = 0; i < chain.length; i++) {
+ var line = chain[i];
+ if (line.from) line.from.historyAfter = line;
+ else this.first = line;
+ if (line.to) line.to.historyBefore = line;
+ else this.last = line;
+ }
+ },
+
+ // Get the line object after/before a given node.
+ after: function(node) {
+ return node ? node.historyAfter : this.first;
+ },
+ before: function(node) {
+ return node ? node.historyBefore : this.last;
+ },
+
+ // Mark a node as touched if it has not already been marked.
+ setTouched: function(node) {
+ if (node) {
+ if (!node.historyTouched) {
+ this.touched.push(node);
+ node.historyTouched = true;
+ }
+ }
+ else {
+ this.firstTouched = true;
+ }
+ },
+
+ // Store a new set of undo info, throw away info if there is more of
+ // it than allowed.
+ addUndoLevel: function(diffs) {
+ this.history.push(diffs);
+ if (this.history.length > this.maxDepth)
+ this.history.shift();
+ },
+
+ // Build chains from a set of touched nodes.
+ touchedChains: function() {
+ var self = this;
+
+ // The temp system is a crummy hack to speed up determining
+ // whether a (currently touched) node has a line object associated
+ // with it. nullTemp is used to store the object for the first
+ // line, other nodes get it stored in their historyTemp property.
+ var nullTemp = null;
+ function temp(node) {return node ? node.historyTemp : nullTemp;}
+ function setTemp(node, line) {
+ if (node) node.historyTemp = line;
+ else nullTemp = line;
+ }
+
+ function buildLine(node) {
+ var text = [];
+ for (var cur = node ? node.nextSibling : self.container.firstChild;
+ cur && !isBR(cur); cur = cur.nextSibling)
+ if (cur.currentText) text.push(cur.currentText);
+ return {from: node, to: cur, text: cleanText(text.join(""))};
+ }
+
+ // Filter out unchanged lines and nodes that are no longer in the
+ // document. Build up line objects for remaining nodes.
+ var lines = [];
+ if (self.firstTouched) self.touched.push(null);
+ forEach(self.touched, function(node) {
+ if (node && node.parentNode != self.container) return;
+
+ if (node) node.historyTouched = false;
+ else self.firstTouched = false;
+
+ var line = buildLine(node), shadow = self.after(node);
+ if (!shadow || shadow.text != line.text || shadow.to != line.to) {
+ lines.push(line);
+ setTemp(node, line);
+ }
+ });
+
+ // Get the BR element after/before the given node.
+ function nextBR(node, dir) {
+ var link = dir + "Sibling", search = node[link];
+ while (search && !isBR(search))
+ search = search[link];
+ return search;
+ }
+
+ // Assemble line objects into chains by scanning the DOM tree
+ // around them.
+ var chains = []; self.touched = [];
+ forEach(lines, function(line) {
+ // Note that this makes the loop skip line objects that have
+ // been pulled into chains by lines before them.
+ if (!temp(line.from)) return;
+
+ var chain = [], curNode = line.from, safe = true;
+ // Put any line objects (referred to by temp info) before this
+ // one on the front of the array.
+ while (true) {
+ var curLine = temp(curNode);
+ if (!curLine) {
+ if (safe) break;
+ else curLine = buildLine(curNode);
+ }
+ chain.unshift(curLine);
+ setTemp(curNode, null);
+ if (!curNode) break;
+ safe = self.after(curNode);
+ curNode = nextBR(curNode, "previous");
+ }
+ curNode = line.to; safe = self.before(line.from);
+ // Add lines after this one at end of array.
+ while (true) {
+ if (!curNode) break;
+ var curLine = temp(curNode);
+ if (!curLine) {
+ if (safe) break;
+ else curLine = buildLine(curNode);
+ }
+ chain.push(curLine);
+ setTemp(curNode, null);
+ safe = self.before(curNode);
+ curNode = nextBR(curNode, "next");
+ }
+ chains.push(chain);
+ });
+
+ return chains;
+ },
+
+ // Find the 'shadow' of a given chain by following the links in the
+ // DOM nodes at its start and end.
+ shadowChain: function(chain) {
+ var shadows = [], next = this.after(chain[0].from), end = chain[chain.length - 1].to;
+ while (true) {
+ shadows.push(next);
+ var nextNode = next.to;
+ if (!nextNode || nextNode == end)
+ break;
+ else
+ next = nextNode.historyAfter || this.before(end);
+ // (The this.before(end) is a hack -- FF sometimes removes
+ // properties from BR nodes, in which case the best we can hope
+ // for is to not break.)
+ }
+ return shadows;
+ },
+
+ // Update the DOM tree to contain the lines specified in a given
+ // chain, link this chain into the DOM nodes.
+ applyChain: function(chain) {
+ // Some attempt is made to prevent the cursor from jumping
+ // randomly when an undo or redo happens. It still behaves a bit
+ // strange sometimes.
+ var cursor = select.cursorPos(this.container, false), self = this;
+
+ // Remove all nodes in the DOM tree between from and to (null for
+ // start/end of container).
+ function removeRange(from, to) {
+ var pos = from ? from.nextSibling : self.container.firstChild;
+ while (pos != to) {
+ var temp = pos.nextSibling;
+ removeElement(pos);
+ pos = temp;
+ }
+ }
+
+ var start = chain[0].from, end = chain[chain.length - 1].to;
+ // Clear the space where this change has to be made.
+ removeRange(start, end);
+
+ // Insert the content specified by the chain into the DOM tree.
+ for (var i = 0; i < chain.length; i++) {
+ var line = chain[i];
+ // The start and end of the space are already correct, but BR
+ // tags inside it have to be put back.
+ if (i > 0)
+ self.container.insertBefore(line.from, end);
+
+ // Add the text.
+ var node = makePartSpan(fixSpaces(line.text), this.container.ownerDocument);
+ self.container.insertBefore(node, end);
+ // See if the cursor was on this line. Put it back, adjusting
+ // for changed line length, if it was.
+ if (cursor && cursor.node == line.from) {
+ var cursordiff = 0;
+ var prev = this.after(line.from);
+ if (prev && i == chain.length - 1) {
+ // Only adjust if the cursor is after the unchanged part of
+ // the line.
+ for (var match = 0; match < cursor.offset &&
+ line.text.charAt(match) == prev.text.charAt(match); match++);
+ if (cursor.offset > match)
+ cursordiff = line.text.length - prev.text.length;
+ }
+ select.setCursorPos(this.container, {node: line.from, offset: Math.max(0, cursor.offset + cursordiff)});
+ }
+ // Cursor was in removed line, this is last new line.
+ else if (cursor && (i == chain.length - 1) && cursor.node && cursor.node.parentNode != this.container) {
+ select.setCursorPos(this.container, {node: line.from, offset: line.text.length});
+ }
+ }
+
+ // Anchor the chain in the DOM tree.
+ this.linkChain(chain);
+ return start;
+ }
+};
Added: shindig/branches/2.0.x/content/editor/CodeMirror-0.8/js/util.js
URL: http://svn.apache.org/viewvc/shindig/branches/2.0.x/content/editor/CodeMirror-0.8/js/util.js?rev=1005506&view=auto
==============================================================================
--- shindig/branches/2.0.x/content/editor/CodeMirror-0.8/js/util.js (added)
+++ shindig/branches/2.0.x/content/editor/CodeMirror-0.8/js/util.js Thu Oct 7 15:54:09 2010
@@ -0,0 +1,130 @@
+/* A few useful utility functions. */
+
+// Capture a method on an object.
+function method(obj, name) {
+ return function() {obj[name].apply(obj, arguments);};
+}
+
+// The value used to signal the end of a sequence in iterators.
+var StopIteration = {toString: function() {return "StopIteration"}};
+
+// Apply a function to each element in a sequence.
+function forEach(iter, f) {
+ if (iter.next) {
+ try {while (true) f(iter.next());}
+ catch (e) {if (e != StopIteration) throw e;}
+ }
+ else {
+ for (var i = 0; i < iter.length; i++)
+ f(iter[i]);
+ }
+}
+
+// Map a function over a sequence, producing an array of results.
+function map(iter, f) {
+ var accum = [];
+ forEach(iter, function(val) {accum.push(f(val));});
+ return accum;
+}
+
+// Create a predicate function that tests a string againsts a given
+// regular expression. No longer used but might be used by 3rd party
+// parsers.
+function matcher(regexp){
+ return function(value){return regexp.test(value);};
+}
+
+// Test whether a DOM node has a certain CSS class. Much faster than
+// the MochiKit equivalent, for some reason.
+function hasClass(element, className){
+ var classes = element.className;
+ return classes && new RegExp("(^| )" + className + "($| )").test(classes);
+}
+
+// Insert a DOM node after another node.
+function insertAfter(newNode, oldNode) {
+ var parent = oldNode.parentNode;
+ parent.insertBefore(newNode, oldNode.nextSibling);
+ return newNode;
+}
+
+function removeElement(node) {
+ if (node.parentNode)
+ node.parentNode.removeChild(node);
+}
+
+function clearElement(node) {
+ while (node.firstChild)
+ node.removeChild(node.firstChild);
+}
+
+// Check whether a node is contained in another one.
+function isAncestor(node, child) {
+ while (child = child.parentNode) {
+ if (node == child)
+ return true;
+ }
+ return false;
+}
+
+// The non-breaking space character.
+var nbsp = "\u00a0";
+var matching = {"{": "}", "[": "]", "(": ")",
+ "}": "{", "]": "[", ")": "("};
+
+// Standardize a few unportable event properties.
+function normalizeEvent(event) {
+ if (!event.stopPropagation) {
+ event.stopPropagation = function() {this.cancelBubble = true;};
+ event.preventDefault = function() {this.returnValue = false;};
+ }
+ if (!event.stop) {
+ event.stop = function() {
+ this.stopPropagation();
+ this.preventDefault();
+ };
+ }
+
+ if (event.type == "keypress") {
+ event.code = (event.charCode == null) ? event.keyCode : event.charCode;
+ event.character = String.fromCharCode(event.code);
+ }
+ return event;
+}
+
+// Portably register event handlers.
+function addEventHandler(node, type, handler, removeFunc) {
+ function wrapHandler(event) {
+ handler(normalizeEvent(event || window.event));
+ }
+ if (typeof node.addEventListener == "function") {
+ node.addEventListener(type, wrapHandler, false);
+ if (removeFunc) return function() {node.removeEventListener(type, wrapHandler, false);};
+ }
+ else {
+ node.attachEvent("on" + type, wrapHandler);
+ if (removeFunc) return function() {node.detachEvent("on" + type, wrapHandler);};
+ }
+}
+
+function nodeText(node) {
+ return node.textContent || node.innerText || node.nodeValue || "";
+}
+
+function nodeTop(node) {
+ var top = 0;
+ while (node.offsetParent) {
+ top += node.offsetTop;
+ node = node.offsetParent;
+ }
+ return top;
+}
+
+function isBR(node) {
+ var nn = node.nodeName;
+ return nn == "BR" || nn == "br";
+}
+function isSpan(node) {
+ var nn = node.nodeName;
+ return nn == "SPAN" || nn == "span";
+}
Added: shindig/branches/2.0.x/content/editor/index.html
URL: http://svn.apache.org/viewvc/shindig/branches/2.0.x/content/editor/index.html?rev=1005506&view=auto
==============================================================================
--- shindig/branches/2.0.x/content/editor/index.html (added)
+++ shindig/branches/2.0.x/content/editor/index.html Thu Oct 7 15:54:09 2010
@@ -0,0 +1,99 @@
+<!--
+ * 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.
+-->
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="CodeMirror-0.8/js/codemirror.js" type="text/javascript"></script>
+ <link rel="stylesheet" type="text/css" href="CodeMirror-0.8/css/docs.css"/>
+ <link rel="stylesheet" type="text/css" href="/container/gadgets.css"/>
+ <style type="text/css">
+ body {
+ padding:20px;
+ font-family:Helvetica;
+ }
+ h1 {
+ border-bottom:1px solid #ddd;
+ font-size:120%;
+ }
+ .gadgets-render {
+ padding-top: 15px;
+ padding-bottom: 15px;
+ }
+ #gadget iframe {
+ margin: 5px;
+ border: none;
+ height: 300px;
+ width: 300px;
+ }
+ </style>
+</head>
+<body>
+ <h1>OpenSocial Gadget Editor</h1>
+ <div class="border">
+ <textarea id="code" cols="110" rows="30">
+<?xml version="1.0" encoding="UTF-8"?>
+<Module>
+<ModulePrefs title="Example">
+</ModulePrefs>
+<Content>
+<![CDATA[
+Hello World?
+]]>
+</Content>
+</Module>
+ </textarea>
+ </div>
+
+ <div class="gadgets-render">
+ <input type="button" value="Render Gadget" onclick="render();">
+ </div>
+
+ <div class="gadgets-gadget-chrome">
+ <div class="gadgets-gadget-title-bar">
+ <span class="gadgets-gadget-title">Rendered Gadget</span>
+ </div>
+ <div id="gadget" class="gadgets-gadget-content">
+ <form id="form" target="frame" method="post">
+ <input type="hidden" name="rawxml" value="" id="rawxml"></input>
+ </form>
+
+ <iframe id="frame" name="frame" src=""></iframe>
+ </div>
+ </div>
+
+ <script type="text/javascript">
+ var editor = CodeMirror.fromTextArea('code', {
+ height: "160px",
+ parserfile: "/parsexml.js",
+ stylesheet: "CodeMirror-0.8/css/xmlcolors.css",
+ path: "CodeMirror-0.8/js/",
+ continuousScanning: 500,
+ initCallback: render
+ });
+
+ function render() {
+ document.getElementById('rawxml').value = editor.getCode();
+ var url = '/gadgets/ifr?url=http://example.org';
+ var form = document.getElementById('form');
+ form.action = url;
+ form.submit();
+ }
+ </script>
+</body>
+</html>
Added: shindig/branches/2.0.x/content/samplecontainer/examples/media/Media.xml
URL: http://svn.apache.org/viewvc/shindig/branches/2.0.x/content/samplecontainer/examples/media/Media.xml?rev=1005506&view=auto
==============================================================================
--- shindig/branches/2.0.x/content/samplecontainer/examples/media/Media.xml (added)
+++ shindig/branches/2.0.x/content/samplecontainer/examples/media/Media.xml Thu Oct 7 15:54:09 2010
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ * 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.
+-->
+<Module>
+ <ModulePrefs title="Albums and MediaItems">
+ <Require feature="osapi"/>
+ <Require feature="dynamic-height"/>
+ </ModulePrefs>
+
+ <Content type="html"><![CDATA[
+ <html>
+ <head>
+ <!-- Source imports -->
+ <script src='http://ajax.googleapis.com/ajax/libs/dojo/1.5/dojo/dojo.xd.js' type='text/javascript' djConfig='parseOnLoad:true, isDebug:true'></script>
+ <script src='Social.js' type='text/javascript'></script>
+ <script src='MediaUI.js' type='text/javascript'></script>
+
+
+ <!-- Styling -->
+ <link rel="stylesheet" type="text/css" href="http://ajax.googleapis.com/ajax/libs/dojo/1.5/dijit/themes/tundra/tundra.css"></link>
+ <link rel="stylesheet" type="text/css" href="styles.css">
+ <style type="text/css">
+ </style>
+
+ <!-- DOJO requires -->
+ <script type='text/javascript'>
+ dojo.require('dijit.form.Button');
+ dojo.require('dijit.form.Form');
+ dojo.require('dijit.form.TextBox');
+ dojo.require('dijit.form.ValidationTextBox');
+ dojo.require('dijit.Dialog');
+ dojo.require('dijit.form.Textarea');
+ dojo.require('dijit.layout.ContentPane');
+ dojo.require('dijit.layout.TabContainer');
+ </script>
+
+ <!-- JavaScript -->
+ <script type="text/javascript">
+ <!-- Entry point to the gadget -->
+ function init() {
+ console.log("dojo initialized");
+ new MediaUI(new SocialWrapper()).init();
+ }
+
+ <!-- Register entry point -->
+ dojo.addOnLoad(init); // TODO: work-around to tundra.css issue
+ //gadgets.util.registerOnLoadHandler(function() {
+ //dojo.addOnLoad(init);
+ //});
+ </script>
+ </head>
+ <body class="tundra">
+ </body>
+ </html>
+ ]]></Content>
+</Module>
\ No newline at end of file