X-Git-Url: https://git.mdrn.pl/redakcja.git/blobdiff_plain/9b25359a45e5498ac09b51b96919d515c1cd5d15..28cd732838488b94daa3ff66dbb8a22a11de459e:/redakcja/static/js/lib/codemirror/codemirror.js diff --git a/redakcja/static/js/lib/codemirror/codemirror.js b/redakcja/static/js/lib/codemirror/codemirror.js index 8475989c..57e44be7 100644 --- a/redakcja/static/js/lib/codemirror/codemirror.js +++ b/redakcja/static/js/lib/codemirror/codemirror.js @@ -26,13 +26,15 @@ var CodeMirror = (function(){ // options to a specific CodeMirror constructor. See manual.html for // their meaning. setDefaults(CodeMirrorConfig, { - stylesheet: "", + stylesheet: [], path: "", parserfile: [], basefiles: ["util.js", "stringstream.js", "select.js", "undo.js", "editor.js", "tokenize.js"], iframeClass: null, passDelay: 200, passTime: 50, + lineNumberDelay: 200, + lineNumberTime: 50, continuousScanning: false, saveFunction: null, onChange: null, @@ -41,8 +43,9 @@ var CodeMirror = (function(){ disableSpellcheck: true, textWrapping: true, readOnly: false, - width: "100%", + width: "", height: "300px", + minHeight: 100, autoMatchParens: false, parserConfig: null, tabMode: "indent", // or "spaces", "default", "shift" @@ -50,124 +53,131 @@ var CodeMirror = (function(){ activeTokens: null, cursorActivity: null, lineNumbers: false, - indentUnit: 2 + indentUnit: 2, + domain: null }); - function wrapLineNumberDiv(place) { - return function(node) { - var container = document.createElement("div"), - nums = document.createElement("div"), - scroller = document.createElement("div"); - container.style.position = "relative"; - nums.style.position = "absolute"; - nums.style.height = "100%"; - if (nums.style.setExpression) { - try {nums.style.setExpression("height", "this.previousSibling.offsetHeight + 'px'");} - catch(e) {} // Seems to throw 'Not Implemented' on some IE8 versions - } - nums.style.top = "0px"; - nums.style.overflow = "hidden"; - place(container); - container.appendChild(node); - container.appendChild(nums); - scroller.className = "CodeMirror-line-numbers"; - nums.appendChild(scroller); + function addLineNumberDiv(container) { + var nums = document.createElement("DIV"), + scroller = document.createElement("DIV"); + nums.style.position = "absolute"; + nums.style.height = "100%"; + if (nums.style.setExpression) { + try {nums.style.setExpression("height", "this.previousSibling.offsetHeight + 'px'");} + catch(e) {} // Seems to throw 'Not Implemented' on some IE8 versions } + nums.style.top = "0px"; + nums.style.left = "0px"; + nums.style.overflow = "hidden"; + container.appendChild(nums); + scroller.className = "CodeMirror-line-numbers"; + nums.appendChild(scroller); + scroller.innerHTML = "
1
"; + return nums; } - function applyLineNumbers(frame) { - var win = frame.contentWindow, doc = win.document, - nums = frame.nextSibling, scroller = nums.firstChild; - - var nextNum = 1; - - function update() { - var diff = 20 + Math.max(doc.body.offsetHeight, frame.offsetHeight) - scroller.offsetHeight; - for (var n = Math.ceil(diff / 10); n > 0; n--) { - var div = document.createElement("div"); - div.appendChild(document.createTextNode(nextNum++)); - scroller.appendChild(div); - } - nums.scrollTop = doc.body.scrollTop || doc.documentElement.scrollTop || 0; - } + function frameHTML(options) { + if (typeof options.parserfile == "string") + options.parserfile = [options.parserfile]; + if (typeof options.stylesheet == "string") + options.stylesheet = [options.stylesheet]; - update(); - win.addEventHandler(win, "scroll", update); - win.addEventHandler(win, "resize", update); + var html = [""]; + // Hack to work around a bunch of IE8-specific problems. + html.push(""); + forEach(options.stylesheet, function(file) { + html.push(""); + }); + forEach(options.basefiles.concat(options.parserfile), function(file) { + if (!/^https?:/.test(file)) file = options.path + file; + html.push(""); - }); - html.push(""); - - var doc = this.win.document; - doc.open(); - doc.write(html.join("")); - doc.close(); + this.win = frame.contentWindow; + if (!options.domain || !internetExplorer) { + this.win.document.open(); + this.win.document.write(frameHTML(options)); + this.win.document.close(); + } } CodeMirror.prototype = { init: function() { if (this.options.initCallback) this.options.initCallback(this); - if (this.options.lineNumbers) applyLineNumbers(this.frame); + if (this.options.lineNumbers) this.activateLineNumbers(); if (this.options.reindentOnLoad) this.reindent(); + if (this.options.height == "dynamic") this.setDynamicHeight(); }, getCode: function() {return this.editor.getCode();}, setCode: function(code) {this.editor.importCode(code);}, - selection: function() {return this.editor.selectedText();}, + selection: function() {this.focusIfIE(); return this.editor.selectedText();}, reindent: function() {this.editor.reindent();}, - reindentSelection: function() {this.editor.reindentSelection(null);}, + reindentSelection: function() {this.focusIfIE(); this.editor.reindentSelection(null);}, + focusIfIE: function() { + // in IE, a lot of selection-related functionality only works when the frame is focused + if (this.win.select.ie_selection) this.focus(); + }, focus: function() { this.win.focus(); if (this.editor.selectionSnapshot) // IE hack - this.win.select.selectCoords(this.win, this.editor.selectionSnapshot); + this.win.select.setBookmark(this.win.document.body, this.editor.selectionSnapshot); }, replaceSelection: function(text) { this.focus(); @@ -177,8 +187,8 @@ var CodeMirror = (function(){ replaceChars: function(text, start, end) { this.editor.replaceChars(text, start, end); }, - getSearchCursor: function(string, fromCursor, regexp, case_sensitive) { - return this.editor.getSearchCursor(string, fromCursor, regexp, case_sensitive); + getSearchCursor: function(string, fromCursor, caseFold) { + return this.editor.getSearchCursor(string, fromCursor, caseFold); }, undo: function() {this.editor.history.undo();}, @@ -189,18 +199,76 @@ var CodeMirror = (function(){ grabKeys: function(callback, filter) {this.editor.grabKeys(callback, filter);}, ungrabKeys: function() {this.editor.ungrabKeys();}, - setParser: function(name) {this.editor.setParser(name);}, - - cursorPosition: function(start) { - if (this.win.select.ie_selection) this.focus(); - return this.editor.cursorPosition(start); + setParser: function(name, parserConfig) {this.editor.setParser(name, parserConfig);}, + setSpellcheck: function(on) {this.win.document.body.spellcheck = on;}, + setStylesheet: function(names) { + if (typeof names === "string") names = [names]; + var activeStylesheets = {}; + var matchedNames = {}; + var links = this.win.document.getElementsByTagName("link"); + // Create hashes of active stylesheets and matched names. + // This is O(n^2) but n is expected to be very small. + for (var x = 0, link; link = links[x]; x++) { + if (link.rel.indexOf("stylesheet") !== -1) { + for (var y = 0; y < names.length; y++) { + var name = names[y]; + if (link.href.substring(link.href.length - name.length) === name) { + activeStylesheets[link.href] = true; + matchedNames[name] = true; + } + } + } + } + // Activate the selected stylesheets and disable the rest. + for (var x = 0, link; link = links[x]; x++) { + if (link.rel.indexOf("stylesheet") !== -1) { + link.disabled = !(link.href in activeStylesheets); + } + } + // Create any new stylesheets. + for (var y = 0; y < names.length; y++) { + var name = names[y]; + if (!(name in matchedNames)) { + var link = this.win.document.createElement("link"); + link.rel = "stylesheet"; + link.type = "text/css"; + link.href = name; + this.win.document.getElementsByTagName('head')[0].appendChild(link); + } + } + }, + setTextWrapping: function(on) { + if (on == this.options.textWrapping) return; + this.win.document.body.style.whiteSpace = on ? "" : "nowrap"; + this.options.textWrapping = on; + if (this.lineNumbers) { + this.setLineNumbers(false); + this.setLineNumbers(true); + } }, + setIndentUnit: function(unit) {this.win.indentUnit = unit;}, + setUndoDepth: function(depth) {this.editor.history.maxDepth = depth;}, + setTabMode: function(mode) {this.options.tabMode = mode;}, + setLineNumbers: function(on) { + if (on && !this.lineNumbers) { + this.lineNumbers = addLineNumberDiv(this.wrapping); + this.activateLineNumbers(); + } + else if (!on && this.lineNumbers) { + this.wrapping.removeChild(this.lineNumbers); + this.wrapping.style.marginLeft = ""; + this.lineNumbers = null; + } + }, + + cursorPosition: function(start) {this.focusIfIE(); return this.editor.cursorPosition(start);}, firstLine: function() {return this.editor.firstLine();}, lastLine: function() {return this.editor.lastLine();}, nextLine: function(line) {return this.editor.nextLine(line);}, prevLine: function(line) {return this.editor.prevLine(line);}, lineContent: function(line) {return this.editor.lineContent(line);}, setLineContent: function(line, content) {this.editor.setLineContent(line, content);}, + removeLine: function(line){this.editor.removeLine(line);}, insertIntoLine: function(line, position, content) {this.editor.insertIntoLine(line, position, content);}, selectLines: function(startLine, startOffset, endLine, endOffset) { this.win.focus(); @@ -220,14 +288,168 @@ var CodeMirror = (function(){ } return num; }, - - // Old number-based line interface - jumpToLine: function(n) { - this.selectLines(this.nthLine(n), 0); + jumpToLine: function(line) { + if (typeof line == "number") line = this.nthLine(line); + this.selectLines(line, 0); this.win.focus(); }, - currentLine: function() { - return this.lineNumber(this.cursorPosition().line); + currentLine: function() { // Deprecated, but still there for backward compatibility + return this.lineNumber(this.cursorLine()); + }, + cursorLine: function() { + return this.cursorPosition().line; + }, + cursorCoords: function(start) {return this.editor.cursorCoords(start);}, + + activateLineNumbers: function() { + var frame = this.frame, win = frame.contentWindow, doc = win.document, body = doc.body, + nums = this.lineNumbers, scroller = nums.firstChild, self = this; + var barWidth = null; + + function sizeBar() { + if (frame.offsetWidth == 0) return; + for (var root = frame; root.parentNode; root = root.parentNode); + if (!nums.parentNode || root != document || !win.Editor) { + // Clear event handlers (their nodes might already be collected, so try/catch) + try{clear();}catch(e){} + clearInterval(sizeInterval); + return; + } + + /*if (nums.offsetWidth != barWidth) { + barWidth = nums.offsetWidth; + frame.parentNode.style.paddingLeft = barWidth + "px"; + }*/ + } + function doScroll() { + nums.scrollTop = body.scrollTop || doc.documentElement.scrollTop || 0; + } + // Cleanup function, registered by nonWrapping and wrapping. + var clear = function(){}; + sizeBar(); + var sizeInterval = setInterval(sizeBar, 500); + + function ensureEnoughLineNumbers(fill) { + var lineHeight = scroller.firstChild.offsetHeight; + if (lineHeight == 0) return; + var targetHeight = 50 + Math.max(body.offsetHeight, Math.max(frame.offsetHeight, body.scrollHeight || 0)), + lastNumber = Math.ceil(targetHeight / lineHeight); + for (var i = scroller.childNodes.length; i <= lastNumber; i++) { + var div = document.createElement("DIV"); + div.appendChild(document.createTextNode(fill ? String(i + 1) : "\u00a0")); + scroller.appendChild(div); + } + } + + function nonWrapping() { + function update() { + ensureEnoughLineNumbers(true); + doScroll(); + } + self.updateNumbers = update; + var onScroll = win.addEventHandler(win, "scroll", doScroll, true), + onResize = win.addEventHandler(win, "resize", update, true); + clear = function(){ + onScroll(); onResize(); + if (self.updateNumbers == update) self.updateNumbers = null; + }; + update(); + } + + function wrapping() { + var node, lineNum, next, pos, changes = [], styleNums = self.options.styleNumbers; + + function setNum(n, node) { + // Does not typically happen (but can, if you mess with the + // document during the numbering) + if (!lineNum) lineNum = scroller.appendChild(document.createElement("DIV")); + if (styleNums) styleNums(lineNum, node, n); + // Changes are accumulated, so that the document layout + // doesn't have to be recomputed during the pass + changes.push(lineNum); changes.push(n); + pos = lineNum.offsetHeight + lineNum.offsetTop; + lineNum = lineNum.nextSibling; + } + function commitChanges() { + for (var i = 0; i < changes.length; i += 2) + changes[i].innerHTML = changes[i + 1]; + changes = []; + } + function work() { + if (!scroller.parentNode || scroller.parentNode != self.lineNumbers) return; + + var endTime = new Date().getTime() + self.options.lineNumberTime; + while (node) { + setNum(next++, node.previousSibling); + for (; node && !win.isBR(node); node = node.nextSibling) { + var bott = node.offsetTop + node.offsetHeight; + while (scroller.offsetHeight && bott - 3 > pos) setNum(" "); + } + if (node) node = node.nextSibling; + if (new Date().getTime() > endTime) { + commitChanges(); + pending = setTimeout(work, self.options.lineNumberDelay); + return; + } + } + while (lineNum) setNum(next++); + commitChanges(); + doScroll(); + } + function start(firstTime) { + doScroll(); + ensureEnoughLineNumbers(firstTime); + node = body.firstChild; + lineNum = scroller.firstChild; + pos = 0; + next = 1; + work(); + } + + start(true); + var pending = null; + function update() { + if (pending) clearTimeout(pending); + if (self.editor.allClean()) start(); + else pending = setTimeout(update, 200); + } + self.updateNumbers = update; + var onScroll = win.addEventHandler(win, "scroll", doScroll, true), + onResize = win.addEventHandler(win, "resize", update, true); + clear = function(){ + if (pending) clearTimeout(pending); + if (self.updateNumbers == update) self.updateNumbers = null; + onScroll(); + onResize(); + }; + } + (this.options.textWrapping || this.options.styleNumbers ? wrapping : nonWrapping)(); + }, + + setDynamicHeight: function() { + var self = this, activity = self.options.cursorActivity, win = self.win, body = win.document.body, + lineHeight = null, timeout = null, vmargin = 2 * self.frame.offsetTop; + body.style.overflowY = "hidden"; + win.document.documentElement.style.overflowY = "hidden"; + this.frame.scrolling = "no"; + + function updateHeight() { + for (var span = body.firstChild, sawBR = false; span; span = span.nextSibling) + if (win.isSpan(span) && span.offsetHeight) { + lineHeight = span.offsetHeight; + if (!sawBR) vmargin = 2 * (self.frame.offsetTop + span.offsetTop + body.offsetTop + (internetExplorer ? 10 : 0)); + break; + } + if (lineHeight) + self.wrapping.style.height = Math.max(vmargin + lineHeight * (body.getElementsByTagName("BR").length + 1), + self.options.minHeight) + "px"; + } + setTimeout(updateHeight, 100); + self.options.cursorActivity = function(x) { + if (activity) activity(x); + clearTimeout(timeout); + timeout = setTimeout(updateHeight, 200); + }; } }; @@ -260,6 +482,15 @@ var CodeMirror = (function(){ area.form.addEventListener("submit", updateField, false); else area.form.attachEvent("onsubmit", updateField); + var realSubmit = area.form.submit; + function wrapSubmit() { + updateField(); + // Can't use realSubmit.apply because IE6 is too stupid + area.form.submit = realSubmit; + area.form.submit(); + area.form.submit = wrapSubmit; + } + area.form.submit = wrapSubmit; } function insert(frame) { @@ -271,6 +502,18 @@ var CodeMirror = (function(){ area.style.display = "none"; var mirror = new CodeMirror(insert, options); + mirror.toTextArea = function() { + area.parentNode.removeChild(mirror.wrapping); + area.style.display = ""; + if (area.form) { + area.form.submit = realSubmit; + if (typeof area.form.removeEventListener == "function") + area.form.removeEventListener("submit", updateField, false); + else + area.form.detachEvent("onsubmit", updateField); + } + }; + return mirror; };