diff options
author | gspencer@google.com <gspencer@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-05-27 23:15:42 +0000 |
---|---|---|
committer | gspencer@google.com <gspencer@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-05-27 23:15:42 +0000 |
commit | 05b47f7a8c5451f858dc220df0e3a97542edace6 (patch) | |
tree | a2273d619f0625c9d44d40842845ccce2eac1045 /o3d/samples/third_party | |
parent | 5cdc8bdb4c847cefe7f4542bd10c9880c2c557a0 (diff) | |
download | chromium_src-05b47f7a8c5451f858dc220df0e3a97542edace6.zip chromium_src-05b47f7a8c5451f858dc220df0e3a97542edace6.tar.gz chromium_src-05b47f7a8c5451f858dc220df0e3a97542edace6.tar.bz2 |
This is the O3D source tree's initial commit to the Chromium tree. It
is not built or referenced at all by the chrome build yet, and doesn't
yet build in it's new home. We'll change that shortly.
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@17035 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'o3d/samples/third_party')
25 files changed, 6028 insertions, 0 deletions
diff --git a/o3d/samples/third_party/codemirror/LICENSE b/o3d/samples/third_party/codemirror/LICENSE new file mode 100644 index 0000000..12134c0 --- /dev/null +++ b/o3d/samples/third_party/codemirror/LICENSE @@ -0,0 +1,23 @@ + Copyright (c) 2007 Marijn Haverbeke + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any + damages arising from the use of this software. + + Permission is granted to anyone to use this software for any + purpose, including commercial applications, and to alter it and + redistribute it freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must + not claim that you wrote the original software. If you use this + software in a product, an acknowledgment in the product + documentation would be appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must + not be misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. + + Marijn Haverbeke + marijnh at gmail diff --git a/o3d/samples/third_party/codemirror/README.o3d b/o3d/samples/third_party/codemirror/README.o3d new file mode 100644 index 0000000..dcee190 --- /dev/null +++ b/o3d/samples/third_party/codemirror/README.o3d @@ -0,0 +1,5 @@ +This is the CodeMirror tool, Copyright (c) 2007 Marijn Haverbeke, version 0.58. +Please see the LICENSE file. None of the code was modified, but some files +haven't been included for simplicity. +The originally version is available at: +http://marijn.haverbeke.nl/codemirror/codemirror-0.58.zip diff --git a/o3d/samples/third_party/codemirror/css/csscolors.css b/o3d/samples/third_party/codemirror/css/csscolors.css new file mode 100644 index 0000000..100c93f --- /dev/null +++ b/o3d/samples/third_party/codemirror/css/csscolors.css @@ -0,0 +1,47 @@ +.editbox { + margin: .4em; + padding: 0; + font-family: monospace; + font-size: 10pt; + color: black; +} + +pre.code, .editbox { + color: #666666; +} + +.editbox p { + margin: 0; +} + +span.css-at { + color: #770088; +} + +span.css-unit { + color: #228811; +} + +span.css-value { + color: #770088; +} + +span.css-identifier { + color: black; +} + +span.css-important { + color: #0000FF; +} + +span.css-colorcode { + color: #004499; +} + +span.css-comment { + color: #AA7700; +} + +span.css-string { + color: #AA2222; +} diff --git a/o3d/samples/third_party/codemirror/css/docs.css b/o3d/samples/third_party/codemirror/css/docs.css new file mode 100644 index 0000000..4906d0b --- /dev/null +++ b/o3d/samples/third_party/codemirror/css/docs.css @@ -0,0 +1,38 @@ +body { + margin: 0; + font-family: tahoma, arial, sans-serif; + padding: 3em 6em; + color: black; +} + +h1 { + font-size: 22pt; +} + +h2 { + font-size: 14pt; +} + +p.rel { + padding-left: 2em; + text-indent: -2em; +} + +div.border { + border: 1px solid black; + padding: 3px; +} + +code { + font-family: courier, monospace; + font-size: 90%; + color: #277; +} + +pre.code { + margin: 1.1em 12px; + border: 1px solid #CCCCCC; + color: black; + padding: .4em; + font-family: courier, monospace; +} diff --git a/o3d/samples/third_party/codemirror/css/jscolors.css b/o3d/samples/third_party/codemirror/css/jscolors.css new file mode 100644 index 0000000..950b440 --- /dev/null +++ b/o3d/samples/third_party/codemirror/css/jscolors.css @@ -0,0 +1,47 @@ +.editbox { + margin: .4em; + padding: 0; + font-family: monospace; + font-size: 10pt; + color: black; +} + +pre.code, .editbox { + color: #666666; +} + +.editbox p { + margin: 0; +} + +span.js-keyword { + color: #770088; +} + +span.js-atom { + color: #228811; +} + +span.js-variable { + color: black; +} + +span.js-variabledef { + color: #0000FF; +} + +span.js-localvariable { + color: #004499; +} + +span.js-property { + color: black; +} + +span.js-comment { + color: #AA7700; +} + +span.js-string { + color: #AA2222; +} diff --git a/o3d/samples/third_party/codemirror/css/xmlcolors.css b/o3d/samples/third_party/codemirror/css/xmlcolors.css new file mode 100644 index 0000000..aa26579 --- /dev/null +++ b/o3d/samples/third_party/codemirror/css/xmlcolors.css @@ -0,0 +1,51 @@ +.editbox { + margin: .4em; + padding: 0; + font-family: monospace; + font-size: 10pt; + color: black; +} + +.editbox p { + margin: 0; +} + +span.xml-tagname { + color: #A0B; +} + +span.xml-attribute { + color: #281; +} + +span.xml-punctuation { + color: black; +} + +span.xml-attname { + color: #00F; +} + +span.xml-comment { + color: #A70; +} + +span.xml-cdata { + color: #48A; +} + +span.xml-processing { + color: #999; +} + +span.xml-entity { + color: #A22; +} + +span.xml-error { + color: #F00; +} + +span.xml-text { + color: black; +} diff --git a/o3d/samples/third_party/codemirror/js/codemirror.js b/o3d/samples/third_party/codemirror/js/codemirror.js new file mode 100644 index 0000000..9b7b5db --- /dev/null +++ b/o3d/samples/third_party/codemirror/js/codemirror.js @@ -0,0 +1,189 @@ +/* CodeMirror main module + * + * Implements the CodeMirror constructor and prototype, which take care + * of initializing the editor frame, and providing the outside interface. + */ + +// The CodeMirrorConfig object is used to specify a default +// configuration. If you specify such an object before loading this +// file, the values you put into it will override the defaults given +// below. You can also assign to it after loading. +var CodeMirrorConfig = window.CodeMirrorConfig || {}; + +var CodeMirror = (function(){ + function setDefaults(object, defaults) { + for (var option in defaults) { + if (!object.hasOwnProperty(option)) + object[option] = defaults[option]; + } + } + function forEach(array, action) { + for (var i = 0; i < array.length; i++) + action(array[i]); + } + + // These default options can be overridden by passing a set of + // options to a specific CodeMirror constructor. See manual.html for + // their meaning. + setDefaults(CodeMirrorConfig, { + stylesheet: "", + path: "", + parserfile: [], + basefiles: ["util.js", "stringstream.js", "select.js", "undo.js", "editor.js", "tokenize.js"], + linesPerPass: 15, + passDelay: 200, + continuousScanning: false, + saveFunction: null, + onChange: null, + undoDepth: 20, + undoDelay: 800, + disableSpellcheck: true, + textWrapping: true, + readOnly: false, + width: "100%", + height: "300px", + autoMatchParens: false, + parserConfig: null, + dumbTabs: false + }); + + function CodeMirror(place, options) { + // Use passed options, if any, to override defaults. + this.options = options = options || {}; + setDefaults(options, CodeMirrorConfig); + + var frame = this.frame = document.createElement("IFRAME"); + frame.style.border = "0"; + frame.style.width = options.width; + frame.style.height = options.height; + // display: block occasionally suppresses some Firefox bugs, so we + // always add it, redundant as it sounds. + frame.style.display = "block"; + + if (place.appendChild) + place.appendChild(frame); + else + place(frame); + + // Link back to this object, so that the editor can fetch options + // and add a reference to itself. + frame.CodeMirror = this; + this.win = frame.contentWindow; + + if (typeof options.parserfile == "string") + options.parserfile = [options.parserfile]; + if (typeof options.stylesheet == "string") + options.stylesheet = [options.stylesheet]; + + var html = ["<html><head>"]; + forEach(options.stylesheet, function(file) { + html.push("<link rel=\"stylesheet\" type=\"text/css\" href=\"" + file + "\"/>"); + }); + forEach(options.basefiles.concat(options.parserfile), function(file) { + html.push("<script type=\"text/javascript\" src=\"" + options.path + file + "\"></script>"); + }); + html.push("</head><body style=\"border-width: 0;\" class=\"editbox\" spellcheck=\"" + + (options.disableSpellcheck ? "false" : "true") + "\"></body></html>"); + + var doc = this.win.document; + doc.open(); + doc.write(html.join("")); + doc.close(); + } + + CodeMirror.prototype = { + getCode: function() { + return this.editor.getCode(); + }, + setCode: function(code) { + this.editor.importCode(code); + }, + focus: function() { + this.win.focus(); + }, + jumpToChar: function(start, end) { + this.editor.jumpToChar(start, end); + this.focus(); + }, + jumpToLine: function(line) { + this.editor.jumpToLine(line); + this.focus(); + }, + currentLine: function() { + return this.editor.currentLine(); + }, + selection: function() { + return this.editor.selectedText(); + }, + reindent: function() { + this.editor.reindent(); + }, + replaceSelection: function(text, focus) { + var result = this.editor.replaceSelection(text); + if (focus) this.focus(); + return result; + }, + replaceChars: function(text, start, end) { + this.editor.replaceChars(text, start, end); + }, + getSearchCursor: function(string, fromCursor) { + return this.editor.getSearchCursor(string, fromCursor); + } + }; + + CodeMirror.replace = function(element) { + if (typeof element == "string") + element = document.getElementById(element); + return function(newElement) { + element.parentNode.replaceChild(newElement, element); + }; + }; + + CodeMirror.fromTextArea = function(area, options) { + if (typeof area == "string") + area = document.getElementById(area); + + options = options || {}; + if (area.style.width) options.width = area.style.width; + if (area.style.height) options.height = area.style.height; + if (options.content == null) options.content = area.value; + + if (area.form) { + function updateField() { + area.value = mirror.getCode(); + } + if (typeof area.form.addEventListener == "function") + area.form.addEventListener("submit", updateField, false); + else + area.form.attachEvent("onsubmit", updateField); + } + + function insert(frame) { + if (area.nextSibling) + area.parentNode.insertBefore(frame, area.nextSibling); + else + area.parentNode.appendChild(frame); + } + + area.style.display = "none"; + var mirror = new CodeMirror(insert, options); + return mirror; + }; + + CodeMirror.isProbablySupported = function() { + // This is rather awful, but can be useful. + var match; + if (window.opera) + return Number(window.opera.version()) >= 9.52; + else if (/Apple Computers, Inc/.test(navigator.vendor) && (match = navigator.userAgent.match(/Version\/(\d+(?:\.\d+)?)\./))) + return Number(match[1]) >= 3; + else if (document.selection && window.ActiveXObject && (match = navigator.userAgent.match(/MSIE (\d+(?:\.\d*)?)\b/))) + return Number(match[1]) >= 6; + else if (match = navigator.userAgent.match(/gecko\/(\d{8})/i)) + return Number(match[1]) >= 20050901; + else + return null; + }; + + return CodeMirror; +})(); diff --git a/o3d/samples/third_party/codemirror/js/editor.js b/o3d/samples/third_party/codemirror/js/editor.js new file mode 100644 index 0000000..d963025 --- /dev/null +++ b/o3d/samples/third_party/codemirror/js/editor.js @@ -0,0 +1,1052 @@ +/* The Editor object manages the content of the editable frame. It + * catches events, colours nodes, and indents lines. This file also + * holds some functions for transforming arbitrary DOM structures into + * plain sequences of <span> and <br> elements + */ + +var Editor = (function(){ + // The HTML elements whose content should be suffixed by a newline + // when converting them to flat text. + var newlineElements = {"P": true, "DIV": true, "LI": true}; + + // Create a set of white-space characters that will not be collapsed + // by the browser, but will not break text-wrapping either. + function safeWhiteSpace(n) { + var buffer = [], nb = true; + for (; n > 0; n--) { + buffer.push((nb || n == 1) ? nbsp : " "); + nb = !nb; + } + return buffer.join(""); + } + + function splitSpaces(string) { + if (string == " ") return nbsp; + else return string.replace(/[\t \u00a0]{2,}/g, function(s) {return safeWhiteSpace(s.length);}); + } + function asEditorLines(string) { + return splitSpaces(string.replace(/\u00a0/g, " ")).replace(/\r\n?/g, "\n").split("\n"); + } + + var internetExplorer = document.selection && window.ActiveXObject && /MSIE/.test(navigator.userAgent); + + // Helper function for traverseDOM. Flattens an arbitrary DOM node + // into an array of textnodes and <br> tags. + function simplifyDOM(root) { + var doc = root.ownerDocument; + var result = []; + var leaving = false; + + function simplifyNode(node) { + if (node.nodeType == 3) { + var text = node.nodeValue = splitSpaces(node.nodeValue.replace(/[\n\r]/g, "")); + if (text.length) leaving = false; + result.push(node); + } + else if (node.nodeName == "BR" && node.childNodes.length == 0) { + leaving = true; + result.push(node); + } + else { + forEach(node.childNodes, simplifyNode); + if (!leaving && newlineElements.hasOwnProperty(node.nodeName)) { + leaving = true; + result.push(doc.createElement("BR")); + } + } + } + + simplifyNode(root); + return result; + } + + // Creates a MochiKit-style iterator that goes over a series of DOM + // nodes. The values it yields are strings, the textual content of + // the nodes. It makes sure that all nodes up to and including the + // one whose text is being yielded have been 'normalized' to be just + // <span> and <br> elements. + // See the story.html file for some short remarks about the use of + // continuation-passing style in this iterator. + function traverseDOM(start){ + function yield(value, c){cc = c; return value;} + function push(fun, arg, c){return function(){return fun(arg, c);};} + function stop(){cc = stop; throw StopIteration;}; + var cc = push(scanNode, start, stop); + var owner = start.ownerDocument; + var nodeQueue = []; + + // Create a function that can be used to insert nodes after the + // one given as argument. + function pointAt(node){ + var parent = node.parentNode; + var next = node.nextSibling; + return function(newnode) { + parent.insertBefore(newnode, next); + }; + } + var point = null; + + // Insert a normalized node at the current point. If it is a text + // node, wrap it in a <span>, and give that span a currentText + // property -- this is used to cache the nodeValue, because + // directly accessing nodeValue is horribly slow on some browsers. + // The dirty property is used by the highlighter to determine + // which parts of the document have to be re-highlighted. + function insertPart(part){ + var text = "\n"; + if (part.nodeType == 3) { + text = part.nodeValue; + var span = owner.createElement("SPAN"); + span.className = "part"; + span.appendChild(part); + part = span; + part.currentText = text; + } + part.dirty = true; + nodeQueue.push(part); + point(part); + return text; + } + + // Extract the text and newlines from a DOM node, insert them into + // the document, and yield the textual content. Used to replace + // non-normalized nodes. + function writeNode(node, c){ + var toYield = []; + forEach(simplifyDOM(node), function(part) { + toYield.push(insertPart(part)); + }); + return yield(toYield.join(""), c); + } + + // Check whether a node is a normalized <span> element. + function partNode(node){ + if (node.nodeName == "SPAN" && node.childNodes.length == 1 && node.firstChild.nodeType == 3){ + node.currentText = node.firstChild.nodeValue; + return true; + } + return false; + } + + // Handle a node. Add its successor to the continuation if there + // is one, find out whether the node is normalized. If it is, + // yield its content, otherwise, normalize it (writeNode will take + // care of yielding). + function scanNode(node, c){ + if (node.nextSibling) + c = push(scanNode, node.nextSibling, c); + + if (partNode(node)){ + nodeQueue.push(node); + return yield(node.currentText, c); + } + else if (node.nodeName == "BR") { + nodeQueue.push(node); + return yield("\n", c); + } + else { + point = pointAt(node); + removeElement(node); + return writeNode(node, c); + } + } + + // MochiKit iterators are objects with a next function that + // returns the next value or throws StopIteration when there are + // no more values. + return {next: function(){return cc();}, nodes: nodeQueue}; + } + + // Determine the text size of a processed node. + function nodeSize(node) { + if (node.nodeName == "BR") + return 1; + else + return node.currentText.length; + } + + // Search backwards through the top-level nodes until the next BR or + // the start of the frame. + function startOfLine(node) { + while (node && node.nodeName != "BR") + node = node.previousSibling; + return node; + } + + function cleanText(text) { + return text.replace(/\u00a0/g, " "); + } + + // Client interface for searching the content of the editor. Create + // these by calling CodeMirror.getSearchCursor. To use, call + // findNext on the resulting object -- this returns a boolean + // indicating whether anything was found, and can be called again to + // skip to the next find. Use the select and replace methods to + // actually do something with the found locations. + function SearchCursor(editor, string, fromCursor) { + this.editor = editor; + this.history = editor.history; + this.history.commit(); + + // Are we currently at an occurrence of the search string? + this.atOccurrence = false; + // The object stores a set of nodes coming after its current + // position, so that when the current point is taken out of the + // DOM tree, we can still try to continue. + this.fallbackSize = 15; + var cursor; + // Start from the cursor when specified and a cursor can be found. + if (fromCursor && (cursor = select.cursorPos(this.editor.container))) { + this.line = cursor.node; + this.offset = cursor.offset; + } + else { + this.line = null; + this.offset = 0; + } + this.valid = !!string; + + // Create a matcher function based on the kind of string we have. + var target = string.split("\n"), self = this;; + this.matches = (target.length == 1) ? + // For one-line strings, searching can be done simply by calling + // indexOf on the current line. + function() { + var match = cleanText(self.history.textAfter(self.line).slice(self.offset)).indexOf(string); + if (match > -1) + return {from: {node: self.line, offset: self.offset + match}, + to: {node: self.line, offset: self.offset + match + string.length}}; + } : + // Multi-line strings require internal iteration over lines, and + // some clunky checks to make sure the first match ends at the + // end of the line and the last match starts at the start. + function() { + var firstLine = cleanText(self.history.textAfter(self.line).slice(self.offset)); + var match = firstLine.lastIndexOf(target[0]); + if (match == -1 || match != firstLine.length - target[0].length) + return false; + var startOffset = self.offset + match; + + var line = self.history.nodeAfter(self.line); + for (var i = 1; i < target.length - 1; i++) { + if (cleanText(self.history.textAfter(line)) != target[i]) + return false; + line = self.history.nodeAfter(line); + } + + if (cleanText(self.history.textAfter(line)).indexOf(target[target.length - 1]) != 0) + return false; + + return {from: {node: self.line, offset: startOffset}, + to: {node: line, offset: target[target.length - 1].length}}; + }; + } + + SearchCursor.prototype = { + findNext: function() { + if (!this.valid) return false; + this.atOccurrence = false; + var self = this; + + // Go back to the start of the document if the current line is + // no longer in the DOM tree. + if (this.line && !this.line.parentNode) { + this.line = null; + this.offset = 0; + } + + // Set the cursor's position one character after the given + // position. + function saveAfter(pos) { + if (self.history.textAfter(pos.node).length < pos.offset) { + self.line = pos.node; + self.offset = pos.offset + 1; + } + else { + self.line = self.history.nodeAfter(pos.node); + self.offset = 0; + } + } + + while (true) { + var match = this.matches(); + // Found the search string. + if (match) { + this.atOccurrence = match; + saveAfter(match.from); + return true; + } + this.line = this.history.nodeAfter(this.line); + this.offset = 0; + // End of document. + if (!this.line) { + this.valid = false; + return false; + } + } + }, + + select: function() { + if (this.atOccurrence) { + select.setCursorPos(this.editor.container, this.atOccurrence.from, this.atOccurrence.to); + select.scrollToCursor(this.editor.container); + } + }, + + replace: function(string) { + if (this.atOccurrence) { + var end = this.editor.replaceRange(this.atOccurrence.from, this.atOccurrence.to, string); + this.line = end.node; + this.offset = end.offset; + this.atOccurrence = false; + } + } + }; + + // The Editor object is the main inside-the-iframe interface. + function Editor(options) { + this.options = options; + this.parent = parent; + this.doc = document; + this.container = this.doc.body; + this.win = window; + this.history = new History(this.container, this.options.undoDepth, this.options.undoDelay, + this, options.onChange); + + if (!Editor.Parser) + throw "No parser loaded."; + if (options.parserConfig && Editor.Parser.configure) + Editor.Parser.configure(options.parserConfig); + + if (!options.textWrapping) + this.container.style.whiteSpace = "pre"; + + select.setCursorPos(this.container, {node: null, offset: 0}); + + this.dirty = []; + if (options.content) + this.importCode(options.content); + else // FF acts weird when the editable document is completely empty + this.container.appendChild(this.doc.createElement("BR")); + + if (!options.readOnly) { + if (options.continuousScanning !== false) { + this.scanner = this.documentScanner(options.linesPerPass); + this.delayScanning(); + } + + function setEditable() { + // In IE, designMode frames can not run any scripts, so we use + // contentEditable instead. + if (document.body.contentEditable != undefined && /MSIE/.test(navigator.userAgent)) + document.body.contentEditable = "true"; + else + document.designMode = "on"; + } + + // If setting the frame editable fails, try again when the user + // focus it (happens when the frame is not visible on + // initialisation, in Firefox). + try { + setEditable(); + } + catch(e) { + var focusEvent = addEventHandler(document, "focus", function() { + removeEventHandler(focusEvent); + setEditable(); + }); + } + + addEventHandler(document, "keydown", method(this, "keyDown")); + addEventHandler(document, "keypress", method(this, "keyPress")); + addEventHandler(document, "keyup", method(this, "keyUp")); + addEventHandler(document.body, "paste", method(this, "markCursorDirty")); + addEventHandler(document.body, "cut", method(this, "markCursorDirty")); + if (this.options.autoMatchParens) + addEventHandler(document.body, "click", method(this, "scheduleParenBlink")); + } + } + + function isSafeKey(code) { + return (code >= 16 && code <= 18) || // shift, control, alt + (code >= 33 && code <= 40); // arrows, home, end + } + + Editor.prototype = { + // Import a piece of code into the editor. + importCode: function(code) { + this.history.push(null, null, asEditorLines(code)); + this.history.reset(); + }, + + // Extract the code from the editor. + getCode: function() { + if (!this.container.firstChild) + return ""; + + var accum = []; + forEach(traverseDOM(this.container.firstChild), method(accum, "push")); + return cleanText(accum.join("")); + }, + + // Move the cursor to the start of a specific line (counting from 1). + jumpToLine: function(line) { + if (line <= 1 || !this.container.firstChild) { + select.focusAfterNode(null, this.container); + } + else { + var pos = this.container.firstChild; + while (true) { + if (pos.nodeName == "BR") line--; + if (line <= 1 || !pos.nextSibling) break; + pos = pos.nextSibling; + } + select.focusAfterNode(pos, this.container); + } + select.scrollToCursor(this.container); + }, + + // Find the line that the cursor is currently on. + currentLine: function() { + var pos = select.cursorPos(this.container, true), line = 1; + if (!pos) return 1; + for (cursor = pos.node; cursor; cursor = cursor.previousSibling) + if (cursor.nodeName == "BR") line++; + return line; + }, + + // Retrieve the selected text. + selectedText: function() { + var h = this.history; + h.commit(); + + var start = select.cursorPos(this.container, true), + end = select.cursorPos(this.container, false); + if (!start || !end) return ""; + + if (start.node == end.node) + return h.textAfter(start.node).slice(start.offset, end.offset); + + var text = [h.textAfter(start.node).slice(start.offset)]; + for (pos = h.nodeAfter(start.node); pos != end.node; pos = h.nodeAfter(pos)) + text.push(h.textAfter(pos)); + text.push(h.textAfter(end.node).slice(0, end.offset)); + return cleanText(text.join("\n")); + }, + + // Replace the selection with another piece of text. + replaceSelection: function(text) { + this.history.commit(); + var start = select.cursorPos(this.container, true), + end = select.cursorPos(this.container, false); + if (!start || !end) return false; + + end = this.replaceRange(start, end, text); + select.setCursorPos(this.container, start, end); + return true; + }, + + replaceRange: function(from, to, text) { + var lines = asEditorLines(text); + lines[0] = this.history.textAfter(from.node).slice(0, from.offset) + lines[0]; + var lastLine = lines[lines.length - 1]; + lines[lines.length - 1] = lastLine + this.history.textAfter(to.node).slice(to.offset); + var end = this.history.nodeAfter(to.node); + this.history.push(from.node, end, lines); + return {node: this.history.nodeBefore(end), + offset: lastLine.length}; + }, + + getSearchCursor: function(string, fromCursor) { + return new SearchCursor(this, string, fromCursor); + }, + + // Re-indent the whole buffer + reindent: function() { + if (this.container.firstChild) + this.indentRegion(null, this.container.lastChild); + }, + + // Intercept enter and tab, and assign their new functions. + keyDown: function(event) { + // Don't scan when the user is typing. + this.delayScanning(); + // Schedule a paren-highlight event, if configured. + if (this.options.autoMatchParens) + this.scheduleParenBlink(); + + if (event.keyCode == 13) { // enter + if (event.ctrlKey) { + this.reparseBuffer(); + } + else { + select.insertNewlineAtCursor(this.win); + this.indentAtCursor(); + select.scrollToCursor(this.container); + } + event.stop(); + } + else if (event.keyCode == 9) { // tab + this.handleTab(); + event.stop(); + } + else if (event.ctrlKey) { + if (event.keyCode == 90 || event.keyCode == 8) { // Z, backspace + this.history.undo(); + event.stop(); + } + else if (event.keyCode == 89) { // Y + this.history.redo(); + event.stop(); + } + else if (event.keyCode == 83 && this.options.saveFunction) { // S + this.options.saveFunction(); + event.stop(); + } + } + }, + + // Check for characters that should re-indent the current line, + // and prevent Opera from handling enter and tab anyway. + keyPress: function(event) { + var electric = Editor.Parser.electricChars; + // Hack for Opera, and Firefox on OS X, in which stopping a + // keydown event does not prevent the associated keypress event + // from happening, so we have to cancel enter and tab again + // here. + if (event.code == 13 || event.code == 9) + event.stop(); + else if ((event.character == "[" || event.character == "]") && event.ctrlKey) + event.stop(), this.blinkParens(); + else if (electric && electric.indexOf(event.character) != -1) + this.parent.setTimeout(method(this, "indentAtCursor"), 0); + }, + + // Mark the node at the cursor dirty when a non-safe key is + // released. + keyUp: function(event) { + if (internetExplorer) + this.container.createTextRange().execCommand("unlink"); + + if (!isSafeKey(event.keyCode)) + this.markCursorDirty(); + }, + + // Indent the line following a given <br>, or null for the first + // line. If given a <br> element, this must have been highlighted + // so that it has an indentation method. Returns the whitespace + // element that has been modified or created (if any). + indentLineAfter: function(start) { + // whiteSpace is the whitespace span at the start of the line, + // or null if there is no such node. + var whiteSpace = start ? start.nextSibling : this.container.firstChild; + if (whiteSpace && !hasClass(whiteSpace, "whitespace")) + whiteSpace = null; + + // Sometimes the start of the line can influence the correct + // indentation, so we retrieve it. + var firstText = whiteSpace ? whiteSpace.nextSibling : (start ? start.nextSibling : this.container.firstChild); + var nextChars = (start && firstText && firstText.currentText) ? firstText.currentText : ""; + + // Ask the lexical context for the correct indentation, and + // compute how much this differs from the current indentation. + var indent = start ? start.indentation(nextChars) : 0; + var indentDiff = indent - (whiteSpace ? whiteSpace.currentText.length : 0); + + // If there is too much, this is just a matter of shrinking a span. + if (indentDiff < 0) { + if (indent == 0) { + removeElement(whiteSpace); + whiteSpace = null; + } + else { + whiteSpace.currentText = safeWhiteSpace(indent); + whiteSpace.firstChild.nodeValue = whiteSpace.currentText; + } + } + // Not enough... + else if (indentDiff > 0) { + // If there is whitespace, we grow it. + if (whiteSpace) { + whiteSpace.currentText = safeWhiteSpace(indent); + whiteSpace.firstChild.nodeValue = whiteSpace.currentText; + } + // Otherwise, we have to add a new whitespace node. + else { + whiteSpace = this.doc.createElement("SPAN"); + whiteSpace.className = "part whitespace"; + whiteSpace.appendChild(this.doc.createTextNode(safeWhiteSpace(indent))); + if (start) + insertAfter(whiteSpace, start); + else + this.container.insertBefore(whiteSpace, this.container.firstChild); + } + } + return whiteSpace; + }, + + // Re-highlight the selected part of the document. + highlightAtCursor: function() { + var pos = select.selectionTopNode(this.container, true); + var to = select.selectionTopNode(this.container, false); + if (pos === false || !to) return; + // Skip one node ahead to make sure the cursor itself is + // *inside* a highlighted line. + if (to.nextSibling) to = to.nextSibling; + + var sel = select.markSelection(this.win); + var toIsText = to.nodeType == 3; + if (!toIsText) to.dirty = true; + + // Highlight lines as long as to is in the document and dirty. + while (to.parentNode == this.container && (toIsText || to.dirty)) { + var result = this.highlight(pos, 1, true); + if (result) pos = result.node; + if (!result || result.left) break; + } + select.selectMarked(sel); + }, + + // When tab is pressed with text selected, the whole selection is + // re-indented, when nothing is selected, the line with the cursor + // is re-indented. + handleTab: function() { + if (this.options.dumbTabs) { + select.insertTabAtCursor(this.win); + } + else { + var start = select.selectionTopNode(this.container, true), + end = select.selectionTopNode(this.container, false); + if (start === false || end === false) return; + + if (start == end) + this.indentAtCursor(); + else + this.indentRegion(start, end); + } + }, + + // Delay (or initiate) the next paren blink event. + scheduleParenBlink: function() { + if (this.parenEvent) this.parent.clearTimeout(this.parenEvent); + this.parenEvent = this.parent.setTimeout(method(this, "blinkParens"), 300); + }, + + // Take the token before the cursor. If it contains a character in + // '()[]{}', search for the matching paren/brace/bracket, and + // highlight them in green for a moment, or red if no proper match + // was found. + blinkParens: function() { + // Clear the event property. + if (this.parenEvent) this.parent.clearTimeout(this.parenEvent); + this.parenEvent = null; + + // Extract a 'paren' from a piece of text. + function paren(node) { + if (node.currentText) { + var match = node.currentText.match(/^[\s\u00a0]*([\(\)\[\]{}])[\s\u00a0]*$/); + return match && match[1]; + } + } + // Determine the direction a paren is facing. + function forward(ch) { + return /[\(\[\{]/.test(ch); + } + + this.highlightAtCursor(); + var cursor = select.selectionTopNode(this.container, true), ch, self = this; + if (!cursor || !(ch = paren(cursor))) return; + // We only look for tokens with the same className. + var className = cursor.className, dir = forward(ch), match = matching[ch]; + + // Since parts of the document might not have been properly + // highlighted, and it is hard to know in advance which part we + // have to scan, we just try, and when we find dirty nodes we + // abort, parse them, and re-try. + function tryFindMatch() { + var stack = [], ch, ok = true;; + for (var runner = cursor; runner; runner = dir ? runner.nextSibling : runner.previousSibling) { + if (runner.className == className && runner.nodeName == "SPAN" && (ch = paren(runner))) { + if (forward(ch) == dir) + stack.push(ch); + else if (!stack.length) + ok = false; + else if (stack.pop() != matching[ch]) + ok = false; + if (!stack.length) break; + } + else if (runner.dirty || runner.nodeName != "SPAN" && runner.nodeName != "BR") { + return {node: runner, status: "dirty"}; + } + } + return {node: runner, status: runner && ok}; + } + // Temporarily give the relevant nodes a colour. + function blink(node, ok) { + node.style.fontWeight = "bold"; + node.style.color = ok ? "#8F8" : "#F88"; + self.parent.setTimeout(function() {node.style.fontWeight = ""; node.style.color = "";}, 500); + } + + while (true) { + var found = tryFindMatch(); + if (found.status == "dirty") { + this.highlight(found.node, 1); + // Needed because in some corner cases a highlight does not + // reach a node. + found.node.dirty = false; + continue; + } + else { + blink(cursor, found.status); + if (found.node) blink(found.node, found.status); + break; + } + } + }, + + // Adjust the amount of whitespace at the start of the line that + // the cursor is on so that it is indented properly. + indentAtCursor: function() { + if (!this.container.firstChild) return; + // The line has to have up-to-date lexical information, so we + // highlight it first. + this.highlightAtCursor(); + var cursor = select.selectionTopNode(this.container, false); + // If we couldn't determine the place of the cursor, + // there's nothing to indent. + if (cursor === false) + return; + var lineStart = startOfLine(cursor); + var whiteSpace = this.indentLineAfter(lineStart); + if (cursor == lineStart && whiteSpace) + cursor = whiteSpace; + // This means the indentation has probably messed up the cursor. + if (cursor == whiteSpace) + select.focusAfterNode(cursor, this.container); + }, + + // Indent all lines whose start falls inside of the current + // selection. + indentRegion: function(current, end) { + var sel = select.markSelection(this.win); + if (!current) + this.indentLineAfter(current); + else + current = startOfLine(current.previousSibling); + end = startOfLine(end); + + while (true) { + var result = this.highlight(current, 1); + var next = result ? result.node : null; + + while (current != next) + current = current ? current.nextSibling : this.container.firstChild; + if (next) + this.indentLineAfter(next); + if (current == end) + break; + } + select.selectMarked(sel); + }, + + // Find the node that the cursor is in, mark it as dirty, and make + // sure a highlight pass is scheduled. + markCursorDirty: function() { + var cursor = select.selectionTopNode(this.container, false); + if (cursor !== false && this.container.firstChild) { + this.scheduleHighlight(); + this.addDirtyNode(cursor || this.container.firstChild); + } + }, + + reparseBuffer: function() { + forEach(this.container.childNodes, function(node) {node.dirty = true;}); + if (this.container.firstChild) + this.addDirtyNode(this.container.firstChild); + }, + + // Add a node to the set of dirty nodes, if it isn't already in + // there. + addDirtyNode: function(node) { + node = node || this.container.firstChild; + if (!node) return; + + for (var i = 0; i < this.dirty.length; i++) + if (this.dirty[i] == node) return; + + if (node.nodeType != 3) + node.dirty = true; + this.dirty.push(node); + }, + + // Cause a highlight pass to happen in options.passDelay + // milliseconds. Clear the existing timeout, if one exists. This + // way, the passes do not happen while the user is typing, and + // should as unobtrusive as possible. + scheduleHighlight: function() { + // Timeouts are routed through the parent window, because on + // some browsers designMode windows do not fire timeouts. + var self = this; + this.parent.clearTimeout(this.highlightTimeout); + this.highlightTimeout = this.parent.setTimeout(function(){self.highlightDirty();}, this.options.passDelay); + }, + + // Fetch one dirty node, and remove it from the dirty set. + getDirtyNode: function() { + while (this.dirty.length > 0) { + var found = this.dirty.pop(); + // IE8 sometimes throws an unexplainable 'invalid argument' + // exception for found.parentNode + try { + // If the node has been coloured in the meantime, or is no + // longer in the document, it should not be returned. + if ((found.dirty || found.nodeType == 3) && found.parentNode) + return found; + } catch (e) {} + } + return null; + }, + + // Pick dirty nodes, and highlight them, until + // options.linesPerPass lines have been highlighted. The highlight + // method will continue to next lines as long as it finds dirty + // nodes. It returns an object indicating the amount of lines + // left, and information about the place where it stopped. If + // there are dirty nodes left after this function has spent all + // its lines, it shedules another highlight to finish the job. + highlightDirty: function(force) { + var lines = force ? Infinity : this.options.linesPerPass; + var sel = select.markSelection(this.win); + var start; + while (lines > 0 && (start = this.getDirtyNode())){ + var result = this.highlight(start, lines); + if (result) { + lines = result.left; + if (result.node && result.dirty) + this.addDirtyNode(result.node); + } + } + select.selectMarked(sel); + if (start) + this.scheduleHighlight(); + return this.dirty.length == 0; + }, + + // Creates a function that, when called through a timeout, will + // continuously re-parse the document. + documentScanner: function(linesPer) { + var self = this, pos = null; + return function() { + // If the current node is no longer in the document... oh + // well, we start over. + if (pos && pos.parentNode != self.container) + pos = null; + var sel = select.markSelection(self.win); + var result = self.highlight(pos, linesPer, true); + select.selectMarked(sel); + pos = result ? (result.node && result.node.nextSibling) : null; + self.delayScanning(); + }; + }, + + // Starts the continuous scanning process for this document after + // a given interval. + delayScanning: function() { + if (this.scanner) { + this.parent.clearTimeout(this.documentScan); + this.documentScan = this.parent.setTimeout(this.scanner, this.options.continuousScanning); + } + }, + + // The function that does the actual highlighting/colouring (with + // help from the parser and the DOM normalizer). Its interface is + // rather overcomplicated, because it is used in different + // situations: ensuring that a certain line is highlighted, or + // highlighting up to X lines starting from a certain point. The + // 'from' argument gives the node at which it should start. If + // this is null, it will start at the beginning of the frame. When + // a number of lines is given with the 'lines' argument, it will + // colour no more than that amount. If at any time it comes across + // a 'clean' line (no dirty nodes), it will stop, except when + // 'cleanLines' is true. + highlight: function(from, lines, cleanLines){ + var container = this.container, self = this; + + if (!container.firstChild) + return; + // Backtrack to the first node before from that has a partial + // parse stored. + while (from && (!from.parserFromHere || from.dirty)) + from = from.previousSibling; + // If we are at the end of the document, do nothing. + if (from && !from.nextSibling) + return; + + // Check whether a part (<span> node) and the corresponding token + // match. + function correctPart(token, part){ + return !part.reduced && part.currentText == token.value && part.className == "part " + token.style; + } + // Shorten the text associated with a part by chopping off + // characters from the front. Note that only the currentText + // property gets changed. For efficiency reasons, we leave the + // nodeValue alone -- we set the reduced flag to indicate that + // this part must be replaced. + function shortenPart(part, minus){ + part.currentText = part.currentText.substring(minus); + part.reduced = true; + } + // Create a part corresponding to a given token. + function tokenPart(token){ + var part = self.doc.createElement("SPAN"); + part.className = "part " + token.style; + part.appendChild(self.doc.createTextNode(token.value)); + part.currentText = token.value; + return part; + } + + // Get the token stream. If from is null, we start with a new + // parser from the start of the frame, otherwise a partial parse + // is resumed. + var traversal = traverseDOM(from ? from.nextSibling : container.firstChild), + stream = stringStream(traversal), + parsed = from ? from.parserFromHere(stream) : Editor.Parser.make(stream); + + // parts is an interface to make it possible to 'delay' fetching + // the next DOM node until we are completely done with the one + // before it. This is necessary because often the next node is + // not yet available when we want to proceed past the current + // one. + var parts = { + current: null, + // Fetch current node. + get: function(){ + if (!this.current) + this.current = traversal.nodes.shift(); + return this.current; + }, + // Advance to the next part (do not fetch it yet). + next: function(){ + this.current = null; + }, + // Remove the current part from the DOM tree, and move to the + // next. + remove: function(){ + container.removeChild(this.get()); + this.current = null; + }, + // Advance to the next part that is not empty, discarding empty + // parts. + getNonEmpty: function(){ + var part = this.get(); + // Allow empty nodes when they are alone on a line, needed + // for the FF cursor bug workaround (see select.js, + // insertNewlineAtCursor). + while (part && part.nodeName == "SPAN" && part.currentText == ""){ + var old = part; + this.remove(); + part = this.get(); + // Adjust selection information, if any. See select.js for details. + select.replaceSelection(old.firstChild, part.firstChild || part, 0, 0); + } + return part; + } + }; + + var lineDirty = false, lineNodes = 0; + + // This forEach loops over the tokens from the parsed stream, and + // at the same time uses the parts object to proceed through the + // corresponding DOM nodes. + forEach(parsed, function(token){ + var part = parts.getNonEmpty(); + + if (token.value == "\n"){ + // The idea of the two streams actually staying synchronized + // is such a long shot that we explicitly check. + if (part.nodeName != "BR") + throw "Parser out of sync. Expected BR."; + + if (part.dirty || !part.indentation) lineDirty = true; + if (lineDirty) self.history.touch(from); + from = part; + + // Every <br> gets a copy of the parser state and a lexical + // context assigned to it. The first is used to be able to + // later resume parsing from this point, the second is used + // for indentation. + part.parserFromHere = parsed.copy(); + part.indentation = token.indentation; + part.dirty = false; + // A clean line with more than one node means we are done. + // Throwing a StopIteration is the way to break out of a + // MochiKit forEach loop. + if ((lines !== undefined && --lines <= 0) || (!lineDirty && lineNodes > 1 && !cleanLines)) + throw StopIteration; + lineDirty = false; lineNodes = 0; + parts.next(); + } + else { + if (part.nodeName != "SPAN") + throw "Parser out of sync. Expected SPAN."; + if (part.dirty) + lineDirty = true; + lineNodes++; + + // If the part matches the token, we can leave it alone. + if (correctPart(token, part)){ + part.dirty = false; + parts.next(); + } + // Otherwise, we have to fix it. + else { + lineDirty = true; + // Insert the correct part. + var newPart = tokenPart(token); + container.insertBefore(newPart, part); + var tokensize = token.value.length; + var offset = 0; + // Eat up parts until the text for this token has been + // removed, adjusting the stored selection info (see + // select.js) in the process. + while (tokensize > 0) { + part = parts.get(); + var partsize = part.currentText.length; + select.replaceSelection(part.firstChild, newPart.firstChild, tokensize, offset); + if (partsize > tokensize){ + shortenPart(part, tokensize); + tokensize = 0; + } + else { + tokensize -= partsize; + offset += partsize; + parts.remove(); + } + } + } + } + }); + if (lineDirty) this.history.touch(from); + + // The function returns some status information that is used by + // hightlightDirty to determine whether and where it has to + // continue. + return {left: lines, + node: parts.get(), + dirty: lineDirty}; + } + }; + + return Editor; +})(); + +addEventHandler(window, "load", function() { + var CodeMirror = window.frameElement.CodeMirror; + CodeMirror.editor = new Editor(CodeMirror.options); + if (CodeMirror.options.initCallback) { + this.parent.setTimeout(function(){ + CodeMirror.options.initCallback(CodeMirror); + }, 0); + } +}); diff --git a/o3d/samples/third_party/codemirror/js/mirrorframe.js b/o3d/samples/third_party/codemirror/js/mirrorframe.js new file mode 100644 index 0000000..56a6c42 --- /dev/null +++ b/o3d/samples/third_party/codemirror/js/mirrorframe.js @@ -0,0 +1,83 @@ +/* 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) 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) { + if (!this.mirror.replaceSelection("function " + name + "() {\n \n}\n\n" + name + ".prototype = {\n \n};\n", true)) + alert("Place the cursor in the document first."); + } + }, + + reindent: function() { + this.mirror.reindent(); + } +}; diff --git a/o3d/samples/third_party/codemirror/js/parsecss.js b/o3d/samples/third_party/codemirror/js/parsecss.js new file mode 100644 index 0000000..a49a8b0 --- /dev/null +++ b/o3d/samples/third_party/codemirror/js/parsecss.js @@ -0,0 +1,155 @@ +/* Simple parser for CSS */ + +var CSSParser = Editor.Parser = (function() { + var tokenizeCSS = (function() { + function normal(source, setState) { + var ch = source.next(); + if (ch == "@") { + source.nextWhile(matcher(/\w/)); + return "css-at"; + } + else if (ch == "/" && source.equals("*")) { + setState(inCComment); + return null; + } + else if (ch == "<" && source.equals("!")) { + setState(inSGMLComment); + return null; + } + else if (ch == "=") { + return "css-compare"; + } + else if (source.equals("=") && (ch == "~" || ch == "|")) { + source.next(); + return "css-compare"; + } + else if (ch == "\"" || ch == "'") { + setState(inString(ch)); + return null; + } + else if (ch == "#") { + source.nextWhile(matcher(/\w/)); + return "css-hash"; + } + else if (ch == "!") { + source.nextWhile(matcher(/[ \t]/)); + source.nextWhile(matcher(/\w/)); + return "css-important"; + } + else if (/\d/.test(ch)) { + source.nextWhile(matcher(/[\w.%]/)); + return "css-unit"; + } + else if (/[,.+>*\/]/.test(ch)) { + return "css-select-op"; + } + else if (/[;{}:\[\]]/.test(ch)) { + return "css-punctuation"; + } + else { + source.nextWhile(matcher(/[\w\\\-_]/)); + return "css-identifier"; + } + } + + function inCComment(source, setState) { + var maybeEnd = false; + while (!source.endOfLine()) { + var ch = source.next(); + if (maybeEnd && ch == "/") { + setState(normal); + break; + } + maybeEnd = (ch == "*"); + } + return "css-comment"; + } + + function inSGMLComment(source, setState) { + var dashes = 0; + while (!source.endOfLine()) { + var ch = source.next(); + if (dashes >= 2 && ch == ">") { + setState(normal); + break; + } + dashes = (ch == "-") ? dashes + 1 : 0; + } + return "css-comment"; + } + + function inString(quote) { + return function(source, setState) { + var escaped = false; + while (!source.endOfLine()) { + var ch = source.next(); + if (ch == quote && !escaped) + break; + escaped = ch == "\\"; + } + if (!escaped) + setState(normal); + return "css-string"; + }; + } + + return function(source, startState) { + return tokenizer(source, startState || normal); + }; + })(); + + function indentCSS(inBraces, inRule, base) { + return function(nextChars) { + if (!inBraces || /^\}/.test(nextChars)) return base; + else if (inRule) return base + 4; + else return base + 2; + }; + } + + // This is a very simplistic parser -- since CSS does not really + // nest, it works acceptably well, but some nicer colouroing could + // be provided with a more complicated parser. + function parseCSS(source, basecolumn) { + basecolumn = basecolumn || 0; + var tokens = tokenizeCSS(source); + var inBraces = false, inRule = false; + + var iter = { + next: function() { + var token = tokens.next(), style = token.style, content = token.content; + + if (style == "css-identifier" && inRule) + token.style = "css-value"; + if (style == "css-hash") + token.style = inRule ? "css-colorcode" : "css-identifier"; + + if (content == "\n") + token.indentation = indentCSS(inBraces, inRule, basecolumn); + + if (content == "{") + inBraces = true; + else if (content == "}") + inBraces = inRule = false; + else if (inBraces && content == ";") + inRule = false; + else if (inBraces && style != "css-comment" && style != "whitespace") + inRule = true; + + return token; + }, + + copy: function() { + var _inBraces = inBraces, _inRule = inRule, _tokenState = tokens.state; + return function(source) { + tokens = tokenizeCSS(source, _tokenState); + inBraces = _inBraces; + inRule = _inRule; + return iter; + }; + } + }; + return iter; + } + + return {make: parseCSS, electricChars: "}"}; +})(); diff --git a/o3d/samples/third_party/codemirror/js/parsehtmlmixed.js b/o3d/samples/third_party/codemirror/js/parsehtmlmixed.js new file mode 100644 index 0000000..f093b63 --- /dev/null +++ b/o3d/samples/third_party/codemirror/js/parsehtmlmixed.js @@ -0,0 +1,66 @@ +var HTMLMixedParser = Editor.Parser = (function() { + if (!(CSSParser && JSParser && XMLParser)) + throw new Error("CSS, JS, and XML parsers must be loaded for HTML mixed mode to work."); + XMLParser.configure({useHTMLKludges: true}); + + function stringAhead(stream, string) { + stream.nextWhile(matcher(/[\s\u00a0]/)); + var found = stream.matches(string, false); + stream.reset(); + return found; + } + + function parseMixed(stream) { + var htmlParser = XMLParser.make(stream), localParser = null, inTag = false; + var iter = {next: top, copy: copy}; + + function top() { + var token = htmlParser.next(); + if (token.content == "<") + inTag = true; + else if (token.style == "xml-tagname" && inTag === true) + inTag = token.content.toLowerCase(); + else if (token.content == ">") { + if (inTag == "script") + iter.next = local(JSParser, "</script"); + else if (inTag == "style") + iter.next = local(CSSParser, "</style"); + inTag = false; + } + return token; + } + function local(parser, tag) { + localParser = parser.make(stream, htmlParser.indentation() + 2); + return function() { + if (stringAhead(stream, tag)) { + localParser = null; + iter.next = top; + return top(); + } + var token = localParser.next(); + var lt = token.value.lastIndexOf("<"), sz = Math.min(token.value.length - lt, tag.length); + if (lt != -1 && token.value.slice(lt, lt + sz).toLowerCase() == tag.slice(0, sz) && stringAhead(stream, tag.slice(sz))) { + stream.push(token.value.slice(lt)); + token.value = token.value.slice(0, lt); + } + return token; + }; + } + + function copy() { + var _html = htmlParser.copy(), _local = localParser && localParser.copy(), + _next = iter.next, _inTag = inTag; + return function(_stream) { + stream = _stream; + htmlParser = _html(_stream); + localParser = _local && _local(_stream); + iter.next = _next; + inTag = _inTag; + return iter; + }; + } + return iter; + } + + return {make: parseMixed, electricChars: "{}/"}; +})(); diff --git a/o3d/samples/third_party/codemirror/js/parsejavascript.js b/o3d/samples/third_party/codemirror/js/parsejavascript.js new file mode 100644 index 0000000..afd5a29 --- /dev/null +++ b/o3d/samples/third_party/codemirror/js/parsejavascript.js @@ -0,0 +1,322 @@ +/* Parse function for JavaScript. Makes use of the tokenizer from + * tokenizejavascript.js. Note that your parsers do not have to be + * this complicated -- if you don't want to recognize local variables, + * in many languages it is enough to just look for braces, semicolons, + * parentheses, etc, and know when you are inside a string or comment. + * + * See manual.html for more info about the parser interface. + */ + +var JSParser = Editor.Parser = (function() { + // Token types that can be considered to be atoms. + var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true}; + // Constructor for the lexical context objects. + function JSLexical(indented, column, type, align, prev) { + // indentation at start of this line + this.indented = indented; + // column at which this scope was opened + this.column = column; + // type of scope ('vardef', 'stat' (statement), 'form' (special form), '[', '{', or '(') + this.type = type; + // '[', '{', or '(' blocks that have any text after their opening + // character are said to be 'aligned' -- any lines below are + // indented all the way to the opening character. + if (align != null) + this.align = align; + // Parent scope, if any. + this.prev = prev; + } + // My favourite JavaScript indentation rules. + function indentJS(lexical) { + return function(firstChars) { + var firstChar = firstChars && firstChars.charAt(0); + var closing = firstChar == lexical.type; + if (lexical.type == "vardef") + return lexical.indented + 4; + else if (lexical.type == "form" && firstChar == "{") + return lexical.indented; + else if (lexical.type == "stat" || lexical.type == "form") + return lexical.indented + 2; + else if (lexical.align) + return lexical.column - (closing ? 1 : 0); + else + return lexical.indented + (closing ? 0 : 2); + }; + } + + // The parser-iterator-producing function itself. + function parseJS(input, basecolumn) { + // Wrap the input in a token stream + var tokens = tokenizeJavaScript(input); + // The parser state. cc is a stack of actions that have to be + // performed to finish the current statement. For example we might + // know that we still need to find a closing parenthesis and a + // semicolon. Actions at the end of the stack go first. It is + // initialized with an infinitely looping action that consumes + // whole statements. + var cc = [statements]; + // Context contains information about the current local scope, the + // variables defined in that, and the scopes above it. + var context = null; + // The lexical scope, used mostly for indentation. + var lexical = new JSLexical((basecolumn || 0) - 2, 0, "block", false); + // Current column, and the indentation at the start of the current + // line. Used to create lexical scope objects. + var column = 0; + var indented = 0; + // Variables which are used by the mark, cont, and pass functions + // below to communicate with the driver loop in the 'next' + // function. + var consume, marked; + + // The iterator object. + var parser = {next: next, copy: copy}; + + function next(){ + // Start by performing any 'lexical' actions (adjusting the + // lexical variable), or the operations below will be working + // with the wrong lexical state. + while(cc[cc.length - 1].lex) + cc.pop()(); + + // Fetch a token. + var token = tokens.next(); + + // Adjust column and indented. + if (token.type == "whitespace" && column == 0) + indented = token.value.length; + column += token.value.length; + if (token.content == "\n"){ + indented = column = 0; + // If the lexical scope's align property is still undefined at + // the end of the line, it is an un-aligned scope. + if (!("align" in lexical)) + lexical.align = false; + // Newline tokens get an indentation function associated with + // them. + token.indentation = indentJS(lexical); + } + // No more processing for meaningless tokens. + if (token.type == "whitespace" || token.type == "comment") + return token; + // When a meaningful token is found and the lexical scope's + // align is undefined, it is an aligned scope. + if (!("align" in lexical)) + lexical.align = true; + + // Execute actions until one 'consumes' the token and we can + // return it. Marked is used to + while(true){ + consume = marked = false; + // Take and execute the topmost action. + cc.pop()(token.type, token.content); + if (consume){ + // Marked is used to change the style of the current token. + if (marked) + token.style = marked; + // Here we differentiate between local and global variables. + else if (token.type == "variable" && inScope(token.content)) + token.style = "js-localvariable"; + return token; + } + } + } + + // This makes a copy of the parser state. It stores all the + // stateful variables in a closure, and returns a function that + // will restore them when called with a new input stream. Note + // that the cc array has to be copied, because it is contantly + // being modified. Lexical objects are not mutated, and context + // objects are not mutated in a harmful way, so they can be shared + // between runs of the parser. + function copy(){ + var _context = context, _lexical = lexical, _cc = cc.concat([]), _tokenState = tokens.state; + + return function(input){ + context = _context; + lexical = _lexical; + cc = _cc.concat([]); // copies the array + column = indented = 0; + tokens = tokenizeJavaScript(input, _tokenState); + return parser; + }; + } + + // Helper function for pushing a number of actions onto the cc + // stack in reverse order. + function push(fs){ + for (var i = fs.length - 1; i >= 0; i--) + cc.push(fs[i]); + } + // cont and pass are used by the action functions to add other + // actions to the stack. cont will cause the current token to be + // consumed, pass will leave it for the next action. + function cont(){ + push(arguments); + consume = true; + } + function pass(){ + push(arguments); + consume = false; + } + // Used to change the style of the current token. + function mark(style){ + marked = style; + } + + // Push a new scope. Will automatically link the the current + // scope. + function pushcontext(){ + context = {prev: context, vars: {"this": true, "arguments": true}}; + } + // Pop off the current scope. + function popcontext(){ + context = context.prev; + } + // Register a variable in the current scope. + function register(varname){ + if (context){ + mark("js-variabledef"); + context.vars[varname] = true; + } + } + // Check whether a variable is defined in the current scope. + function inScope(varname){ + var cursor = context; + while (cursor) { + if (cursor.vars[varname]) + return true; + cursor = cursor.prev; + } + return false; + } + + // Push a new lexical context of the given type. + function pushlex(type){ + var result = function(){ + lexical = new JSLexical(indented, column, type, null, lexical) + }; + result.lex = true; + return result; + } + // Pop off the current lexical context. + function poplex(){ + lexical = lexical.prev; + } + poplex.lex = true; + // The 'lex' flag on these actions is used by the 'next' function + // to know they can (and have to) be ran before moving on to the + // next token. + + // Creates an action that discards tokens until it finds one of + // the given type. + function expect(wanted){ + return function(type){ + if (type == wanted) cont(); + else cont(arguments.callee); + }; + } + + // Looks for a statement, and then calls itself. + function statements(type){ + return pass(statement, statements); + } + // Dispatches various types of statements based on the type of the + // current token. + function statement(type){ + if (type == "var") cont(pushlex("vardef"), vardef1, expect(";"), poplex); + else if (type == "keyword a") cont(pushlex("form"), expression, statement, poplex); + else if (type == "keyword b") cont(pushlex("form"), statement, poplex); + else if (type == "{") cont(pushlex("}"), block, poplex); + else if (type == "function") cont(functiondef); + else if (type == "for") cont(pushlex("form"), expect("("), pushlex(")"), forspec1, expect(")"), poplex, statement, poplex); + else if (type == "variable") cont(pushlex("stat"), maybelabel); + else if (type == "case") cont(expression, expect(":")); + else if (type == "catch") cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"), statement, poplex, popcontext); + else pass(pushlex("stat"), expression, expect(";"), poplex); + } + // Dispatch expression types. + function expression(type){ + if (atomicTypes.hasOwnProperty(type)) cont(maybeoperator); + else if (type == "function") cont(functiondef); + else if (type == "keyword c") cont(expression); + else if (type == "(") cont(pushlex(")"), expression, expect(")"), poplex); + else if (type == "operator") cont(expression); + else if (type == "[") cont(pushlex("]"), commasep(expression), expect("]"), poplex); + else if (type == "{") cont(pushlex("}"), commasep(objprop), expect("}"), poplex); + } + // Called for places where operators, function calls, or + // subscripts are valid. Will skip on to the next action if none + // is found. + function maybeoperator(type){ + if (type == "operator") cont(expression); + else if (type == "(") cont(pushlex(")"), expression, commasep(expression), expect(")"), poplex); + else if (type == ".") cont(property, maybeoperator); + else if (type == "[") cont(pushlex("]"), expression, expect("]"), poplex); + } + // When a statement starts with a variable name, it might be a + // label. If no colon follows, it's a regular statement. + function maybelabel(type){ + if (type == ":") cont(poplex, statement); + else pass(maybeoperator, expect(";"), poplex); + } + // Property names need to have their style adjusted -- the + // tokenizer think they are variables. + function property(type){ + if (type == "variable") {mark("js-property"); cont();} + } + // This parses a property and its value in an object literal. + function objprop(type){ + if (type == "variable") mark("js-property"); + if (atomicTypes.hasOwnProperty(type)) cont(expect(":"), expression); + } + // Parses a comma-separated list of the things that are recognized + // by the 'what' argument. + function commasep(what){ + function proceed(type) { + if (type == ",") cont(what, proceed); + }; + return function() { + pass(what, proceed); + }; + } + // Look for statements until a closing brace is found. + function block(type){ + if (type == "}") cont(); + else pass(statement, block); + } + // Variable definitions are split into two actions -- 1 looks for + // a name or the end of the definition, 2 looks for an '=' sign or + // a comma. + function vardef1(type, value){ + if (type == "variable"){register(value); cont(vardef2);} + else cont(); + } + function vardef2(type){ + if (type == "operator") cont(expression, vardef2); + else if (type == ",") cont(vardef1); + } + // For loops. + function forspec1(type, value){ + if (type == "var") cont(vardef1, forspec2); + else cont(expression, forspec2); + } + function forspec2(type){ + if (type == ",") cont(forspec1); + if (type == ";") cont(expression, expect(";"), expression); + } + // A function definition creates a new context, and the variables + // in its argument list have to be added to this context. + function functiondef(type, value){ + if (type == "variable"){register(value); cont(functiondef);} + else if (type == "(") cont(pushcontext, commasep(funarg), expect(")"), statement, popcontext); + } + function funarg(type, value){ + if (type == "variable"){register(value); cont();} + } + + return parser; + } + + return {make: parseJS, electricChars: "{}"}; +})(); diff --git a/o3d/samples/third_party/codemirror/js/parsesparql.js b/o3d/samples/third_party/codemirror/js/parsesparql.js new file mode 100644 index 0000000..2f2b904 --- /dev/null +++ b/o3d/samples/third_party/codemirror/js/parsesparql.js @@ -0,0 +1,162 @@ +Editor.Parser = (function() { + function wordRegexp(words) { + return new RegExp("^(?:" + words.join("|") + ")$", "i"); + } + var ops = wordRegexp(["str", "lang", "langmatches", "datatype", "bound", "sameterm", "isiri", "isuri", + "isblank", "isliteral", "union", "a"]); + var keywords = wordRegexp(["base", "prefix", "select", "distinct", "reduced", "construct", "describe", + "ask", "from", "named", "where", "order", "limit", "offset", "filter", "optional", + "graph", "by", "asc", "desc", ]); + var operatorChars = /[*+\-<>=&|]/; + + var tokenizeSparql = (function() { + function normal(source, setState) { + var ch = source.next(); + if (ch == "$" || ch == "?") { + source.nextWhile(matcher(/[\w\d]/)); + return "sp-var"; + } + else if (ch == "<" && !source.applies(matcher(/[\s\u00a0=]/))) { + source.nextWhile(matcher(/[^\s\u00a0>]/)); + if (source.equals(">")) source.next(); + return "sp-uri"; + } + else if (ch == "\"" || ch == "'") { + setState(inLiteral(ch)); + return null; + } + else if (/[{}\(\),\.;\[\]]/.test(ch)) { + return "sp-punc"; + } + else if (ch == "#") { + while (!source.endOfLine()) source.next(); + return "sp-comment"; + } + else if (operatorChars.test(ch)) { + source.nextWhile(matcher(operatorChars)); + return "sp-operator"; + } + else if (ch == ":") { + source.nextWhile(matcher(/[\w\d\._\-]/)); + return "sp-prefixed"; + } + else { + source.nextWhile(matcher(/[_\w\d]/)); + if (source.equals(":")) { + source.next(); + source.nextWhile(matcher(/[\w\d_\-]/)); + return "sp-prefixed"; + } + var word = source.get(), type; + if (ops.test(word)) + type = "sp-operator"; + else if (keywords.test(word)) + type = "sp-keyword"; + else + type = "sp-word"; + return {style: type, content: word}; + } + } + + function inLiteral(quote) { + return function(source, setState) { + var escaped = false; + while (!source.endOfLine()) { + var ch = source.next(); + if (ch == quote && !escaped) { + setState(normal); + break; + } + escaped = ch == "\\"; + } + return "sp-literal"; + }; + } + + return function(source, startState) { + return tokenizer(source, startState || normal); + }; + })(); + + function indentSparql(context) { + return function(nextChars) { + var firstChar = nextChars && nextChars.charAt(0); + if (/[\]\}]/.test(firstChar)) + while (context && context.type == "pattern") context = context.prev; + + var closing = context && firstChar == matching[context.type]; + if (!context) + return 0; + else if (context.type == "pattern") + return context.col; + else if (context.align) + return context.col - (closing ? context.width : 0); + else + return context.indent + (closing ? 0 : 2); + } + } + + function parseSparql(source) { + var tokens = tokenizeSparql(source); + var context = null, indent = 0, col = 0; + function pushContext(type, width) { + context = {prev: context, indent: indent, col: col, type: type, width: width}; + } + function popContext() { + context = context.prev; + } + + var iter = { + next: function() { + var token = tokens.next(), type = token.style, content = token.content, width = token.value.length; + + if (content == "\n") { + token.indentation = indentSparql(context); + indent = col = 0; + if (context && context.align == null) context.align = false; + } + else if (type == "whitespace" && col == 0) { + indent = width; + } + else if (type != "sp-comment" && context && context.align == null) { + context.align = true; + } + + if (content != "\n") col += width; + + if (/[\[\{\(]/.test(content)) { + pushContext(content, width); + } + else if (/[\]\}\)]/.test(content)) { + while (context && context.type == "pattern") + popContext(); + if (context && content == matching[context.type]) + popContext(); + } + else if (content == "." && context && context.type == "pattern") { + popContext(); + } + else if ((type == "sp-word" || type == "sp-prefixed" || type == "sp-uri" || type == "sp-var" || type == "sp-literal") && + context && /[\{\[]/.test(context.type)) { + pushContext("pattern", width); + } + + return token; + }, + + copy: function() { + var _context = context, _indent = indent, _col = col, _tokenState = tokens.state; + return function(source) { + tokens = tokenizeSparql(source, _tokenState); + context = _context; + indent = _indent; + col = _col; + return iter; + }; + } + }; + return iter; + } + + return {make: parseSparql, electricChars: "}]"}; +})(); diff --git a/o3d/samples/third_party/codemirror/js/parsexml.js b/o3d/samples/third_party/codemirror/js/parsexml.js new file mode 100644 index 0000000..73acd16 --- /dev/null +++ b/o3d/samples/third_party/codemirror/js/parsexml.js @@ -0,0 +1,292 @@ +/* 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} + }; + var NoKludges = {autoSelfClosers: {}, doNotIndent: {}}; + var UseKludges = Kludges; + + // 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.matches("[CDATA[", true)) { + setState(inBlock("xml-cdata", "]]>")); + return null; + } + else { + return "xml-text"; + } + } + else if (source.matches("--", true)) { + setState(inBlock("xml-comment", "-->")); + return null; + } + else { + return "xml-text"; + } + } + else if (source.equals("?")) { + source.next(); + setState(inBlock("xml-processing", "?>")); + return null; + } + 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.nextWhile(matcher(/[^&<\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.nextWhile(matcher(/[^\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) { + var matches = []; + while (!source.endOfLine()) { + var ch = source.next(), newMatches = []; + if (ch == terminator.charAt(0)) + matches.push(terminator); + for (var i = 0; i < matches.length; i++) { + var match = matches[i]; + if (match.charAt(0) == ch) { + if (match.length == 1) {setState(inText); break;} + else newMatches.push(match.slice(1)); + } + } + matches = newMatches; + } + 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); + var cc = [base]; + var tokenNr = 0, indented = 0; + var currentTag = null, context = null; + var consume, marked; + + 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 mark(style) { + marked = style; + } + function expect(text) { + return function(style, content) { + if (content == text) cont(); + else mark("xml-error") || 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) { + var context = baseContext; + if (context && context.noIndent) + return 0; + if (context && /^<\//.test(nextChars)) + context = context.prev; + while (context && !context.startOfLine) + context = context.prev; + if (context) + return context.indent + 2; + else + return 0; + }; + } + + function base() { + return pass(element, base); + } + var harmlessTokens = {"xml-text": true, "xml-entity": true, "xml-comment": true, + "xml-cdata": true, "xml-processing": true}; + function element(style, content) { + if (content == "<") cont(tagname, attributes, endtag(tokenNr == 1)); + else if (content == "</") cont(closetagname, expect(">")); + else if (content == "<?") cont(tagname, attributes, expect("?>")); + else if (harmlessTokens.hasOwnProperty(style)) cont(); + else mark("xml-error") || cont(); + } + function tagname(style, content) { + if (style == "xml-name") { + currentTag = content.toLowerCase(); + mark("xml-tagname"); + cont(); + } + else { + currentTag = null; + pass(); + } + } + function closetagname(style, content) { + if (style == "xml-name" && context && content.toLowerCase() == context.name) { + popContext(); + mark("xml-tagname"); + } + else { + mark("xml-error"); + } + cont(); + } + function endtag(startOfLine) { + return function(style, content) { + if (content == "/>" || (content == ">" && UseKludges.autoSelfClosers.hasOwnProperty(currentTag))) cont(); + else if (content == ">") pushContext(currentTag, startOfLine) || cont(); + else mark("xml-error") || cont(arguments.callee); + }; + } + function attributes(style) { + if (style == "xml-name") mark("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(){ + var 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 = marked = false; + cc.pop()(token.style, token.content); + if (consume){ + if (marked) + token.style = marked; + 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) + UseKludges = Kludges; + else + UseKludges = NoKludges; + } + }; +})(); diff --git a/o3d/samples/third_party/codemirror/js/select.js b/o3d/samples/third_party/codemirror/js/select.js new file mode 100644 index 0000000..383a7f1 --- /dev/null +++ b/o3d/samples/third_party/codemirror/js/select.js @@ -0,0 +1,500 @@ +/* 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() { + var 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); + } + + // Used to prevent restoring a selection when we do not need to. + var documentChanged = false; + + var fourSpaces = "\u00a0\u00a0\u00a0\u00a0"; + + // Most functions are defined in two ways, one for the IE selection + // model, one for the W3C one. + if (ie_selection) { + // Store the current selection in such a way that it can be + // restored after we manipulated the DOM tree. For IE, we store + // pixel coordinates. + select.markSelection = function (win) { + var selection = win.document.selection; + var start = selection.createRange(), end = start.duplicate(); + var bookmark = start.getBookmark(); + start.collapse(true); + end.collapse(false); + + documentChanged = false; + var body = win.document.body; + // And we better hope no fool gave this window a padding or a + // margin, or all these computations will be in vain. + return {start: {x: start.boundingLeft + body.scrollLeft - 1, + y: start.boundingTop + body.scrollTop}, + end: {x: end.boundingLeft + body.scrollLeft - 1, + y: end.boundingTop + body.scrollTop}, + window: win, + bookmark: bookmark}; + }; + + // Restore a stored selection. + select.selectMarked = function(sel) { + if (!sel || !documentChanged) + return; + + documentChanged = false; + var range1 = sel.window.document.body.createTextRange(), range2 = range1.duplicate(); + var done = false; + if (sel.start.y >= 0 && sel.end.y < sel.window.document.body.clientHeight) { + // This can fail for various hard-to-handle reasons, so we + // fall back to moveToBookmark when it throws. + try { + range1.moveToPoint(sel.start.x, sel.start.y); + range2.moveToPoint(sel.end.x, sel.end.y); + range1.setEndPoint("EndToStart", range2); + done = true; + } catch(e) {} + } + if (!done) done = range1.moveToBookmark(sel.bookmark); + if (done) range1.select(); + }; + + + // See W3C model for the actual role of this function. Here it + // just sets a flag indicating the selection should be restored. + select.replaceSelection = function(){ + documentChanged = true; + }; + + // 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(); + range.collapse(start); + var around = range.parentElement(); + if (around && isAncestor(container, around)) { + // Only use this node if the selection is not at its start. + var range2 = range.duplicate(); + range2.moveToElementText(around); + if (range.compareEndPoints("StartToStart", range2) == -1) + return topLevelNodeAt(around, container); + } + // Fall-back hack + range.pasteHTML("<span id='xxx-temp-xxx'></span>"); + var temp = container.ownerDocument.getElementById("xxx-temp-xxx"); + if (temp) { + var result = topLevelNodeBefore(temp, container); + removeElement(temp); + return result; + } + return false; + }; + + // 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(); + }; + + 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 && topNode.nodeName != "BR") + 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(); + } + + // Make sure the cursor is visible. + select.scrollToCursor = function(container) { + var selection = container.ownerDocument.selection; + if (!selection) return null; + selection.createRange().scrollIntoView(); + }; + } + // W3C model + else { + // This is used to fix an issue with getting the scroll position + // in Opera. + var opera_scroll = !window.scrollX && !window.scrollY; + + // 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) { + documentChanged = false; + var selection = win.getSelection(); + if (!selection || selection.rangeCount == 0) + return null; + var range = selection.getRangeAt(0); + + var result = {start: {node: range.startContainer, offset: range.startOffset}, + end: {node: range.endContainer, offset: range.endOffset}, + window: win, + scrollX: opera_scroll && win.document.body.scrollLeft, + scrollY: opera_scroll && win.document.body.scrollTop}; + + // We want the nodes right at the cursor, not one of their + // ancestors with a suitable offset. This goes down the DOM tree + // until a 'leaf' is reached (or is it *up* the DOM tree?). + function normalize(point){ + while (point.node.nodeType != 3 && point.node.nodeName != "BR") { + var newNode = point.node.childNodes[point.offset] || point.node.nextSibling; + point.offset = 0; + while (!newNode && point.node.parentNode) { + point.node = point.node.parentNode; + newNode = point.node.nextSibling; + } + point.node = newNode; + if (!newNode) + break; + } + } + + normalize(result.start); + normalize(result.end); + // Make the links back to the selection object (see + // replaceSelection). + if (result.start.node) + result.start.node.selectStart = result.start; + if (result.end.node) + result.end.node.selectEnd = result.end; + + return result; + }; + + select.selectMarked = function (sel) { + if (!sel || !documentChanged) + return; + var win = sel.window; + var range = win.document.createRange(); + + function setPoint(point, which) { + if (point.node) { + // Remove the link back to the selection. + delete point.node["select" + which]; + // 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); + } + } + + // Have to restore the scroll position of the frame in Opera. + if (opera_scroll){ + sel.window.document.body.scrollLeft = sel.scrollX; + sel.window.document.body.scrollTop = sel.scrollY; + } + setPoint(sel.end, "End"); + setPoint(sel.start, "Start"); + selectRange(range, win); + }; + + // This is called by the code in codemirror.js whenever it is + // replacing a part of the DOM tree. 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.replaceSelection = function(oldNode, newNode, length, offset) { + documentChanged = true; + function replace(which) { + var selObj = oldNode["select" + which]; + if (selObj) { + if (selObj.offset > length) { + selObj.offset -= length; + } + else { + newNode["select" + which] = selObj; + delete oldNode["select" + which]; + selObj.node = newNode; + selObj.offset += (offset || 0); + } + } + } + replace("Start"); + replace("End"); + }; + + // Helper for selecting a range object. + function selectRange(range, window) { + var selection = window.getSelection(); + 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] && container.childNodes[range.startOffset].nodeName == "BR") + 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 == "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); + }; + + insertNodeAtCursor = function(window, node) { + var range = selectionRange(window); + if (!range) return; + + range.deleteContents(); + range.insertNode(node); + range.setEndAfter(node); + range.collapse(false); + selectRange(range, window); + return node; + } + + select.insertNewlineAtCursor = function(window) { + 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 && topNode.nodeName != "BR") + 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 (!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); + }; + + select.scrollToCursor = function(container) { + var body = container.ownerDocument.body, win = container.ownerDocument.defaultView; + var element = select.selectionTopNode(container, true) || container.firstChild; + + // In Opera, BR elements *always* have a scrollTop property of zero. Go Opera. + while (element && !element.offsetTop) + element = element.previousSibling; + + var y = 0, pos = element; + while (pos && pos.offsetParent) { + y += pos.offsetTop; + pos = pos.offsetParent; + } + + var screen_y = y - body.scrollTop; + if (screen_y < 0 || screen_y > win.innerHeight - 10) + win.scrollTo(0, y); + }; + } +}()); diff --git a/o3d/samples/third_party/codemirror/js/stringstream.js b/o3d/samples/third_party/codemirror/js/stringstream.js new file mode 100644 index 0000000..17ff8a5 --- /dev/null +++ b/o3d/samples/third_party/codemirror/js/stringstream.js @@ -0,0 +1,111 @@ +/* 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 strings throw + * an exception when this happens. + */ + +(function(){ + // Generic operations that apply to stringstreams. + var base = { + more: function() { + return this.peek() !== null; + }, + applies: function(test) { + var next = this.peek(); + return (next !== null && test(next)); + }, + nextWhile: function(test) { + while (this.applies(test)) + this.next(); + }, + equals: function(ch) { + return ch === this.peek(); + }, + endOfLine: function() { + var next = this.peek(); + return next == null || next == "\n"; + }, + matches: function(string, caseSensitive) { + for (var i = 0; i < string.length; i++) { + var ch = this.peek(); + if (!ch || string.charAt(i) != (caseSensitive ? ch : ch.toLowerCase())) + return false; + this.next(); + } + return true; + } + }; + + // Make a string 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. + window.stringStream = function(source){ + source = iter(source); + var current = "", pos = 0; + var peeked = null, accum = ""; + + return update({ + peek: function() { + if (!peeked) { + try {peeked = this.step();} + catch (e) { + if (e != StopIteration) throw e; + else peeked = null; + } + } + return peeked; + }, + step: function() { + if (peeked) { + var temp = peeked; + peeked = null; + return temp; + } + while (pos == current.length) { + accum += current; + current = ""; // In case source.next() throws + pos = 0; + current = source.next(); + } + return current.charAt(pos++); + + }, + next: function() { + try {return this.step();} + catch (e) { + if (e == StopIteration && accum.length > 0) + throw "End of stringstream reached without emptying buffer ('" + accum + "')."; + else + throw e; + } + }, + get: function() { + var temp = accum; + var realPos = peeked ? pos - 1 : pos; + accum = ""; + if (realPos > 0){ + temp += current.slice(0, realPos); + current = current.slice(realPos); + pos = peeked ? 1 : 0; + } + return temp; + }, + reset: function() { + current = accum + current; + accum = ""; + pos = 0; + peeked = null; + }, + push: function(str) { + current = current.slice(0, pos) + str + current.slice(pos); + } + }, base); + }; +})(); diff --git a/o3d/samples/third_party/codemirror/js/tokenize.js b/o3d/samples/third_party/codemirror/js/tokenize.js new file mode 100644 index 0000000..379a12e --- /dev/null +++ b/o3d/samples/third_party/codemirror/js/tokenize.js @@ -0,0 +1,57 @@ +// A frame 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; +} diff --git a/o3d/samples/third_party/codemirror/js/tokenizejavascript.js b/o3d/samples/third_party/codemirror/js/tokenizejavascript.js new file mode 100644 index 0000000..24d0529 --- /dev/null +++ b/o3d/samples/third_party/codemirror/js/tokenizejavascript.js @@ -0,0 +1,168 @@ +/* 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, result){ + var escaped = false; + var next; + while(!source.endOfLine()){ + var next = source.next(); + if (next == end && !escaped) + break; + escaped = next == "\\"; + } + return result; + } + + // 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: style}; + } + // keywords that take a parenthised expression, and then a + // statement (if) + var keywordA = result("keyword a", "js-keyword"); + // keywords that take just a statement (else) + var keywordB = result("keyword b", "js-keyword"); + // keywords that optionally take an expression, and form a + // statement (return) + var keywordC = result("keyword c", "js-keyword"); + var operator = result("operator", "js-keyword"); + var atom = result("atom", "js-atom"); + return { + "if": keywordA, "switch": 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", "js-keyword"), "function": result("function", "js-keyword"), "catch": result("catch", "js-keyword"), + "for": result("for", "js-keyword"), "case": result("case", "js-keyword"), + "true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom + }; + }(); + + // Some helper regexp matchers. + var isOperatorChar = matcher(/[\+\-\*\&\%\/=<>!\?]/); + var isDigit = matcher(/[0-9]/); + var isHexDigit = matcher(/[0-9A-Fa-f]/); + var isWordChar = matcher(/[\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(inComment, regexp) { + return function(source, setState) { + var newInComment = inComment; + var type = jsToken(inComment, regexp, source, function(c) {newInComment = c;}); + var newRegexp = type.type == "operator" || type.type == "keyword c" || type.type.match(/^[\[{}\(,;:]$/); + if (newRegexp != regexp || newInComment != inComment) + setState(jsTokenState(newInComment, newRegexp)); + return type; + }; + } + + // The token reader, inteded 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(inComment, regexp, source, setComment) { + function readHexNumber(){ + source.next(); // skip the 'x' + source.nextWhile(isHexDigit); + return {type: "number", style: "js-atom"}; + } + + function readNumber() { + source.nextWhile(isDigit); + if (source.equals(".")){ + source.next(); + source.nextWhile(isDigit); + } + if (source.equals("e") || source.equals("E")){ + source.next(); + if (source.equals("-")) + source.next(); + source.nextWhile(isDigit); + } + 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.nextWhile(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.nextWhile(matcher(/[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 (inComment) to indicate whether we + // are inside a /* */ sequence. + function readMultilineComment(start){ + var newInComment = true; + var maybeEnd = (start == "*"); + while (true) { + if (source.endOfLine()) + break; + var next = source.next(); + if (next == "/" && maybeEnd){ + newInComment = false; + break; + } + maybeEnd = (next == "*"); + } + setComment(newInComment); + return {type: "comment", style: "js-comment"}; + } + function readOperator() { + source.nextWhile(isOperatorChar); + return {type: "operator", style: "js-operator"}; + } + + // Fetch the next token. Dispatches on first character in the + // stream, or first two characters when the first is a slash. + var ch = source.next(); + if (inComment) + return readMultilineComment(ch); + else if (ch == "\"" || ch == "'") + return nextUntilUnescaped(source, ch, {type: "string", style: "js-string"}); + // 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 (isDigit(ch)) + return readNumber(); + else if (ch == "/"){ + if (source.equals("*")) + { source.next(); return readMultilineComment(ch); } + else if (source.equals("/")) + return nextUntilUnescaped(source, null, {type: "comment", style: "js-comment"}); + else if (regexp) + return readRegexp(); + else + return readOperator(); + } + else if (isOperatorChar(ch)) + return readOperator(); + else + return readWord(); + } + + // The external interface to the tokenizer. + return function(source, startState) { + return tokenizer(source, startState || jsTokenState(false, true)); + }; +})(); diff --git a/o3d/samples/third_party/codemirror/js/undo.js b/o3d/samples/third_party/codemirror/js/undo.js new file mode 100644 index 0000000..a8edc51 --- /dev/null +++ b/o3d/samples/third_party/codemirror/js/undo.js @@ -0,0 +1,386 @@ +/** + * 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 History 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 History(container, maxDepth, commitDelay, editor, onChange) { + this.container = container; + this.maxDepth = maxDepth; this.commitDelay = commitDelay; + this.editor = editor; this.parent = editor.parent; + this.onChange = onChange; + // 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 = []; +} + +History.prototype = { + // Schedule a commit (if no other touches come in for commitDelay + // milliseconds). + scheduleCommit: function() { + this.parent.clearTimeout(this.commitTimeout); + this.commitTimeout = this.parent.setTimeout(method(this, "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. + this.redoHistory.push(this.updateTo(this.history.pop(), "applyChain")); + if (this.onChange) this.onChange(); + } + }, + + // Redo the last undone change. + redo: function() { + this.commit(); + if (this.redoHistory.length) { + // The inverse of undo, basically. + this.addUndoLevel(this.updateTo(this.redoHistory.pop(), "applyChain")); + if (this.onChange) this.onChange(); + } + }, + + // 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: lines[i]}); + from = end; + } + this.pushChains([chain], from == null && to == null); + }, + + pushChains: function(chains, doNotHighlight) { + this.commit(doNotHighlight); + this.addUndoLevel(this.updateTo(chains, "applyChain")); + this.redoHistory = []; + }, + + // 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 (this.editor.highlightDirty()) this.commit(); + 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 = []; + if (this.onChange) this.onChange(); + } + }, + + // [ 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(); + }, + + // 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; + // Compare two strings, treating nbsps as spaces. + function compareText(a, b) { + return a.replace(/\u00a0/g, " ") == b.replace(/\u00a0/g, " "); + } + + // 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; + } + + // 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.historyTouched = false; + if (node.parentNode != self.container) + return; + } + else { + self.firstTouched = false; + } + + var text = []; + for (var cur = node ? node.nextSibling : self.container.firstChild; + cur && cur.nodeName != "BR"; cur = cur.nextSibling) + if (cur.currentText) text.push(cur.currentText); + + var line = {from: node, to: cur, text: text.join("")}; + var shadow = self.after(node); + if (!shadow || !compareText(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 && search.nodeName != "BR") + 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; + // 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) break; + chain.unshift(curLine); + setTemp(curNode, null); + if (!curNode) break; + curNode = nextBR(curNode, "previous"); + } + curNode = line.to; + // Add lines after this one at end of array. + while (true) { + var curLine = temp(curNode); + if (!curLine || !curNode) break; + chain.push(curLine); + setTemp(curNode, null); + curNode = nextBR(curNode, "next"); + } + + // Chains that can not determine a valid 'shadow' -- a chain + // currently stored in the DOM tree that has the same start and + // end point -- are put back into the touched set, hoping they + // will be valid next time. + if (self.after(chain[0].from) && self.before(chain[chain.length - 1].to)) + chains.push(chain); + else + forEach(chain, function(line) {self.setTouched(line.from);}); + }); + + 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; + } + 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); + + // Build a function that will insert nodes before the end node of + // this chain. + var insert = end ? + function(node) {self.container.insertBefore(node, end);} + : function(node) {self.container.appendChild(node);}; + + // 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) + insert(line.from); + // Add the text. + var textNode = this.container.ownerDocument.createTextNode(line.text); + insert(textNode); + // 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; + } +}; diff --git a/o3d/samples/third_party/codemirror/js/util.js b/o3d/samples/third_party/codemirror/js/util.js new file mode 100644 index 0000000..f866a29 --- /dev/null +++ b/o3d/samples/third_party/codemirror/js/util.js @@ -0,0 +1,134 @@ +/* A few useful utility functions. */ + +// Capture a method on an object. +function method(obj, name) { + return function() {obj[name].apply(obj, arguments);}; +} + +// Write properties from an object into another object. +function update(obj, from) { + for (var name in from) + obj[name] = from[name]; + return obj; +} + +// The value used to signal the end of a sequence in iterators. +var StopIteration = {toString: function() {return "StopIteration"}}; + +// Checks whether the argument is an iterator or a regular sequence, +// turns it into an iterator. +function iter(seq) { + var i = 0; + if (seq.next) return seq; + else return { + next: function() { + if (i >= seq.length) throw StopIteration; + else return seq[++i]; + } + }; +} + +// 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. +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") { + if (event.charCode === 0 || event.charCode == undefined) + event.code = event.keyCode; + else + event.code = event.charCode; + event.character = String.fromCharCode(event.code); + } + return event; +} + +// Portably register event handlers. +function addEventHandler(node, type, handler) { + function wrapHandler(event) { + handler(normalizeEvent(event || window.event)); + } + if (typeof node.addEventListener == "function") { + node.addEventListener(type, wrapHandler, false); + return function() { node.removeEventListener(type, wrapHandler, false); }; + } + else { + node.attachEvent("on" + type, wrapHandler); + return function() { node.detachEvent("on" + type, wrapHandler); }; + } +} + +function removeEventHandler(handler) { + handler(); +} diff --git a/o3d/samples/third_party/lightbox/LICENSE-lightbox-iframe.txt b/o3d/samples/third_party/lightbox/LICENSE-lightbox-iframe.txt new file mode 100644 index 0000000..b4fe311 --- /dev/null +++ b/o3d/samples/third_party/lightbox/LICENSE-lightbox-iframe.txt @@ -0,0 +1,66 @@ +According to particletree.com, everything they publish is under the following +Creative Commons license. This version of lightbox-iframe is a derivative of +the one from particletree.com, as stated in the source files. + + +http://creativecommons.org/licenses/by/2.5/ + + +Attribution 2.5 + +CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM ITS USE. +License + +THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED. + +BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE TO BE BOUND BY THE TERMS OF THIS LICENSE. THE LICENSOR GRANTS YOU THE RIGHTS CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND CONDITIONS. + +1. Definitions + +"Collective Work" means a work, such as a periodical issue, anthology or encyclopedia, in which the Work in its entirety in unmodified form, along with a number of other contributions, constituting separate and independent works in themselves, are assembled into a collective whole. A work that constitutes a Collective Work will not be considered a Derivative Work (as defined below) for the purposes of this License. +"Derivative Work" means a work based upon the Work or upon the Work and other pre-existing works, such as a translation, musical arrangement, dramatization, fictionalization, motion picture version, sound recording, art reproduction, abridgment, condensation, or any other form in which the Work may be recast, transformed, or adapted, except that a work that constitutes a Collective Work will not be considered a Derivative Work for the purpose of this License. For the avoidance of doubt, where the Work is a musical composition or sound recording, the synchronization of the Work in timed-relation with a moving image ("synching") will be considered a Derivative Work for the purpose of this License. +"Licensor" means the individual or entity that offers the Work under the terms of this License. +"Original Author" means the individual or entity who created the Work. +"Work" means the copyrightable work of authorship offered under the terms of this License. +"You" means an individual or entity exercising rights under this License who has not previously violated the terms of this License with respect to the Work, or who has received express permission from the Licensor to exercise rights under this License despite a previous violation. +2. Fair Use Rights. Nothing in this license is intended to reduce, limit, or restrict any rights arising from fair use, first sale or other limitations on the exclusive rights of the copyright owner under copyright law or other applicable laws. + +3. License Grant. Subject to the terms and conditions of this License, Licensor hereby grants You a worldwide, royalty-free, non-exclusive, perpetual (for the duration of the applicable copyright) license to exercise the rights in the Work as stated below: + +to reproduce the Work, to incorporate the Work into one or more Collective Works, and to reproduce the Work as incorporated in the Collective Works; +to create and reproduce Derivative Works; +to distribute copies or phonorecords of, display publicly, perform publicly, and perform publicly by means of a digital audio transmission the Work including as incorporated in Collective Works; +to distribute copies or phonorecords of, display publicly, perform publicly, and perform publicly by means of a digital audio transmission Derivative Works. +For the avoidance of doubt, where the work is a musical composition: + +Performance Royalties Under Blanket Licenses. Licensor waives the exclusive right to collect, whether individually or via a performance rights society (e.g. ASCAP, BMI, SESAC), royalties for the public performance or public digital performance (e.g. webcast) of the Work. +Mechanical Rights and Statutory Royalties. Licensor waives the exclusive right to collect, whether individually or via a music rights agency or designated agent (e.g. Harry Fox Agency), royalties for any phonorecord You create from the Work ("cover version") and distribute, subject to the compulsory license created by 17 USC Section 115 of the US Copyright Act (or the equivalent in other jurisdictions). +Webcasting Rights and Statutory Royalties. For the avoidance of doubt, where the Work is a sound recording, Licensor waives the exclusive right to collect, whether individually or via a performance-rights society (e.g. SoundExchange), royalties for the public digital performance (e.g. webcast) of the Work, subject to the compulsory license created by 17 USC Section 114 of the US Copyright Act (or the equivalent in other jurisdictions). +The above rights may be exercised in all media and formats whether now known or hereafter devised. The above rights include the right to make such modifications as are technically necessary to exercise the rights in other media and formats. All rights not expressly granted by Licensor are hereby reserved. + +4. Restrictions.The license granted in Section 3 above is expressly made subject to and limited by the following restrictions: + +You may distribute, publicly display, publicly perform, or publicly digitally perform the Work only under the terms of this License, and You must include a copy of, or the Uniform Resource Identifier for, this License with every copy or phonorecord of the Work You distribute, publicly display, publicly perform, or publicly digitally perform. You may not offer or impose any terms on the Work that alter or restrict the terms of this License or the recipients' exercise of the rights granted hereunder. You may not sublicense the Work. You must keep intact all notices that refer to this License and to the disclaimer of warranties. You may not distribute, publicly display, publicly perform, or publicly digitally perform the Work with any technological measures that control access or use of the Work in a manner inconsistent with the terms of this License Agreement. The above applies to the Work as incorporated in a Collective Work, but this does not require the Collective Work apart from the Work itself to be made subject to the terms of this License. If You create a Collective Work, upon notice from any Licensor You must, to the extent practicable, remove from the Collective Work any credit as required by clause 4(b), as requested. If You create a Derivative Work, upon notice from any Licensor You must, to the extent practicable, remove from the Derivative Work any credit as required by clause 4(b), as requested. +If you distribute, publicly display, publicly perform, or publicly digitally perform the Work or any Derivative Works or Collective Works, You must keep intact all copyright notices for the Work and provide, reasonable to the medium or means You are utilizing: (i) the name of the Original Author (or pseudonym, if applicable) if supplied, and/or (ii) if the Original Author and/or Licensor designate another party or parties (e.g. a sponsor institute, publishing entity, journal) for attribution in Licensor's copyright notice, terms of service or by other reasonable means, the name of such party or parties; the title of the Work if supplied; to the extent reasonably practicable, the Uniform Resource Identifier, if any, that Licensor specifies to be associated with the Work, unless such URI does not refer to the copyright notice or licensing information for the Work; and in the case of a Derivative Work, a credit identifying the use of the Work in the Derivative Work (e.g., "French translation of the Work by Original Author," or "Screenplay based on original Work by Original Author"). Such credit may be implemented in any reasonable manner; provided, however, that in the case of a Derivative Work or Collective Work, at a minimum such credit will appear where any other comparable authorship credit appears and in a manner at least as prominent as such other comparable authorship credit. +5. Representations, Warranties and Disclaimer + +UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU. + +6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +7. Termination + +This License and the rights granted hereunder will terminate automatically upon any breach by You of the terms of this License. Individuals or entities who have received Derivative Works or Collective Works from You under this License, however, will not have their licenses terminated provided such individuals or entities remain in full compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will survive any termination of this License. +Subject to the above terms and conditions, the license granted here is perpetual (for the duration of the applicable copyright in the Work). Notwithstanding the above, Licensor reserves the right to release the Work under different license terms or to stop distributing the Work at any time; provided, however that any such election will not serve to withdraw this License (or any other license that has been, or is required to be, granted under the terms of this License), and this License will continue in full force and effect unless terminated as stated above. +8. Miscellaneous + +Each time You distribute or publicly digitally perform the Work or a Collective Work, the Licensor offers to the recipient a license to the Work on the same terms and conditions as the license granted to You under this License. +Each time You distribute or publicly digitally perform a Derivative Work, Licensor offers to the recipient a license to the original Work on the same terms and conditions as the license granted to You under this License. +If any provision of this License is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this License, and without further action by the parties to this agreement, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. +No term or provision of this License shall be deemed waived and no breach consented to unless such waiver or consent shall be in writing and signed by the party to be charged with such waiver or consent. +This License constitutes the entire agreement between the parties with respect to the Work licensed here. There are no understandings, agreements or representations with respect to the Work not specified here. Licensor shall not be bound by any additional provisions that may appear in any communication from You. This License may not be modified without the mutual written agreement of the Licensor and You. +Creative Commons is not a party to this License, and makes no warranty whatsoever in connection with the Work. Creative Commons will not be liable to You or any party on any legal theory for any damages whatsoever, including without limitation any general, special, incidental or consequential damages arising in connection to this license. Notwithstanding the foregoing two (2) sentences, if Creative Commons has expressly identified itself as the Licensor hereunder, it shall have all rights and obligations of Licensor. + +Except for the limited purpose of indicating to the public that the Work is licensed under the CCPL, neither party will use the trademark "Creative Commons" or any related trademark or logo of Creative Commons without the prior written consent of Creative Commons. Any permitted use will be in compliance with Creative Commons' then-current trademark usage guidelines, as may be published on its website or otherwise made available upon request from time to time. + +Creative Commons may be contacted at http://creativecommons.org/. diff --git a/o3d/samples/third_party/lightbox/LICENSE-prototype.txt b/o3d/samples/third_party/lightbox/LICENSE-prototype.txt new file mode 100644 index 0000000..61e4918 --- /dev/null +++ b/o3d/samples/third_party/lightbox/LICENSE-prototype.txt @@ -0,0 +1,16 @@ +Copyright (c) 2005-2008 Sam Stephenson + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/o3d/samples/third_party/lightbox/lightbox-iframe.js b/o3d/samples/third_party/lightbox/lightbox-iframe.js new file mode 100644 index 0000000..33e1889 --- /dev/null +++ b/o3d/samples/third_party/lightbox/lightbox-iframe.js @@ -0,0 +1,199 @@ +/* +Created By: Chris Campbell +Modified By: Noah Winecoff (http://www.findmotive.com) +Website: http://particletree.com +Date: 2/1/2006 + +Inspired by the lightbox implementation found at http://www.huddletogether.com/projects/lightbox/ +*/ + +/*-------------------------------GLOBAL VARIABLES------------------------------------*/ + +var detect = navigator.userAgent.toLowerCase(); +var OS,browser,version,total,thestring; + +/*-----------------------------------------------------------------------------------------------*/ + +//Browser detect script origionally created by Peter Paul Koch at http://www.quirksmode.org/ + +function getBrowserInfo() { + if (checkIt('konqueror')) { + browser = "Konqueror"; + OS = "Linux"; + } + else if (checkIt('safari')) browser = "Safari" + else if (checkIt('omniweb')) browser = "OmniWeb" + else if (checkIt('opera')) browser = "Opera" + else if (checkIt('webtv')) browser = "WebTV"; + else if (checkIt('icab')) browser = "iCab" + else if (checkIt('msie')) browser = "Internet Explorer" + else if (!checkIt('compatible')) { + browser = "Netscape Navigator" + version = detect.charAt(8); + } + else browser = "An unknown browser"; + + if (!version) version = detect.charAt(place + thestring.length); + + if (!OS) { + if (checkIt('linux')) OS = "Linux"; + else if (checkIt('x11')) OS = "Unix"; + else if (checkIt('mac')) OS = "Mac" + else if (checkIt('win')) OS = "Windows" + else OS = "an unknown operating system"; + } +} + +function checkIt(string) { + place = detect.indexOf(string) + 1; + thestring = string; + return place; +} + +/*-----------------------------------------------------------------------------------------------*/ + +Event.observe(window, 'load', initialize, false); +Event.observe(window, 'load', getBrowserInfo, false); +Event.observe(window, 'unload', Event.unloadCache, false); + +var lightbox = Class.create(); + +lightbox.prototype = { + + yPos : 0, + xPos : 0, + + initialize: function(ctrl) { + this.content = ctrl.href; + Event.observe(ctrl, 'click', this.activate.bindAsEventListener(this), false); + ctrl.onclick = function(){return false;}; + }, + + // Turn everything on - mainly the IE fixes + activate: function(){ + if (browser == 'Internet Explorer'){ + this.getScroll(); + this.prepareIE('100%', 'hidden'); + this.setScroll(0,0); + this.hideSelects('hidden'); + } + this.displayLightbox("block"); + }, + + // Ie requires height to 100% and overflow hidden or else you can scroll down past the lightbox + prepareIE: function(height, overflow){ + bod = document.getElementsByTagName('body')[0]; + bod.style.height = height; + bod.style.overflow = overflow; + + htm = document.getElementsByTagName('html')[0]; + htm.style.height = height; + htm.style.overflow = overflow; + }, + + // In IE, select elements hover on top of the lightbox + hideSelects: function(visibility){ + selects = document.getElementsByTagName('select'); + for(i = 0; i < selects.length; i++) { + selects[i].style.visibility = visibility; + } + }, + + // Taken from lightbox implementation found at http://www.huddletogether.com/projects/lightbox/ + getScroll: function(){ + if (self.pageYOffset) { + this.yPos = self.pageYOffset; + } else if (document.documentElement && document.documentElement.scrollTop){ + this.yPos = document.documentElement.scrollTop; + } else if (document.body) { + this.yPos = document.body.scrollTop; + } + }, + + setScroll: function(x, y){ + window.scrollTo(x, y); + }, + + displayLightbox: function(display){ + $('overlay').style.display = display; + $('lightbox').style.display = display; + if(display != 'none') this.loadInfo(); + }, + + // Write an iFrame instead of using an AJAX call to pull the content + loadInfo: function() { + info = "<div id='lbContent'><center><a href='#' class='lbAction' rel='deactivate'>close (x)</a></center><iframe frameborder=\"0\" width=\"100%\" height=\"100%\" src=\"" + this.content + "\"</iframe></div>"; + new Insertion.Before($('lbLoadMessage'), info) + $('lightbox').className = "done"; + this.actions(); + + }, + + // Search through new links within the lightbox, and attach click event + actions: function(){ + lbActions = document.getElementsByClassName('lbAction'); + + for(i = 0; i < lbActions.length; i++) { + Event.observe(lbActions[i], 'click', this[lbActions[i].rel].bindAsEventListener(this), false); + lbActions[i].onclick = function(){return false;}; + } + + }, + + // Example of creating your own functionality once lightbox is initiated + insert: function(e){ + link = Event.element(e).parentNode; + Element.remove($('lbContent')); + + var myAjax = new Ajax.Request( + link.href, + {method: 'post', parameters: "", onComplete: this.processInfo.bindAsEventListener(this)} + ); + + }, + + // Example of creating your own functionality once lightbox is initiated + deactivate: function(){ + Element.remove($('lbContent')); + + if (browser == "Internet Explorer"){ + this.setScroll(0,this.yPos); + this.prepareIE("auto", "auto"); + this.hideSelects("visible"); + } + + this.displayLightbox("none"); + } +} + +/*-----------------------------------------------------------------------------------------------*/ + +// Onload, make all links that need to trigger a lightbox active +function initialize(){ + addLightboxMarkup(); + lbox = document.getElementsByClassName('lbOn'); + for(i = 0; i < lbox.length; i++) { + valid = new lightbox(lbox[i]); + } +} + +// Add in markup necessary to make this work. Basically two divs: +// Overlay holds the shadow +// Lightbox is the centered square that the content is put into. +function addLightboxMarkup() { + bod = document.getElementsByTagName('body')[0]; + overlay = document.createElement('div'); + overlay.id = 'overlay'; + lb = document.createElement('div'); + lb.id = 'lbinner'; + // lb.className = 'loading'; + lb.innerHTML = '<div id="lbLoadMessage">' + + 'loading' + + '</div>'; + lbcontainer = document.createElement('div'); + lbcontainer.id = 'lightbox'; + lb.className = 'loading'; + lbcontainer.appendChild(lb); + bod.appendChild(overlay); + bod.appendChild(lbcontainer); +}
\ No newline at end of file diff --git a/o3d/samples/third_party/lightbox/lightbox.css b/o3d/samples/third_party/lightbox/lightbox.css new file mode 100644 index 0000000..363969b --- /dev/null +++ b/o3d/samples/third_party/lightbox/lightbox.css @@ -0,0 +1,74 @@ +/* - - - - - - - - - - - - - - - - - - - - - + +Title : Lightbox CSS +Author : Kevin Hale +Modified By: Noah Winecoff (http://www.findmotive.com) +URL : http://particletree.com/features/lightbox-gone-wild/ + +Created : January 13, 2006 +Modified : February 1, 2006 + +- - - - - - - - - - - - - - - - - - - - - */ + +#lightbox{ + display:none; +/* position: absolute;*/ + top:0%; +/* left:50%;*/ + z-index:9999; + width:100%; + height:100%; +} +#lightbox[id]{ + position:fixed; +} + +#lbinner { + margin: 0 auto; + border:10px solid #555555; + background:#FDFCE9; + text-align:left; + max-width: 900px; + width:100%; + height:90%; + margin-top: 10px; + z-index:9999; + padding-bottom: 20px; +} + +#overlay{ + display:none; + position:absolute; + top:0; + left:0; + width:100%; + height:100%; + z-index:5000; + background-color:#000; + -moz-opacity: 0.8; + opacity:.80; + filter: alpha(opacity=80); +} +#overlay[id]{ + position:fixed; +} + +#lightbox.done #lbLoadMessage{ + display:none; +} +#lightbox.done #lbContent{ + display:block; + width: 100%; + height: 100%; +} +#lightbox.loading #lbContent{ + display:none; +} +#lightbox.loading #lbLoadMessage{ + display:block; +} + +#lightbox.done img{ + width:100%; + height:100%; +}
\ No newline at end of file diff --git a/o3d/samples/third_party/lightbox/prototype.js b/o3d/samples/third_party/lightbox/prototype.js new file mode 100644 index 0000000..b19bf53 --- /dev/null +++ b/o3d/samples/third_party/lightbox/prototype.js @@ -0,0 +1,1785 @@ +/* Prototype JavaScript framework, version 1.4.0 + * (c) 2005 Sam Stephenson <sam@conio.net> + * + * THIS FILE IS AUTOMATICALLY GENERATED. When sending patches, please diff + * against the source tree, available from the Prototype darcs repository. + * + * Prototype is freely distributable under the terms of an MIT-style license. + * + * For details, see the Prototype web site: http://prototype.conio.net/ + * +/*--------------------------------------------------------------------------*/ + +var Prototype = { + Version: '1.4.0', + ScriptFragment: '(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)', + + emptyFunction: function() {}, + K: function(x) {return x} +} + +var Class = { + create: function() { + return function() { + this.initialize.apply(this, arguments); + } + } +} + +var Abstract = new Object(); + +Object.extend = function(destination, source) { + for (property in source) { + destination[property] = source[property]; + } + return destination; +} + +Object.inspect = function(object) { + try { + if (object == undefined) return 'undefined'; + if (object == null) return 'null'; + return object.inspect ? object.inspect() : object.toString(); + } catch (e) { + if (e instanceof RangeError) return '...'; + throw e; + } +} + +Function.prototype.bind = function() { + var __method = this, args = $A(arguments), object = args.shift(); + return function() { + return __method.apply(object, args.concat($A(arguments))); + } +} + +Function.prototype.bindAsEventListener = function(object) { + var __method = this; + return function(event) { + return __method.call(object, event || window.event); + } +} + +Object.extend(Number.prototype, { + toColorPart: function() { + var digits = this.toString(16); + if (this < 16) return '0' + digits; + return digits; + }, + + succ: function() { + return this + 1; + }, + + times: function(iterator) { + $R(0, this, true).each(iterator); + return this; + } +}); + +var Try = { + these: function() { + var returnValue; + + for (var i = 0; i < arguments.length; i++) { + var lambda = arguments[i]; + try { + returnValue = lambda(); + break; + } catch (e) {} + } + + return returnValue; + } +} + +/*--------------------------------------------------------------------------*/ + +var PeriodicalExecuter = Class.create(); +PeriodicalExecuter.prototype = { + initialize: function(callback, frequency) { + this.callback = callback; + this.frequency = frequency; + this.currentlyExecuting = false; + + this.registerCallback(); + }, + + registerCallback: function() { + setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); + }, + + onTimerEvent: function() { + if (!this.currentlyExecuting) { + try { + this.currentlyExecuting = true; + this.callback(); + } finally { + this.currentlyExecuting = false; + } + } + } +} + +/*--------------------------------------------------------------------------*/ + +function $() { + var elements = new Array(); + + for (var i = 0; i < arguments.length; i++) { + var element = arguments[i]; + if (typeof element == 'string') + element = document.getElementById(element); + + if (arguments.length == 1) + return element; + + elements.push(element); + } + + return elements; +} +Object.extend(String.prototype, { + stripTags: function() { + return this.replace(/<\/?[^>]+>/gi, ''); + }, + + stripScripts: function() { + return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), ''); + }, + + extractScripts: function() { + var matchAll = new RegExp(Prototype.ScriptFragment, 'img'); + var matchOne = new RegExp(Prototype.ScriptFragment, 'im'); + return (this.match(matchAll) || []).map(function(scriptTag) { + return (scriptTag.match(matchOne) || ['', ''])[1]; + }); + }, + + evalScripts: function() { + return this.extractScripts().map(eval); + }, + + escapeHTML: function() { + var div = document.createElement('div'); + var text = document.createTextNode(this); + div.appendChild(text); + return div.innerHTML; + }, + + unescapeHTML: function() { + var div = document.createElement('div'); + div.innerHTML = this.stripTags(); + return div.childNodes[0] ? div.childNodes[0].nodeValue : ''; + }, + + toQueryParams: function() { + var pairs = this.match(/^\??(.*)$/)[1].split('&'); + return pairs.inject({}, function(params, pairString) { + var pair = pairString.split('='); + params[pair[0]] = pair[1]; + return params; + }); + }, + + toArray: function() { + return this.split(''); + }, + + camelize: function() { + var oStringList = this.split('-'); + if (oStringList.length == 1) return oStringList[0]; + + var camelizedString = this.indexOf('-') == 0 + ? oStringList[0].charAt(0).toUpperCase() + oStringList[0].substring(1) + : oStringList[0]; + + for (var i = 1, len = oStringList.length; i < len; i++) { + var s = oStringList[i]; + camelizedString += s.charAt(0).toUpperCase() + s.substring(1); + } + + return camelizedString; + }, + + inspect: function() { + return "'" + this.replace('\\', '\\\\').replace("'", '\\\'') + "'"; + } +}); + +String.prototype.parseQuery = String.prototype.toQueryParams; + +var $break = new Object(); +var $continue = new Object(); + +var Enumerable = { + each: function(iterator) { + var index = 0; + try { + this._each(function(value) { + try { + iterator(value, index++); + } catch (e) { + if (e != $continue) throw e; + } + }); + } catch (e) { + if (e != $break) throw e; + } + }, + + all: function(iterator) { + var result = true; + this.each(function(value, index) { + result = result && !!(iterator || Prototype.K)(value, index); + if (!result) throw $break; + }); + return result; + }, + + any: function(iterator) { + var result = true; + this.each(function(value, index) { + if (result = !!(iterator || Prototype.K)(value, index)) + throw $break; + }); + return result; + }, + + collect: function(iterator) { + var results = []; + this.each(function(value, index) { + results.push(iterator(value, index)); + }); + return results; + }, + + detect: function (iterator) { + var result; + this.each(function(value, index) { + if (iterator(value, index)) { + result = value; + throw $break; + } + }); + return result; + }, + + findAll: function(iterator) { + var results = []; + this.each(function(value, index) { + if (iterator(value, index)) + results.push(value); + }); + return results; + }, + + grep: function(pattern, iterator) { + var results = []; + this.each(function(value, index) { + var stringValue = value.toString(); + if (stringValue.match(pattern)) + results.push((iterator || Prototype.K)(value, index)); + }) + return results; + }, + + include: function(object) { + var found = false; + this.each(function(value) { + if (value == object) { + found = true; + throw $break; + } + }); + return found; + }, + + inject: function(memo, iterator) { + this.each(function(value, index) { + memo = iterator(memo, value, index); + }); + return memo; + }, + + invoke: function(method) { + var args = $A(arguments).slice(1); + return this.collect(function(value) { + return value[method].apply(value, args); + }); + }, + + max: function(iterator) { + var result; + this.each(function(value, index) { + value = (iterator || Prototype.K)(value, index); + if (value >= (result || value)) + result = value; + }); + return result; + }, + + min: function(iterator) { + var result; + this.each(function(value, index) { + value = (iterator || Prototype.K)(value, index); + if (value <= (result || value)) + result = value; + }); + return result; + }, + + partition: function(iterator) { + var trues = [], falses = []; + this.each(function(value, index) { + ((iterator || Prototype.K)(value, index) ? + trues : falses).push(value); + }); + return [trues, falses]; + }, + + pluck: function(property) { + var results = []; + this.each(function(value, index) { + results.push(value[property]); + }); + return results; + }, + + reject: function(iterator) { + var results = []; + this.each(function(value, index) { + if (!iterator(value, index)) + results.push(value); + }); + return results; + }, + + sortBy: function(iterator) { + return this.collect(function(value, index) { + return {value: value, criteria: iterator(value, index)}; + }).sort(function(left, right) { + var a = left.criteria, b = right.criteria; + return a < b ? -1 : a > b ? 1 : 0; + }).pluck('value'); + }, + + toArray: function() { + return this.collect(Prototype.K); + }, + + zip: function() { + var iterator = Prototype.K, args = $A(arguments); + if (typeof args.last() == 'function') + iterator = args.pop(); + + var collections = [this].concat(args).map($A); + return this.map(function(value, index) { + iterator(value = collections.pluck(index)); + return value; + }); + }, + + inspect: function() { + return '#<Enumerable:' + this.toArray().inspect() + '>'; + } +} + +Object.extend(Enumerable, { + map: Enumerable.collect, + find: Enumerable.detect, + select: Enumerable.findAll, + member: Enumerable.include, + entries: Enumerable.toArray +}); +var $A = Array.from = function(iterable) { + if (!iterable) return []; + if (iterable.toArray) { + return iterable.toArray(); + } else { + var results = []; + for (var i = 0; i < iterable.length; i++) + results.push(iterable[i]); + return results; + } +} + +Object.extend(Array.prototype, Enumerable); + +Array.prototype._reverse = Array.prototype.reverse; + +Object.extend(Array.prototype, { + _each: function(iterator) { + for (var i = 0; i < this.length; i++) + iterator(this[i]); + }, + + clear: function() { + this.length = 0; + return this; + }, + + first: function() { + return this[0]; + }, + + last: function() { + return this[this.length - 1]; + }, + + compact: function() { + return this.select(function(value) { + return value != undefined || value != null; + }); + }, + + flatten: function() { + return this.inject([], function(array, value) { + return array.concat(value.constructor == Array ? + value.flatten() : [value]); + }); + }, + + without: function() { + var values = $A(arguments); + return this.select(function(value) { + return !values.include(value); + }); + }, + + indexOf: function(object) { + for (var i = 0; i < this.length; i++) + if (this[i] == object) return i; + return -1; + }, + + reverse: function(inline) { + return (inline !== false ? this : this.toArray())._reverse(); + }, + + shift: function() { + var result = this[0]; + for (var i = 0; i < this.length - 1; i++) + this[i] = this[i + 1]; + this.length--; + return result; + }, + + inspect: function() { + return '[' + this.map(Object.inspect).join(', ') + ']'; + } +}); +var Hash = { + _each: function(iterator) { + for (key in this) { + var value = this[key]; + if (typeof value == 'function') continue; + + var pair = [key, value]; + pair.key = key; + pair.value = value; + iterator(pair); + } + }, + + keys: function() { + return this.pluck('key'); + }, + + values: function() { + return this.pluck('value'); + }, + + merge: function(hash) { + return $H(hash).inject($H(this), function(mergedHash, pair) { + mergedHash[pair.key] = pair.value; + return mergedHash; + }); + }, + + toQueryString: function() { + return this.map(function(pair) { + return pair.map(encodeURIComponent).join('='); + }).join('&'); + }, + + inspect: function() { + return '#<Hash:{' + this.map(function(pair) { + return pair.map(Object.inspect).join(': '); + }).join(', ') + '}>'; + } +} + +function $H(object) { + var hash = Object.extend({}, object || {}); + Object.extend(hash, Enumerable); + Object.extend(hash, Hash); + return hash; +} +ObjectRange = Class.create(); +Object.extend(ObjectRange.prototype, Enumerable); +Object.extend(ObjectRange.prototype, { + initialize: function(start, end, exclusive) { + this.start = start; + this.end = end; + this.exclusive = exclusive; + }, + + _each: function(iterator) { + var value = this.start; + do { + iterator(value); + value = value.succ(); + } while (this.include(value)); + }, + + include: function(value) { + if (value < this.start) + return false; + if (this.exclusive) + return value < this.end; + return value <= this.end; + } +}); + +var $R = function(start, end, exclusive) { + return new ObjectRange(start, end, exclusive); +} + +var Ajax = { + getTransport: function() { + return Try.these( + function() {return new ActiveXObject('Msxml2.XMLHTTP')}, + function() {return new ActiveXObject('Microsoft.XMLHTTP')}, + function() {return new XMLHttpRequest()} + ) || false; + }, + + activeRequestCount: 0 +} + +Ajax.Responders = { + responders: [], + + _each: function(iterator) { + this.responders._each(iterator); + }, + + register: function(responderToAdd) { + if (!this.include(responderToAdd)) + this.responders.push(responderToAdd); + }, + + unregister: function(responderToRemove) { + this.responders = this.responders.without(responderToRemove); + }, + + dispatch: function(callback, request, transport, json) { + this.each(function(responder) { + if (responder[callback] && typeof responder[callback] == 'function') { + try { + responder[callback].apply(responder, [request, transport, json]); + } catch (e) {} + } + }); + } +}; + +Object.extend(Ajax.Responders, Enumerable); + +Ajax.Responders.register({ + onCreate: function() { + Ajax.activeRequestCount++; + }, + + onComplete: function() { + Ajax.activeRequestCount--; + } +}); + +Ajax.Base = function() {}; +Ajax.Base.prototype = { + setOptions: function(options) { + this.options = { + method: 'post', + asynchronous: true, + parameters: '' + } + Object.extend(this.options, options || {}); + }, + + responseIsSuccess: function() { + return this.transport.status == undefined + || this.transport.status == 0 + || (this.transport.status >= 200 && this.transport.status < 300); + }, + + responseIsFailure: function() { + return !this.responseIsSuccess(); + } +} + +Ajax.Request = Class.create(); +Ajax.Request.Events = + ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; + +Ajax.Request.prototype = Object.extend(new Ajax.Base(), { + initialize: function(url, options) { + this.transport = Ajax.getTransport(); + this.setOptions(options); + this.request(url); + }, + + request: function(url) { + var parameters = this.options.parameters || ''; + if (parameters.length > 0) parameters += '&_='; + + try { + this.url = url; + if (this.options.method == 'get' && parameters.length > 0) + this.url += (this.url.match(/\?/) ? '&' : '?') + parameters; + + Ajax.Responders.dispatch('onCreate', this, this.transport); + + this.transport.open(this.options.method, this.url, + this.options.asynchronous); + + if (this.options.asynchronous) { + this.transport.onreadystatechange = this.onStateChange.bind(this); + setTimeout((function() {this.respondToReadyState(1)}).bind(this), 10); + } + + this.setRequestHeaders(); + + var body = this.options.postBody ? this.options.postBody : parameters; + this.transport.send(this.options.method == 'post' ? body : null); + + } catch (e) { + this.dispatchException(e); + } + }, + + setRequestHeaders: function() { + var requestHeaders = + ['X-Requested-With', 'XMLHttpRequest', + 'X-Prototype-Version', Prototype.Version]; + + if (this.options.method == 'post') { + requestHeaders.push('Content-type', + 'application/x-www-form-urlencoded'); + + /* Force "Connection: close" for Mozilla browsers to work around + * a bug where XMLHttpReqeuest sends an incorrect Content-length + * header. See Mozilla Bugzilla #246651. + */ + if (this.transport.overrideMimeType) + requestHeaders.push('Connection', 'close'); + } + + if (this.options.requestHeaders) + requestHeaders.push.apply(requestHeaders, this.options.requestHeaders); + + for (var i = 0; i < requestHeaders.length; i += 2) + this.transport.setRequestHeader(requestHeaders[i], requestHeaders[i+1]); + }, + + onStateChange: function() { + var readyState = this.transport.readyState; + if (readyState != 1) + this.respondToReadyState(this.transport.readyState); + }, + + header: function(name) { + try { + return this.transport.getResponseHeader(name); + } catch (e) {} + }, + + evalJSON: function() { + try { + return eval(this.header('X-JSON')); + } catch (e) {} + }, + + evalResponse: function() { + try { + return eval(this.transport.responseText); + } catch (e) { + this.dispatchException(e); + } + }, + + respondToReadyState: function(readyState) { + var event = Ajax.Request.Events[readyState]; + var transport = this.transport, json = this.evalJSON(); + + if (event == 'Complete') { + try { + (this.options['on' + this.transport.status] + || this.options['on' + (this.responseIsSuccess() ? 'Success' : 'Failure')] + || Prototype.emptyFunction)(transport, json); + } catch (e) { + this.dispatchException(e); + } + + if ((this.header('Content-type') || '').match(/^text\/javascript/i)) + this.evalResponse(); + } + + try { + (this.options['on' + event] || Prototype.emptyFunction)(transport, json); + Ajax.Responders.dispatch('on' + event, this, transport, json); + } catch (e) { + this.dispatchException(e); + } + + /* Avoid memory leak in MSIE: clean up the oncomplete event handler */ + if (event == 'Complete') + this.transport.onreadystatechange = Prototype.emptyFunction; + }, + + dispatchException: function(exception) { + (this.options.onException || Prototype.emptyFunction)(this, exception); + Ajax.Responders.dispatch('onException', this, exception); + } +}); + +Ajax.Updater = Class.create(); + +Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), { + initialize: function(container, url, options) { + this.containers = { + success: container.success ? $(container.success) : $(container), + failure: container.failure ? $(container.failure) : + (container.success ? null : $(container)) + } + + this.transport = Ajax.getTransport(); + this.setOptions(options); + + var onComplete = this.options.onComplete || Prototype.emptyFunction; + this.options.onComplete = (function(transport, object) { + this.updateContent(); + onComplete(transport, object); + }).bind(this); + + this.request(url); + }, + + updateContent: function() { + var receiver = this.responseIsSuccess() ? + this.containers.success : this.containers.failure; + var response = this.transport.responseText; + + if (!this.options.evalScripts) + response = response.stripScripts(); + + if (receiver) { + if (this.options.insertion) { + new this.options.insertion(receiver, response); + } else { + Element.update(receiver, response); + } + } + + if (this.responseIsSuccess()) { + if (this.onComplete) + setTimeout(this.onComplete.bind(this), 10); + } + } +}); + +Ajax.PeriodicalUpdater = Class.create(); +Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), { + initialize: function(container, url, options) { + this.setOptions(options); + this.onComplete = this.options.onComplete; + + this.frequency = (this.options.frequency || 2); + this.decay = (this.options.decay || 1); + + this.updater = {}; + this.container = container; + this.url = url; + + this.start(); + }, + + start: function() { + this.options.onComplete = this.updateComplete.bind(this); + this.onTimerEvent(); + }, + + stop: function() { + this.updater.onComplete = undefined; + clearTimeout(this.timer); + (this.onComplete || Prototype.emptyFunction).apply(this, arguments); + }, + + updateComplete: function(request) { + if (this.options.decay) { + this.decay = (request.responseText == this.lastText ? + this.decay * this.options.decay : 1); + + this.lastText = request.responseText; + } + this.timer = setTimeout(this.onTimerEvent.bind(this), + this.decay * this.frequency * 1000); + }, + + onTimerEvent: function() { + this.updater = new Ajax.Updater(this.container, this.url, this.options); + } +}); +document.getElementsByClassName = function(className, parentElement) { + var children = ($(parentElement) || document.body).getElementsByTagName('*'); + return $A(children).inject([], function(elements, child) { + if (child.className.match(new RegExp("(^|\\s)" + className + "(\\s|$)"))) + elements.push(child); + return elements; + }); +} + +/*--------------------------------------------------------------------------*/ + +if (!window.Element) { + var Element = new Object(); +} + +Object.extend(Element, { + visible: function(element) { + return $(element).style.display != 'none'; + }, + + toggle: function() { + for (var i = 0; i < arguments.length; i++) { + var element = $(arguments[i]); + Element[Element.visible(element) ? 'hide' : 'show'](element); + } + }, + + hide: function() { + for (var i = 0; i < arguments.length; i++) { + var element = $(arguments[i]); + element.style.display = 'none'; + } + }, + + show: function() { + for (var i = 0; i < arguments.length; i++) { + var element = $(arguments[i]); + element.style.display = ''; + } + }, + + remove: function(element) { + element = $(element); + element.parentNode.removeChild(element); + }, + + update: function(element, html) { + $(element).innerHTML = html.stripScripts(); + setTimeout(function() {html.evalScripts()}, 10); + }, + + getHeight: function(element) { + element = $(element); + return element.offsetHeight; + }, + + classNames: function(element) { + return new Element.ClassNames(element); + }, + + hasClassName: function(element, className) { + if (!(element = $(element))) return; + return Element.classNames(element).include(className); + }, + + addClassName: function(element, className) { + if (!(element = $(element))) return; + return Element.classNames(element).add(className); + }, + + removeClassName: function(element, className) { + if (!(element = $(element))) return; + return Element.classNames(element).remove(className); + }, + + // removes whitespace-only text node children + cleanWhitespace: function(element) { + element = $(element); + for (var i = 0; i < element.childNodes.length; i++) { + var node = element.childNodes[i]; + if (node.nodeType == 3 && !/\S/.test(node.nodeValue)) + Element.remove(node); + } + }, + + empty: function(element) { + return $(element).innerHTML.match(/^\s*$/); + }, + + scrollTo: function(element) { + element = $(element); + var x = element.x ? element.x : element.offsetLeft, + y = element.y ? element.y : element.offsetTop; + window.scrollTo(x, y); + }, + + getStyle: function(element, style) { + element = $(element); + var value = element.style[style.camelize()]; + if (!value) { + if (document.defaultView && document.defaultView.getComputedStyle) { + var css = document.defaultView.getComputedStyle(element, null); + value = css ? css.getPropertyValue(style) : null; + } else if (element.currentStyle) { + value = element.currentStyle[style.camelize()]; + } + } + + if (window.opera && ['left', 'top', 'right', 'bottom'].include(style)) + if (Element.getStyle(element, 'position') == 'static') value = 'auto'; + + return value == 'auto' ? null : value; + }, + + setStyle: function(element, style) { + element = $(element); + for (name in style) + element.style[name.camelize()] = style[name]; + }, + + getDimensions: function(element) { + element = $(element); + if (Element.getStyle(element, 'display') != 'none') + return {width: element.offsetWidth, height: element.offsetHeight}; + + // All *Width and *Height properties give 0 on elements with display none, + // so enable the element temporarily + var els = element.style; + var originalVisibility = els.visibility; + var originalPosition = els.position; + els.visibility = 'hidden'; + els.position = 'absolute'; + els.display = ''; + var originalWidth = element.clientWidth; + var originalHeight = element.clientHeight; + els.display = 'none'; + els.position = originalPosition; + els.visibility = originalVisibility; + return {width: originalWidth, height: originalHeight}; + }, + + makePositioned: function(element) { + element = $(element); + var pos = Element.getStyle(element, 'position'); + if (pos == 'static' || !pos) { + element._madePositioned = true; + element.style.position = 'relative'; + // Opera returns the offset relative to the positioning context, when an + // element is position relative but top and left have not been defined + if (window.opera) { + element.style.top = 0; + element.style.left = 0; + } + } + }, + + undoPositioned: function(element) { + element = $(element); + if (element._madePositioned) { + element._madePositioned = undefined; + element.style.position = + element.style.top = + element.style.left = + element.style.bottom = + element.style.right = ''; + } + }, + + makeClipping: function(element) { + element = $(element); + if (element._overflow) return; + element._overflow = element.style.overflow; + if ((Element.getStyle(element, 'overflow') || 'visible') != 'hidden') + element.style.overflow = 'hidden'; + }, + + undoClipping: function(element) { + element = $(element); + if (element._overflow) return; + element.style.overflow = element._overflow; + element._overflow = undefined; + } +}); + +var Toggle = new Object(); +Toggle.display = Element.toggle; + +/*--------------------------------------------------------------------------*/ + +Abstract.Insertion = function(adjacency) { + this.adjacency = adjacency; +} + +Abstract.Insertion.prototype = { + initialize: function(element, content) { + this.element = $(element); + this.content = content.stripScripts(); + + if (this.adjacency && this.element.insertAdjacentHTML) { + try { + this.element.insertAdjacentHTML(this.adjacency, this.content); + } catch (e) { + if (this.element.tagName.toLowerCase() == 'tbody' || this.element.tagName.toLowerCase() == 'tr') { + this.insertContent(this.contentFromAnonymousTable()); + } else { + throw e; + } + } + } else { + this.range = this.element.ownerDocument.createRange(); + if (this.initializeRange) this.initializeRange(); + this.insertContent([this.range.createContextualFragment(this.content)]); + } + + setTimeout(function() {content.evalScripts()}, 10); + }, + + contentFromAnonymousTable: function() { + var div = document.createElement('div'); + div.innerHTML = '<table><tbody>' + this.content + '</tbody></table>'; + return $A(div.childNodes[0].childNodes[0].childNodes); + } +} + +var Insertion = new Object(); + +Insertion.Before = Class.create(); +Insertion.Before.prototype = Object.extend(new Abstract.Insertion('beforeBegin'), { + initializeRange: function() { + this.range.setStartBefore(this.element); + }, + + insertContent: function(fragments) { + fragments.each((function(fragment) { + this.element.parentNode.insertBefore(fragment, this.element); + }).bind(this)); + } +}); + +Insertion.Top = Class.create(); +Insertion.Top.prototype = Object.extend(new Abstract.Insertion('afterBegin'), { + initializeRange: function() { + this.range.selectNodeContents(this.element); + this.range.collapse(true); + }, + + insertContent: function(fragments) { + fragments.reverse(false).each((function(fragment) { + this.element.insertBefore(fragment, this.element.firstChild); + }).bind(this)); + } +}); + +Insertion.Bottom = Class.create(); +Insertion.Bottom.prototype = Object.extend(new Abstract.Insertion('beforeEnd'), { + initializeRange: function() { + this.range.selectNodeContents(this.element); + this.range.collapse(this.element); + }, + + insertContent: function(fragments) { + fragments.each((function(fragment) { + this.element.appendChild(fragment); + }).bind(this)); + } +}); + +Insertion.After = Class.create(); +Insertion.After.prototype = Object.extend(new Abstract.Insertion('afterEnd'), { + initializeRange: function() { + this.range.setStartAfter(this.element); + }, + + insertContent: function(fragments) { + fragments.each((function(fragment) { + this.element.parentNode.insertBefore(fragment, + this.element.nextSibling); + }).bind(this)); + } +}); + +/*--------------------------------------------------------------------------*/ + +Element.ClassNames = Class.create(); +Element.ClassNames.prototype = { + initialize: function(element) { + this.element = $(element); + }, + + _each: function(iterator) { + this.element.className.split(/\s+/).select(function(name) { + return name.length > 0; + })._each(iterator); + }, + + set: function(className) { + this.element.className = className; + }, + + add: function(classNameToAdd) { + if (this.include(classNameToAdd)) return; + this.set(this.toArray().concat(classNameToAdd).join(' ')); + }, + + remove: function(classNameToRemove) { + if (!this.include(classNameToRemove)) return; + this.set(this.select(function(className) { + return className != classNameToRemove; + }).join(' ')); + }, + + toString: function() { + return this.toArray().join(' '); + } +} + +Object.extend(Element.ClassNames.prototype, Enumerable); +var Field = { + clear: function() { + for (var i = 0; i < arguments.length; i++) + $(arguments[i]).value = ''; + }, + + focus: function(element) { + $(element).focus(); + }, + + present: function() { + for (var i = 0; i < arguments.length; i++) + if ($(arguments[i]).value == '') return false; + return true; + }, + + select: function(element) { + $(element).select(); + }, + + activate: function(element) { + element = $(element); + element.focus(); + if (element.select) + element.select(); + } +} + +/*--------------------------------------------------------------------------*/ + +var Form = { + serialize: function(form) { + var elements = Form.getElements($(form)); + var queryComponents = new Array(); + + for (var i = 0; i < elements.length; i++) { + var queryComponent = Form.Element.serialize(elements[i]); + if (queryComponent) + queryComponents.push(queryComponent); + } + + return queryComponents.join('&'); + }, + + getElements: function(form) { + form = $(form); + var elements = new Array(); + + for (tagName in Form.Element.Serializers) { + var tagElements = form.getElementsByTagName(tagName); + for (var j = 0; j < tagElements.length; j++) + elements.push(tagElements[j]); + } + return elements; + }, + + getInputs: function(form, typeName, name) { + form = $(form); + var inputs = form.getElementsByTagName('input'); + + if (!typeName && !name) + return inputs; + + var matchingInputs = new Array(); + for (var i = 0; i < inputs.length; i++) { + var input = inputs[i]; + if ((typeName && input.type != typeName) || + (name && input.name != name)) + continue; + matchingInputs.push(input); + } + + return matchingInputs; + }, + + disable: function(form) { + var elements = Form.getElements(form); + for (var i = 0; i < elements.length; i++) { + var element = elements[i]; + element.blur(); + element.disabled = 'true'; + } + }, + + enable: function(form) { + var elements = Form.getElements(form); + for (var i = 0; i < elements.length; i++) { + var element = elements[i]; + element.disabled = ''; + } + }, + + findFirstElement: function(form) { + return Form.getElements(form).find(function(element) { + return element.type != 'hidden' && !element.disabled && + ['input', 'select', 'textarea'].include(element.tagName.toLowerCase()); + }); + }, + + focusFirstElement: function(form) { + Field.activate(Form.findFirstElement(form)); + }, + + reset: function(form) { + $(form).reset(); + } +} + +Form.Element = { + serialize: function(element) { + element = $(element); + var method = element.tagName.toLowerCase(); + var parameter = Form.Element.Serializers[method](element); + + if (parameter) { + var key = encodeURIComponent(parameter[0]); + if (key.length == 0) return; + + if (parameter[1].constructor != Array) + parameter[1] = [parameter[1]]; + + return parameter[1].map(function(value) { + return key + '=' + encodeURIComponent(value); + }).join('&'); + } + }, + + getValue: function(element) { + element = $(element); + var method = element.tagName.toLowerCase(); + var parameter = Form.Element.Serializers[method](element); + + if (parameter) + return parameter[1]; + } +} + +Form.Element.Serializers = { + input: function(element) { + switch (element.type.toLowerCase()) { + case 'submit': + case 'hidden': + case 'password': + case 'text': + return Form.Element.Serializers.textarea(element); + case 'checkbox': + case 'radio': + return Form.Element.Serializers.inputSelector(element); + } + return false; + }, + + inputSelector: function(element) { + if (element.checked) + return [element.name, element.value]; + }, + + textarea: function(element) { + return [element.name, element.value]; + }, + + select: function(element) { + return Form.Element.Serializers[element.type == 'select-one' ? + 'selectOne' : 'selectMany'](element); + }, + + selectOne: function(element) { + var value = '', opt, index = element.selectedIndex; + if (index >= 0) { + opt = element.options[index]; + value = opt.value; + if (!value && !('value' in opt)) + value = opt.text; + } + return [element.name, value]; + }, + + selectMany: function(element) { + var value = new Array(); + for (var i = 0; i < element.length; i++) { + var opt = element.options[i]; + if (opt.selected) { + var optValue = opt.value; + if (!optValue && !('value' in opt)) + optValue = opt.text; + value.push(optValue); + } + } + return [element.name, value]; + } +} + +/*--------------------------------------------------------------------------*/ + +var $F = Form.Element.getValue; + +/*--------------------------------------------------------------------------*/ + +Abstract.TimedObserver = function() {} +Abstract.TimedObserver.prototype = { + initialize: function(element, frequency, callback) { + this.frequency = frequency; + this.element = $(element); + this.callback = callback; + + this.lastValue = this.getValue(); + this.registerCallback(); + }, + + registerCallback: function() { + setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); + }, + + onTimerEvent: function() { + var value = this.getValue(); + if (this.lastValue != value) { + this.callback(this.element, value); + this.lastValue = value; + } + } +} + +Form.Element.Observer = Class.create(); +Form.Element.Observer.prototype = Object.extend(new Abstract.TimedObserver(), { + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.Observer = Class.create(); +Form.Observer.prototype = Object.extend(new Abstract.TimedObserver(), { + getValue: function() { + return Form.serialize(this.element); + } +}); + +/*--------------------------------------------------------------------------*/ + +Abstract.EventObserver = function() {} +Abstract.EventObserver.prototype = { + initialize: function(element, callback) { + this.element = $(element); + this.callback = callback; + + this.lastValue = this.getValue(); + if (this.element.tagName.toLowerCase() == 'form') + this.registerFormCallbacks(); + else + this.registerCallback(this.element); + }, + + onElementEvent: function() { + var value = this.getValue(); + if (this.lastValue != value) { + this.callback(this.element, value); + this.lastValue = value; + } + }, + + registerFormCallbacks: function() { + var elements = Form.getElements(this.element); + for (var i = 0; i < elements.length; i++) + this.registerCallback(elements[i]); + }, + + registerCallback: function(element) { + if (element.type) { + switch (element.type.toLowerCase()) { + case 'checkbox': + case 'radio': + Event.observe(element, 'click', this.onElementEvent.bind(this)); + break; + case 'password': + case 'text': + case 'textarea': + case 'select-one': + case 'select-multiple': + Event.observe(element, 'change', this.onElementEvent.bind(this)); + break; + } + } + } +} + +Form.Element.EventObserver = Class.create(); +Form.Element.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), { + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.EventObserver = Class.create(); +Form.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), { + getValue: function() { + return Form.serialize(this.element); + } +}); +if (!window.Event) { + var Event = new Object(); +} + +Object.extend(Event, { + KEY_BACKSPACE: 8, + KEY_TAB: 9, + KEY_RETURN: 13, + KEY_ESC: 27, + KEY_LEFT: 37, + KEY_UP: 38, + KEY_RIGHT: 39, + KEY_DOWN: 40, + KEY_DELETE: 46, + + element: function(event) { + return event.target || event.srcElement; + }, + + isLeftClick: function(event) { + return (((event.which) && (event.which == 1)) || + ((event.button) && (event.button == 1))); + }, + + pointerX: function(event) { + return event.pageX || (event.clientX + + (document.documentElement.scrollLeft || document.body.scrollLeft)); + }, + + pointerY: function(event) { + return event.pageY || (event.clientY + + (document.documentElement.scrollTop || document.body.scrollTop)); + }, + + stop: function(event) { + if (event.preventDefault) { + event.preventDefault(); + event.stopPropagation(); + } else { + event.returnValue = false; + event.cancelBubble = true; + } + }, + + // find the first node with the given tagName, starting from the + // node the event was triggered on; traverses the DOM upwards + findElement: function(event, tagName) { + var element = Event.element(event); + while (element.parentNode && (!element.tagName || + (element.tagName.toUpperCase() != tagName.toUpperCase()))) + element = element.parentNode; + return element; + }, + + observers: false, + + _observeAndCache: function(element, name, observer, useCapture) { + if (!this.observers) this.observers = []; + if (element.addEventListener) { + this.observers.push([element, name, observer, useCapture]); + element.addEventListener(name, observer, useCapture); + } else if (element.attachEvent) { + this.observers.push([element, name, observer, useCapture]); + element.attachEvent('on' + name, observer); + } + }, + + unloadCache: function() { + if (!Event.observers) return; + for (var i = 0; i < Event.observers.length; i++) { + Event.stopObserving.apply(this, Event.observers[i]); + Event.observers[i][0] = null; + } + Event.observers = false; + }, + + observe: function(element, name, observer, useCapture) { + var element = $(element); + useCapture = useCapture || false; + + if (name == 'keypress' && + (navigator.appVersion.match(/Konqueror|Safari|KHTML/) + || element.attachEvent)) + name = 'keydown'; + + this._observeAndCache(element, name, observer, useCapture); + }, + + stopObserving: function(element, name, observer, useCapture) { + var element = $(element); + useCapture = useCapture || false; + + if (name == 'keypress' && + (navigator.appVersion.match(/Konqueror|Safari|KHTML/) + || element.detachEvent)) + name = 'keydown'; + + if (element.removeEventListener) { + element.removeEventListener(name, observer, useCapture); + } else if (element.detachEvent) { + element.detachEvent('on' + name, observer); + } + } +}); + +/* prevent memory leaks in IE */ +Event.observe(window, 'unload', Event.unloadCache, false); +var Position = { + // set to true if needed, warning: firefox performance problems + // NOT neeeded for page scrolling, only if draggable contained in + // scrollable elements + includeScrollOffsets: false, + + // must be called before calling withinIncludingScrolloffset, every time the + // page is scrolled + prepare: function() { + this.deltaX = window.pageXOffset + || document.documentElement.scrollLeft + || document.body.scrollLeft + || 0; + this.deltaY = window.pageYOffset + || document.documentElement.scrollTop + || document.body.scrollTop + || 0; + }, + + realOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.scrollTop || 0; + valueL += element.scrollLeft || 0; + element = element.parentNode; + } while (element); + return [valueL, valueT]; + }, + + cumulativeOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + } while (element); + return [valueL, valueT]; + }, + + positionedOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + if (element) { + p = Element.getStyle(element, 'position'); + if (p == 'relative' || p == 'absolute') break; + } + } while (element); + return [valueL, valueT]; + }, + + offsetParent: function(element) { + if (element.offsetParent) return element.offsetParent; + if (element == document.body) return element; + + while ((element = element.parentNode) && element != document.body) + if (Element.getStyle(element, 'position') != 'static') + return element; + + return document.body; + }, + + // caches x/y coordinate pair to use with overlap + within: function(element, x, y) { + if (this.includeScrollOffsets) + return this.withinIncludingScrolloffsets(element, x, y); + this.xcomp = x; + this.ycomp = y; + this.offset = this.cumulativeOffset(element); + + return (y >= this.offset[1] && + y < this.offset[1] + element.offsetHeight && + x >= this.offset[0] && + x < this.offset[0] + element.offsetWidth); + }, + + withinIncludingScrolloffsets: function(element, x, y) { + var offsetcache = this.realOffset(element); + + this.xcomp = x + offsetcache[0] - this.deltaX; + this.ycomp = y + offsetcache[1] - this.deltaY; + this.offset = this.cumulativeOffset(element); + + return (this.ycomp >= this.offset[1] && + this.ycomp < this.offset[1] + element.offsetHeight && + this.xcomp >= this.offset[0] && + this.xcomp < this.offset[0] + element.offsetWidth); + }, + + // within must be called directly before + overlap: function(mode, element) { + if (!mode) return 0; + if (mode == 'vertical') + return ((this.offset[1] + element.offsetHeight) - this.ycomp) / + element.offsetHeight; + if (mode == 'horizontal') + return ((this.offset[0] + element.offsetWidth) - this.xcomp) / + element.offsetWidth; + }, + + clone: function(source, target) { + source = $(source); + target = $(target); + target.style.position = 'absolute'; + var offsets = this.cumulativeOffset(source); + target.style.top = offsets[1] + 'px'; + target.style.left = offsets[0] + 'px'; + target.style.width = source.offsetWidth + 'px'; + target.style.height = source.offsetHeight + 'px'; + }, + + page: function(forElement) { + var valueT = 0, valueL = 0; + + var element = forElement; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + + // Safari fix + if (element.offsetParent==document.body) + if (Element.getStyle(element,'position')=='absolute') break; + + } while (element = element.offsetParent); + + element = forElement; + do { + valueT -= element.scrollTop || 0; + valueL -= element.scrollLeft || 0; + } while (element = element.parentNode); + + return [valueL, valueT]; + }, + + clone: function(source, target) { + var options = Object.extend({ + setLeft: true, + setTop: true, + setWidth: true, + setHeight: true, + offsetTop: 0, + offsetLeft: 0 + }, arguments[2] || {}) + + // find page position of source + source = $(source); + var p = Position.page(source); + + // find coordinate system to use + target = $(target); + var delta = [0, 0]; + var parent = null; + // delta [0,0] will do fine with position: fixed elements, + // position:absolute needs offsetParent deltas + if (Element.getStyle(target,'position') == 'absolute') { + parent = Position.offsetParent(target); + delta = Position.page(parent); + } + + // correct by body offsets (fixes Safari) + if (parent == document.body) { + delta[0] -= document.body.offsetLeft; + delta[1] -= document.body.offsetTop; + } + + // set position + if(options.setLeft) target.style.left = (p[0] - delta[0] + options.offsetLeft) + 'px'; + if(options.setTop) target.style.top = (p[1] - delta[1] + options.offsetTop) + 'px'; + if(options.setWidth) target.style.width = source.offsetWidth + 'px'; + if(options.setHeight) target.style.height = source.offsetHeight + 'px'; + }, + + absolutize: function(element) { + element = $(element); + if (element.style.position == 'absolute') return; + Position.prepare(); + + var offsets = Position.positionedOffset(element); + var top = offsets[1]; + var left = offsets[0]; + var width = element.clientWidth; + var height = element.clientHeight; + + element._originalLeft = left - parseFloat(element.style.left || 0); + element._originalTop = top - parseFloat(element.style.top || 0); + element._originalWidth = element.style.width; + element._originalHeight = element.style.height; + + element.style.position = 'absolute'; + element.style.top = top + 'px';; + element.style.left = left + 'px';; + element.style.width = width + 'px';; + element.style.height = height + 'px';; + }, + + relativize: function(element) { + element = $(element); + if (element.style.position == 'relative') return; + Position.prepare(); + + element.style.position = 'relative'; + var top = parseFloat(element.style.top || 0) - (element._originalTop || 0); + var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0); + + element.style.top = top + 'px'; + element.style.left = left + 'px'; + element.style.height = element._originalHeight; + element.style.width = element._originalWidth; + } +} + +// Safari returns margins on body which is incorrect if the child is absolutely +// positioned. For performance reasons, redefine Position.cumulativeOffset for +// KHTML/WebKit only. +if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) { + Position.cumulativeOffset = function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + if (element.offsetParent == document.body) + if (Element.getStyle(element, 'position') == 'absolute') break; + + element = element.offsetParent; + } while (element); + + return [valueL, valueT]; + } +}
\ No newline at end of file |