Created
February 1, 2013 11:28
-
-
Save maditnerd/4690773 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function wysiwyg() { | |
this.currentElmt; | |
this.toolbar; | |
this.toolbarWidgets; | |
this.widgets = []; | |
this.selector; | |
this.toolbarDocument; | |
this.currentDocument; | |
this.enable; | |
this.isMultiple = false; | |
this.textarea; | |
this.allowedTags = { | |
"div":["style","dir","lang","title"], | |
"span":["style","dir","lang","title"], | |
"p":["style","dir","lang","title"], | |
"pre":["style","dir","lang","title"], | |
"br":["style"] | |
}; | |
this.allowedStyles = {}, | |
/* Build and init toolbar */ | |
this.init = function (selector, toolbarWidgets, toolbarDocument, currentDocument) { | |
/* Init WYSIWYG vars*/ | |
this.selector = selector; | |
this.toolbarWidgets = toolbarWidgets; | |
if(typeof toolbarDocument == "undefined") this.toolbarDocument = document; | |
else this.toolbarDocument = toolbarDocument; | |
if(typeof currentDocument == "undefined") this.currentDocument = document; | |
else this.currentDocument = currentDocument; | |
var $this = this; | |
/* Select element to convert into WYSIWYG */ | |
var el = $(this.selector,this.currentDocument); | |
/*if(el[0].tagName == "TEXTAREA") { | |
this.toolbarWidgets.push("code"); | |
}*/ | |
/* If it's not already done */ | |
if($('.HTML5editorToolbar[data-selector="' + this.selector + '"]',this.toolbarDocument).length == 0){ | |
/* We build a toolbar which is associated with the WYSIWYG */ | |
this.toolbar = this.buildToolbar(); | |
/* If textarea mode : simple WYSIWYG conneted with a textarea */ | |
if(el[0].tagName == "TEXTAREA") { | |
this.textarea = el; | |
this.textarea.before(this.toolbar); | |
el[0].style.margin = "0"; | |
var singleEditor = $("<div>",this.toolbarDocument); | |
singleEditor.attr("id", "editor" + ($(".HTML5editorToolbar",this.currentDocument).length + 1)) | |
.attr("contenteditable","true").attr("spellcheck", "false").attr("data-textarea",this.selector) | |
.addClass("HTML5Editor") | |
.html(this.textarea.val()) | |
.get(0).style = el[0].style | |
this.currentElmt = singleEditor[0]; | |
this.textarea.hide() | |
.before(singleEditor); | |
this.selector = "#" + singleEditor.attr("id"); | |
}else{ | |
/* If multiple mode: one toolbar for severals contenteditable divs */ | |
$("body",this.toolbarDocument).append(this.toolbar); | |
this.toolbar.addClass("multiple"); | |
this.isMultiple = true; | |
} | |
} | |
this.toolbar.show(); | |
$(this.currentDocument).on("mouseup",this.selector,function(e){ | |
$this.checkCommands(); | |
$(".popover").hide(); | |
}) | |
.on("paste drop",this.selector,function(e){ | |
var eltm = this; | |
setTimeout(function() {$this.sanitize(eltm)},20); | |
}); | |
$(this.selector,this.currentDocument).each(function(){ | |
if($this.isMultiple){ | |
this.setAttribute("spellcheck", "false"); | |
this.setAttribute("contenteditable", "true"); | |
/* Listen focus event on all div that matches selector in order to position and resize wysiwyg */ | |
$(this).on("focus",function (e) { | |
if($this.enable){ | |
$this.setDIV(e.target); | |
this.innerHTML = $this.format(this.innerHTML); | |
} | |
}) | |
.on("keyup drop paste",function(e) { | |
this.setAttribute("data-modified","1"); | |
$this.checkCommands(); | |
}); | |
}else{ | |
/* Copy the content of WYSIWYG to the textarea */ | |
$(this).on("keyup change",function(e) { | |
$this.textarea[0].value = $this.currentElmt.innerHTML; | |
$this.checkCommands(); | |
e.preventDefault(); | |
}) | |
.on("blur",function(e) { | |
this.innerHTML = $this.format(this.innerHTML); | |
if(this.isMultiple) $($this.toolbar,this.toolbarDocument).hide(); | |
}); | |
/* Copy the content of textarea to the WYSIWYG */ | |
$this.textarea.on("keyup blur",function(e) { | |
$this.currentElmt.innerHTML = $this.textarea[0].value; | |
e.preventDefault(); | |
}) | |
} | |
}); | |
/* Init for first use */ | |
this.setCommand("styleWithCSS"); | |
if(this.isMultiple) $this.setDIV($(this.selector, this.currentDocument).get(0)); | |
this.enable = true; | |
} | |
this.buildToolbar = function(){ | |
var toolbar = $("<div>"); | |
toolbar.addClass("HTML5editorToolbar") | |
.data("selector",this.selector) | |
.html('<iframe src="about:blank" class="popover"></iframe><ul class="tabs"></ul><div class="commands"></div>'); | |
for (var i=0; i < this.toolbarWidgets.length; i++) { | |
eval("var widget = new wysiwyg_" + this.toolbarWidgets[i] + "();"); | |
var tag = $("<div>",this.toolbarDocument); | |
tag.addClass('btn'); | |
tag.html(widget.getAdmin()); | |
if(typeof widget.allowedTags != "undefined") jQuery.extend(this.allowedTags, widget.allowedTags); | |
if(typeof widget.allowedStyles != "undefined") jQuery.extend(this.allowedStyles, widget.allowedStyles); | |
this.widgets[widget.name] = widget; | |
if($(".toolbar_" + widget.category,toolbar).length == 0){ | |
var tabTitle = $("<a>") | |
.attr("href", "#" + "toolbar_" + widget.category) | |
.html(widget.category); | |
var item = $("<li>"); | |
item.append(tabTitle); | |
$(".tabs",toolbar).append(item); | |
var tab = $("<div>"); | |
tab.addClass("toolbar_" + widget.category); | |
if(i==0) tab.css('display','block'); | |
$(".commands",toolbar).append(tab); | |
} | |
$(".toolbar_" + widget.category,toolbar).append(tag); | |
} | |
var $this = this; | |
/* Manage Tabs */ | |
$(toolbar).on("mousedown",".tabs a",function(e){ | |
$(this).addClass("current"); | |
$(".commands > div",$this.toolbar).hide(); | |
if(this.textContent == "code"){ | |
var toolbar = $this.toolbar[0]; | |
toolbar.nextSibling.nextSibling.style.display = "block"; | |
toolbar.nextSibling.style.display = "none"; | |
}else{ | |
if(!$this.isMultiple) { | |
var toolbar = $this.toolbar[0]; | |
toolbar.nextSibling.nextSibling.style.display = "none"; | |
toolbar.nextSibling.style.display = "block"; | |
} | |
$(".tabs a",$this.toolbar).removeClass("current"); | |
$(e.target.getAttribute("href").replace("#","."),$this.toolbar).show(); | |
} | |
e.preventDefault(); | |
}) | |
.on("mousedown","*",function(e){ | |
//$(this).triggerHandler("click"); | |
e.preventDefault(); | |
}) | |
/* Listen click action on BTN && Listen change action on selects*/ | |
.on("mousedown change",".HTML5editorAction",function(e){ | |
$this.widgets[this.getAttribute("data-name")].onClick(e, $this); | |
e.preventDefault(); | |
}); | |
return toolbar; | |
} | |
this.format = function( code ) { | |
var html = ''; | |
var pad = 0; | |
code = code.replace(/(>)\s*(<)(\/*)/g, '$1\r\n$2$3'); | |
$.each(code.split('\r\n'), function(index, node) { | |
var indent = 0; | |
if (node.match( /.+<\/\w[^>]*>$/ )) { | |
indent = 0; | |
} else if (node.match( /^<\/\w/ )) { | |
if (pad != 0) pad -= 1; | |
} else if (node.match( /^<\w[^>]*[^\/]>.*$/ ) && !node.match( /^<(br|img).*>\s?$/ ) ) { | |
indent = 1; | |
} | |
var padding = ''; | |
for (var i = 0; i < pad; i++) padding += ' '; | |
html += padding + node + '\r\n'; | |
pad += indent; | |
}); | |
return html; | |
} | |
/* position and resize wysiwyg */ | |
this.setDIV = function (currentElmt) { | |
this.currentElmt = currentElmt; | |
if(this.isMultiple) this.toolbar.css("width",$(this.currentElmt).width()); | |
var offset = $(this.currentElmt).offset(); | |
var top = offset.top - 55; | |
if(top < 0){ | |
top = offset.top + $(this.currentElmt).height() + 30; | |
} | |
this.toolbar.css("top",top + "px"); | |
this.toolbar.css("left", offset.left + "px"); | |
$(".HTML5editorToolbar").show(); | |
} | |
/* Exec a command on current active contenteditable div */ | |
this.setCommand = function (command, value) { | |
if(navigator.userAgent.toLowerCase().indexOf('firefox') > -1) this.currentDocument.execCommand("styleWithCSS", false, value); // fix firefox | |
this.currentDocument.execCommand(command, false, value); | |
$(this.currentElmt).attr("data-modified","1").trigger('keyup'); | |
this.checkCommands(); | |
} | |
/* Check wich command could be exec or not */ | |
this.checkCommands = function () { | |
var $this = this; | |
$("[data-command]",this.toolbar).each(function(){ | |
var el = $(this.parentNode); | |
var enabled = $this.currentDocument.queryCommandEnabled(this.getAttribute("data-command")); | |
if(enabled){ | |
el.removeClass("inactive"); | |
}else{ | |
if(!el.hasClass("inactive")) el.addClass("inactive"); | |
} | |
if(enabled && $this.currentDocument.queryCommandState(this.getAttribute("data-command"))){ | |
$this.widgets[this.getAttribute("data-name")].setCurrentValue(el, $this.currentDocument.queryCommandValue(this.getAttribute("data-command"))); | |
if(!el.hasClass("active")) el.addClass("active"); | |
}else{ | |
el.removeClass("active"); | |
} | |
}); | |
} | |
this.sanitize = function(elmt) { | |
for(var a = 0; a < elmt.childNodes.length; a++) { | |
node = elmt.childNodes[a]; | |
if(node.nodeType == 1){ | |
if(typeof this.allowedTags[node.tagName.toLowerCase()] == "undefined") { | |
if(window.getComputedStyle(node,null).getPropertyValue("display") == 'block') var span = document.createElement("div");// todo ie x.currentStyle[styleProp]; | |
else var span = document.createElement("span"); | |
var attrs = node.attributes; | |
for(var i=0;i < attrs.length;i++) span.setAttribute(attrs[i].nodeName,attrs[i].nodeValue); | |
span.innerHTML = node.innerHTML;// do with dom, not innerHTML | |
var newNode = node.parentNode.insertBefore(span,node); | |
node.parentNode.removeChild(node); | |
node = newNode; | |
} | |
var attrs = node.attributes; | |
for(var i=0;i < attrs.length;i++){ | |
if(this.allowedTags[node.tagName.toLowerCase()].indexOf(attrs[i].nodeName) == -1) { //indexOf not for ie8 | |
node.removeAttribute(attrs[i].nodeName); | |
i--; | |
}else if(attrs[i].nodeName == "style"){ | |
var styles = node.style; | |
for(var y=0;y < styles.length;y++){ | |
if(typeof this.allowedStyles[styles[y]] == "undefined") { | |
styles.removeProperty(styles[y]); //removeProperty not for ie8 : removeAttribute | |
y--; | |
} | |
} | |
if(styles.length == 0) node.removeAttribute("style"); | |
else node.setAttribute("style", styles.cssText); | |
} | |
} | |
/* if the node is empty and useless */ | |
if(node.tagName != "BR" && node.getBoundingClientRect().height == 0) { | |
node.parentNode.removeChild(node); | |
a--; | |
} | |
/* if the node is useless */ | |
else if((node.tagName == "SPAN" && !node.hasAttribute("style")) || (!node.hasAttribute("style") && node.parentNode.childNodes.length == 1)){ | |
for(var z = 0; z < node.childNodes.length; z++) { | |
var childNode = node.childNodes[z]; | |
node.parentNode.insertBefore(childNode,node); | |
z-- | |
} | |
node.parentNode.removeChild(node); | |
a--; | |
} | |
if (node.hasChildNodes()) this.sanitize(node); | |
} | |
} | |
}, | |
this.disable = function () { | |
this.enable = false; | |
if(typeof this.toolbar != "undefined") { | |
this.toolbar.hide(); | |
$(this.selector,this.currentDocument).attr("contenteditable", "false"); | |
} | |
} | |
} | |
function wysiwyg_btn() { | |
this.category = "format"; | |
this.getAdmin = function(){ | |
return '<div style="background:url(' + this.icon + ') no-repeat center;width:25px;height:25px;border:0;" class="HTML5editorAction" data-name="' + this.name + '" data-command="' + this.command + '"></div>'; | |
} | |
this.onClick = function(e, editor){ | |
editor.setCommand(this.command); | |
} | |
this.setCurrentValue = function(elmt, value){} | |
} | |
function wysiwyg_bold() { | |
wysiwyg_btn.call(this); | |
this.name = this.command = "bold"; | |
this.icon = "data:image/gif;base64,R0lGODlhFgAWAID/AMDAwAAAACH5BAEAAAAALAAAAAAWABYAQAInhI+pa+H9mJy0LhdgtrxzDG5WGFVk6aXqyk6Y9kXvKKNuLbb6zgMFADs="; | |
this.allowedStyles = { | |
"font-weight": /.*/ | |
}; | |
} | |
function wysiwyg_underline() { | |
wysiwyg_btn.call(this); | |
this.name = this.command = "underline"; | |
this.icon = "data:image/gif;base64,R0lGODlhFgAWAKECAAAAAF9vj////////yH5BAEAAAIALAAAAAAWABYAAAIrlI+py+0Po5zUgAsEzvEeL4Ea15EiJJ5PSqJmuwKBEKgxVuXWtun+DwxCCgA7"; | |
this.allowedStyles = { | |
"text-decoration": /.*/ | |
}; | |
} | |
function wysiwyg_italic() { | |
wysiwyg_btn.call(this); | |
this.name = this.command = "italic"; | |
this.icon = "data:image/gif;base64,R0lGODlhFgAWAKEDAAAAAF9vj5WIbf///yH5BAEAAAMALAAAAAAWABYAAAIjnI+py+0Po5x0gXvruEKHrF2BB1YiCWgbMFIYpsbyTNd2UwAAOw=="; | |
this.allowedStyles = { | |
"font-style": /.*/ | |
}; | |
} | |
function wysiwyg_justifyLeft() { | |
wysiwyg_btn.call(this); | |
this.name = this.command = "justifyLeft"; | |
this.icon = "data:image/gif;base64,R0lGODlhFgAWAID/AMDAwAAAACH5BAEAAAAALAAAAAAWABYAQAIghI+py+0Po5y02ouz3jL4D4JMGELkGYxo+qzl4nKyXAAAOw=="; | |
this.allowedStyles = { | |
"text-align": /.*/ | |
}; | |
} | |
function wysiwyg_justifyCenter() { | |
wysiwyg_btn.call(this); | |
this.name = this.command = "justifyCenter"; | |
this.icon = "data:image/gif;base64,R0lGODlhFgAWAID/AMDAwAAAACH5BAEAAAAALAAAAAAWABYAQAIfhI+py+0Po5y02ouz3jL4D4JOGI7kaZ5Bqn4sycVbAQA7"; | |
this.allowedStyles = { | |
"text-align": /.*/ | |
}; | |
} | |
function wysiwyg_justifyRight() { | |
wysiwyg_btn.call(this); | |
this.name = this.command = "justifyRight"; | |
this.icon = "data:image/gif;base64,R0lGODlhFgAWAID/AMDAwAAAACH5BAEAAAAALAAAAAAWABYAQAIghI+py+0Po5y02ouz3jL4D4JQGDLkGYxouqzl43JyVgAAOw=="; | |
this.allowedStyles = { | |
"text-align": /.*/ | |
}; | |
} | |
function wysiwyg_justifyFull() { | |
wysiwyg_btn.call(this); | |
this.name = this.command = "justifyFull"; | |
this.icon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAALBJREFUeNpi/P//PwMlgImBQsACNoUJbE4jEGsBMSMUM2FhM0HxZSAu/ffvHwMjyAtQA0gGIANYkPhrsdiGbCsyG4QdQZqo6oKNBGxEx9ZUd8E2ImyFYWYgNqHIBSB9IIzsgt1E2MwG1MQOpDmAWIFkF6Ane7gLgAxmRkbGyUCmLhZbWYHyHFBbQbazAvEFIPYF4u9gFwA1gwKFF4g5oQFJCPwB4s9AvRADBjQ3AgQYAIOVSAdZa5U/AAAAAElFTkSuQmCC"; | |
this.allowedStyles = { | |
"text-align": /.*/ | |
}; | |
} | |
function wysiwyg_strikeThrough() { | |
wysiwyg_btn.call(this); | |
this.name = this.command = "strikeThrough"; | |
this.icon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAACfSURBVCjPY/jPgB8yUFNBiWDBzOy01PKEmZG7sSrIe5dVDqIjygP/Y1GQm5b2P7kDwvbAZkK6S8L/6P8hM32N/zPYu2C1InJ36P/A/x7/bc+YoSooLy3/D4Px/23+SyC5G8kEf0EIbZSmfdfov9wZDCvc0uzLYWyZ/2J3MRTYppn/14eaIvKOvxxDgUma7ju1M/LlkmnC5bwdNIoL7BAAWzr8P9A5d4gAAAAASUVORK5CYII="; | |
this.allowedStyles = { | |
"text-decoration": /.*/ | |
}; | |
} | |
function wysiwyg_subscript() { | |
wysiwyg_btn.call(this); | |
this.name = this.command = "subscript"; | |
this.icon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABYAAAAWCAYAAADEtGw7AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAARhJREFUeNrsU4FtgzAQfHcDM4I9AiuYEegIMAKMACOYEfAI8QjOCHgEPIL7b+SoIUoaJW2kSjnpxfsRp/vzAfDGvwXbD4ZhiPtZXdcwTROEENIZe/YT8cd+oJQC730qznkqKSUTQgBV13WPKSY45+I4jmeKrbV3Kb2q+M88ztBaR2PM6TzPMxRF8bxiWv876PJ+RXHTNLEsy9Rnv3ELqKqKLeuakmPslhK8X2gryW4Sr/gRqaM0tG3LcgSzYrLEer5tpTjYYwCP/OPnOfGFFX3fp6hRZikdVJSIHDd6r0RIJdFzH16ciqegD0ts9BLdssa7L+8WDm4jsuhDhz4fPYDYbMe/dIvkQ8Skkp4ne7ExvWTwxkvxJcAAeyp5PYg93M0AAAAASUVORK5CYII="; | |
this.allowedStyles = { | |
"vertical-align": /.*/ | |
}; | |
} | |
function wysiwyg_superscript() { | |
wysiwyg_btn.call(this); | |
this.name = this.command = "superscript"; | |
this.icon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABYAAAAWCAYAAADEtGw7AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAARVJREFUeNrsU4FtwyAQfEddAI9gRrBHsEdgBToCHsEeAY9gRggjOCPACDDClyeKFSdK1Fpqqko56cSDxPPc3wO88W9R7LnkQkBajY15zxjAZ8c3uT72JL4kFC0De4rg4y98Wc0Oibfnh5dpPAzD3etCCJimCWI8/znF+Z4+Ojx5AJkkaXhZPK24bVvw3mey1BUi57yoqgqISik4LgGJpO0gGNB7zgUkPnXFsiw4juOmYmvtWqnUZ03XnqXA9FtXPLSb1hqNMet+nmcoy/Lb9nzYPKryGqTxT3B4+eRJKbGu6xxf9E7yQNd1u6YVQhpXshxpvA6BUpgckUmN3VUxVUorWYusR+j7/tbreW2apoA3/gxfAgwA01J5qh+9fJUAAAAASUVORK5CYII="; | |
this.allowedStyles = { | |
"vertical-align": /.*/ | |
}; | |
} | |
function wysiwyg_orderedList() { | |
wysiwyg_btn.call(this); | |
this.name = "orderedList"; | |
this.command = "insertOrderedList"; | |
this.icon = "data:image/gif;base64,R0lGODlhFgAWAMIGAAAAADljwliE35GjuaezxtHa7P///////yH5BAEAAAcALAAAAAAWABYAAAM2eLrc/jDKSespwjoRFvggCBUBoTFBeq6QIAysQnRHaEOzyaZ07Lu9lUBnC0UGQU1K52s6n5oEADs="; | |
this.allowedTags = { | |
"ol":["id","class","style","dir","lang","title"], | |
"li":["id","class","style","dir","lang","title"] | |
}; | |
this.allowedStyles = { | |
"list-style": /.*/, | |
"list-style-image": /.*/, | |
"list-style-position": /.*/, | |
"list-style-type": /.*/ | |
}; | |
} | |
function wysiwyg_unOrderedList() { | |
wysiwyg_btn.call(this); | |
this.name = "unOrderedList"; | |
this.command = "insertUnorderedList"; | |
this.icon = "data:image/gif;base64,R0lGODlhFgAWAMIGAAAAAB1ChF9vj1iE33mOrqezxv///////yH5BAEAAAcALAAAAAAWABYAAAMyeLrc/jDKSesppNhGRlBAKIZRERBbqm6YtnbfMY7lud64UwiuKnigGQliQuWOyKQykgAAOw=="; | |
this.allowedTags = { | |
"ul":["id","class","style","dir","lang","title"], | |
"li":["id","class","style","dir","lang","title"] | |
}; | |
this.allowedStyles = { | |
"list-style": /.*/, | |
"list-style-image": /.*/, | |
"list-style-position": /.*/, | |
"list-style-type": /.*/ | |
}; | |
} | |
function wysiwyg_undo() { | |
wysiwyg_btn.call(this); | |
this.name = this.command = "undo"; | |
this.icon = "data:image/gif;base64,R0lGODlhFgAWAOMKADljwliE33mOrpGjuYKl8aezxqPD+7/I19DV3NHa7P///////////////////////yH5BAEKAA8ALAAAAAAWABYAAARR8MlJq7046807TkaYeJJBnES4EeUJvIGapWYAC0CsocQ7SDlWJkAkCA6ToMYWIARGQF3mRQVIEjkkSVLIbSfEwhdRIH4fh/DZMICe3/C4nBQBADs="; | |
} | |
function wysiwyg_redo() { | |
wysiwyg_btn.call(this); | |
this.name = this.command = "redo"; | |
this.icon = "data:image/gif;base64,R0lGODlhFgAWAMIHAB1ChDljwl9vj1iE34Kl8aPD+7/I1////yH5BAEKAAcALAAAAAAWABYAAANKeLrc/jDKSesyphi7SiEgsVXZEATDICqBVJjpqWZt9NaEDNbQK1wCQsxlYnxMAImhyDoFAElJasRRvAZVRqqQXUy7Cgx4TC6bswkAOw=="; | |
} | |
function wysiwyg_copy() { | |
wysiwyg_btn.call(this); | |
this.name = this.command = "copy"; | |
this.icon = "data:image/gif;base64,R0lGODlhFgAWAIQcAB1ChBFNsTRLYyJYwjljwl9vj1iE31iGzF6MnHWX9HOdz5GjuYCl2YKl8ZOt4qezxqK63aK/9KPD+7DI3b/I17LM/MrL1MLY9NHa7OPs++bx/Pv8/f///////////////yH5BAEAAB8ALAAAAAAWABYAAAWG4CeOZGmeaKqubOum1SQ/kPVOW749BeVSus2CgrCxHptLBbOQxCSNCCaF1GUqwQbBd0JGJAyGJJiobE+LnCaDcXAaEoxhQACgNw0FQx9kP+wmaRgYFBQNeAoGihCAJQsCkJAKOhgXEw8BLQYciooHf5o7EA+kC40qBKkAAAGrpy+wsbKzIiEAOw=="; | |
this.onClick = function(e, editor){ | |
if(typeof(window.clipboardData)=="undefined") { | |
alert("Your navigateur preferences don't allow this action. Please use CTRL + C"); | |
}else{ | |
editor.setCommand("copy"); | |
} | |
} | |
} | |
function wysiwyg_paste() { | |
wysiwyg_btn.call(this); | |
this.name = this.command = "paste"; | |
this.icon = "data:image/gif;base64,R0lGODlhFgAWAIQUAD04KTRLY2tXQF9vj414WZWIbXmOrpqbmpGjudClFaezxsa0cb/I1+3YitHa7PrkIPHvbuPs+/fvrvv8/f///////////////////////////////////////////////yH5BAEAAB8ALAAAAAAWABYAAAWN4CeOZGmeaKqubGsusPvBSyFJjVDs6nJLB0khR4AkBCmfsCGBQAoCwjF5gwquVykSFbwZE+AwIBV0GhFog2EwIDchjwRiQo9E2Fx4XD5R+B0DDAEnBXBhBhN2DgwDAQFjJYVhCQYRfgoIDGiQJAWTCQMRiwwMfgicnVcAAAMOaK+bLAOrtLUyt7i5uiUhADs="; | |
this.onClick = function(e, editor){ | |
if(typeof(window.clipboardData)=="undefined") { | |
alert("Your navigateur preferences don't allow this action. Please use CTRL + V"); | |
}else{ | |
editor.setCommand("copy"); | |
} | |
} | |
} | |
function wysiwyg_cut() { | |
wysiwyg_btn.call(this); | |
this.name = this.command = "cut"; | |
this.icon = "data:image/gif;base64,R0lGODlhFgAWAIQSAB1ChBFNsRJTySJYwjljwkxwl19vj1dusYODhl6MnHmOrpqbmpGjuaezxrCztcDCxL/I18rL1P///////////////////////////////////////////////////////yH5BAEAAB8ALAAAAAAWABYAAAVu4CeOZGmeaKqubDs6TNnEbGNApNG0kbGMi5trwcA9GArXh+FAfBAw5UexUDAQESkRsfhJPwaH4YsEGAAJGisRGAQY7UCC9ZAXBB+74LGCRxIEHwAHdWooDgGJcwpxDisQBQRjIgkDCVlfmZqbmiEAOw=="; | |
this.onClick = function(e, editor){ | |
if(typeof(window.clipboardData)=="undefined") { | |
alert("Your navigateur preferences don't allow this action. Please use CTRL + X"); | |
}else{ | |
editor.setCommand("copy"); | |
} | |
} | |
} | |
function wysiwyg_outdent() { | |
wysiwyg_btn.call(this); | |
this.name = this.command = "outdent"; | |
this.icon = "data:image/gif;base64,R0lGODlhFgAWAMIHAAAAADljwliE35GjuaezxtDV3NHa7P///yH5BAEAAAcALAAAAAAWABYAAAM2eLrc/jDKCQG9F2i7u8agQgyK1z2EIBil+TWqEMxhMczsYVJ3e4ahk+sFnAgtxSQDqWw6n5cEADs="; | |
this.allowedTags = { | |
"blockquote":["id","class","style","dir","lang","title"] | |
}; | |
this.allowedStyles = { | |
"margin": /.*/, | |
"border": /.*/, | |
"padding": /.*/ | |
}; | |
} | |
function wysiwyg_indent() { | |
wysiwyg_btn.call(this); | |
this.name = this.command = "indent"; | |
this.icon = "data:image/gif;base64,R0lGODlhFgAWAOMIAAAAADljwl9vj1iE35GjuaezxtDV3NHa7P///////////////////////////////yH5BAEAAAgALAAAAAAWABYAAAQ7EMlJq704650B/x8gemMpgugwHJNZXodKsO5oqUOgo5KhBwWESyMQsCRDHu9VOyk5TM9zSpFSr9gsJwIAOw=="; | |
this.allowedTags = { | |
"blockquote":["id","class","style","dir","lang","title"] | |
}; | |
this.allowedStyles = { | |
"margin": /.*/, | |
"border": /.*/, | |
"padding": /.*/ | |
}; | |
} | |
function wysiwyg_removeFormat() { | |
wysiwyg_btn.call(this); | |
this.name = this.command = "removeFormat"; | |
this.icon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABYAAAAWCAYAAADEtGw7AAAABGdBTUEAALGPC/xhBQAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAAOxAAADsQBlSsOGwAAAAd0SU1FB9oECQMCKPI8CIIAAAAIdEVYdENvbW1lbnQA9syWvwAAAuhJREFUOMtjYBgFxAB501ZWBvVaL2nHnlmk6mXCJbF69zU+Hz/9fB5O1lx+bg45qhl8/fYr5it3XrP/YWTUvvvk3VeqGXz70TvbJy8+Wv39+2/Hz19/mGwjZzuTYjALuoBv9jImaXHeyD3H7kU8fPj2ICML8z92dlbtMzdeiG3fco7J08foH1kurkm3E9iw54YvKwuTuom+LPt/BgbWf3//sf37/1/c02cCG1lB8f//f95DZx74MTMzshhoSm6szrQ/a6Ir/Z2RkfEjBxuLYFpDiDi6Af///2ckaHBp7+7wmavP5n76+P2ClrLIYl8H9W36auJCbCxM4szMTJac7Kza////R3H1w2cfWAgafPbqs5g7D95++/P1B4+ECK8tAwMDw/1H7159+/7r7ZcvPz4fOHbzEwMDwx8GBgaGnNatfHZx8zqrJ+4VJBh5CQEGOySEua/v3n7hXmqI8WUGBgYGL3vVG7fuPK3i5GD9/fja7ZsMDAzMG/Ze52mZeSj4yu1XEq/ff7W5dvfVAS1lsXc4Db7z8C3r8p7Qjf///2dnZGxlqJuyr3rPqQd/Hhyu7oSpYWScylDQsd3kzvnH738wMDzj5GBN1VIWW4c3KDon7VOvm7S3paB9u5qsU5/x5KUnlY+eexQbkLNsErK61+++VnAJcfkyMTIwffj0QwZbJDKjcETs1Y8evyd48toz8y/ffzv//vPP4veffxpX77z6l5JewHPu8MqTDAwMDLzyrjb/mZm0JcT5Lj+89+Ybm6zz95oMh7s4XbygN3Sluq4Mj5K8iKMgP4f0////fv77//8nLy+7MCcXmyYDAwODS9jM9tcvPypd35pne3ljdjvj26+H2dhYpuENikgfvQeXNmSl3tqepxXsqhXPyc666s+fv1fMdKR3TK72zpix8nTc7bdfhfkEeVbC9KhbK/9iYWHiErbu6MWbY/7//8/4//9/pgOnH6jGVazvFDRtq2VgiBIZrUTIBgCk+ivHvuEKwAAAAABJRU5ErkJggg=="; | |
this.onClick = function(e, editor){ | |
editor.setCommand("removeFormat"); | |
editor.setCommand("backColor","transparent"); | |
} | |
} | |
function wysiwyg_createLink() { | |
wysiwyg_btn.call(this); | |
this.name = this.command = "createLink"; | |
this.icon = "data:image/gif;base64,R0lGODlhFgAWAOMKAB1ChDRLY19vj3mOrpGjuaezxrCztb/I19Ha7Pv8/f///////////////////////yH5BAEKAA8ALAAAAAAWABYAAARY8MlJq7046827/2BYIQVhHg9pEgVGIklyDEUBy/RlE4FQF4dCj2AQXAiJQDCWQCAEBwIioEMQBgSAFhDAGghGi9XgHAhMNoSZgJkJei33UESv2+/4vD4TAQA7"; | |
this.category = "insert"; | |
this.allowedTags = { | |
"a":["id","class","style","dir","lang","title","accesskey","tabindex","charset","coords","href","hreflang","name","rel","rev","shape","target"] | |
}; | |
this.onClick = function(e, editor){ | |
var link = prompt('URL','http:\/\/'); | |
if(link && link.length > 0){ | |
editor.setCommand("createLink", link); | |
} | |
} | |
} | |
function wysiwyg_unlink() { | |
wysiwyg_btn.call(this); | |
this.name = this.command = "unlink"; | |
this.category = "insert"; | |
this.icon = "data:image/gif;base64,R0lGODlhFgAWAOMKAB1ChDRLY19vj3mOrpGjuaezxrCztb/I19Ha7Pv8/f///////////////////////yH5BAEKAA8ALAAAAAAWABYAAARY8MlJq7046827/2BYIQVhHg9pEgVGIklyDEUBy/RlE4FQF4dCj2AQXAiJQDCWQCAEBwIioEMQBgSAFhDAGghGi9XgHAhMNoSZgJkJei33UESv2+/4vD4TAQA7"; | |
} | |
function wysiwyg_selectSquel() { | |
this.name = this.command = "formatBlock"; | |
this.category = "format"; | |
this.values = { | |
"p":"Paragraph", | |
"h1":"Heading 1", | |
"h2":"Heading 2", | |
"h3":"Heading 3", | |
"h4":"Heading 4", | |
"h5":"Heading 5", | |
"h6":"Heading 6", | |
"pre":"Preformatted", | |
"blockquote":"Blockquote" | |
}; | |
this.allowedTags = { | |
"h1":["id","class","style","dir","lang","title"], | |
"h2":["id","class","style","dir","lang","title"], | |
"h3":["id","class","style","dir","lang","title"], | |
"h4":["id","class","style","dir","lang","title"], | |
"h5":["id","class","style","dir","lang","title"], | |
"h6":["id","class","style","dir","lang","title"] | |
}; | |
this.template = function(i, val){ | |
return '<div class="option" data-option="' + i + '"><' + i + '>' + val + '</' + i + '></div>'; | |
}; | |
this.defaultText = 'Font Family'; | |
this.getAdmin = function(){ | |
return '<div style=";height:25px;border:0;" class="HTML5editorAction select" data-name="' + this.name + '" data-command="' + this.command + '"><span class="temoin temoinSelect">' + this.defaultText + '</span></div>'; | |
} | |
this.onClick = function(e, editor){ | |
var html = "<style>.option{cursor:pointer}</style>"; | |
for (i in this.values) { | |
html += this.template(i, this.values[i]); | |
} | |
var prop = $('.popover',editor.toolbar).css({ | |
width:"190px", | |
height:"200px" | |
}).appendTo($(e.target).closest(".btn")).show().contents().find("body").empty().append(html); | |
var $this = this; | |
$(".option",prop).on("mousedown",function(e){ | |
editor.setCommand($this.command,$(this).data("option")); | |
$('.popover',editor.toolbar).hide(); | |
$(".option",prop).off("mousedown"); | |
}); | |
} | |
this.setCurrentValue = function(elmt, type){ | |
$(".temoin", elmt).text(this.values[type]); | |
} | |
} | |
function wysiwyg_formatBlock() { | |
wysiwyg_selectSquel.call(this); | |
this.name = this.command = "formatBlock"; | |
this.category = "format"; | |
this.values = { | |
"p":"Paragraph", | |
"h1":"Heading 1", | |
"h2":"Heading 2", | |
"h3":"Heading 3", | |
"h4":"Heading 4", | |
"h5":"Heading 5", | |
"h6":"Heading 6", | |
"pre":"Preformatted", | |
"blockquote":"Blockquote" | |
}; | |
this.allowedTags = { | |
"h1":["id","class","style","dir","lang","title"], | |
"h2":["id","class","style","dir","lang","title"], | |
"h3":["id","class","style","dir","lang","title"], | |
"h4":["id","class","style","dir","lang","title"], | |
"h5":["id","class","style","dir","lang","title"], | |
"h6":["id","class","style","dir","lang","title"] | |
}; | |
this.template = function(i, val){ | |
return '<div class="option" data-option="' + i + '"><' + i + '>' + val + '</' + i + '></div>'; | |
}; | |
this.defaultText = 'Format'; | |
} | |
function wysiwyg_fontName() { | |
wysiwyg_selectSquel.call(this); | |
this.name = this.command = "fontName"; | |
this.category = "format"; | |
this.values = { | |
"arial, helvetica, sans-serif":"Arial", | |
"'arial black', avant garde;":"Arial Black", | |
"'book antiqua', palatino":"Book Antiqua", | |
"'comic sans ms', sans-serif":"Comic Sans MS", | |
"courier new, courier":"Courier New", | |
"georgia, palatino":"Georgia", | |
"helvetica":"Helvetica", | |
"impact, chicago":"Impact", | |
"symbol":"Symbol", | |
"tahoma, arial, helvetica, sans-serif":"Tahoma", | |
"terminal, monaco":"Terminal", | |
"'times new roman', times":"Times New Roman", | |
"'trebuchet ms', geneva":"Trebuchet MS", | |
"verdana, geneva":"Verdana", | |
"webdings":"Webdings", | |
"wingdings, 'zapf dingbats'":"Wingdings" | |
}; | |
this.template = function(i, val){ | |
return '<div class="option" data-option="' + i + '" style="font-family:' + i + '">' + val + '</div>'; | |
}; | |
this.defaultText = 'Font Family'; | |
this.allowedStyles = { | |
"font-family": /.*/ | |
}; | |
} | |
function wysiwyg_fontSize() { | |
wysiwyg_selectSquel.call(this); | |
this.name = this.command = "fontSize"; | |
this.category = "format"; | |
this.values = { | |
"1":"8", | |
"2":"10", | |
"3":"12", | |
"4":"14", | |
"5":"18", | |
"6":"24", | |
"7":"36" | |
}; | |
this.template = function(i, val){ | |
return '<div class="option" data-option="' + i + '" style="font-size:' + val + 'pt">' + val + '</div>'; | |
}; | |
this.defaultText = 'Font Size'; | |
this.allowedStyles = { | |
"font-size": /.*/ | |
}; | |
} | |
function wysiwyg_colorPicker() { | |
this.pickerColorImg = "data:image/png;base64,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"; | |
this.pickerColorImgObj; | |
this.palette = ["#000000","#434343","#666666","#999999","#B7B7B7","#cccccc","#D9D9D9","#EFEFEF","#F3F3F3","#ffffff", | |
"#980000","red","#F90","yellow","lime","cyan","#4A86E8","blue","#90F","magenta", | |
"#E6B8AF","#F4CCCC","#FCE5CD","#FFF2CC","#D9EAD3","#D0E0E3","#C9DAF8","#CFE2F3","#D9D2E9","#EAD1DC", | |
"#DD7E6B","#EA9999","#F9CB9C","#FFE599","#B6D7A8","#A2C4C9","#A4C2F4","#9FC5E8","#B4A7D6","#D5A6BD", | |
"#CC4125","#E06666","#F6B26B","#FFD966","#93C47D","#76A5AF","#6D9EEB","#6FA8DC","#8E7CC3","#C27BA0", | |
"#A61C00","#C00","#E69138","#F1C232","#6AA84F","#45818E","#3C78D8","#3D85C6","#674EA7","#A64D79", | |
"#85200C","#900"," #B45F06"," #BF9000","#38761D","#134F5C","#15C","#0B5394","#351C75","#741B47", | |
"#5B0F00","#600","#783F04","#7F6000","#274E13","#0C343D","#1C4587","#073763","#20124D","#4C1130"]; | |
this.getAdmin = function(){ | |
this.pickerColorImgObj = new Image(); | |
this.pickerColorImgObj.src = this.pickerColorImg; | |
return '<div class="HTML5editorAction" style="position:relative;width:25px;height:25px;position:relative;background:url(' + this.icon + ') no-repeat center 2px;" data-name="' + this.name + '"data-command="' + this.command + '"><div class="temoin" style="position:absolute;top:19px;height:3px;left:2px;right:3px;"></div></div>'; | |
} | |
this.onClick = function(e, editor){ | |
var html = '<style>.pick{float:left;width:16px;height:16px;margin:1px;cursor:pointer}.pick:hover{outline:1px solid #777}</style><div style="width: 180px;"><div class="pick" style="height:16px;width:auto;float:none;color:#ddd;font-size:11px;font-family:arial;padding:0 2px;background:transparent">Transparent</div>'; | |
for (var i = 0; i < this.palette.length; i++) { | |
html += '<div class="pick" style="background:' + this.palette[i] + '"></div>'; | |
} | |
html += '<div class="picker" style="display:none">'; | |
html += '<div style="float:left;width:22px;height:22px;margin: 1px;border: 1px solid #999;" class="picked_color_preview"></div>'; | |
html += '<div style="float:left;width:50px;height:22px;margin-left: 7px;" class="picked_color_rgb"></div><div style="clear:both"></div>'; | |
html += '<canvas style="width:180px;height:180px;" class="mycolorpicker"></canvas></div>'; | |
var prop = $('.popover',editor.toolbar).css({ | |
width:"200px", | |
height:"185px" | |
}).appendTo($(e.target).closest(".btn")).show().contents().find("body").empty().append(html); | |
var temoin = $(".temoin",e.target); | |
var c = $('.mycolorpicker',prop).get(0); | |
var ctx = c.getContext('2d'); | |
c.width = this.pickerColorImgObj.width; | |
c.height = this.pickerColorImgObj.height; | |
ctx.drawImage(this.pickerColorImgObj,0,0); | |
var $this = this; | |
$(c,prop).on("mousemove.Wcolorpicker", function(event){ | |
var x = event.originalEvent.layerX - c.offsetLeft; | |
var y = event.originalEvent.layerY - c.offsetTop; | |
var img_data = ctx.getImageData(x, y, 1, 1).data; | |
var rgb = $this.rgbToHex(img_data[0],img_data[1],img_data[2]); | |
$('.picked_color_preview',prop).css("background-color",rgb); | |
$('.picked_color_rgb',prop).text(rgb); | |
}); | |
$(c,prop).on("click.Wcolorpicker", function(event){ | |
editor.setCommand($this.command,$('.picked_color_rgb',prop).text()); | |
temoin.css("background-color",$('.picked_color_rgb',prop).text()); | |
$('.popover',editor.toolbar).hide(); | |
$(c,prop).off(".Wcolorpicker"); | |
event.stopPropagation(); | |
}); | |
$(".pick",prop).on("mousedown.Wcolorpicker", function(event){ | |
editor.setCommand($this.command,$(this).css("background-color")); | |
temoin.css("background-color",$(this).css("background-color")); | |
$('.popover',editor.toolbar).hide(); | |
$(c,prop).off(".Wcolorpicker"); | |
event.preventDefault(); | |
}); | |
} | |
this.setCurrentValue = function(elmt, color){ | |
//$(".temoin", elmt).css("background-color",color); | |
} | |
this.rgbToHex = function(r, g, b){ | |
return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1); | |
} | |
} | |
function wysiwyg_foreColor() { | |
wysiwyg_colorPicker.call(this); | |
this.name = this.command = "foreColor"; | |
this.category = "format"; | |
this.icon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH1gEEDyMK75EyNwAAAi5JREFUOMut0k9I02EYB/Dv+74/98fy56opYymUa61RQ1I6denfpRIMDLzoTcmDHYTA6NhFtB0qFEHsFOQxgyFUsA4aqbHTmC1xiX+alPsna7r22/u8HbzoXF3sOX+fz/s8vA9DmWruemnjnG0SKQ0AOGdFIlUTGu/MlGa1cgBj6L5xqYF3tfgAAOOBMH8/H+sG8ORAtszrXBN8x3//umk6/F0BQKOrhj1+MV0oSrKGxjtpb56XAiTlNZezWuhHTZgMLqjJ4IIC5zihWwRJebU0fwDQBJ7eueIRs5ENKCVXlJIrc5E4zrsdQhN49k/A1zFWb9KY1113HK+DEZnPF/ry+ULfTCgm9WodhlH0+jrG6v8KcODR7cvn2JfVNOI/MiBJUyRpKpHMYjO1A6fDzqDwsCzgbR8xS5LdF9wO9vbToiKSo5GJHiMy0WNIkqPRpXVlr6llUhbvedtHzAe+kaRsazzrZKmcxPJ6koyC0eu5+7wXAIqGgcxWVtoddcJisSL3K9sG4NU+QHA27DlTx5fi22i+6BMWs0ClWcMRs4BurUCVVROzX9NYP1Yrctns8D7A1er3mSq4TsKKdx8+y2QiKcodWGWVTZ5s8AoFpbta/b7Ymwfh3VNlGHI4nWJxNYXkZgJEZF4O9Bf2Np9uGTTlttLbxu88bHaHSG6sDQG4yXf3p6ZoJIq5mY9QRAOlzQCwHOgvQKnBWHgeP9e+QRE14X8UO3VrQB0G4Ied4A+X1/MuJnM+zQAAAABJRU5ErkJggg=="; | |
this.allowedStyles = { | |
"color": /.*/ | |
}; | |
} | |
function wysiwyg_hiliteColor() { | |
wysiwyg_colorPicker.call(this); | |
this.name = this.command = "backColor"; | |
this.category = "format"; | |
this.icon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAe5JREFUeNqMUz1LI1EUPe9FRJGIsqAgmDjuBKZKoYuCilqk0EYrG3+AYiUWIhayKUQQwWA5hUHFzir+ghijRjEiFpIwJCAWwoZMxM6YzOydcSabHaPxwuFy7nvncO/7YKoK6Po/pNMdkOWx4ObmUVDT3mvOXB2cECXoNny+P3pnZ/l3dc2BcafBWCwm4fBwBKenklksl8vY2prG+fk7TyQk7OxM4+rK5B8MMDqaQnd3DtGoWDFYXo4gHhfx+PgDZ2ciFhcjGBhImeu5HHB/D1xeWgbh8CC83hxKpZK5oTobM9vciGIReH1FV3UHJ4riRibT/p8wGAxgeFiBx5PH0JCCjY0AdfITLy9oIpNjMpZtg3FN05ggqMzIxM1swOtVTe7xqMzvzxDPTJJBoK8P/b29mKFbmWtAnXh+JgdWgN9fmKDW1w3x0xMKNzfIUqNJ/pnw9vaB5i+YBtRuLfE8bUt+6GB7m2NvL1bhX4mN9YrB7m7REvJvi41gq6ucHk3NSX4JgrCWSChTTnE6DUQiQCpVQ7WwwOFyuXwEORQK6aIoXhwc4DocRv/KCiBJdU5dlhlsMef8mDG2RMKWukIr3PE4rvN52OJZqrURWgkthGZCo3V2xtzMeQtumhc0b7KnR9vPZnFnGbzVgPlt/gowAIULFFoZgfsOAAAAAElFTkSuQmCC"; | |
this.allowedStyles = { | |
"background-color": /.*/, | |
}; | |
} | |
function wysiwyg_insertImage() { | |
wysiwyg_btn.call(this); | |
this.name = this.command = "insertImage"; | |
this.category = "insert"; | |
this.icon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAIfSURBVDjLpZPNS5RRFMZ/577v+L5jmlmNoBgE4iLIWkgxmTtx4R8QLXLRB1GYG4lAwlWkCH1sShcRuIgWYUQoBIUVgojLyowWLSRhSCNtchzn672nxYxT6hRBD/cuzuW5D+c5H6Kq/A9cgM6+0VtBTk4tJwM/kS7BspvDsAc7w4w8uXGyxwUIrHRev9AcqYlERMRFAS3+E1RBdSNWglyGs9eenwbyAsuJwIvsjUjX7QfU7duF51gC9cBUYYT8NYJjhM8fZ+nvuUg2EClaSKbBGJfGhv0cjLbiGAfVAMQFEYwIIgZjDCHHYO2WGmzY9DwfP1yRz/cv0KLJLQLZTIpsah1EULVYDbDWIICq4khALpNE1W7PQBW+xmN8W4qTtTmsBvxIL5IJ6pECp8ZbYX0tDmpKC3xZLCe0kPr1oBFUU0XyCmEWFnT7HNgC3zhlGMcr6TtITJBLvKK6+jtX7z/ElDV4cGJzBn9COv6MPZXTNDcfpX53I6/nnrL+ftKPdtfddAHUWgRYmp8rKRAKPabtSAeBCThc287Eh1GiTS3Mfxq75OZnLd+coYG+YvQ7rtzpJyQVdBw4B8DltnuMzw4DY74LsDNs4jaXqqotl3wLC4KFw+panLnYNG9jU/S2jzD44gx+vlYpF2CHZx6dH3h5LJnVJmtL7dJxf+bdtNdyqJXx2WHKxGXqzSTAkPzrOke76waBLqASWAWGZ+7Gen8CJf/dMYh8E3AAAAAASUVORK5CYII="; | |
this.allowedStyles = { | |
"padding": /.*/,"float": /.*/ | |
}; | |
this.allowedTags = { | |
"img":["id","src","alt","title","class","style","dir","lang","title"] | |
}; | |
this.onClick = function(e, editor){ | |
var html = 'URL <input type="text" class="URLimg"><input type="button" value="Add" class="addIMG">'; | |
var prop = $('.popover',editor.toolbar).css({ | |
width:"300px", | |
height:"50px" | |
}).appendTo(e.target.parentNode).show().contents().find("body").empty().append(html); | |
var $this = this; | |
$(".addIMG",prop).on("mousedown",function(event){ | |
console.dir($(".URLimg",prop).val()); | |
editor.setCommand($this.command,$(".URLimg",prop).val()); | |
$('.popover',editor.toolbar).hide(); | |
$(".addIMG",prop).off("mousedown"); | |
}); | |
} | |
} | |
function wysiwyg_code() { | |
this.name = "code"; | |
this.command = "bold"; // fix firefox | |
this.category = "code"; | |
this.allowedTags = { | |
"code":["id","src","alt","title","class","style","dir","lang","title","data-language"] | |
}; | |
this.getAdmin = function(){ | |
return ''; | |
} | |
this.onClick = function(e, editor){ | |
} | |
}function blockAdminToolbar() { | |
this.initBefore = function () { | |
/* Tabs */ | |
$('.sidebar').on('click','.icons', function(){ | |
var elmt = this.parentNode; | |
var rel = elmt.getAttribute("rel"); | |
var parent = $(elmt).closest(".contenttab"); | |
$(".block",parent).hide(); | |
if( elmt.classList.contains('active')) { | |
elmt.classList.remove('active'); | |
rel = ''; | |
}else { | |
$(".mainTab",parent).removeClass('active'); | |
elmt.classList.add('active'); | |
} | |
ParsimonyAdmin.setCookie($(elmt).closest(".sidebar").data("side") + "ToolbarPanel",rel,999); | |
}); | |
$('#admin').on('click','.ssTab',function(){ | |
var parent = $(this).parent().parent(); | |
parent.find(".tabPanel").hide(); | |
parent.find("." + this.getAttribute('rel')).show(); | |
parent.find(".ssTab").removeClass('active'); | |
this.classList.add('active'); | |
}); | |
} | |
} | |
ParsimonyAdmin.setPlugin(new blockAdminToolbar());function blockAdminMenu() { | |
this.initBefore = function () { | |
/* Orientation and resolution */ | |
$("#toolbar").on('change','#changeres', function(e) { | |
var res = this.value; | |
$("#currentRes").text(res); | |
if(res == 'max'){ | |
var height = ParsimonyAdmin.currentBody.offsetHeight + 250; | |
if(screen.height > height) height = screen.height - 28; | |
ParsimonyAdmin.$iframe.css({ | |
"width": "100%", | |
"height": height + "px" | |
}); | |
res = ["max","max"]; | |
}else{ | |
res = res.split(/x/); | |
if($("#changeorientation").length == 0 || ($("#changeorientation").val() == 'portrait' && ParsimonyAdmin.getCookie("landscape") == 'portrait')){ | |
ParsimonyAdmin.$iframe.css({ | |
"width": res[0] + "px", | |
"height": res[1] + "px" | |
}); | |
}else{ | |
ParsimonyAdmin.$iframe.css({ | |
"width": res[1] + "px", | |
"height": res[0] + "px" | |
}); | |
} | |
} | |
ParsimonyAdmin.setCookie("screenX",res[0],999); | |
ParsimonyAdmin.setCookie("screenY",res[1],999); | |
ParsimonyAdmin.setCookie("landscape",$("#changeorientation").val(),999); | |
ParsimonyAdmin.$currentBody.removeClass("landscape portrait").addClass($("#changeorientation").val()); | |
}) | |
.on('change','#changeorientation', function(e) { | |
ParsimonyAdmin.setCookie("landscape",$("#changeorientation").val(),999); | |
$("#changeres").trigger("change"); | |
}); | |
} | |
} | |
ParsimonyAdmin.setPlugin(new blockAdminMenu());function blockAdminModules() { | |
this.initBefore = function () { | |
$(".modeleajout").click(function(e){ | |
e.preventDefault(); | |
e.stopPropagation(); | |
ParsimonyAdmin.displayConfBox(BASE_PATH + "admin/action",t(($(this).data('title') || $(this).attr('title') || $(this).data('tooltip'))),"TOKEN=" + TOKEN + "&model=" + $(this).attr('rel') + "&action=getViewAdminModel"); | |
}); | |
} | |
this.init = function () { | |
$("#left_sidebar").on('click','div.titleTab', function(){ | |
var next = $(this).next(); | |
if(next.is('div')) $(this).next().slideToggle('fast'); | |
}); | |
} | |
this.loadCreationMode = function () { | |
//highlight link on list page | |
var src = ParsimonyAdmin.currentWindow.location.href.toLocaleString().replace("http://",""); | |
var src = src.substring(src.indexOf(BASE_PATH)).replace("?parsiframe=ok","").replace("parsiframe=ok",""); | |
var itemLink = $('.sublist[data-url="' + src + '"]'); | |
if(itemLink.length > 0){ | |
$(".sublist.selected").removeClass('selected'); | |
itemLink.addClass('selected'); | |
} | |
} | |
} | |
ParsimonyAdmin.setPlugin(new blockAdminModules());function blockAdminBlocks() { | |
this.dragLastDomId = ""; | |
this.dragMiddle = 0; | |
this.isAddBlock = false; | |
this.blocks = []; | |
this.startDragging = function () { | |
ParsimonyAdmin.$currentBody.append(document.getElementById("dropInPage" )); | |
$("#right_sidebar .mainTab").removeClass("active"); | |
$(".paneltree").addClass("active"); | |
} | |
this.changeBlockPosition = function (blockType, idBlock, idNextBlock, startIdParentBlock, stopIdParentBlock, startTypeCont, stopTypeCont, action, content){ | |
if(typeof startIdParentBlock == "undefined" || typeof stopIdParentBlock == "undefined"){ | |
alert(t('Error in your DOM, perhaps an HTML tag isn\'t closed.')); | |
return false | |
}; | |
if(idNextBlock == undefined || idNextBlock==idBlock) idNextBlock = "last"; | |
var contentToAdd = ''; | |
if(typeof content != "undefined") contentToAdd = content; | |
ParsimonyAdmin.postData(BASE_PATH + "admin/" + action,{ | |
TOKEN: TOKEN , | |
popBlock: blockType , | |
idBlock: idBlock, | |
id_next_block:idNextBlock , | |
startParentBlock: startIdParentBlock , | |
parentBlock:stopIdParentBlock , | |
start_typecont:startTypeCont , | |
stop_typecont:stopTypeCont , | |
IDPage: $(".container_page",ParsimonyAdmin.currentBody).data('page'), | |
content: contentToAdd | |
},function(data){ | |
ParsimonyAdmin.execResult(data); | |
ParsimonyAdmin.returnToShelter(); | |
ParsimonyAdmin.updateUI(); | |
}); | |
} | |
this.loadEditMode = function () { | |
$this = this; | |
ParsimonyAdmin.$currentDocument.on('click.edit','.block',function(e){ | |
var blockInst = (typeof $this.blocks["block_" + this.classList[1]] != "undefined") ? $this.blocks["block_" + this.classList[1]] : $this.blocks['block_block']; | |
blockInst.onClickEdit.apply(this, [e]); | |
}); | |
} | |
this.loadCreationMode = function () { | |
$this = this; | |
//Dispatch menu action event : configure / design / delete | |
$(document).add('#config_tree_selector').on('click.creation',".config_destroy, .cssblock, .configure_block",function(e){ | |
var blockInst = (typeof $this.blocks["block_" + this.classList[1]] != "undefined") ? $this.blocks["block_" + this.classList[1]] : $this.blocks['block_block']; | |
eval("blockInst." + $(this).data("action") + ".apply(this, [e]);"); | |
}) | |
/* Hide overlay when user don't pick a block */ | |
.on('mouseover.creation',"body", function(event) { | |
document.getElementById("blockOverlay").style.display = "none"; | |
}) | |
.on('dragenter.creation',"#admintoolbar", function(e) { | |
e.stopPropagation(); | |
ParsimonyAdmin.returnToShelter(); | |
}); | |
/* HTML5 drag n drop*/ | |
$("#panelblocks").on('dragstart.creation',".admin_core_block", function( event ){ | |
$this.isAddBlock = true; | |
var evt = event.originalEvent; | |
evt.dataTransfer.setDragImage(this,15,15); | |
evt.dataTransfer.setData("parsimony/addblock", JSON.stringify({blockType:this.id})); | |
evt.dataTransfer.effectAllowed = 'copy'; | |
$this.startDragging(); | |
}); | |
$("#menu").add('#paneltree').on('dragstart.creation',".move_block",function( event ){ | |
$this.isAddBlock = false; | |
var evt = event.originalEvent; | |
var elmt = $("#" + ParsimonyAdmin.inProgress,ParsimonyAdmin.currentBody); | |
evt.dataTransfer.setDragImage(elmt[0],15,15); | |
var startTypeCont = ParsimonyAdmin.whereIAm(ParsimonyAdmin.inProgress); | |
if(elmt.parent().closest(".container").hasClass("container_page")) startIdParentBlock = elmt.parent().closest(".container").data('page'); | |
else startIdParentBlock = elmt.parent().closest(".container").attr('id'); | |
if(startIdParentBlock == 'content') startIdParentBlock = $(".container_page",ParsimonyAdmin.currentBody).data('page'); | |
evt.dataTransfer.setData("parsimony/moveblock", JSON.stringify({idBlock:ParsimonyAdmin.inProgress,startIdParentBlock:startIdParentBlock,startTypeCont:startTypeCont})); | |
evt.dataTransfer.effectAllowed = 'copyMove'; | |
$this.startDragging(); | |
}); | |
$('#conf_box_content').on('click.creation',"#dialog-ok",function(e){ | |
e.preventDefault(); | |
var idBlock = $("#dialog-id").val(); | |
var obj = JSON.parse($("#dialog-id-options").val()); | |
if(idBlock != ''){ | |
if( obj.idNextBlock == '' || obj.stopIdParentBlock == '' || ParsimonyAdmin.whereIAm("dropInTree") == '') alert("stop"); | |
var content = ''; | |
if(typeof obj.content != "undefined") content = obj.content; | |
$this.changeBlockPosition(obj.blockType,idBlock,obj.idNextBlock,'',obj.stopIdParentBlock,'',ParsimonyAdmin.whereIAm("dropInTree"),"addBlock",content); | |
}else{ | |
alert(t('Please enter your ID')); | |
} | |
ParsimonyAdmin.closeConfBox(); | |
}); | |
ParsimonyAdmin.$currentBody.on('click.creation','.block',function(e){ | |
var blockInst = (typeof $this.blocks["block_" + this.classList[1]] != "undefined") ? $this.blocks["block_" + this.classList[1]] : $this.blocks['block_block']; | |
blockInst.onClickCreation.apply(this, [e]); | |
}) | |
.on('mouseover.creation',".block", function(event) { | |
event.stopImmediatePropagation(); | |
var offset = this.getBoundingClientRect(); | |
var leftOffsetFrame = document.getElementById("parsiframe").offsetLeft; | |
if(ParsimonyAdmin.inProgress != this.id) document.getElementById("blockOverlay").style.cssText = "display:block;top:" + offset.top + "px;left:" + (offset.left + leftOffsetFrame + 40) + "px;width:" + $(this).outerWidth() + "px;height:" + $(this).outerHeight() + "px"; | |
else document.getElementById("blockOverlay").style.display = "none"; | |
}); | |
ParsimonyAdmin.$currentBody.add('#paneltree') | |
.on('dragenter.creation','.block,.tree_selector', function(e) { | |
if( e.originalEvent.dataTransfer.types != null){ | |
e.stopImmediatePropagation(); | |
//if(e.type == 'dragenter' || Math.floor ( Math.random() * 12 ) == 3) { | |
var isContainer = false; | |
if((this.classList.contains("container") && !this.classList.contains("tree_selector")) || this.id =='treedom_container') isContainer = true; | |
if(e.type == 'dragenter' || ($this.dragLastDomId != this.id || | |
( $this.dragMiddlePos == 1 && (e.originalEvent.pageY > $this.dragMiddle)) || | |
( $this.dragMiddlePos == 0 && (e.originalEvent.pageY < $this.dragMiddle)))){ | |
var theBlock = this; | |
if(this.classList.contains("tree_selector")) theBlock = ParsimonyAdmin.currentDocument.getElementById(this.id.split("treedom_")[1]); | |
var theBlockTree = document.getElementById("treedom_" + theBlock.id); | |
var dropInPage = ParsimonyAdmin.currentDocument.getElementById("dropInPage") || $("#dropInPage").appendTo(ParsimonyAdmin.currentBody).get(0); | |
$this.dragLastDomId = this.id; | |
$this.dragMiddle = $(this).offset().top + this.offsetHeight/2; | |
if(e.originalEvent.pageY < $this.dragMiddle && !isContainer){ | |
if(!$this.isAddBlock && ParsimonyAdmin.inProgress == theBlock.id) return true; | |
$this.dragMiddlePos = 1; | |
$(theBlock).before(dropInPage); | |
theBlockTree.parentNode.insertBefore(document.getElementById( "dropInTree" ),theBlockTree); | |
}else{ | |
if(!$this.isAddBlock && theBlock.nextElementSibling.id == ParsimonyAdmin.inProgress) return true; | |
$this.dragMiddlePos = 0; | |
if(theBlock.classList.contains("container") && $(theBlock).children(".dropInContainer").length > 0){ | |
if(!$this.isAddBlock && theBlock.id == ParsimonyAdmin.inProgress) return true; | |
$(".dropInContainerChild:first",theBlock).append(dropInPage); | |
theBlockTree.appendChild(document.getElementById( "dropInTree" ),theBlockTree); | |
}else if(theBlock.classList.contains("container") && !isContainer){ | |
theBlock.parentNode.insertBefore(dropInPage,theBlock); | |
theBlockTree.parentNode.insertBefore(document.getElementById( "dropInTree" ),theBlockTree); | |
}else if(theBlock.parentNode.classList.contains("container") && !isContainer){ | |
theBlock.parentNode.insertBefore(dropInPage,theBlock.nextSibling); | |
theBlockTree.parentNode.insertBefore(document.getElementById( "dropInTree" ),theBlockTree.nextSibling); | |
} | |
} | |
} | |
dropInPage = theBlock = theBlockTree = null; | |
return false; | |
}else{ | |
return true; | |
} | |
}) | |
.on('dragover.creation','.block,.tree_selector', function(e) { | |
if( e.originalEvent.dataTransfer.types != null){ | |
e.stopImmediatePropagation(); | |
e.preventDefault(); /* Firefox fix */ | |
return false; | |
}else{ | |
return true; | |
} | |
}) | |
.on('dragover.creation dragenter.creation','.marqueurdragndrop', function(e) { | |
if( e.originalEvent.dataTransfer.types != null){ | |
e.stopImmediatePropagation(); | |
e.preventDefault(); /* Firefox fix */ | |
return false; | |
}else{ | |
return true; | |
} | |
}) | |
.on('drop.creation','.container,.tree_selector',function( event ){ | |
event.stopPropagation(); | |
var evt = event.originalEvent; | |
evt.preventDefault(); /* Firefox fix */ | |
evt.stopPropagation(); | |
var elmt = $( "#dropInPage" ,ParsimonyAdmin.currentBody); | |
if(elmt.length > 0){ | |
var stopIdParentBlock = ""; | |
if(elmt.closest(".container").hasClass("container_page")) stopIdParentBlock = elmt.closest(".container").data('page'); | |
else stopIdParentBlock = elmt.closest(".container").attr('id'); | |
var idNextBlock = elmt.next(".block").attr('id'); | |
/* Move block action */ | |
if(evt.dataTransfer.getData("parsimony/moveblock").length > 0){ | |
var obj = JSON.parse(evt.dataTransfer.getData("parsimony/moveblock")); | |
if(obj.idBlock == '' || idNextBlock == '' || obj.startIdParentBlock == '' || stopIdParentBlock == '' || obj.startTypeCont == '' || ParsimonyAdmin.whereIAm("dropInTree") == '') alert("stop"); | |
$this.changeBlockPosition('',obj.idBlock,idNextBlock,obj.startIdParentBlock,stopIdParentBlock,obj.startTypeCont,ParsimonyAdmin.whereIAm("dropInTree"),"moveBlock"); | |
}else{ | |
/* Add a block or other types of things */ | |
var obj; | |
if(evt.dataTransfer.getData("parsimony/addblock").length > 0){ | |
obj = JSON.stringify({blockType:JSON.parse(evt.dataTransfer.getData("parsimony/addblock")).blockType,stopIdParentBlock:stopIdParentBlock,idNextBlock:idNextBlock}); | |
}else if(evt.dataTransfer.getData("text/plain").length > 0){ | |
obj = JSON.stringify({blockType:"core\\blocks\\wysiwyg",stopIdParentBlock:stopIdParentBlock,idNextBlock:idNextBlock,content:evt.dataTransfer.getData("text/plain")}); | |
} | |
ParsimonyAdmin.displayConfBox("#dialog","Entrez un identifiant pour ce nouveau bloc"); | |
$("#dialog-id-options").val(obj); | |
$("#dialog-id").val('').trigger("focus"); | |
} | |
} | |
}); | |
} | |
this.unloadCreationMode = function(){ | |
ParsimonyAdmin.$currentBody.add('#paneltree').add('#conf_box_content').add(document).add('#config_tree_selector').add("#menu").off('.creation'); | |
} | |
this.setBlock = function (block) { | |
this.blocks["block_" + block.name] = block; | |
} | |
} | |
function block() { | |
this.name = "block"; | |
var me = this; | |
this.onClickEdit = function (e) { | |
e.stopPropagation(); | |
} | |
this.onClickCreation = function (e) { | |
e.stopPropagation(); | |
ParsimonyAdmin.selectBlock(this.id); | |
if(e.trad != true && e.link != true) ParsimonyAdmin.closeParsiadminMenu(); | |
ParsimonyAdmin.addTitleParsiadminMenu('#' + ParsimonyAdmin.inProgress); | |
ParsimonyAdmin.addOptionParsiadminMenu('<a href="#" class="configure_block" rel="getViewConfigBlock" data-action="onConfigure" title="Configuration' + ' #' + ParsimonyAdmin.inProgress + '"><span class="ui-icon ui-icon-wrench floatleft"></span>'+ t('Configure') +'</a>'); | |
var CSSProps = ''; | |
if(typeof me.stylableElements != "undefined"){ | |
$.each(me.stylableElements, function(index, value) { | |
CSSProps += '<a href="#" onclick="blockAdminCSS.displayCSSConf(CSSTHEMEPATH, \'#\' + ParsimonyAdmin.inProgress + \' ' + value + '\');return false;" data-css="' + value + '"><span class="ui-icon ui-icon-pencil floatleft"></span>'+ t('Design') + ' ' + t(index) + '</a>'; | |
}); | |
CSSProps = '<span class="ui-icon ui-icon-carat-1-e floatright"></span><div class="none CSSProps">' + CSSProps + '</a>'; | |
} | |
ParsimonyAdmin.addOptionParsiadminMenu('<div class="CSSDesign"><a href="#" class="cssblock" data-action="onDesign"><span class="ui-icon ui-icon-pencil floatleft"></span>' + t('Design') + '</a>' + CSSProps + '</div>'); | |
if(this.id != "container") ParsimonyAdmin.addOptionParsiadminMenu('<a href="#" draggable="true" class="move_block" style="cursor:move"><span class="ui-icon ui-icon-arrow-4 floatleft"></span>'+ t('Move') +'</a>'); | |
if(this.id != "container" && this.id != "content") ParsimonyAdmin.addOptionParsiadminMenu('<a href="#" class="config_destroy" data-action="onDelete"><span class="ui-icon ui-icon-closethick floatleft"></span>'+ t('Delete') +'</a>'); | |
ParsimonyAdmin.openParsiadminMenu(e.pageX || ($(window).width()/2),e.pageY || ($(window).height()/2)); | |
} | |
this.onConfigure = function () { | |
var parentId = ''; | |
var inProgress = $("#treedom_" + ParsimonyAdmin.inProgress); | |
if(inProgress.length > 0){ | |
if(inProgress.parent().closest(".container").attr("id") == "treedom_content") parentId = inProgress.parent().closest("#treedom_content").data('page'); | |
else parentId = inProgress.parent().closest(".container").attr('id').replace("treedom_",""); | |
} | |
ParsimonyAdmin.displayConfBox(BASE_PATH + "admin/action",$(this).attr('title'),"TOKEN=" + TOKEN + "&idBlock=" + ParsimonyAdmin.inProgress + "&parentBlock=" + parentId + "&typeProgress=" + ParsimonyAdmin.typeProgress + "&action=" + $(this).attr('rel') +"&IDPage=" + $(".container_page",ParsimonyAdmin.currentBody).data('page')); | |
} | |
this.onDesign = function (e) { | |
e.preventDefault(); | |
ParsimonyAdmin.selectBlock(ParsimonyAdmin.inProgress); | |
blockAdminCSS.displayCSSConf(CSSTHEMEPATH, "#" + ParsimonyAdmin.inProgress); | |
} | |
this.onCreate = function () { | |
} | |
this.onDelete = function (e) { | |
ParsimonyAdmin.destroyBlock(); | |
} | |
this.onSaveConfig = function () { | |
} | |
}function blockAdminTree() { | |
this.initBefore = function () { | |
/* Help on Tree*/ | |
$('#right_sidebar').on('click','#treelegend',function(){ | |
$('#treelegend2').slideToggle(); | |
}).on('click','.arrow_tree',function(event){ | |
event.stopPropagation(); | |
$(this).toggleClass('down'); | |
$(this).nextAll('ul,li').toggleClass('none'); | |
}); | |
} | |
this.initIframe = function () { | |
$('#treedom_container').attr('title','Site structure'); | |
$('#treedom_content').attr('title','Dynamic content'); | |
} | |
this.init = function () { | |
$('#right_sidebar').on('click','.tree_selector', function(event){ | |
event.stopPropagation(); | |
ParsimonyAdmin.selectBlock(this.id.split("treedom_")[1]); | |
if($("#" + this.id.split("treedom_")[1],ParsimonyAdmin.currentBody).length > 0){ | |
$("body").animate({ | |
scrollTop : $("#" + this.id.split("treedom_")[1],ParsimonyAdmin.currentBody).offset().top -50 | |
},"fast"); | |
} | |
}).on('mouseenter','.tree_selector', function(event){ | |
event.stopPropagation(); | |
var ids = this.id.split("treedom_")[1]; | |
$(".selection-block:not(#" + ParsimonyAdmin.inProgress + ")",ParsimonyAdmin.currentBody).removeClass("selection-block"); | |
$("#" + ids,ParsimonyAdmin.currentBody).trigger('mouseover'); | |
}); | |
$("#treedom_content").on('mouseover mouseout',function(event){ | |
var dom = $(".container_page",ParsimonyAdmin.currentBody).get(0); | |
if(typeof dom.style != "undefined"){ | |
if (event.type == 'mouseover') { | |
dom.style.outline = '5px #c8007a solid'; | |
} else { | |
dom.style.outline = 'none'; | |
} | |
} | |
}); | |
} | |
} | |
ParsimonyAdmin.setPlugin(new blockAdminTree());/** | |
* Drag n'Drop - jQuery Plugin | |
* | |
* NOTICE OF LICENSE | |
* | |
* This source file is subject to the Open Software License (OSL 3.0) | |
* that is bundled with this package in the file LICENSE.txt. | |
* It is also available through the world-wide-web at this URL: | |
* http://opensource.org/licenses/osl-3.0.php | |
* If you did not receive a copy of the license and are unable to | |
* obtain it through the world-wide-web, please send an email | |
* to contact@parsimony-cms.com so we can send you a copy immediately. | |
* | |
* DISCLAIMER | |
* | |
* Do not edit or add to this file if you wish to upgrade Parsimony to newer | |
* versions in the future. If you wish to customize Parsimony for your | |
* needs please refer to http://www.parsimony.mobi for more information. | |
* | |
* @authors Julien Gras et Benoît Lorillot | |
* @copyright Julien Gras et Benoît Lorillot | |
* @version Release: 1.0 | |
* @category Drag n'Drop - jQuery Plugin | |
* Requires: jQuery v1.4.2+ | |
* @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) | |
*/ | |
(function( $ ){ | |
var methods = { | |
init : function( options ) { | |
params = $.extend( { | |
stopDraggable: function() {}, | |
stopResizable: function() {}, | |
initPos:{} | |
}, options); | |
var initContext = $("#overlays"); | |
params.initContext = initContext; | |
var context = this.closest("html"); | |
$(document).off("mousemove").off("mouseup"); | |
$(".parsimonyDND",initContext).off("click").off("mousedown").off("click",".parsimonyResize").find(".parsimonyResize").off("mousedown"); | |
$(".parsimonyResize",initContext).remove(); | |
$(".parsimonyDND",context).removeClass("parsimonyDND"); | |
return this.each(function() { | |
var $this = $(this); | |
var doc = $this.closest("body"); | |
$this.addClass("parsimonyDND"); | |
params.initPos = { | |
left : isNaN(parseFloat($this.css("left"))) ? 'initial' : $this.css("left"), | |
top : isNaN(parseFloat($this.css("top"))) ? 'initial' : $this.css("top"), | |
width : isNaN(parseFloat($this.css("width"))) ? 'initial' : $this.css("width"), | |
height : isNaN(parseFloat($this.css("height"))) ? 'initial' : $this.css("height") | |
}; | |
initContext.append('<div class="parsimonyDND"><div class="parsimonyResizeInfo"> <span class="parsimonyResizeReInit spanDND ui-icon-arrowrefresh-1-w" title="Reinit"></span> </a><span class="parsimonyResizeClose spanDND closedesign ui-icon-closethick"></span> </div><div class="parsimonyResize se"></div><div class="parsimonyResize nw"></div><div class="parsimonyResize ne"></div><div class="parsimonyResize sw"></div></div>'); | |
var offset = $(this).offset(); | |
var offsetFrame = ParsimonyAdmin.$iframe.offset(); | |
$(".parsimonyDND",initContext).css({ | |
position: "absolute", | |
left : (offset.left + offsetFrame.left + 40), | |
top : offset.top, | |
width : params.initPos.width, | |
height : params.initPos.height | |
}); | |
var dnd = $(".parsimonyDND", initContext); | |
dnd.on("mousedown.parsimonyDND",function(e){ | |
$("#overlays").css("pointer-events","all"); | |
var $this = $(".parsimonyDND",context); | |
if($this.css('position')=="static"){ | |
$this.css('position','relative'); | |
$('#panelcss select[name="position"]').val('relative'); | |
} | |
var dndstart = { | |
$this : $this, | |
left : isNaN(parseFloat($this.css("left"))) ? 0 : $this.css("left"), | |
top : isNaN(parseFloat($this.css("top"))) ? 0 : $this.css("top"), | |
pageX : e.pageX, | |
pageY : e.pageY | |
}; | |
$(document).add(doc).on("mousemove.parsimonyDND",dndstart,function(e){ | |
$this.css({ | |
left: parseFloat(dndstart.left) + e.pageX - dndstart.pageX + "px", | |
top: parseFloat(dndstart.top) + e.pageY - dndstart.pageY + "px" | |
}); | |
document.getElementById("box_top").value = $this.css("top"); | |
document.getElementById("box_left").value = $this.css("left"); | |
$this.parsimonyDND("updatePosition"); | |
}).on("mouseup.parsimonyDND",dndstart,function(e){ | |
$("#overlays").css("pointer-events","none"); | |
params.stopDraggable(e,$this); | |
$(document).add(doc).off("mousemove").off("mouseup"); | |
}); | |
}); | |
initContext.on("click.parsimonyDND",function(e){ | |
e.stopImmediatePropagation(); | |
}); | |
initContext.on("click.parsimonyDND",".parsimonyResize",function(e){ | |
e.stopImmediatePropagation(); | |
}); | |
initContext.find(".parsimonyResizeReInit").on("click.parsimonyDND",function(e){ | |
e.preventDefault(); | |
$this.closest(".parsimonyDND").parsimonyDND("reInit"); | |
}); | |
initContext.find(".parsimonyResizeClose").on("click.parsimonyDND",function(e){ | |
e.preventDefault(); | |
$this.closest(".parsimonyDND").parsimonyDND("destroy"); | |
}); | |
initContext.find(".parsimonyResize").on("mousedown.parsimonyDND",function(e){ | |
e.stopImmediatePropagation(); | |
$("#overlays").css("pointer-events","all"); | |
if($this.css('position')=="static"){ | |
$this.css('position','relative'); | |
$('#panelcss select[name="position"]').val('relative'); | |
} | |
var parent = $(".parsimonyDND",context); | |
var bounds = parent.get(0).getBoundingClientRect(); | |
var dndstart = { | |
$this : parent, | |
width : bounds.width, | |
height : bounds.height, | |
top : isNaN(parseFloat(parent.css("top"))) ? 0 : parent.css("top"),//$(this).css('top'), | |
left : isNaN(parseFloat(parent.css("left"))) ? 0 : parent.css("left"),//$(this).css('left'), | |
pageX : e.pageX, | |
pageY : e.pageY, | |
dir : $(this).attr('class').replace("parsimonyResize ", "") | |
}; | |
$(document).add(doc).on("mousemove.parsimonyDND",dndstart,function(e){ | |
switch(dndstart.dir){ | |
case "se": | |
$this.css({ | |
width: parseFloat(dndstart.width) + (e.pageX - dndstart.pageX) + "px", | |
height: parseFloat(dndstart.height) + (e.pageY - dndstart.pageY) + "px" | |
}); | |
break; | |
case "nw": | |
$this.css({ | |
top: parseFloat(dndstart.top) + (e.pageY - dndstart.pageY) + "px", | |
left: parseFloat(dndstart.left) + (e.pageX - dndstart.pageX) + "px", | |
width: parseFloat(dndstart.width) - (e.pageX - dndstart.pageX) + "px", | |
height: parseFloat(dndstart.height) - (e.pageY - dndstart.pageY) + "px" | |
}); | |
break; | |
case "ne": | |
$this.css({ | |
top: parseFloat(dndstart.top) + (e.pageY - dndstart.pageY) + "px", | |
width: parseFloat(dndstart.width) + (e.pageX - dndstart.pageX) + "px", | |
height: parseFloat(dndstart.height) - (e.pageY - dndstart.pageY) + "px" | |
}); | |
break; | |
case "sw": | |
$this.css({ | |
left: parseFloat(dndstart.left) + (e.pageX - dndstart.pageX) + "px", | |
width: parseFloat(dndstart.width) - (e.pageX - dndstart.pageX) + "px", | |
height: parseFloat(dndstart.height) + (e.pageY - dndstart.pageY) + "px" | |
}); | |
break; | |
} | |
$this.parsimonyDND("updatePosition"); | |
document.getElementById("box_width").value = $this.width() + "px"; | |
document.getElementById("box_height").value = $this.height() + "px"; | |
document.getElementById("box_top").value = $this.css("top"); | |
document.getElementById("box_left").value = $this.css("left"); | |
}).on("mouseup",dndstart,function(e){ | |
$("#overlays").css("pointer-events","none"); | |
params.stopResizable(e,$this); | |
$(document).add(doc).off("mousemove").off("mouseup"); | |
}); | |
}); | |
}); | |
}, | |
reInit : function( ) { | |
return this.each(function(){ | |
$(this).css({ | |
top: params.initPos.top, | |
left: params.initPos.left, | |
width: params.initPos.width, | |
height: params.initPos.height | |
}); | |
$(this).parsimonyDND("updatePosition"); | |
params.stopResizable("",$(this)); | |
}) | |
}, | |
updatePosition : function( ) { | |
return this.each(function(){ | |
var bounds1 = this.getBoundingClientRect(); | |
var bounds = $(this).offset(); | |
var offsetFrame = ParsimonyAdmin.$iframe.offset(); | |
$(".parsimonyDND",params.initContext).css({ | |
top: bounds.top + "px", | |
left: bounds.left + offsetFrame.left + 40 + "px", | |
width: bounds1.width + "px", | |
height: bounds1.height + "px" | |
}); | |
}) | |
}, | |
destroy : function( ) { | |
return this.each(function(){ | |
$(".parsimonyDND",params.initContext).remove(); | |
$(this).removeClass('parsimonyDND'); | |
$(this).off('.parsimonyDND'); | |
}) | |
} | |
}; | |
$.fn.parsimonyDND = function( method ) { | |
if ( methods[method] ) { | |
return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 )); | |
} else if ( typeof method === 'object' || ! method ) { | |
return methods.init.apply( this, arguments ); | |
} | |
}; | |
})( jQuery );// CodeMirror version 3.02 | |
// | |
// CodeMirror is the only global var we claim | |
window.CodeMirror = (function() { | |
"use strict"; | |
// BROWSER SNIFFING | |
// Crude, but necessary to handle a number of hard-to-feature-detect | |
// bugs and behavior differences. | |
var gecko = /gecko\/\d/i.test(navigator.userAgent); | |
var ie = /MSIE \d/.test(navigator.userAgent); | |
var ie_lt8 = ie && (document.documentMode == null || document.documentMode < 8); | |
var ie_lt9 = ie && (document.documentMode == null || document.documentMode < 9); | |
var webkit = /WebKit\//.test(navigator.userAgent); | |
var qtwebkit = webkit && /Qt\/\d+\.\d+/.test(navigator.userAgent); | |
var chrome = /Chrome\//.test(navigator.userAgent); | |
var opera = /Opera\//.test(navigator.userAgent); | |
var safari = /Apple Computer/.test(navigator.vendor); | |
var khtml = /KHTML\//.test(navigator.userAgent); | |
var mac_geLion = /Mac OS X 1\d\D([7-9]|\d\d)\D/.test(navigator.userAgent); | |
var mac_geMountainLion = /Mac OS X 1\d\D([8-9]|\d\d)\D/.test(navigator.userAgent); | |
var phantom = /PhantomJS/.test(navigator.userAgent); | |
var ios = /AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent); | |
// This is woefully incomplete. Suggestions for alternative methods welcome. | |
var mobile = ios || /Android|webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(navigator.userAgent); | |
var mac = ios || /Mac/.test(navigator.platform); | |
var windows = /windows/i.test(navigator.platform); | |
var opera_version = opera && navigator.userAgent.match(/Version\/(\d*\.\d*)/); | |
if (opera_version) opera_version = Number(opera_version[1]); | |
// Some browsers use the wrong event properties to signal cmd/ctrl on OS X | |
var flipCtrlCmd = mac && (qtwebkit || opera && (opera_version == null || opera_version < 12.11)); | |
// Optimize some code when these features are not used | |
var sawReadOnlySpans = false, sawCollapsedSpans = false; | |
// CONSTRUCTOR | |
function CodeMirror(place, options) { | |
if (!(this instanceof CodeMirror)) return new CodeMirror(place, options); | |
this.options = options = options || {}; | |
// Determine effective options based on given values and defaults. | |
for (var opt in defaults) if (!options.hasOwnProperty(opt) && defaults.hasOwnProperty(opt)) | |
options[opt] = defaults[opt]; | |
setGuttersForLineNumbers(options); | |
var display = this.display = makeDisplay(place); | |
display.wrapper.CodeMirror = this; | |
updateGutters(this); | |
if (options.autofocus && !mobile) focusInput(this); | |
this.view = makeView(new BranchChunk([new LeafChunk([makeLine("", null, textHeight(display))])])); | |
this.nextOpId = 0; | |
loadMode(this); | |
themeChanged(this); | |
if (options.lineWrapping) | |
this.display.wrapper.className += " CodeMirror-wrap"; | |
// Initialize the content. | |
this.setValue(options.value || ""); | |
// Override magic textarea content restore that IE sometimes does | |
// on our hidden textarea on reload | |
if (ie) setTimeout(bind(resetInput, this, true), 20); | |
this.view.history = makeHistory(); | |
registerEventHandlers(this); | |
// IE throws unspecified error in certain cases, when | |
// trying to access activeElement before onload | |
var hasFocus; try { hasFocus = (document.activeElement == display.input); } catch(e) { } | |
if (hasFocus || (options.autofocus && !mobile)) setTimeout(bind(onFocus, this), 20); | |
else onBlur(this); | |
operation(this, function() { | |
for (var opt in optionHandlers) | |
if (optionHandlers.propertyIsEnumerable(opt)) | |
optionHandlers[opt](this, options[opt], Init); | |
for (var i = 0; i < initHooks.length; ++i) initHooks[i](this); | |
})(); | |
} | |
// DISPLAY CONSTRUCTOR | |
function makeDisplay(place) { | |
var d = {}; | |
var input = d.input = elt("textarea", null, null, "position: absolute; padding: 0; width: 1px; height: 1em; outline: none;"); | |
if (webkit) input.style.width = "1000px"; | |
else input.setAttribute("wrap", "off"); | |
input.setAttribute("autocorrect", "off"); input.setAttribute("autocapitalize", "off"); | |
// Wraps and hides input textarea | |
d.inputDiv = elt("div", [input], null, "overflow: hidden; position: relative; width: 3px; height: 0px;"); | |
// The actual fake scrollbars. | |
d.scrollbarH = elt("div", [elt("div", null, null, "height: 1px")], "CodeMirror-hscrollbar"); | |
d.scrollbarV = elt("div", [elt("div", null, null, "width: 1px")], "CodeMirror-vscrollbar"); | |
d.scrollbarFiller = elt("div", null, "CodeMirror-scrollbar-filler"); | |
// DIVs containing the selection and the actual code | |
d.lineDiv = elt("div"); | |
d.selectionDiv = elt("div", null, null, "position: relative; z-index: 1"); | |
// Blinky cursor, and element used to ensure cursor fits at the end of a line | |
d.cursor = elt("div", "\u00a0", "CodeMirror-cursor"); | |
// Secondary cursor, shown when on a 'jump' in bi-directional text | |
d.otherCursor = elt("div", "\u00a0", "CodeMirror-cursor CodeMirror-secondarycursor"); | |
// Used to measure text size | |
d.measure = elt("div", null, "CodeMirror-measure"); | |
// Wraps everything that needs to exist inside the vertically-padded coordinate system | |
d.lineSpace = elt("div", [d.measure, d.selectionDiv, d.lineDiv, d.cursor, d.otherCursor], | |
null, "position: relative; outline: none"); | |
// Moved around its parent to cover visible view | |
d.mover = elt("div", [elt("div", [d.lineSpace], "CodeMirror-lines")], null, "position: relative"); | |
// Set to the height of the text, causes scrolling | |
d.sizer = elt("div", [d.mover], "CodeMirror-sizer"); | |
// D is needed because behavior of elts with overflow: auto and padding is inconsistent across browsers | |
d.heightForcer = elt("div", "\u00a0", null, "position: absolute; height: " + scrollerCutOff + "px"); | |
// Will contain the gutters, if any | |
d.gutters = elt("div", null, "CodeMirror-gutters"); | |
d.lineGutter = null; | |
// Helper element to properly size the gutter backgrounds | |
var scrollerInner = elt("div", [d.sizer, d.heightForcer, d.gutters], null, "position: relative; min-height: 100%"); | |
// Provides scrolling | |
d.scroller = elt("div", [scrollerInner], "CodeMirror-scroll"); | |
d.scroller.setAttribute("tabIndex", "-1"); | |
// The element in which the editor lives. | |
d.wrapper = elt("div", [d.inputDiv, d.scrollbarH, d.scrollbarV, | |
d.scrollbarFiller, d.scroller], "CodeMirror"); | |
// Work around IE7 z-index bug | |
if (ie_lt8) { d.gutters.style.zIndex = -1; d.scroller.style.paddingRight = 0; } | |
if (place.appendChild) place.appendChild(d.wrapper); else place(d.wrapper); | |
// Needed to hide big blue blinking cursor on Mobile Safari | |
if (ios) input.style.width = "0px"; | |
if (!webkit) d.scroller.draggable = true; | |
// Needed to handle Tab key in KHTML | |
if (khtml) { d.inputDiv.style.height = "1px"; d.inputDiv.style.position = "absolute"; } | |
// Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8). | |
else if (ie_lt8) d.scrollbarH.style.minWidth = d.scrollbarV.style.minWidth = "18px"; | |
// Current visible range (may be bigger than the view window). | |
d.viewOffset = d.showingFrom = d.showingTo = d.lastSizeC = 0; | |
// Used to only resize the line number gutter when necessary (when | |
// the amount of lines crosses a boundary that makes its width change) | |
d.lineNumWidth = d.lineNumInnerWidth = d.lineNumChars = null; | |
// See readInput and resetInput | |
d.prevInput = ""; | |
// Set to true when a non-horizontal-scrolling widget is added. As | |
// an optimization, widget aligning is skipped when d is false. | |
d.alignWidgets = false; | |
// Flag that indicates whether we currently expect input to appear | |
// (after some event like 'keypress' or 'input') and are polling | |
// intensively. | |
d.pollingFast = false; | |
// Self-resetting timeout for the poller | |
d.poll = new Delayed(); | |
// True when a drag from the editor is active | |
d.draggingText = false; | |
d.cachedCharWidth = d.cachedTextHeight = null; | |
d.measureLineCache = []; | |
d.measureLineCachePos = 0; | |
// Tracks when resetInput has punted to just putting a short | |
// string instead of the (large) selection. | |
d.inaccurateSelection = false; | |
// Used to adjust overwrite behaviour when a paste has been | |
// detected | |
d.pasteIncoming = false; | |
// Used for measuring wheel scrolling granularity | |
d.wheelDX = d.wheelDY = d.wheelStartX = d.wheelStartY = null; | |
return d; | |
} | |
// VIEW CONSTRUCTOR | |
function makeView(doc) { | |
var selPos = {line: 0, ch: 0}; | |
return { | |
doc: doc, | |
// frontier is the point up to which the content has been parsed, | |
frontier: 0, highlight: new Delayed(), | |
sel: {from: selPos, to: selPos, head: selPos, anchor: selPos, shift: false, extend: false}, | |
scrollTop: 0, scrollLeft: 0, | |
overwrite: false, focused: false, | |
// Tracks the maximum line length so that | |
// the horizontal scrollbar can be kept | |
// static when scrolling. | |
maxLine: getLine(doc, 0), | |
maxLineLength: 0, | |
maxLineChanged: false, | |
suppressEdits: false, | |
goalColumn: null, | |
cantEdit: false, | |
keyMaps: [], | |
overlays: [], | |
modeGen: 0 | |
}; | |
} | |
// STATE UPDATES | |
// Used to get the editor into a consistent state again when options change. | |
function loadMode(cm) { | |
var doc = cm.view.doc; | |
cm.view.mode = CodeMirror.getMode(cm.options, cm.options.mode); | |
doc.iter(0, doc.size, function(line) { | |
if (line.stateAfter) line.stateAfter = null; | |
if (line.styles) line.styles = null; | |
}); | |
cm.view.frontier = 0; | |
startWorker(cm, 100); | |
cm.view.modeGen++; | |
if (cm.curOp) regChange(cm, 0, doc.size); | |
} | |
function wrappingChanged(cm) { | |
var doc = cm.view.doc, th = textHeight(cm.display); | |
if (cm.options.lineWrapping) { | |
cm.display.wrapper.className += " CodeMirror-wrap"; | |
var perLine = cm.display.scroller.clientWidth / charWidth(cm.display) - 3; | |
doc.iter(0, doc.size, function(line) { | |
if (line.height == 0) return; | |
var guess = Math.ceil(line.text.length / perLine) || 1; | |
if (guess != 1) updateLineHeight(line, guess * th); | |
}); | |
cm.display.sizer.style.minWidth = ""; | |
} else { | |
cm.display.wrapper.className = cm.display.wrapper.className.replace(" CodeMirror-wrap", ""); | |
computeMaxLength(cm.view); | |
doc.iter(0, doc.size, function(line) { | |
if (line.height != 0) updateLineHeight(line, th); | |
}); | |
} | |
regChange(cm, 0, doc.size); | |
clearCaches(cm); | |
setTimeout(function(){updateScrollbars(cm.display, cm.view.doc.height);}, 100); | |
} | |
function keyMapChanged(cm) { | |
var style = keyMap[cm.options.keyMap].style; | |
cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-keymap-\S+/g, "") + | |
(style ? " cm-keymap-" + style : ""); | |
} | |
function themeChanged(cm) { | |
cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-s-\S+/g, "") + | |
cm.options.theme.replace(/(^|\s)\s*/g, " cm-s-"); | |
clearCaches(cm); | |
} | |
function guttersChanged(cm) { | |
updateGutters(cm); | |
updateDisplay(cm, true); | |
} | |
function updateGutters(cm) { | |
var gutters = cm.display.gutters, specs = cm.options.gutters; | |
removeChildren(gutters); | |
for (var i = 0; i < specs.length; ++i) { | |
var gutterClass = specs[i]; | |
var gElt = gutters.appendChild(elt("div", null, "CodeMirror-gutter " + gutterClass)); | |
if (gutterClass == "CodeMirror-linenumbers") { | |
cm.display.lineGutter = gElt; | |
gElt.style.width = (cm.display.lineNumWidth || 1) + "px"; | |
} | |
} | |
gutters.style.display = i ? "" : "none"; | |
} | |
function lineLength(doc, line) { | |
if (line.height == 0) return 0; | |
var len = line.text.length, merged, cur = line; | |
while (merged = collapsedSpanAtStart(cur)) { | |
var found = merged.find(); | |
cur = getLine(doc, found.from.line); | |
len += found.from.ch - found.to.ch; | |
} | |
cur = line; | |
while (merged = collapsedSpanAtEnd(cur)) { | |
var found = merged.find(); | |
len -= cur.text.length - found.from.ch; | |
cur = getLine(doc, found.to.line); | |
len += cur.text.length - found.to.ch; | |
} | |
return len; | |
} | |
function computeMaxLength(view) { | |
view.maxLine = getLine(view.doc, 0); | |
view.maxLineLength = lineLength(view.doc, view.maxLine); | |
view.maxLineChanged = true; | |
view.doc.iter(1, view.doc.size, function(line) { | |
var len = lineLength(view.doc, line); | |
if (len > view.maxLineLength) { | |
view.maxLineLength = len; | |
view.maxLine = line; | |
} | |
}); | |
} | |
// Make sure the gutters options contains the element | |
// "CodeMirror-linenumbers" when the lineNumbers option is true. | |
function setGuttersForLineNumbers(options) { | |
var found = false; | |
for (var i = 0; i < options.gutters.length; ++i) { | |
if (options.gutters[i] == "CodeMirror-linenumbers") { | |
if (options.lineNumbers) found = true; | |
else options.gutters.splice(i--, 1); | |
} | |
} | |
if (!found && options.lineNumbers) | |
options.gutters.push("CodeMirror-linenumbers"); | |
} | |
// SCROLLBARS | |
// Re-synchronize the fake scrollbars with the actual size of the | |
// content. Optionally force a scrollTop. | |
function updateScrollbars(d /* display */, docHeight) { | |
var totalHeight = docHeight + 2 * paddingTop(d); | |
d.sizer.style.minHeight = d.heightForcer.style.top = totalHeight + "px"; | |
var scrollHeight = Math.max(totalHeight, d.scroller.scrollHeight); | |
var needsH = d.scroller.scrollWidth > d.scroller.clientWidth; | |
var needsV = scrollHeight > d.scroller.clientHeight; | |
if (needsV) { | |
d.scrollbarV.style.display = "block"; | |
d.scrollbarV.style.bottom = needsH ? scrollbarWidth(d.measure) + "px" : "0"; | |
d.scrollbarV.firstChild.style.height = | |
(scrollHeight - d.scroller.clientHeight + d.scrollbarV.clientHeight) + "px"; | |
} else d.scrollbarV.style.display = ""; | |
if (needsH) { | |
d.scrollbarH.style.display = "block"; | |
d.scrollbarH.style.right = needsV ? scrollbarWidth(d.measure) + "px" : "0"; | |
d.scrollbarH.firstChild.style.width = | |
(d.scroller.scrollWidth - d.scroller.clientWidth + d.scrollbarH.clientWidth) + "px"; | |
} else d.scrollbarH.style.display = ""; | |
if (needsH && needsV) { | |
d.scrollbarFiller.style.display = "block"; | |
d.scrollbarFiller.style.height = d.scrollbarFiller.style.width = scrollbarWidth(d.measure) + "px"; | |
} else d.scrollbarFiller.style.display = ""; | |
if (mac_geLion && scrollbarWidth(d.measure) === 0) | |
d.scrollbarV.style.minWidth = d.scrollbarH.style.minHeight = mac_geMountainLion ? "18px" : "12px"; | |
} | |
function visibleLines(display, doc, viewPort) { | |
var top = display.scroller.scrollTop, height = display.wrapper.clientHeight; | |
if (typeof viewPort == "number") top = viewPort; | |
else if (viewPort) {top = viewPort.top; height = viewPort.bottom - viewPort.top;} | |
top = Math.floor(top - paddingTop(display)); | |
var bottom = Math.ceil(top + height); | |
return {from: lineAtHeight(doc, top), to: lineAtHeight(doc, bottom)}; | |
} | |
// LINE NUMBERS | |
function alignHorizontally(cm) { | |
var display = cm.display; | |
if (!display.alignWidgets && (!display.gutters.firstChild || !cm.options.fixedGutter)) return; | |
var comp = compensateForHScroll(display) - display.scroller.scrollLeft + cm.view.scrollLeft; | |
var gutterW = display.gutters.offsetWidth, l = comp + "px"; | |
for (var n = display.lineDiv.firstChild; n; n = n.nextSibling) if (n.alignable) { | |
for (var i = 0, a = n.alignable; i < a.length; ++i) a[i].style.left = l; | |
} | |
if (cm.options.fixedGutter) | |
display.gutters.style.left = (comp + gutterW) + "px"; | |
} | |
function maybeUpdateLineNumberWidth(cm) { | |
if (!cm.options.lineNumbers) return false; | |
var doc = cm.view.doc, last = lineNumberFor(cm.options, doc.size - 1), display = cm.display; | |
if (last.length != display.lineNumChars) { | |
var test = display.measure.appendChild(elt("div", [elt("div", last)], | |
"CodeMirror-linenumber CodeMirror-gutter-elt")); | |
var innerW = test.firstChild.offsetWidth, padding = test.offsetWidth - innerW; | |
display.lineGutter.style.width = ""; | |
display.lineNumInnerWidth = Math.max(innerW, display.lineGutter.offsetWidth - padding); | |
display.lineNumWidth = display.lineNumInnerWidth + padding; | |
display.lineNumChars = display.lineNumInnerWidth ? last.length : -1; | |
display.lineGutter.style.width = display.lineNumWidth + "px"; | |
return true; | |
} | |
return false; | |
} | |
function lineNumberFor(options, i) { | |
return String(options.lineNumberFormatter(i + options.firstLineNumber)); | |
} | |
function compensateForHScroll(display) { | |
return display.scroller.getBoundingClientRect().left - display.sizer.getBoundingClientRect().left; | |
} | |
// DISPLAY DRAWING | |
function updateDisplay(cm, changes, viewPort) { | |
var oldFrom = cm.display.showingFrom, oldTo = cm.display.showingTo; | |
var updated = updateDisplayInner(cm, changes, viewPort); | |
if (updated) { | |
signalLater(cm, cm, "update", cm); | |
if (cm.display.showingFrom != oldFrom || cm.display.showingTo != oldTo) | |
signalLater(cm, cm, "viewportChange", cm, cm.display.showingFrom, cm.display.showingTo); | |
} | |
updateSelection(cm); | |
updateScrollbars(cm.display, cm.view.doc.height); | |
return updated; | |
} | |
// Uses a set of changes plus the current scroll position to | |
// determine which DOM updates have to be made, and makes the | |
// updates. | |
function updateDisplayInner(cm, changes, viewPort) { | |
var display = cm.display, doc = cm.view.doc; | |
if (!display.wrapper.clientWidth) { | |
display.showingFrom = display.showingTo = display.viewOffset = 0; | |
return; | |
} | |
// Compute the new visible window | |
// If scrollTop is specified, use that to determine which lines | |
// to render instead of the current scrollbar position. | |
var visible = visibleLines(display, doc, viewPort); | |
// Bail out if the visible area is already rendered and nothing changed. | |
if (changes !== true && changes.length == 0 && | |
visible.from > display.showingFrom && visible.to < display.showingTo) | |
return; | |
if (changes && maybeUpdateLineNumberWidth(cm)) | |
changes = true; | |
var gutterW = display.sizer.style.marginLeft = display.gutters.offsetWidth + "px"; | |
display.scrollbarH.style.left = cm.options.fixedGutter ? gutterW : "0"; | |
// When merged lines are present, the line that needs to be | |
// redrawn might not be the one that was changed. | |
if (changes !== true && sawCollapsedSpans) | |
for (var i = 0; i < changes.length; ++i) { | |
var ch = changes[i], merged; | |
while (merged = collapsedSpanAtStart(getLine(doc, ch.from))) { | |
var from = merged.find().from.line; | |
if (ch.diff) ch.diff -= ch.from - from; | |
ch.from = from; | |
} | |
} | |
// Used to determine which lines need their line numbers updated | |
var positionsChangedFrom = changes === true ? 0 : Infinity; | |
if (cm.options.lineNumbers && changes && changes !== true) | |
for (var i = 0; i < changes.length; ++i) | |
if (changes[i].diff) { positionsChangedFrom = changes[i].from; break; } | |
var from = Math.max(visible.from - cm.options.viewportMargin, 0); | |
var to = Math.min(doc.size, visible.to + cm.options.viewportMargin); | |
if (display.showingFrom < from && from - display.showingFrom < 20) from = display.showingFrom; | |
if (display.showingTo > to && display.showingTo - to < 20) to = Math.min(doc.size, display.showingTo); | |
if (sawCollapsedSpans) { | |
from = lineNo(visualLine(doc, getLine(doc, from))); | |
while (to < doc.size && lineIsHidden(getLine(doc, to))) ++to; | |
} | |
// Create a range of theoretically intact lines, and punch holes | |
// in that using the change info. | |
var intact = changes === true ? [] : | |
computeIntact([{from: display.showingFrom, to: display.showingTo}], changes); | |
// Clip off the parts that won't be visible | |
var intactLines = 0; | |
for (var i = 0; i < intact.length; ++i) { | |
var range = intact[i]; | |
if (range.from < from) range.from = from; | |
if (range.to > to) range.to = to; | |
if (range.from >= range.to) intact.splice(i--, 1); | |
else intactLines += range.to - range.from; | |
} | |
if (intactLines == to - from && from == display.showingFrom && to == display.showingTo) | |
return; | |
intact.sort(function(a, b) {return a.from - b.from;}); | |
var focused = document.activeElement; | |
if (intactLines < (to - from) * .7) display.lineDiv.style.display = "none"; | |
patchDisplay(cm, from, to, intact, positionsChangedFrom); | |
display.lineDiv.style.display = ""; | |
if (document.activeElement != focused && focused.offsetHeight) focused.focus(); | |
var different = from != display.showingFrom || to != display.showingTo || | |
display.lastSizeC != display.wrapper.clientHeight; | |
// This is just a bogus formula that detects when the editor is | |
// resized or the font size changes. | |
if (different) display.lastSizeC = display.wrapper.clientHeight; | |
display.showingFrom = from; display.showingTo = to; | |
startWorker(cm, 100); | |
var prevBottom = display.lineDiv.offsetTop; | |
for (var node = display.lineDiv.firstChild, height; node; node = node.nextSibling) if (node.lineObj) { | |
if (ie_lt8) { | |
var bot = node.offsetTop + node.offsetHeight; | |
height = bot - prevBottom; | |
prevBottom = bot; | |
} else { | |
var box = node.getBoundingClientRect(); | |
height = box.bottom - box.top; | |
} | |
var diff = node.lineObj.height - height; | |
if (height < 2) height = textHeight(display); | |
if (diff > .001 || diff < -.001) { | |
updateLineHeight(node.lineObj, height); | |
var widgets = node.lineObj.widgets; | |
if (widgets) for (var i = 0; i < widgets.length; ++i) | |
widgets[i].height = widgets[i].node.offsetHeight; | |
} | |
} | |
display.viewOffset = heightAtLine(cm, getLine(doc, from)); | |
// Position the mover div to align with the current virtual scroll position | |
display.mover.style.top = display.viewOffset + "px"; | |
if (visibleLines(display, doc, viewPort).to >= to) | |
updateDisplayInner(cm, [], viewPort); | |
return true; | |
} | |
function computeIntact(intact, changes) { | |
for (var i = 0, l = changes.length || 0; i < l; ++i) { | |
var change = changes[i], intact2 = [], diff = change.diff || 0; | |
for (var j = 0, l2 = intact.length; j < l2; ++j) { | |
var range = intact[j]; | |
if (change.to <= range.from && change.diff) { | |
intact2.push({from: range.from + diff, to: range.to + diff}); | |
} else if (change.to <= range.from || change.from >= range.to) { | |
intact2.push(range); | |
} else { | |
if (change.from > range.from) | |
intact2.push({from: range.from, to: change.from}); | |
if (change.to < range.to) | |
intact2.push({from: change.to + diff, to: range.to + diff}); | |
} | |
} | |
intact = intact2; | |
} | |
return intact; | |
} | |
function getDimensions(cm) { | |
var d = cm.display, left = {}, width = {}; | |
for (var n = d.gutters.firstChild, i = 0; n; n = n.nextSibling, ++i) { | |
left[cm.options.gutters[i]] = n.offsetLeft; | |
width[cm.options.gutters[i]] = n.offsetWidth; | |
} | |
return {fixedPos: compensateForHScroll(d), | |
gutterTotalWidth: d.gutters.offsetWidth, | |
gutterLeft: left, | |
gutterWidth: width, | |
wrapperWidth: d.wrapper.clientWidth}; | |
} | |
function patchDisplay(cm, from, to, intact, updateNumbersFrom) { | |
var dims = getDimensions(cm); | |
var display = cm.display, lineNumbers = cm.options.lineNumbers; | |
if (!intact.length && (!webkit || !cm.display.currentWheelTarget)) | |
removeChildren(display.lineDiv); | |
var container = display.lineDiv, cur = container.firstChild; | |
function rm(node) { | |
var next = node.nextSibling; | |
if (webkit && mac && cm.display.currentWheelTarget == node) { | |
node.style.display = "none"; | |
node.lineObj = null; | |
} else { | |
node.parentNode.removeChild(node); | |
} | |
return next; | |
} | |
var nextIntact = intact.shift(), lineNo = from; | |
cm.view.doc.iter(from, to, function(line) { | |
if (nextIntact && nextIntact.to == lineNo) nextIntact = intact.shift(); | |
if (lineIsHidden(line)) { | |
if (line.height != 0) updateLineHeight(line, 0); | |
if (line.widgets && cur.previousSibling) for (var i = 0; i < line.widgets.length; ++i) | |
if (line.widgets[i].showIfHidden) { | |
var prev = cur.previousSibling; | |
if (prev.nodeType == "pre") { | |
var wrap = elt("div", null, null, "position: relative"); | |
prev.parentNode.replaceChild(wrap, prev); | |
wrap.appendChild(prev); | |
prev = wrap; | |
} | |
prev.appendChild(buildLineWidget(line.widgets[i], prev, dims)); | |
} | |
} else if (nextIntact && nextIntact.from <= lineNo && nextIntact.to > lineNo) { | |
// This line is intact. Skip to the actual node. Update its | |
// line number if needed. | |
while (cur.lineObj != line) cur = rm(cur); | |
if (lineNumbers && updateNumbersFrom <= lineNo && cur.lineNumber) | |
setTextContent(cur.lineNumber, lineNumberFor(cm.options, lineNo)); | |
cur = cur.nextSibling; | |
} else { | |
// This line needs to be generated. | |
var lineNode = buildLineElement(cm, line, lineNo, dims); | |
container.insertBefore(lineNode, cur); | |
lineNode.lineObj = line; | |
} | |
++lineNo; | |
}); | |
while (cur) cur = rm(cur); | |
} | |
function buildLineElement(cm, line, lineNo, dims) { | |
var lineElement = lineContent(cm, line); | |
var markers = line.gutterMarkers, display = cm.display; | |
if (!cm.options.lineNumbers && !markers && !line.bgClass && !line.wrapClass && | |
(!line.widgets || !line.widgets.length)) return lineElement; | |
// Lines with gutter elements or a background class need | |
// to be wrapped again, and have the extra elements added | |
// to the wrapper div | |
var wrap = elt("div", null, line.wrapClass, "position: relative"); | |
if (cm.options.lineNumbers || markers) { | |
var gutterWrap = wrap.appendChild(elt("div", null, null, "position: absolute; left: " + | |
(cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + "px")); | |
if (cm.options.fixedGutter) wrap.alignable = [gutterWrap]; | |
if (cm.options.lineNumbers && (!markers || !markers["CodeMirror-linenumbers"])) | |
wrap.lineNumber = gutterWrap.appendChild( | |
elt("div", lineNumberFor(cm.options, lineNo), | |
"CodeMirror-linenumber CodeMirror-gutter-elt", | |
"left: " + dims.gutterLeft["CodeMirror-linenumbers"] + "px; width: " | |
+ display.lineNumInnerWidth + "px")); | |
if (markers) | |
for (var k = 0; k < cm.options.gutters.length; ++k) { | |
var id = cm.options.gutters[k], found = markers.hasOwnProperty(id) && markers[id]; | |
if (found) | |
gutterWrap.appendChild(elt("div", [found], "CodeMirror-gutter-elt", "left: " + | |
dims.gutterLeft[id] + "px; width: " + dims.gutterWidth[id] + "px")); | |
} | |
} | |
// Kludge to make sure the styled element lies behind the selection (by z-index) | |
if (line.bgClass) | |
wrap.appendChild(elt("div", "\u00a0", line.bgClass + " CodeMirror-linebackground")); | |
wrap.appendChild(lineElement); | |
if (line.widgets) for (var i = 0, ws = line.widgets; i < ws.length; ++i) { | |
var widget = ws[i], node = buildLineWidget(widget, wrap, dims); | |
if (widget.above) | |
wrap.insertBefore(node, cm.options.lineNumbers && line.height != 0 ? gutterWrap : lineElement); | |
else | |
wrap.appendChild(node); | |
} | |
if (ie_lt8) wrap.style.zIndex = 2; | |
return wrap; | |
} | |
function buildLineWidget(widget, wrap, dims) { | |
var node = elt("div", [widget.node], "CodeMirror-linewidget"); | |
node.widget = widget; | |
if (widget.noHScroll) { | |
(wrap.alignable || (wrap.alignable = [])).push(node); | |
var width = dims.wrapperWidth; | |
node.style.left = dims.fixedPos + "px"; | |
if (!widget.coverGutter) { | |
width -= dims.gutterTotalWidth; | |
node.style.paddingLeft = dims.gutterTotalWidth + "px"; | |
} | |
node.style.width = width + "px"; | |
} | |
if (widget.coverGutter) { | |
node.style.zIndex = 5; | |
node.style.position = "relative"; | |
if (!widget.noHScroll) node.style.marginLeft = -dims.gutterTotalWidth + "px"; | |
} | |
return node; | |
} | |
// SELECTION / CURSOR | |
function updateSelection(cm) { | |
var display = cm.display; | |
var collapsed = posEq(cm.view.sel.from, cm.view.sel.to); | |
if (collapsed || cm.options.showCursorWhenSelecting) | |
updateSelectionCursor(cm); | |
else | |
display.cursor.style.display = display.otherCursor.style.display = "none"; | |
if (!collapsed) | |
updateSelectionRange(cm); | |
else | |
display.selectionDiv.style.display = "none"; | |
// Move the hidden textarea near the cursor to prevent scrolling artifacts | |
var headPos = cursorCoords(cm, cm.view.sel.head, "div"); | |
var wrapOff = display.wrapper.getBoundingClientRect(), lineOff = display.lineDiv.getBoundingClientRect(); | |
display.inputDiv.style.top = Math.max(0, Math.min(display.wrapper.clientHeight - 10, | |
headPos.top + lineOff.top - wrapOff.top)) + "px"; | |
display.inputDiv.style.left = Math.max(0, Math.min(display.wrapper.clientWidth - 10, | |
headPos.left + lineOff.left - wrapOff.left)) + "px"; | |
} | |
// No selection, plain cursor | |
function updateSelectionCursor(cm) { | |
var display = cm.display, pos = cursorCoords(cm, cm.view.sel.head, "div"); | |
display.cursor.style.left = pos.left + "px"; | |
display.cursor.style.top = pos.top + "px"; | |
display.cursor.style.height = Math.max(0, pos.bottom - pos.top) * cm.options.cursorHeight + "px"; | |
display.cursor.style.display = ""; | |
if (pos.other) { | |
display.otherCursor.style.display = ""; | |
display.otherCursor.style.left = pos.other.left + "px"; | |
display.otherCursor.style.top = pos.other.top + "px"; | |
display.otherCursor.style.height = (pos.other.bottom - pos.other.top) * .85 + "px"; | |
} else { display.otherCursor.style.display = "none"; } | |
} | |
// Highlight selection | |
function updateSelectionRange(cm) { | |
var display = cm.display, doc = cm.view.doc, sel = cm.view.sel; | |
var fragment = document.createDocumentFragment(); | |
var clientWidth = display.lineSpace.offsetWidth, pl = paddingLeft(cm.display); | |
function add(left, top, width, bottom) { | |
if (top < 0) top = 0; | |
fragment.appendChild(elt("div", null, "CodeMirror-selected", "position: absolute; left: " + left + | |
"px; top: " + top + "px; width: " + (width == null ? clientWidth - left : width) + | |
"px; height: " + (bottom - top) + "px")); | |
} | |
function drawForLine(line, fromArg, toArg, retTop) { | |
var lineObj = getLine(doc, line); | |
var lineLen = lineObj.text.length, rVal = retTop ? Infinity : -Infinity; | |
function coords(ch) { | |
return charCoords(cm, {line: line, ch: ch}, "div", lineObj); | |
} | |
iterateBidiSections(getOrder(lineObj), fromArg || 0, toArg == null ? lineLen : toArg, function(from, to, dir) { | |
var leftPos = coords(dir == "rtl" ? to - 1 : from); | |
var rightPos = coords(dir == "rtl" ? from : to - 1); | |
var left = leftPos.left, right = rightPos.right; | |
if (rightPos.top - leftPos.top > 3) { // Different lines, draw top part | |
add(left, leftPos.top, null, leftPos.bottom); | |
left = pl; | |
if (leftPos.bottom < rightPos.top) add(left, leftPos.bottom, null, rightPos.top); | |
} | |
if (toArg == null && to == lineLen) right = clientWidth; | |
if (fromArg == null && from == 0) left = pl; | |
rVal = retTop ? Math.min(rightPos.top, rVal) : Math.max(rightPos.bottom, rVal); | |
if (left < pl + 1) left = pl; | |
add(left, rightPos.top, right - left, rightPos.bottom); | |
}); | |
return rVal; | |
} | |
if (sel.from.line == sel.to.line) { | |
drawForLine(sel.from.line, sel.from.ch, sel.to.ch); | |
} else { | |
var fromObj = getLine(doc, sel.from.line); | |
var cur = fromObj, merged, path = [sel.from.line, sel.from.ch], singleLine; | |
while (merged = collapsedSpanAtEnd(cur)) { | |
var found = merged.find(); | |
path.push(found.from.ch, found.to.line, found.to.ch); | |
if (found.to.line == sel.to.line) { | |
path.push(sel.to.ch); | |
singleLine = true; | |
break; | |
} | |
cur = getLine(doc, found.to.line); | |
} | |
// This is a single, merged line | |
if (singleLine) { | |
for (var i = 0; i < path.length; i += 3) | |
drawForLine(path[i], path[i+1], path[i+2]); | |
} else { | |
var middleTop, middleBot, toObj = getLine(doc, sel.to.line); | |
if (sel.from.ch) | |
// Draw the first line of selection. | |
middleTop = drawForLine(sel.from.line, sel.from.ch, null, false); | |
else | |
// Simply include it in the middle block. | |
middleTop = heightAtLine(cm, fromObj) - display.viewOffset; | |
if (!sel.to.ch) | |
middleBot = heightAtLine(cm, toObj) - display.viewOffset; | |
else | |
middleBot = drawForLine(sel.to.line, collapsedSpanAtStart(toObj) ? null : 0, sel.to.ch, true); | |
if (middleTop < middleBot) add(pl, middleTop, null, middleBot); | |
} | |
} | |
removeChildrenAndAdd(display.selectionDiv, fragment); | |
display.selectionDiv.style.display = ""; | |
} | |
// Cursor-blinking | |
function restartBlink(cm) { | |
var display = cm.display; | |
clearInterval(display.blinker); | |
var on = true; | |
display.cursor.style.visibility = display.otherCursor.style.visibility = ""; | |
display.blinker = setInterval(function() { | |
if (!display.cursor.offsetHeight) return; | |
display.cursor.style.visibility = display.otherCursor.style.visibility = (on = !on) ? "" : "hidden"; | |
}, cm.options.cursorBlinkRate); | |
} | |
// HIGHLIGHT WORKER | |
function startWorker(cm, time) { | |
if (cm.view.mode.startState && cm.view.frontier < cm.display.showingTo) | |
cm.view.highlight.set(time, bind(highlightWorker, cm)); | |
} | |
function highlightWorker(cm) { | |
var view = cm.view, doc = view.doc; | |
if (view.frontier >= cm.display.showingTo) return; | |
var end = +new Date + cm.options.workTime; | |
var state = copyState(view.mode, getStateBefore(cm, view.frontier)); | |
var changed = [], prevChange; | |
doc.iter(view.frontier, Math.min(doc.size, cm.display.showingTo + 500), function(line) { | |
if (view.frontier >= cm.display.showingFrom) { // Visible | |
var oldStyles = line.styles; | |
line.styles = highlightLine(cm, line, state); | |
var ischange = !oldStyles || oldStyles.length != line.styles.length; | |
for (var i = 0; !ischange && i < oldStyles.length; ++i) | |
ischange = oldStyles[i] != line.styles[i]; | |
if (ischange) { | |
if (prevChange && prevChange.end == view.frontier) prevChange.end++; | |
else changed.push(prevChange = {start: view.frontier, end: view.frontier + 1}); | |
} | |
line.stateAfter = copyState(view.mode, state); | |
} else { | |
processLine(cm, line, state); | |
line.stateAfter = view.frontier % 5 == 0 ? copyState(view.mode, state) : null; | |
} | |
++view.frontier; | |
if (+new Date > end) { | |
startWorker(cm, cm.options.workDelay); | |
return true; | |
} | |
}); | |
if (changed.length) | |
operation(cm, function() { | |
for (var i = 0; i < changed.length; ++i) | |
regChange(this, changed[i].start, changed[i].end); | |
})(); | |
} | |
// Finds the line to start with when starting a parse. Tries to | |
// find a line with a stateAfter, so that it can start with a | |
// valid state. If that fails, it returns the line with the | |
// smallest indentation, which tends to need the least context to | |
// parse correctly. | |
function findStartLine(cm, n) { | |
var minindent, minline, doc = cm.view.doc; | |
for (var search = n, lim = n - 100; search > lim; --search) { | |
if (search == 0) return 0; | |
var line = getLine(doc, search-1); | |
if (line.stateAfter) return search; | |
var indented = countColumn(line.text, null, cm.options.tabSize); | |
if (minline == null || minindent > indented) { | |
minline = search - 1; | |
minindent = indented; | |
} | |
} | |
return minline; | |
} | |
function getStateBefore(cm, n) { | |
var view = cm.view; | |
if (!view.mode.startState) return true; | |
var pos = findStartLine(cm, n), state = pos && getLine(view.doc, pos-1).stateAfter; | |
if (!state) state = startState(view.mode); | |
else state = copyState(view.mode, state); | |
view.doc.iter(pos, n, function(line) { | |
processLine(cm, line, state); | |
var save = pos == n - 1 || pos % 5 == 0 || pos >= view.showingFrom && pos < view.showingTo; | |
line.stateAfter = save ? copyState(view.mode, state) : null; | |
++pos; | |
}); | |
return state; | |
} | |
// POSITION MEASUREMENT | |
function paddingTop(display) {return display.lineSpace.offsetTop;} | |
function paddingLeft(display) { | |
var e = removeChildrenAndAdd(display.measure, elt("pre")).appendChild(elt("span", "x")); | |
return e.offsetLeft; | |
} | |
function measureChar(cm, line, ch, data) { | |
var dir = -1; | |
data = data || measureLine(cm, line); | |
for (var pos = ch;; pos += dir) { | |
var r = data[pos]; | |
if (r) break; | |
if (dir < 0 && pos == 0) dir = 1; | |
} | |
return {left: pos < ch ? r.right : r.left, | |
right: pos > ch ? r.left : r.right, | |
top: r.top, bottom: r.bottom}; | |
} | |
function measureLine(cm, line) { | |
// First look in the cache | |
var display = cm.display, cache = cm.display.measureLineCache; | |
for (var i = 0; i < cache.length; ++i) { | |
var memo = cache[i]; | |
if (memo.text == line.text && memo.markedSpans == line.markedSpans && | |
display.scroller.clientWidth == memo.width) | |
return memo.measure; | |
} | |
var measure = measureLineInner(cm, line); | |
// Store result in the cache | |
var memo = {text: line.text, width: display.scroller.clientWidth, | |
markedSpans: line.markedSpans, measure: measure}; | |
if (cache.length == 16) cache[++display.measureLineCachePos % 16] = memo; | |
else cache.push(memo); | |
return measure; | |
} | |
function measureLineInner(cm, line) { | |
var display = cm.display, measure = emptyArray(line.text.length); | |
var pre = lineContent(cm, line, measure); | |
// IE does not cache element positions of inline elements between | |
// calls to getBoundingClientRect. This makes the loop below, | |
// which gathers the positions of all the characters on the line, | |
// do an amount of layout work quadratic to the number of | |
// characters. When line wrapping is off, we try to improve things | |
// by first subdividing the line into a bunch of inline blocks, so | |
// that IE can reuse most of the layout information from caches | |
// for those blocks. This does interfere with line wrapping, so it | |
// doesn't work when wrapping is on, but in that case the | |
// situation is slightly better, since IE does cache line-wrapping | |
// information and only recomputes per-line. | |
if (ie && !ie_lt8 && !cm.options.lineWrapping && pre.childNodes.length > 100) { | |
var fragment = document.createDocumentFragment(); | |
var chunk = 10, n = pre.childNodes.length; | |
for (var i = 0, chunks = Math.ceil(n / chunk); i < chunks; ++i) { | |
var wrap = elt("div", null, null, "display: inline-block"); | |
for (var j = 0; j < chunk && n; ++j) { | |
wrap.appendChild(pre.firstChild); | |
--n; | |
} | |
fragment.appendChild(wrap); | |
} | |
pre.appendChild(fragment); | |
} | |
removeChildrenAndAdd(display.measure, pre); | |
var outer = display.lineDiv.getBoundingClientRect(); | |
var vranges = [], data = emptyArray(line.text.length), maxBot = pre.offsetHeight; | |
for (var i = 0, cur; i < measure.length; ++i) if (cur = measure[i]) { | |
var size = cur.getBoundingClientRect(); | |
var top = Math.max(0, size.top - outer.top), bot = Math.min(size.bottom - outer.top, maxBot); | |
for (var j = 0; j < vranges.length; j += 2) { | |
var rtop = vranges[j], rbot = vranges[j+1]; | |
if (rtop > bot || rbot < top) continue; | |
if (rtop <= top && rbot >= bot || | |
top <= rtop && bot >= rbot || | |
Math.min(bot, rbot) - Math.max(top, rtop) >= (bot - top) >> 1) { | |
vranges[j] = Math.min(top, rtop); | |
vranges[j+1] = Math.max(bot, rbot); | |
break; | |
} | |
} | |
if (j == vranges.length) vranges.push(top, bot); | |
data[i] = {left: size.left - outer.left, right: size.right - outer.left, top: j}; | |
} | |
for (var i = 0, cur; i < data.length; ++i) if (cur = data[i]) { | |
var vr = cur.top; | |
cur.top = vranges[vr]; cur.bottom = vranges[vr+1]; | |
} | |
return data; | |
} | |
function clearCaches(cm) { | |
cm.display.measureLineCache.length = cm.display.measureLineCachePos = 0; | |
cm.display.cachedCharWidth = cm.display.cachedTextHeight = null; | |
cm.view.maxLineChanged = true; | |
} | |
// Context is one of "line", "div" (display.lineDiv), "local"/null (editor), or "page" | |
function intoCoordSystem(cm, lineObj, rect, context) { | |
if (lineObj.widgets) for (var i = 0; i < lineObj.widgets.length; ++i) if (lineObj.widgets[i].above) { | |
var size = widgetHeight(lineObj.widgets[i]); | |
rect.top += size; rect.bottom += size; | |
} | |
if (context == "line") return rect; | |
if (!context) context = "local"; | |
var yOff = heightAtLine(cm, lineObj); | |
if (context != "local") yOff -= cm.display.viewOffset; | |
if (context == "page") { | |
var lOff = cm.display.lineSpace.getBoundingClientRect(); | |
yOff += lOff.top + (window.pageYOffset || (document.documentElement || document.body).scrollTop); | |
var xOff = lOff.left + (window.pageXOffset || (document.documentElement || document.body).scrollLeft); | |
rect.left += xOff; rect.right += xOff; | |
} | |
rect.top += yOff; rect.bottom += yOff; | |
return rect; | |
} | |
function charCoords(cm, pos, context, lineObj) { | |
if (!lineObj) lineObj = getLine(cm.view.doc, pos.line); | |
return intoCoordSystem(cm, lineObj, measureChar(cm, lineObj, pos.ch), context); | |
} | |
function cursorCoords(cm, pos, context, lineObj, measurement) { | |
lineObj = lineObj || getLine(cm.view.doc, pos.line); | |
if (!measurement) measurement = measureLine(cm, lineObj); | |
function get(ch, right) { | |
var m = measureChar(cm, lineObj, ch, measurement); | |
if (right) m.left = m.right; else m.right = m.left; | |
return intoCoordSystem(cm, lineObj, m, context); | |
} | |
var order = getOrder(lineObj), ch = pos.ch; | |
if (!order) return get(ch); | |
var main, other, linedir = order[0].level; | |
for (var i = 0; i < order.length; ++i) { | |
var part = order[i], rtl = part.level % 2, nb, here; | |
if (part.from < ch && part.to > ch) return get(ch, rtl); | |
var left = rtl ? part.to : part.from, right = rtl ? part.from : part.to; | |
if (left == ch) { | |
// Opera and IE return bogus offsets and widths for edges | |
// where the direction flips, but only for the side with the | |
// lower level. So we try to use the side with the higher | |
// level. | |
if (i && part.level < (nb = order[i-1]).level) here = get(nb.level % 2 ? nb.from : nb.to - 1, true); | |
else here = get(rtl && part.from != part.to ? ch - 1 : ch); | |
if (rtl == linedir) main = here; else other = here; | |
} else if (right == ch) { | |
var nb = i < order.length - 1 && order[i+1]; | |
if (!rtl && nb && nb.from == nb.to) continue; | |
if (nb && part.level < nb.level) here = get(nb.level % 2 ? nb.to - 1 : nb.from); | |
else here = get(rtl ? ch : ch - 1, true); | |
if (rtl == linedir) main = here; else other = here; | |
} | |
} | |
if (linedir && !ch) other = get(order[0].to - 1); | |
if (!main) return other; | |
if (other) main.other = other; | |
return main; | |
} | |
// Coords must be lineSpace-local | |
function coordsChar(cm, x, y) { | |
var doc = cm.view.doc; | |
y += cm.display.viewOffset; | |
if (y < 0) return {line: 0, ch: 0, outside: true}; | |
var lineNo = lineAtHeight(doc, y); | |
if (lineNo >= doc.size) return {line: doc.size - 1, ch: getLine(doc, doc.size - 1).text.length}; | |
if (x < 0) x = 0; | |
for (;;) { | |
var lineObj = getLine(doc, lineNo); | |
var found = coordsCharInner(cm, lineObj, lineNo, x, y); | |
var merged = collapsedSpanAtEnd(lineObj); | |
var mergedPos = merged && merged.find(); | |
if (merged && found.ch >= mergedPos.from.ch) | |
lineNo = mergedPos.to.line; | |
else | |
return found; | |
} | |
} | |
function coordsCharInner(cm, lineObj, lineNo, x, y) { | |
var innerOff = y - heightAtLine(cm, lineObj); | |
var wrongLine = false, cWidth = cm.display.wrapper.clientWidth; | |
var measurement = measureLine(cm, lineObj); | |
function getX(ch) { | |
var sp = cursorCoords(cm, {line: lineNo, ch: ch}, "line", | |
lineObj, measurement); | |
wrongLine = true; | |
if (innerOff > sp.bottom) return Math.max(0, sp.left - cWidth); | |
else if (innerOff < sp.top) return sp.left + cWidth; | |
else wrongLine = false; | |
return sp.left; | |
} | |
var bidi = getOrder(lineObj), dist = lineObj.text.length; | |
var from = lineLeft(lineObj), to = lineRight(lineObj); | |
var fromX = paddingLeft(cm.display), toX = getX(to); | |
if (x > toX) return {line: lineNo, ch: to, outside: wrongLine}; | |
// Do a binary search between these bounds. | |
for (;;) { | |
if (bidi ? to == from || to == moveVisually(lineObj, from, 1) : to - from <= 1) { | |
var after = x - fromX < toX - x, ch = after ? from : to; | |
while (isExtendingChar.test(lineObj.text.charAt(ch))) ++ch; | |
return {line: lineNo, ch: ch, after: after, outside: wrongLine}; | |
} | |
var step = Math.ceil(dist / 2), middle = from + step; | |
if (bidi) { | |
middle = from; | |
for (var i = 0; i < step; ++i) middle = moveVisually(lineObj, middle, 1); | |
} | |
var middleX = getX(middle); | |
if (middleX > x) {to = middle; toX = middleX; if (wrongLine) toX += 1000; dist -= step;} | |
else {from = middle; fromX = middleX; dist = step;} | |
} | |
} | |
var measureText; | |
function textHeight(display) { | |
if (display.cachedTextHeight != null) return display.cachedTextHeight; | |
if (measureText == null) { | |
measureText = elt("pre"); | |
// Measure a bunch of lines, for browsers that compute | |
// fractional heights. | |
for (var i = 0; i < 49; ++i) { | |
measureText.appendChild(document.createTextNode("x")); | |
measureText.appendChild(elt("br")); | |
} | |
measureText.appendChild(document.createTextNode("x")); | |
} | |
removeChildrenAndAdd(display.measure, measureText); | |
var height = measureText.offsetHeight / 50; | |
if (height > 3) display.cachedTextHeight = height; | |
removeChildren(display.measure); | |
return height || 1; | |
} | |
function charWidth(display) { | |
if (display.cachedCharWidth != null) return display.cachedCharWidth; | |
var anchor = elt("span", "x"); | |
var pre = elt("pre", [anchor]); | |
removeChildrenAndAdd(display.measure, pre); | |
var width = anchor.offsetWidth; | |
if (width > 2) display.cachedCharWidth = width; | |
return width || 10; | |
} | |
// OPERATIONS | |
// Operations are used to wrap changes in such a way that each | |
// change won't have to update the cursor and display (which would | |
// be awkward, slow, and error-prone), but instead updates are | |
// batched and then all combined and executed at once. | |
function startOperation(cm) { | |
if (cm.curOp) ++cm.curOp.depth; | |
else cm.curOp = { | |
// Nested operations delay update until the outermost one | |
// finishes. | |
depth: 1, | |
// An array of ranges of lines that have to be updated. See | |
// updateDisplay. | |
changes: [], | |
delayedCallbacks: [], | |
updateInput: null, | |
userSelChange: null, | |
textChanged: null, | |
selectionChanged: false, | |
updateMaxLine: false, | |
id: ++cm.nextOpId | |
}; | |
} | |
function endOperation(cm) { | |
var op = cm.curOp; | |
if (--op.depth) return; | |
cm.curOp = null; | |
var view = cm.view, display = cm.display; | |
if (op.updateMaxLine) computeMaxLength(view); | |
if (view.maxLineChanged && !cm.options.lineWrapping) { | |
var width = measureChar(cm, view.maxLine, view.maxLine.text.length).right; | |
display.sizer.style.minWidth = (width + 3 + scrollerCutOff) + "px"; | |
view.maxLineChanged = false; | |
var maxScrollLeft = Math.max(0, display.sizer.offsetLeft + display.sizer.offsetWidth - display.scroller.clientWidth); | |
if (maxScrollLeft < view.scrollLeft) | |
setScrollLeft(cm, Math.min(display.scroller.scrollLeft, maxScrollLeft), true); | |
} | |
var newScrollPos, updated; | |
if (op.selectionChanged) { | |
var coords = cursorCoords(cm, view.sel.head); | |
newScrollPos = calculateScrollPos(cm, coords.left, coords.top, coords.left, coords.bottom); | |
} | |
if (op.changes.length || newScrollPos && newScrollPos.scrollTop != null) | |
updated = updateDisplay(cm, op.changes, newScrollPos && newScrollPos.scrollTop); | |
if (!updated && op.selectionChanged) updateSelection(cm); | |
if (newScrollPos) scrollCursorIntoView(cm); | |
if (op.selectionChanged) restartBlink(cm); | |
if (view.focused && op.updateInput) | |
resetInput(cm, op.userSelChange); | |
if (op.textChanged) | |
signal(cm, "change", cm, op.textChanged); | |
if (op.selectionChanged) signal(cm, "cursorActivity", cm); | |
for (var i = 0; i < op.delayedCallbacks.length; ++i) op.delayedCallbacks[i](cm); | |
} | |
// Wraps a function in an operation. Returns the wrapped function. | |
function operation(cm1, f) { | |
return function() { | |
var cm = cm1 || this; | |
startOperation(cm); | |
try {var result = f.apply(cm, arguments);} | |
finally {endOperation(cm);} | |
return result; | |
}; | |
} | |
function regChange(cm, from, to, lendiff) { | |
cm.curOp.changes.push({from: from, to: to, diff: lendiff}); | |
} | |
// INPUT HANDLING | |
function slowPoll(cm) { | |
if (cm.view.pollingFast) return; | |
cm.display.poll.set(cm.options.pollInterval, function() { | |
readInput(cm); | |
if (cm.view.focused) slowPoll(cm); | |
}); | |
} | |
function fastPoll(cm) { | |
var missed = false; | |
cm.display.pollingFast = true; | |
function p() { | |
var changed = readInput(cm); | |
if (!changed && !missed) {missed = true; cm.display.poll.set(60, p);} | |
else {cm.display.pollingFast = false; slowPoll(cm);} | |
} | |
cm.display.poll.set(20, p); | |
} | |
// prevInput is a hack to work with IME. If we reset the textarea | |
// on every change, that breaks IME. So we look for changes | |
// compared to the previous content instead. (Modern browsers have | |
// events that indicate IME taking place, but these are not widely | |
// supported or compatible enough yet to rely on.) | |
function readInput(cm) { | |
var input = cm.display.input, prevInput = cm.display.prevInput, view = cm.view, sel = view.sel; | |
if (!view.focused || hasSelection(input) || isReadOnly(cm)) return false; | |
var text = input.value; | |
if (text == prevInput && posEq(sel.from, sel.to)) return false; | |
startOperation(cm); | |
view.sel.shift = false; | |
var same = 0, l = Math.min(prevInput.length, text.length); | |
while (same < l && prevInput[same] == text[same]) ++same; | |
var from = sel.from, to = sel.to; | |
if (same < prevInput.length) | |
from = {line: from.line, ch: from.ch - (prevInput.length - same)}; | |
else if (view.overwrite && posEq(from, to) && !cm.display.pasteIncoming) | |
to = {line: to.line, ch: Math.min(getLine(cm.view.doc, to.line).text.length, to.ch + (text.length - same))}; | |
var updateInput = cm.curOp.updateInput; | |
updateDoc(cm, from, to, splitLines(text.slice(same)), "end", | |
cm.display.pasteIncoming ? "paste" : "input", {from: from, to: to}); | |
cm.curOp.updateInput = updateInput; | |
if (text.length > 1000) input.value = cm.display.prevInput = ""; | |
else cm.display.prevInput = text; | |
endOperation(cm); | |
cm.display.pasteIncoming = false; | |
return true; | |
} | |
function resetInput(cm, user) { | |
var view = cm.view, minimal, selected; | |
if (!posEq(view.sel.from, view.sel.to)) { | |
cm.display.prevInput = ""; | |
minimal = hasCopyEvent && | |
(view.sel.to.line - view.sel.from.line > 100 || (selected = cm.getSelection()).length > 1000); | |
if (minimal) cm.display.input.value = "-"; | |
else cm.display.input.value = selected || cm.getSelection(); | |
if (view.focused) selectInput(cm.display.input); | |
} else if (user) cm.display.prevInput = cm.display.input.value = ""; | |
cm.display.inaccurateSelection = minimal; | |
} | |
function focusInput(cm) { | |
if (cm.options.readOnly != "nocursor" && (ie || document.activeElement != cm.display.input)) | |
cm.display.input.focus(); | |
} | |
function isReadOnly(cm) { | |
return cm.options.readOnly || cm.view.cantEdit; | |
} | |
// EVENT HANDLERS | |
function registerEventHandlers(cm) { | |
var d = cm.display; | |
on(d.scroller, "mousedown", operation(cm, onMouseDown)); | |
on(d.scroller, "dblclick", operation(cm, e_preventDefault)); | |
on(d.lineSpace, "selectstart", function(e) { | |
if (!eventInWidget(d, e)) e_preventDefault(e); | |
}); | |
// Gecko browsers fire contextmenu *after* opening the menu, at | |
// which point we can't mess with it anymore. Context menu is | |
// handled in onMouseDown for Gecko. | |
if (!gecko) on(d.scroller, "contextmenu", function(e) {onContextMenu(cm, e);}); | |
on(d.scroller, "scroll", function() { | |
setScrollTop(cm, d.scroller.scrollTop); | |
setScrollLeft(cm, d.scroller.scrollLeft, true); | |
signal(cm, "scroll", cm); | |
}); | |
on(d.scrollbarV, "scroll", function() { | |
setScrollTop(cm, d.scrollbarV.scrollTop); | |
}); | |
on(d.scrollbarH, "scroll", function() { | |
setScrollLeft(cm, d.scrollbarH.scrollLeft); | |
}); | |
on(d.scroller, "mousewheel", function(e){onScrollWheel(cm, e);}); | |
on(d.scroller, "DOMMouseScroll", function(e){onScrollWheel(cm, e);}); | |
function reFocus() { if (cm.view.focused) setTimeout(bind(focusInput, cm), 0); } | |
on(d.scrollbarH, "mousedown", reFocus); | |
on(d.scrollbarV, "mousedown", reFocus); | |
// Prevent wrapper from ever scrolling | |
on(d.wrapper, "scroll", function() { d.wrapper.scrollTop = d.wrapper.scrollLeft = 0; }); | |
if (!window.registered) window.registered = 0; | |
++window.registered; | |
function onResize() { | |
// Might be a text scaling operation, clear size caches. | |
d.cachedCharWidth = d.cachedTextHeight = null; | |
clearCaches(cm); | |
updateDisplay(cm, true); | |
} | |
on(window, "resize", onResize); | |
// Above handler holds on to the editor and its data structures. | |
// Here we poll to unregister it when the editor is no longer in | |
// the document, so that it can be garbage-collected. | |
setTimeout(function unregister() { | |
for (var p = d.wrapper.parentNode; p && p != document.body; p = p.parentNode) {} | |
if (p) setTimeout(unregister, 5000); | |
else {--window.registered; off(window, "resize", onResize);} | |
}, 5000); | |
on(d.input, "keyup", operation(cm, function(e) { | |
if (cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return; | |
if (e_prop(e, "keyCode") == 16) cm.view.sel.shift = false; | |
})); | |
on(d.input, "input", bind(fastPoll, cm)); | |
on(d.input, "keydown", operation(cm, onKeyDown)); | |
on(d.input, "keypress", operation(cm, onKeyPress)); | |
on(d.input, "focus", bind(onFocus, cm)); | |
on(d.input, "blur", bind(onBlur, cm)); | |
function drag_(e) { | |
if (cm.options.onDragEvent && cm.options.onDragEvent(cm, addStop(e))) return; | |
e_stop(e); | |
} | |
if (cm.options.dragDrop) { | |
on(d.scroller, "dragstart", function(e){onDragStart(cm, e);}); | |
on(d.scroller, "dragenter", drag_); | |
on(d.scroller, "dragover", drag_); | |
on(d.scroller, "drop", operation(cm, onDrop)); | |
} | |
on(d.scroller, "paste", function(e){ | |
if (eventInWidget(d, e)) return; | |
focusInput(cm); | |
fastPoll(cm); | |
}); | |
on(d.input, "paste", function() { | |
d.pasteIncoming = true; | |
fastPoll(cm); | |
}); | |
function prepareCopy() { | |
if (d.inaccurateSelection) { | |
d.prevInput = ""; | |
d.inaccurateSelection = false; | |
d.input.value = cm.getSelection(); | |
selectInput(d.input); | |
} | |
} | |
on(d.input, "cut", prepareCopy); | |
on(d.input, "copy", prepareCopy); | |
// Needed to handle Tab key in KHTML | |
if (khtml) on(d.sizer, "mouseup", function() { | |
if (document.activeElement == d.input) d.input.blur(); | |
focusInput(cm); | |
}); | |
} | |
function eventInWidget(display, e) { | |
for (var n = e_target(e); n != display.wrapper; n = n.parentNode) { | |
if (!n) return true; | |
if (/\bCodeMirror-(?:line)?widget\b/.test(n.className) || | |
n.parentNode == display.sizer && n != display.mover) return true; | |
} | |
} | |
function posFromMouse(cm, e, liberal) { | |
var display = cm.display; | |
if (!liberal) { | |
var target = e_target(e); | |
if (target == display.scrollbarH || target == display.scrollbarH.firstChild || | |
target == display.scrollbarV || target == display.scrollbarV.firstChild || | |
target == display.scrollbarFiller) return null; | |
} | |
var x, y, space = display.lineSpace.getBoundingClientRect(); | |
// Fails unpredictably on IE[67] when mouse is dragged around quickly. | |
try { x = e.clientX; y = e.clientY; } catch (e) { return null; } | |
return coordsChar(cm, x - space.left, y - space.top); | |
} | |
var lastClick, lastDoubleClick; | |
function onMouseDown(e) { | |
var cm = this, display = cm.display, view = cm.view, sel = view.sel, doc = view.doc; | |
sel.shift = e_prop(e, "shiftKey"); | |
if (eventInWidget(display, e)) { | |
if (!webkit) { | |
display.scroller.draggable = false; | |
setTimeout(function(){display.scroller.draggable = true;}, 100); | |
} | |
return; | |
} | |
if (clickInGutter(cm, e)) return; | |
var start = posFromMouse(cm, e); | |
switch (e_button(e)) { | |
case 3: | |
if (gecko) onContextMenu.call(cm, cm, e); | |
return; | |
case 2: | |
if (start) extendSelection(cm, start); | |
setTimeout(bind(focusInput, cm), 20); | |
e_preventDefault(e); | |
return; | |
} | |
// For button 1, if it was clicked inside the editor | |
// (posFromMouse returning non-null), we have to adjust the | |
// selection. | |
if (!start) {if (e_target(e) == display.scroller) e_preventDefault(e); return;} | |
if (!view.focused) onFocus(cm); | |
var now = +new Date, type = "single"; | |
if (lastDoubleClick && lastDoubleClick.time > now - 400 && posEq(lastDoubleClick.pos, start)) { | |
type = "triple"; | |
e_preventDefault(e); | |
setTimeout(bind(focusInput, cm), 20); | |
selectLine(cm, start.line); | |
} else if (lastClick && lastClick.time > now - 400 && posEq(lastClick.pos, start)) { | |
type = "double"; | |
lastDoubleClick = {time: now, pos: start}; | |
e_preventDefault(e); | |
var word = findWordAt(getLine(doc, start.line).text, start); | |
extendSelection(cm, word.from, word.to); | |
} else { lastClick = {time: now, pos: start}; } | |
var last = start; | |
if (cm.options.dragDrop && dragAndDrop && !isReadOnly(cm) && !posEq(sel.from, sel.to) && | |
!posLess(start, sel.from) && !posLess(sel.to, start) && type == "single") { | |
var dragEnd = operation(cm, function(e2) { | |
if (webkit) display.scroller.draggable = false; | |
view.draggingText = false; | |
off(document, "mouseup", dragEnd); | |
off(display.scroller, "drop", dragEnd); | |
if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) < 10) { | |
e_preventDefault(e2); | |
extendSelection(cm, start); | |
focusInput(cm); | |
} | |
}); | |
// Let the drag handler handle this. | |
if (webkit) display.scroller.draggable = true; | |
view.draggingText = dragEnd; | |
// IE's approach to draggable | |
if (display.scroller.dragDrop) display.scroller.dragDrop(); | |
on(document, "mouseup", dragEnd); | |
on(display.scroller, "drop", dragEnd); | |
return; | |
} | |
e_preventDefault(e); | |
if (type == "single") extendSelection(cm, clipPos(doc, start)); | |
var startstart = sel.from, startend = sel.to; | |
function doSelect(cur) { | |
if (type == "single") { | |
extendSelection(cm, clipPos(doc, start), cur); | |
return; | |
} | |
startstart = clipPos(doc, startstart); | |
startend = clipPos(doc, startend); | |
if (type == "double") { | |
var word = findWordAt(getLine(doc, cur.line).text, cur); | |
if (posLess(cur, startstart)) extendSelection(cm, word.from, startend); | |
else extendSelection(cm, startstart, word.to); | |
} else if (type == "triple") { | |
if (posLess(cur, startstart)) extendSelection(cm, startend, clipPos(doc, {line: cur.line, ch: 0})); | |
else extendSelection(cm, startstart, clipPos(doc, {line: cur.line + 1, ch: 0})); | |
} | |
} | |
var editorSize = display.wrapper.getBoundingClientRect(); | |
// Used to ensure timeout re-tries don't fire when another extend | |
// happened in the meantime (clearTimeout isn't reliable -- at | |
// least on Chrome, the timeouts still happen even when cleared, | |
// if the clear happens after their scheduled firing time). | |
var counter = 0; | |
function extend(e) { | |
var curCount = ++counter; | |
var cur = posFromMouse(cm, e, true); | |
if (!cur) return; | |
if (!posEq(cur, last)) { | |
if (!view.focused) onFocus(cm); | |
last = cur; | |
doSelect(cur); | |
var visible = visibleLines(display, doc); | |
if (cur.line >= visible.to || cur.line < visible.from) | |
setTimeout(operation(cm, function(){if (counter == curCount) extend(e);}), 150); | |
} else { | |
var outside = e.clientY < editorSize.top ? -20 : e.clientY > editorSize.bottom ? 20 : 0; | |
if (outside) setTimeout(operation(cm, function() { | |
if (counter != curCount) return; | |
display.scroller.scrollTop += outside; | |
extend(e); | |
}), 50); | |
} | |
} | |
function done(e) { | |
counter = Infinity; | |
var cur = posFromMouse(cm, e); | |
if (cur) doSelect(cur); | |
e_preventDefault(e); | |
focusInput(cm); | |
off(document, "mousemove", move); | |
off(document, "mouseup", up); | |
} | |
var move = operation(cm, function(e) { | |
if (!ie && !e_button(e)) done(e); | |
else extend(e); | |
}); | |
var up = operation(cm, done); | |
on(document, "mousemove", move); | |
on(document, "mouseup", up); | |
} | |
function onDrop(e) { | |
var cm = this; | |
if (eventInWidget(cm.display, e) || (cm.options.onDragEvent && cm.options.onDragEvent(cm, addStop(e)))) | |
return; | |
e_preventDefault(e); | |
var pos = posFromMouse(cm, e, true), files = e.dataTransfer.files; | |
if (!pos || isReadOnly(cm)) return; | |
if (files && files.length && window.FileReader && window.File) { | |
var n = files.length, text = Array(n), read = 0; | |
var loadFile = function(file, i) { | |
var reader = new FileReader; | |
reader.onload = function() { | |
text[i] = reader.result; | |
if (++read == n) { | |
pos = clipPos(cm.view.doc, pos); | |
operation(cm, function() { | |
var end = replaceRange(cm, text.join(""), pos, pos, "paste"); | |
setSelection(cm, pos, end); | |
})(); | |
} | |
}; | |
reader.readAsText(file); | |
}; | |
for (var i = 0; i < n; ++i) loadFile(files[i], i); | |
} else { | |
// Don't do a replace if the drop happened inside of the selected text. | |
if (cm.view.draggingText && !(posLess(pos, cm.view.sel.from) || posLess(cm.view.sel.to, pos))) { | |
cm.view.draggingText(e); | |
// Ensure the editor is re-focused | |
setTimeout(bind(focusInput, cm), 20); | |
return; | |
} | |
try { | |
var text = e.dataTransfer.getData("Text"); | |
if (text) { | |
var curFrom = cm.view.sel.from, curTo = cm.view.sel.to; | |
setSelection(cm, pos, pos); | |
if (cm.view.draggingText) replaceRange(cm, "", curFrom, curTo, "paste"); | |
cm.replaceSelection(text, null, "paste"); | |
focusInput(cm); | |
onFocus(cm); | |
} | |
} | |
catch(e){} | |
} | |
} | |
function clickInGutter(cm, e) { | |
var display = cm.display; | |
try { var mX = e.clientX, mY = e.clientY; } | |
catch(e) { return false; } | |
if (mX >= Math.floor(display.gutters.getBoundingClientRect().right)) return false; | |
e_preventDefault(e); | |
if (!hasHandler(cm, "gutterClick")) return true; | |
var lineBox = display.lineDiv.getBoundingClientRect(); | |
if (mY > lineBox.bottom) return true; | |
mY -= lineBox.top - display.viewOffset; | |
for (var i = 0; i < cm.options.gutters.length; ++i) { | |
var g = display.gutters.childNodes[i]; | |
if (g && g.getBoundingClientRect().right >= mX) { | |
var line = lineAtHeight(cm.view.doc, mY); | |
var gutter = cm.options.gutters[i]; | |
signalLater(cm, cm, "gutterClick", cm, line, gutter, e); | |
break; | |
} | |
} | |
return true; | |
} | |
function onDragStart(cm, e) { | |
if (eventInWidget(cm.display, e)) return; | |
var txt = cm.getSelection(); | |
e.dataTransfer.setData("Text", txt); | |
// Use dummy image instead of default browsers image. | |
// Recent Safari (~6.0.2) have a tendency to segfault when this happens, so we don't do it there. | |
if (e.dataTransfer.setDragImage && !safari) { | |
var img = elt("img", null, null, "position: fixed; left: 0; top: 0;"); | |
if (opera) { | |
img.width = img.height = 1; | |
cm.display.wrapper.appendChild(img); | |
// Force a relayout, or Opera won't use our image for some obscure reason | |
img._top = img.offsetTop; | |
} | |
e.dataTransfer.setDragImage(img, 0, 0); | |
if (opera) img.parentNode.removeChild(img); | |
} | |
} | |
function setScrollTop(cm, val) { | |
if (Math.abs(cm.view.scrollTop - val) < 2) return; | |
cm.view.scrollTop = val; | |
if (!gecko) updateDisplay(cm, [], val); | |
if (cm.display.scroller.scrollTop != val) cm.display.scroller.scrollTop = val; | |
if (cm.display.scrollbarV.scrollTop != val) cm.display.scrollbarV.scrollTop = val; | |
if (gecko) updateDisplay(cm, []); | |
} | |
function setScrollLeft(cm, val, isScroller) { | |
if (isScroller ? val == cm.view.scrollLeft : Math.abs(cm.view.scrollLeft - val) < 2) return; | |
val = Math.min(val, cm.display.scroller.scrollWidth - cm.display.scroller.clientWidth); | |
cm.view.scrollLeft = val; | |
alignHorizontally(cm); | |
if (cm.display.scroller.scrollLeft != val) cm.display.scroller.scrollLeft = val; | |
if (cm.display.scrollbarH.scrollLeft != val) cm.display.scrollbarH.scrollLeft = val; | |
} | |
// Since the delta values reported on mouse wheel events are | |
// unstandardized between browsers and even browser versions, and | |
// generally horribly unpredictable, this code starts by measuring | |
// the scroll effect that the first few mouse wheel events have, | |
// and, from that, detects the way it can convert deltas to pixel | |
// offsets afterwards. | |
// | |
// The reason we want to know the amount a wheel event will scroll | |
// is that it gives us a chance to update the display before the | |
// actual scrolling happens, reducing flickering. | |
var wheelSamples = 0, wheelPixelsPerUnit = null; | |
// Fill in a browser-detected starting value on browsers where we | |
// know one. These don't have to be accurate -- the result of them | |
// being wrong would just be a slight flicker on the first wheel | |
// scroll (if it is large enough). | |
if (ie) wheelPixelsPerUnit = -.53; | |
else if (gecko) wheelPixelsPerUnit = 15; | |
else if (chrome) wheelPixelsPerUnit = -.7; | |
else if (safari) wheelPixelsPerUnit = -1/3; | |
function onScrollWheel(cm, e) { | |
var dx = e.wheelDeltaX, dy = e.wheelDeltaY; | |
if (dx == null && e.detail && e.axis == e.HORIZONTAL_AXIS) dx = e.detail; | |
if (dy == null && e.detail && e.axis == e.VERTICAL_AXIS) dy = e.detail; | |
else if (dy == null) dy = e.wheelDelta; | |
// Webkit browsers on OS X abort momentum scrolls when the target | |
// of the scroll event is removed from the scrollable element. | |
// This hack (see related code in patchDisplay) makes sure the | |
// element is kept around. | |
if (dy && mac && webkit) { | |
for (var cur = e.target; cur != scroll; cur = cur.parentNode) { | |
if (cur.lineObj) { | |
cm.display.currentWheelTarget = cur; | |
break; | |
} | |
} | |
} | |
var display = cm.display, scroll = display.scroller; | |
// On some browsers, horizontal scrolling will cause redraws to | |
// happen before the gutter has been realigned, causing it to | |
// wriggle around in a most unseemly way. When we have an | |
// estimated pixels/delta value, we just handle horizontal | |
// scrolling entirely here. It'll be slightly off from native, but | |
// better than glitching out. | |
if (dx && !gecko && !opera && wheelPixelsPerUnit != null) { | |
if (dy) | |
setScrollTop(cm, Math.max(0, Math.min(scroll.scrollTop + dy * wheelPixelsPerUnit, scroll.scrollHeight - scroll.clientHeight))); | |
setScrollLeft(cm, Math.max(0, Math.min(scroll.scrollLeft + dx * wheelPixelsPerUnit, scroll.scrollWidth - scroll.clientWidth))); | |
e_preventDefault(e); | |
display.wheelStartX = null; // Abort measurement, if in progress | |
return; | |
} | |
if (dy && wheelPixelsPerUnit != null) { | |
var pixels = dy * wheelPixelsPerUnit; | |
var top = cm.view.scrollTop, bot = top + display.wrapper.clientHeight; | |
if (pixels < 0) top = Math.max(0, top + pixels - 50); | |
else bot = Math.min(cm.view.doc.height, bot + pixels + 50); | |
updateDisplay(cm, [], {top: top, bottom: bot}); | |
} | |
if (wheelSamples < 20) { | |
if (display.wheelStartX == null) { | |
display.wheelStartX = scroll.scrollLeft; display.wheelStartY = scroll.scrollTop; | |
display.wheelDX = dx; display.wheelDY = dy; | |
setTimeout(function() { | |
if (display.wheelStartX == null) return; | |
var movedX = scroll.scrollLeft - display.wheelStartX; | |
var movedY = scroll.scrollTop - display.wheelStartY; | |
var sample = (movedY && display.wheelDY && movedY / display.wheelDY) || | |
(movedX && display.wheelDX && movedX / display.wheelDX); | |
display.wheelStartX = display.wheelStartY = null; | |
if (!sample) return; | |
wheelPixelsPerUnit = (wheelPixelsPerUnit * wheelSamples + sample) / (wheelSamples + 1); | |
++wheelSamples; | |
}, 200); | |
} else { | |
display.wheelDX += dx; display.wheelDY += dy; | |
} | |
} | |
} | |
function doHandleBinding(cm, bound, dropShift) { | |
if (typeof bound == "string") { | |
bound = commands[bound]; | |
if (!bound) return false; | |
} | |
// Ensure previous input has been read, so that the handler sees a | |
// consistent view of the document | |
if (cm.display.pollingFast && readInput(cm)) cm.display.pollingFast = false; | |
var view = cm.view, prevShift = view.sel.shift; | |
try { | |
if (isReadOnly(cm)) view.suppressEdits = true; | |
if (dropShift) view.sel.shift = false; | |
bound(cm); | |
} catch(e) { | |
if (e != Pass) throw e; | |
return false; | |
} finally { | |
view.sel.shift = prevShift; | |
view.suppressEdits = false; | |
} | |
return true; | |
} | |
function allKeyMaps(cm) { | |
var maps = cm.view.keyMaps.slice(0); | |
maps.push(cm.options.keyMap); | |
if (cm.options.extraKeys) maps.unshift(cm.options.extraKeys); | |
return maps; | |
} | |
var maybeTransition; | |
function handleKeyBinding(cm, e) { | |
// Handle auto keymap transitions | |
var startMap = getKeyMap(cm.options.keyMap), next = startMap.auto; | |
clearTimeout(maybeTransition); | |
if (next && !isModifierKey(e)) maybeTransition = setTimeout(function() { | |
if (getKeyMap(cm.options.keyMap) == startMap) | |
cm.options.keyMap = (next.call ? next.call(null, cm) : next); | |
}, 50); | |
var name = keyNames[e_prop(e, "keyCode")], handled = false; | |
if (name == null || e.altGraphKey) return false; | |
if (e_prop(e, "altKey")) name = "Alt-" + name; | |
if (e_prop(e, flipCtrlCmd ? "metaKey" : "ctrlKey")) name = "Ctrl-" + name; | |
if (e_prop(e, flipCtrlCmd ? "ctrlKey" : "metaKey")) name = "Cmd-" + name; | |
var stopped = false; | |
function stop() { stopped = true; } | |
var keymaps = allKeyMaps(cm); | |
if (e_prop(e, "shiftKey")) { | |
handled = lookupKey("Shift-" + name, keymaps, | |
function(b) {return doHandleBinding(cm, b, true);}, stop) | |
|| lookupKey(name, keymaps, function(b) { | |
if (typeof b == "string" && /^go[A-Z]/.test(b)) return doHandleBinding(cm, b); | |
}, stop); | |
} else { | |
handled = lookupKey(name, keymaps, | |
function(b) { return doHandleBinding(cm, b); }, stop); | |
} | |
if (stopped) handled = false; | |
if (handled) { | |
e_preventDefault(e); | |
restartBlink(cm); | |
if (ie_lt9) { e.oldKeyCode = e.keyCode; e.keyCode = 0; } | |
} | |
return handled; | |
} | |
function handleCharBinding(cm, e, ch) { | |
var handled = lookupKey("'" + ch + "'", allKeyMaps(cm), | |
function(b) { return doHandleBinding(cm, b, true); }); | |
if (handled) { | |
e_preventDefault(e); | |
restartBlink(cm); | |
} | |
return handled; | |
} | |
var lastStoppedKey = null; | |
function onKeyDown(e) { | |
var cm = this; | |
if (!cm.view.focused) onFocus(cm); | |
if (ie && e.keyCode == 27) { e.returnValue = false; } | |
if (cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return; | |
var code = e_prop(e, "keyCode"); | |
// IE does strange things with escape. | |
cm.view.sel.shift = code == 16 || e_prop(e, "shiftKey"); | |
// First give onKeyEvent option a chance to handle this. | |
var handled = handleKeyBinding(cm, e); | |
if (opera) { | |
lastStoppedKey = handled ? code : null; | |
// Opera has no cut event... we try to at least catch the key combo | |
if (!handled && code == 88 && !hasCopyEvent && e_prop(e, mac ? "metaKey" : "ctrlKey")) | |
cm.replaceSelection(""); | |
} | |
} | |
function onKeyPress(e) { | |
var cm = this; | |
if (cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return; | |
var keyCode = e_prop(e, "keyCode"), charCode = e_prop(e, "charCode"); | |
if (opera && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return;} | |
if (((opera && (!e.which || e.which < 10)) || khtml) && handleKeyBinding(cm, e)) return; | |
var ch = String.fromCharCode(charCode == null ? keyCode : charCode); | |
if (this.options.electricChars && this.view.mode.electricChars && | |
this.options.smartIndent && !isReadOnly(this) && | |
this.view.mode.electricChars.indexOf(ch) > -1) | |
setTimeout(operation(cm, function() {indentLine(cm, cm.view.sel.to.line, "smart");}), 75); | |
if (handleCharBinding(cm, e, ch)) return; | |
fastPoll(cm); | |
} | |
function onFocus(cm) { | |
if (cm.options.readOnly == "nocursor") return; | |
if (!cm.view.focused) { | |
signal(cm, "focus", cm); | |
cm.view.focused = true; | |
if (cm.display.scroller.className.search(/\bCodeMirror-focused\b/) == -1) | |
cm.display.scroller.className += " CodeMirror-focused"; | |
resetInput(cm, true); | |
} | |
slowPoll(cm); | |
restartBlink(cm); | |
} | |
function onBlur(cm) { | |
if (cm.view.focused) { | |
signal(cm, "blur", cm); | |
cm.view.focused = false; | |
cm.display.scroller.className = cm.display.scroller.className.replace(" CodeMirror-focused", ""); | |
} | |
clearInterval(cm.display.blinker); | |
setTimeout(function() {if (!cm.view.focused) cm.view.sel.shift = false;}, 150); | |
} | |
var detectingSelectAll; | |
function onContextMenu(cm, e) { | |
var display = cm.display; | |
if (eventInWidget(display, e)) return; | |
var sel = cm.view.sel; | |
var pos = posFromMouse(cm, e), scrollPos = display.scroller.scrollTop; | |
if (!pos || opera) return; // Opera is difficult. | |
if (posEq(sel.from, sel.to) || posLess(pos, sel.from) || !posLess(pos, sel.to)) | |
operation(cm, setSelection)(cm, pos, pos); | |
var oldCSS = display.input.style.cssText; | |
display.inputDiv.style.position = "absolute"; | |
display.input.style.cssText = "position: fixed; width: 30px; height: 30px; top: " + (e.clientY - 5) + | |
"px; left: " + (e.clientX - 5) + "px; z-index: 1000; background: white; outline: none;" + | |
"border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);"; | |
focusInput(cm); | |
resetInput(cm, true); | |
// Adds "Select all" to context menu in FF | |
if (posEq(sel.from, sel.to)) display.input.value = display.prevInput = " "; | |
function rehide() { | |
display.inputDiv.style.position = "relative"; | |
display.input.style.cssText = oldCSS; | |
if (ie_lt9) display.scrollbarV.scrollTop = display.scroller.scrollTop = scrollPos; | |
slowPoll(cm); | |
// Try to detect the user choosing select-all | |
if (display.input.selectionStart != null) { | |
clearTimeout(detectingSelectAll); | |
var extval = display.input.value = " " + (posEq(sel.from, sel.to) ? "" : display.input.value), i = 0; | |
display.prevInput = " "; | |
display.input.selectionStart = 1; display.input.selectionEnd = extval.length; | |
detectingSelectAll = setTimeout(function poll(){ | |
if (display.prevInput == " " && display.input.selectionStart == 0) | |
operation(cm, commands.selectAll)(cm); | |
else if (i++ < 10) detectingSelectAll = setTimeout(poll, 500); | |
else resetInput(cm); | |
}, 200); | |
} | |
} | |
if (gecko) { | |
e_stop(e); | |
on(window, "mouseup", function mouseup() { | |
off(window, "mouseup", mouseup); | |
setTimeout(rehide, 20); | |
}); | |
} else { | |
setTimeout(rehide, 50); | |
} | |
} | |
// UPDATING | |
// Replace the range from from to to by the strings in newText. | |
// Afterwards, set the selection to selFrom, selTo. | |
function updateDoc(cm, from, to, newText, selUpdate, origin) { | |
// Possibly split or suppress the update based on the presence | |
// of read-only spans in its range. | |
var split = sawReadOnlySpans && | |
removeReadOnlyRanges(cm.view.doc, from, to); | |
if (split) { | |
for (var i = split.length - 1; i >= 1; --i) | |
updateDocInner(cm, split[i].from, split[i].to, [""], origin); | |
if (split.length) | |
return updateDocInner(cm, split[0].from, split[0].to, newText, selUpdate, origin); | |
} else { | |
return updateDocInner(cm, from, to, newText, selUpdate, origin); | |
} | |
} | |
function updateDocInner(cm, from, to, newText, selUpdate, origin) { | |
if (cm.view.suppressEdits) return; | |
var view = cm.view, doc = view.doc, old = []; | |
doc.iter(from.line, to.line + 1, function(line) { | |
old.push(newHL(line.text, line.markedSpans)); | |
}); | |
var startSelFrom = view.sel.from, startSelTo = view.sel.to; | |
var lines = updateMarkedSpans(hlSpans(old[0]), hlSpans(lst(old)), from.ch, to.ch, newText); | |
var retval = updateDocNoUndo(cm, from, to, lines, selUpdate, origin); | |
if (view.history) addChange(cm, from.line, newText.length, old, origin, | |
startSelFrom, startSelTo, view.sel.from, view.sel.to); | |
return retval; | |
} | |
function unredoHelper(cm, type) { | |
var doc = cm.view.doc, hist = cm.view.history; | |
var set = (type == "undo" ? hist.done : hist.undone).pop(); | |
if (!set) return; | |
var anti = {events: [], fromBefore: set.fromAfter, toBefore: set.toAfter, | |
fromAfter: set.fromBefore, toAfter: set.toBefore}; | |
for (var i = set.events.length - 1; i >= 0; i -= 1) { | |
hist.dirtyCounter += type == "undo" ? -1 : 1; | |
var change = set.events[i]; | |
var replaced = [], end = change.start + change.added; | |
doc.iter(change.start, end, function(line) { replaced.push(newHL(line.text, line.markedSpans)); }); | |
anti.events.push({start: change.start, added: change.old.length, old: replaced}); | |
var selPos = i ? null : {from: set.fromBefore, to: set.toBefore}; | |
updateDocNoUndo(cm, {line: change.start, ch: 0}, {line: end - 1, ch: getLine(doc, end-1).text.length}, | |
change.old, selPos, type); | |
} | |
(type == "undo" ? hist.undone : hist.done).push(anti); | |
} | |
function updateDocNoUndo(cm, from, to, lines, selUpdate, origin) { | |
var view = cm.view, doc = view.doc, display = cm.display; | |
if (view.suppressEdits) return; | |
var nlines = to.line - from.line, firstLine = getLine(doc, from.line), lastLine = getLine(doc, to.line); | |
var recomputeMaxLength = false, checkWidthStart = from.line; | |
if (!cm.options.lineWrapping) { | |
checkWidthStart = lineNo(visualLine(doc, firstLine)); | |
doc.iter(checkWidthStart, to.line + 1, function(line) { | |
if (line == view.maxLine) { | |
recomputeMaxLength = true; | |
return true; | |
} | |
}); | |
} | |
var lastHL = lst(lines), th = textHeight(display); | |
// First adjust the line structure | |
if (from.ch == 0 && to.ch == 0 && hlText(lastHL) == "") { | |
// This is a whole-line replace. Treated specially to make | |
// sure line objects move the way they are supposed to. | |
var added = []; | |
for (var i = 0, e = lines.length - 1; i < e; ++i) | |
added.push(makeLine(hlText(lines[i]), hlSpans(lines[i]), th)); | |
updateLine(cm, lastLine, lastLine.text, hlSpans(lastHL)); | |
if (nlines) doc.remove(from.line, nlines, cm); | |
if (added.length) doc.insert(from.line, added); | |
} else if (firstLine == lastLine) { | |
if (lines.length == 1) { | |
updateLine(cm, firstLine, firstLine.text.slice(0, from.ch) + hlText(lines[0]) + | |
firstLine.text.slice(to.ch), hlSpans(lines[0])); | |
} else { | |
for (var added = [], i = 1, e = lines.length - 1; i < e; ++i) | |
added.push(makeLine(hlText(lines[i]), hlSpans(lines[i]), th)); | |
added.push(makeLine(hlText(lastHL) + firstLine.text.slice(to.ch), hlSpans(lastHL), th)); | |
updateLine(cm, firstLine, firstLine.text.slice(0, from.ch) + hlText(lines[0]), hlSpans(lines[0])); | |
doc.insert(from.line + 1, added); | |
} | |
} else if (lines.length == 1) { | |
updateLine(cm, firstLine, firstLine.text.slice(0, from.ch) + hlText(lines[0]) + | |
lastLine.text.slice(to.ch), hlSpans(lines[0])); | |
doc.remove(from.line + 1, nlines, cm); | |
} else { | |
var added = []; | |
updateLine(cm, firstLine, firstLine.text.slice(0, from.ch) + hlText(lines[0]), hlSpans(lines[0])); | |
updateLine(cm, lastLine, hlText(lastHL) + lastLine.text.slice(to.ch), hlSpans(lastHL)); | |
for (var i = 1, e = lines.length - 1; i < e; ++i) | |
added.push(makeLine(hlText(lines[i]), hlSpans(lines[i]), th)); | |
if (nlines > 1) doc.remove(from.line + 1, nlines - 1, cm); | |
doc.insert(from.line + 1, added); | |
} | |
if (cm.options.lineWrapping) { | |
var perLine = Math.max(5, display.scroller.clientWidth / charWidth(display) - 3); | |
doc.iter(from.line, from.line + lines.length, function(line) { | |
if (line.height == 0) return; | |
var guess = (Math.ceil(line.text.length / perLine) || 1) * th; | |
if (guess != line.height) updateLineHeight(line, guess); | |
}); | |
} else { | |
doc.iter(checkWidthStart, from.line + lines.length, function(line) { | |
var len = lineLength(doc, line); | |
if (len > view.maxLineLength) { | |
view.maxLine = line; | |
view.maxLineLength = len; | |
view.maxLineChanged = true; | |
recomputeMaxLength = false; | |
} | |
}); | |
if (recomputeMaxLength) cm.curOp.updateMaxLine = true; | |
} | |
// Adjust frontier, schedule worker | |
view.frontier = Math.min(view.frontier, from.line); | |
startWorker(cm, 400); | |
var lendiff = lines.length - nlines - 1; | |
// Remember that these lines changed, for updating the display | |
regChange(cm, from.line, to.line + 1, lendiff); | |
if (hasHandler(cm, "change")) { | |
// Normalize lines to contain only strings, since that's what | |
// the change event handler expects | |
for (var i = 0; i < lines.length; ++i) | |
if (typeof lines[i] != "string") lines[i] = lines[i].text; | |
var changeObj = {from: from, to: to, text: lines, origin: origin}; | |
if (cm.curOp.textChanged) { | |
for (var cur = cm.curOp.textChanged; cur.next; cur = cur.next) {} | |
cur.next = changeObj; | |
} else cm.curOp.textChanged = changeObj; | |
} | |
// Update the selection | |
var newSelFrom, newSelTo, end = {line: from.line + lines.length - 1, | |
ch: hlText(lastHL).length + (lines.length == 1 ? from.ch : 0)}; | |
if (selUpdate && typeof selUpdate != "string") { | |
if (selUpdate.from) { newSelFrom = selUpdate.from; newSelTo = selUpdate.to; } | |
else newSelFrom = newSelTo = selUpdate; | |
} else if (selUpdate == "end") { | |
newSelFrom = newSelTo = end; | |
} else if (selUpdate == "start") { | |
newSelFrom = newSelTo = from; | |
} else if (selUpdate == "around") { | |
newSelFrom = from; newSelTo = end; | |
} else { | |
var adjustPos = function(pos) { | |
if (posLess(pos, from)) return pos; | |
if (!posLess(to, pos)) return end; | |
var line = pos.line + lendiff; | |
var ch = pos.ch; | |
if (pos.line == to.line) | |
ch += hlText(lastHL).length - (to.ch - (to.line == from.line ? from.ch : 0)); | |
return {line: line, ch: ch}; | |
}; | |
newSelFrom = adjustPos(view.sel.from); | |
newSelTo = adjustPos(view.sel.to); | |
} | |
setSelection(cm, newSelFrom, newSelTo, null, true); | |
return end; | |
} | |
function replaceRange(cm, code, from, to, origin) { | |
if (!to) to = from; | |
if (posLess(to, from)) { var tmp = to; to = from; from = tmp; } | |
return updateDoc(cm, from, to, splitLines(code), null, origin); | |
} | |
// SELECTION | |
function posEq(a, b) {return a.line == b.line && a.ch == b.ch;} | |
function posLess(a, b) {return a.line < b.line || (a.line == b.line && a.ch < b.ch);} | |
function copyPos(x) {return {line: x.line, ch: x.ch};} | |
function clipLine(doc, n) {return Math.max(0, Math.min(n, doc.size-1));} | |
function clipPos(doc, pos) { | |
if (pos.line < 0) return {line: 0, ch: 0}; | |
if (pos.line >= doc.size) return {line: doc.size-1, ch: getLine(doc, doc.size-1).text.length}; | |
var ch = pos.ch, linelen = getLine(doc, pos.line).text.length; | |
if (ch == null || ch > linelen) return {line: pos.line, ch: linelen}; | |
else if (ch < 0) return {line: pos.line, ch: 0}; | |
else return pos; | |
} | |
function isLine(doc, l) {return l >= 0 && l < doc.size;} | |
// If shift is held, this will move the selection anchor. Otherwise, | |
// it'll set the whole selection. | |
function extendSelection(cm, pos, other, bias) { | |
var sel = cm.view.sel; | |
if (sel.shift || sel.extend) { | |
var anchor = sel.anchor; | |
if (other) { | |
var posBefore = posLess(pos, anchor); | |
if (posBefore != posLess(other, anchor)) { | |
anchor = pos; | |
pos = other; | |
} else if (posBefore != posLess(pos, other)) { | |
pos = other; | |
} | |
} | |
setSelection(cm, anchor, pos, bias); | |
} else { | |
setSelection(cm, pos, other || pos, bias); | |
} | |
cm.curOp.userSelChange = true; | |
} | |
// Update the selection. Last two args are only used by | |
// updateDoc, since they have to be expressed in the line | |
// numbers before the update. | |
function setSelection(cm, anchor, head, bias, checkAtomic) { | |
cm.view.goalColumn = null; | |
var sel = cm.view.sel; | |
// Skip over atomic spans. | |
if (checkAtomic || !posEq(anchor, sel.anchor)) | |
anchor = skipAtomic(cm, anchor, bias, checkAtomic != "push"); | |
if (checkAtomic || !posEq(head, sel.head)) | |
head = skipAtomic(cm, head, bias, checkAtomic != "push"); | |
if (posEq(sel.anchor, anchor) && posEq(sel.head, head)) return; | |
sel.anchor = anchor; sel.head = head; | |
var inv = posLess(head, anchor); | |
sel.from = inv ? head : anchor; | |
sel.to = inv ? anchor : head; | |
cm.curOp.updateInput = true; | |
cm.curOp.selectionChanged = true; | |
} | |
function reCheckSelection(cm) { | |
setSelection(cm, cm.view.sel.from, cm.view.sel.to, null, "push"); | |
} | |
function skipAtomic(cm, pos, bias, mayClear) { | |
var doc = cm.view.doc, flipped = false, curPos = pos; | |
var dir = bias || 1; | |
cm.view.cantEdit = false; | |
search: for (;;) { | |
var line = getLine(doc, curPos.line), toClear; | |
if (line.markedSpans) { | |
for (var i = 0; i < line.markedSpans.length; ++i) { | |
var sp = line.markedSpans[i], m = sp.marker; | |
if ((sp.from == null || (m.inclusiveLeft ? sp.from <= curPos.ch : sp.from < curPos.ch)) && | |
(sp.to == null || (m.inclusiveRight ? sp.to >= curPos.ch : sp.to > curPos.ch))) { | |
if (mayClear && m.clearOnEnter) { | |
(toClear || (toClear = [])).push(m); | |
continue; | |
} else if (!m.atomic) continue; | |
var newPos = m.find()[dir < 0 ? "from" : "to"]; | |
if (posEq(newPos, curPos)) { | |
newPos.ch += dir; | |
if (newPos.ch < 0) { | |
if (newPos.line) newPos = clipPos(doc, {line: newPos.line - 1}); | |
else newPos = null; | |
} else if (newPos.ch > line.text.length) { | |
if (newPos.line < doc.size - 1) newPos = {line: newPos.line + 1, ch: 0}; | |
else newPos = null; | |
} | |
if (!newPos) { | |
if (flipped) { | |
// Driven in a corner -- no valid cursor position found at all | |
// -- try again *with* clearing, if we didn't already | |
if (!mayClear) return skipAtomic(cm, pos, bias, true); | |
// Otherwise, turn off editing until further notice, and return the start of the doc | |
cm.view.cantEdit = true; | |
return {line: 0, ch: 0}; | |
} | |
flipped = true; newPos = pos; dir = -dir; | |
} | |
} | |
curPos = newPos; | |
continue search; | |
} | |
} | |
if (toClear) for (var i = 0; i < toClear.length; ++i) toClear[i].clear(); | |
} | |
return curPos; | |
} | |
} | |
// SCROLLING | |
function scrollCursorIntoView(cm) { | |
var view = cm.view; | |
var coords = scrollPosIntoView(cm, view.sel.head); | |
if (!view.focused) return; | |
var display = cm.display, box = display.sizer.getBoundingClientRect(), doScroll = null; | |
if (coords.top + box.top < 0) doScroll = true; | |
else if (coords.bottom + box.top > (window.innerHeight || document.documentElement.clientHeight)) doScroll = false; | |
if (doScroll != null && !phantom) { | |
var hidden = display.cursor.style.display == "none"; | |
if (hidden) { | |
display.cursor.style.display = ""; | |
display.cursor.style.left = coords.left + "px"; | |
display.cursor.style.top = (coords.top - display.viewOffset) + "px"; | |
} | |
display.cursor.scrollIntoView(doScroll); | |
if (hidden) display.cursor.style.display = "none"; | |
} | |
} | |
function scrollPosIntoView(cm, pos) { | |
for (;;) { | |
var changed = false, coords = cursorCoords(cm, pos); | |
var scrollPos = calculateScrollPos(cm, coords.left, coords.top, coords.left, coords.bottom); | |
var startTop = cm.view.scrollTop, startLeft = cm.view.scrollLeft; | |
if (scrollPos.scrollTop != null) { | |
setScrollTop(cm, scrollPos.scrollTop); | |
if (Math.abs(cm.view.scrollTop - startTop) > 1) changed = true; | |
} | |
if (scrollPos.scrollLeft != null) { | |
setScrollLeft(cm, scrollPos.scrollLeft); | |
if (Math.abs(cm.view.scrollLeft - startLeft) > 1) changed = true; | |
} | |
if (!changed) return coords; | |
} | |
} | |
function scrollIntoView(cm, x1, y1, x2, y2) { | |
var scrollPos = calculateScrollPos(cm, x1, y1, x2, y2); | |
if (scrollPos.scrollTop != null) setScrollTop(cm, scrollPos.scrollTop); | |
if (scrollPos.scrollLeft != null) setScrollLeft(cm, scrollPos.scrollLeft); | |
} | |
function calculateScrollPos(cm, x1, y1, x2, y2) { | |
var display = cm.display, pt = paddingTop(display); | |
y1 += pt; y2 += pt; | |
var screen = display.scroller.clientHeight - scrollerCutOff, screentop = display.scroller.scrollTop, result = {}; | |
var docBottom = cm.view.doc.height + 2 * pt; | |
var atTop = y1 < pt + 10, atBottom = y2 + pt > docBottom - 10; | |
if (y1 < screentop) result.scrollTop = atTop ? 0 : Math.max(0, y1); | |
else if (y2 > screentop + screen) result.scrollTop = (atBottom ? docBottom : y2) - screen; | |
var screenw = display.scroller.clientWidth - scrollerCutOff, screenleft = display.scroller.scrollLeft; | |
x1 += display.gutters.offsetWidth; x2 += display.gutters.offsetWidth; | |
var gutterw = display.gutters.offsetWidth; | |
var atLeft = x1 < gutterw + 10; | |
if (x1 < screenleft + gutterw || atLeft) { | |
if (atLeft) x1 = 0; | |
result.scrollLeft = Math.max(0, x1 - 10 - gutterw); | |
} else if (x2 > screenw + screenleft - 3) { | |
result.scrollLeft = x2 + 10 - screenw; | |
} | |
return result; | |
} | |
// API UTILITIES | |
function indentLine(cm, n, how, aggressive) { | |
var doc = cm.view.doc; | |
if (!how) how = "add"; | |
if (how == "smart") { | |
if (!cm.view.mode.indent) how = "prev"; | |
else var state = getStateBefore(cm, n); | |
} | |
var tabSize = cm.options.tabSize; | |
var line = getLine(doc, n), curSpace = countColumn(line.text, null, tabSize); | |
var curSpaceString = line.text.match(/^\s*/)[0], indentation; | |
if (how == "smart") { | |
indentation = cm.view.mode.indent(state, line.text.slice(curSpaceString.length), line.text); | |
if (indentation == Pass) { | |
if (!aggressive) return; | |
how = "prev"; | |
} | |
} | |
if (how == "prev") { | |
if (n) indentation = countColumn(getLine(doc, n-1).text, null, tabSize); | |
else indentation = 0; | |
} | |
else if (how == "add") indentation = curSpace + cm.options.indentUnit; | |
else if (how == "subtract") indentation = curSpace - cm.options.indentUnit; | |
indentation = Math.max(0, indentation); | |
var indentString = "", pos = 0; | |
if (cm.options.indentWithTabs) | |
for (var i = Math.floor(indentation / tabSize); i; --i) {pos += tabSize; indentString += "\t";} | |
if (pos < indentation) indentString += spaceStr(indentation - pos); | |
if (indentString != curSpaceString) | |
replaceRange(cm, indentString, {line: n, ch: 0}, {line: n, ch: curSpaceString.length}, "input"); | |
line.stateAfter = null; | |
} | |
function changeLine(cm, handle, op) { | |
var no = handle, line = handle, doc = cm.view.doc; | |
if (typeof handle == "number") line = getLine(doc, clipLine(doc, handle)); | |
else no = lineNo(handle); | |
if (no == null) return null; | |
if (op(line, no)) regChange(cm, no, no + 1); | |
else return null; | |
return line; | |
} | |
function findPosH(cm, dir, unit, visually) { | |
var doc = cm.view.doc, end = cm.view.sel.head, line = end.line, ch = end.ch; | |
var lineObj = getLine(doc, line); | |
function findNextLine() { | |
var l = line + dir; | |
if (l < 0 || l == doc.size) return false; | |
line = l; | |
return lineObj = getLine(doc, l); | |
} | |
function moveOnce(boundToLine) { | |
var next = (visually ? moveVisually : moveLogically)(lineObj, ch, dir, true); | |
if (next == null) { | |
if (!boundToLine && findNextLine()) { | |
if (visually) ch = (dir < 0 ? lineRight : lineLeft)(lineObj); | |
else ch = dir < 0 ? lineObj.text.length : 0; | |
} else return false; | |
} else ch = next; | |
return true; | |
} | |
if (unit == "char") moveOnce(); | |
else if (unit == "column") moveOnce(true); | |
else if (unit == "word") { | |
var sawWord = false; | |
for (;;) { | |
if (dir < 0) if (!moveOnce()) break; | |
if (isWordChar(lineObj.text.charAt(ch))) sawWord = true; | |
else if (sawWord) {if (dir < 0) {dir = 1; moveOnce();} break;} | |
if (dir > 0) if (!moveOnce()) break; | |
} | |
} | |
return skipAtomic(cm, {line: line, ch: ch}, dir, true); | |
} | |
function findWordAt(line, pos) { | |
var start = pos.ch, end = pos.ch; | |
if (line) { | |
if (pos.after === false || end == line.length) --start; else ++end; | |
var startChar = line.charAt(start); | |
var check = isWordChar(startChar) ? isWordChar : | |
/\s/.test(startChar) ? function(ch) {return /\s/.test(ch);} : | |
function(ch) {return !/\s/.test(ch) && !isWordChar(ch);}; | |
while (start > 0 && check(line.charAt(start - 1))) --start; | |
while (end < line.length && check(line.charAt(end))) ++end; | |
} | |
return {from: {line: pos.line, ch: start}, to: {line: pos.line, ch: end}}; | |
} | |
function selectLine(cm, line) { | |
extendSelection(cm, {line: line, ch: 0}, clipPos(cm.view.doc, {line: line + 1, ch: 0})); | |
} | |
// PROTOTYPE | |
// The publicly visible API. Note that operation(null, f) means | |
// 'wrap f in an operation, performed on its `this` parameter' | |
CodeMirror.prototype = { | |
getValue: function(lineSep) { | |
var text = [], doc = this.view.doc; | |
doc.iter(0, doc.size, function(line) { text.push(line.text); }); | |
return text.join(lineSep || "\n"); | |
}, | |
setValue: operation(null, function(code) { | |
var doc = this.view.doc, top = {line: 0, ch: 0}, lastLen = getLine(doc, doc.size-1).text.length; | |
updateDocInner(this, top, {line: doc.size - 1, ch: lastLen}, splitLines(code), top, top, "setValue"); | |
}), | |
getSelection: function(lineSep) { return this.getRange(this.view.sel.from, this.view.sel.to, lineSep); }, | |
replaceSelection: operation(null, function(code, collapse, origin) { | |
var sel = this.view.sel; | |
updateDoc(this, sel.from, sel.to, splitLines(code), collapse || "around", origin); | |
}), | |
focus: function(){window.focus(); focusInput(this); onFocus(this); fastPoll(this);}, | |
setOption: function(option, value) { | |
var options = this.options, old = options[option]; | |
if (options[option] == value && option != "mode") return; | |
options[option] = value; | |
if (optionHandlers.hasOwnProperty(option)) | |
operation(this, optionHandlers[option])(this, value, old); | |
}, | |
getOption: function(option) {return this.options[option];}, | |
getMode: function() {return this.view.mode;}, | |
addKeyMap: function(map) { | |
this.view.keyMaps.push(map); | |
}, | |
removeKeyMap: function(map) { | |
var maps = this.view.keyMaps; | |
for (var i = 0; i < maps.length; ++i) | |
if ((typeof map == "string" ? maps[i].name : maps[i]) == map) { | |
maps.splice(i, 1); | |
return true; | |
} | |
}, | |
addOverlay: operation(null, function(spec, options) { | |
var mode = spec.token ? spec : CodeMirror.getMode(this.options, spec); | |
if (mode.startState) throw new Error("Overlays may not be stateful."); | |
this.view.overlays.push({mode: mode, modeSpec: spec, opaque: options && options.opaque}); | |
this.view.modeGen++; | |
regChange(this, 0, this.view.doc.size); | |
}), | |
removeOverlay: operation(null, function(spec) { | |
var overlays = this.view.overlays; | |
for (var i = 0; i < overlays.length; ++i) { | |
if (overlays[i].modeSpec == spec) { | |
overlays.splice(i, 1); | |
this.view.modeGen++; | |
regChange(this, 0, this.view.doc.size); | |
return; | |
} | |
} | |
}), | |
undo: operation(null, function() {unredoHelper(this, "undo");}), | |
redo: operation(null, function() {unredoHelper(this, "redo");}), | |
indentLine: operation(null, function(n, dir, aggressive) { | |
if (typeof dir != "string") { | |
if (dir == null) dir = this.options.smartIndent ? "smart" : "prev"; | |
else dir = dir ? "add" : "subtract"; | |
} | |
if (isLine(this.view.doc, n)) indentLine(this, n, dir, aggressive); | |
}), | |
indentSelection: operation(null, function(how) { | |
var sel = this.view.sel; | |
if (posEq(sel.from, sel.to)) return indentLine(this, sel.from.line, how); | |
var e = sel.to.line - (sel.to.ch ? 0 : 1); | |
for (var i = sel.from.line; i <= e; ++i) indentLine(this, i, how); | |
}), | |
historySize: function() { | |
var hist = this.view.history; | |
return {undo: hist.done.length, redo: hist.undone.length}; | |
}, | |
clearHistory: function() {this.view.history = makeHistory();}, | |
markClean: function() { | |
this.view.history.dirtyCounter = 0; | |
this.view.history.lastOp = this.view.history.lastOrigin = null; | |
}, | |
isClean: function () {return this.view.history.dirtyCounter == 0;}, | |
getHistory: function() { | |
var hist = this.view.history; | |
function cp(arr) { | |
for (var i = 0, nw = [], nwelt; i < arr.length; ++i) { | |
var set = arr[i]; | |
nw.push({events: nwelt = [], fromBefore: set.fromBefore, toBefore: set.toBefore, | |
fromAfter: set.fromAfter, toAfter: set.toAfter}); | |
for (var j = 0, elt = set.events; j < elt.length; ++j) { | |
var old = [], cur = elt[j]; | |
nwelt.push({start: cur.start, added: cur.added, old: old}); | |
for (var k = 0; k < cur.old.length; ++k) old.push(hlText(cur.old[k])); | |
} | |
} | |
return nw; | |
} | |
return {done: cp(hist.done), undone: cp(hist.undone)}; | |
}, | |
setHistory: function(histData) { | |
var hist = this.view.history = makeHistory(); | |
hist.done = histData.done; | |
hist.undone = histData.undone; | |
}, | |
// Fetch the parser token for a given character. Useful for hacks | |
// that want to inspect the mode state (say, for completion). | |
getTokenAt: function(pos) { | |
var doc = this.view.doc; | |
pos = clipPos(doc, pos); | |
var state = getStateBefore(this, pos.line), mode = this.view.mode; | |
var line = getLine(doc, pos.line); | |
var stream = new StringStream(line.text, this.options.tabSize); | |
while (stream.pos < pos.ch && !stream.eol()) { | |
stream.start = stream.pos; | |
var style = mode.token(stream, state); | |
} | |
return {start: stream.start, | |
end: stream.pos, | |
string: stream.current(), | |
className: style || null, // Deprecated, use 'type' instead | |
type: style || null, | |
state: state}; | |
}, | |
getStateAfter: function(line) { | |
var doc = this.view.doc; | |
line = clipLine(doc, line == null ? doc.size - 1: line); | |
return getStateBefore(this, line + 1); | |
}, | |
cursorCoords: function(start, mode) { | |
var pos, sel = this.view.sel; | |
if (start == null) pos = sel.head; | |
else if (typeof start == "object") pos = clipPos(this.view.doc, start); | |
else pos = start ? sel.from : sel.to; | |
return cursorCoords(this, pos, mode || "page"); | |
}, | |
charCoords: function(pos, mode) { | |
return charCoords(this, clipPos(this.view.doc, pos), mode || "page"); | |
}, | |
coordsChar: function(coords) { | |
var off = this.display.lineSpace.getBoundingClientRect(); | |
return coordsChar(this, coords.left - off.left, coords.top - off.top); | |
}, | |
defaultTextHeight: function() { return textHeight(this.display); }, | |
markText: operation(null, function(from, to, options) { | |
return markText(this, clipPos(this.view.doc, from), clipPos(this.view.doc, to), | |
options, "range"); | |
}), | |
setBookmark: operation(null, function(pos, widget) { | |
pos = clipPos(this.view.doc, pos); | |
return markText(this, pos, pos, widget ? {replacedWith: widget} : {}, "bookmark"); | |
}), | |
findMarksAt: function(pos) { | |
var doc = this.view.doc; | |
pos = clipPos(doc, pos); | |
var markers = [], spans = getLine(doc, pos.line).markedSpans; | |
if (spans) for (var i = 0; i < spans.length; ++i) { | |
var span = spans[i]; | |
if ((span.from == null || span.from <= pos.ch) && | |
(span.to == null || span.to >= pos.ch)) | |
markers.push(span.marker); | |
} | |
return markers; | |
}, | |
setGutterMarker: operation(null, function(line, gutterID, value) { | |
return changeLine(this, line, function(line) { | |
var markers = line.gutterMarkers || (line.gutterMarkers = {}); | |
markers[gutterID] = value; | |
if (!value && isEmpty(markers)) line.gutterMarkers = null; | |
return true; | |
}); | |
}), | |
clearGutter: operation(null, function(gutterID) { | |
var i = 0, cm = this, doc = cm.view.doc; | |
doc.iter(0, doc.size, function(line) { | |
if (line.gutterMarkers && line.gutterMarkers[gutterID]) { | |
line.gutterMarkers[gutterID] = null; | |
regChange(cm, i, i + 1); | |
if (isEmpty(line.gutterMarkers)) line.gutterMarkers = null; | |
} | |
++i; | |
}); | |
}), | |
addLineClass: operation(null, function(handle, where, cls) { | |
return changeLine(this, handle, function(line) { | |
var prop = where == "text" ? "textClass" : where == "background" ? "bgClass" : "wrapClass"; | |
if (!line[prop]) line[prop] = cls; | |
else if (new RegExp("\\b" + cls + "\\b").test(line[prop])) return false; | |
else line[prop] += " " + cls; | |
return true; | |
}); | |
}), | |
removeLineClass: operation(null, function(handle, where, cls) { | |
return changeLine(this, handle, function(line) { | |
var prop = where == "text" ? "textClass" : where == "background" ? "bgClass" : "wrapClass"; | |
var cur = line[prop]; | |
if (!cur) return false; | |
else if (cls == null) line[prop] = null; | |
else { | |
var upd = cur.replace(new RegExp("^" + cls + "\\b\\s*|\\s*\\b" + cls + "\\b"), ""); | |
if (upd == cur) return false; | |
line[prop] = upd || null; | |
} | |
return true; | |
}); | |
}), | |
addLineWidget: operation(null, function(handle, node, options) { | |
return addLineWidget(this, handle, node, options); | |
}), | |
removeLineWidget: function(widget) { widget.clear(); }, | |
lineInfo: function(line) { | |
if (typeof line == "number") { | |
if (!isLine(this.view.doc, line)) return null; | |
var n = line; | |
line = getLine(this.view.doc, line); | |
if (!line) return null; | |
} else { | |
var n = lineNo(line); | |
if (n == null) return null; | |
} | |
return {line: n, handle: line, text: line.text, gutterMarkers: line.gutterMarkers, | |
textClass: line.textClass, bgClass: line.bgClass, wrapClass: line.wrapClass, | |
widgets: line.widgets}; | |
}, | |
getViewport: function() { return {from: this.display.showingFrom, to: this.display.showingTo};}, | |
addWidget: function(pos, node, scroll, vert, horiz) { | |
var display = this.display; | |
pos = cursorCoords(this, clipPos(this.view.doc, pos)); | |
var top = pos.top, left = pos.left; | |
node.style.position = "absolute"; | |
display.sizer.appendChild(node); | |
if (vert == "over") top = pos.top; | |
else if (vert == "near") { | |
var vspace = Math.max(display.wrapper.clientHeight, this.view.doc.height), | |
hspace = Math.max(display.sizer.clientWidth, display.lineSpace.clientWidth); | |
if (pos.bottom + node.offsetHeight > vspace && pos.top > node.offsetHeight) | |
top = pos.top - node.offsetHeight; | |
if (left + node.offsetWidth > hspace) | |
left = hspace - node.offsetWidth; | |
} | |
node.style.top = (top + paddingTop(display)) + "px"; | |
node.style.left = node.style.right = ""; | |
if (horiz == "right") { | |
left = display.sizer.clientWidth - node.offsetWidth; | |
node.style.right = "0px"; | |
} else { | |
if (horiz == "left") left = 0; | |
else if (horiz == "middle") left = (display.sizer.clientWidth - node.offsetWidth) / 2; | |
node.style.left = left + "px"; | |
} | |
if (scroll) | |
scrollIntoView(this, left, top, left + node.offsetWidth, top + node.offsetHeight); | |
}, | |
lineCount: function() {return this.view.doc.size;}, | |
clipPos: function(pos) {return clipPos(this.view.doc, pos);}, | |
getCursor: function(start) { | |
var sel = this.view.sel, pos; | |
if (start == null || start == "head") pos = sel.head; | |
else if (start == "anchor") pos = sel.anchor; | |
else if (start == "end" || start === false) pos = sel.to; | |
else pos = sel.from; | |
return copyPos(pos); | |
}, | |
somethingSelected: function() {return !posEq(this.view.sel.from, this.view.sel.to);}, | |
setCursor: operation(null, function(line, ch, extend) { | |
var pos = clipPos(this.view.doc, typeof line == "number" ? {line: line, ch: ch || 0} : line); | |
if (extend) extendSelection(this, pos); | |
else setSelection(this, pos, pos); | |
}), | |
setSelection: operation(null, function(anchor, head) { | |
var doc = this.view.doc; | |
setSelection(this, clipPos(doc, anchor), clipPos(doc, head || anchor)); | |
}), | |
extendSelection: operation(null, function(from, to) { | |
var doc = this.view.doc; | |
extendSelection(this, clipPos(doc, from), to && clipPos(doc, to)); | |
}), | |
setExtending: function(val) {this.view.sel.extend = val;}, | |
getLine: function(line) {var l = this.getLineHandle(line); return l && l.text;}, | |
getLineHandle: function(line) { | |
var doc = this.view.doc; | |
if (isLine(doc, line)) return getLine(doc, line); | |
}, | |
getLineNumber: function(line) {return lineNo(line);}, | |
setLine: operation(null, function(line, text) { | |
if (isLine(this.view.doc, line)) | |
replaceRange(this, text, {line: line, ch: 0}, {line: line, ch: getLine(this.view.doc, line).text.length}); | |
}), | |
removeLine: operation(null, function(line) { | |
if (isLine(this.view.doc, line)) | |
replaceRange(this, "", {line: line, ch: 0}, clipPos(this.view.doc, {line: line+1, ch: 0})); | |
}), | |
replaceRange: operation(null, function(code, from, to) { | |
var doc = this.view.doc; | |
from = clipPos(doc, from); | |
to = to ? clipPos(doc, to) : from; | |
return replaceRange(this, code, from, to); | |
}), | |
getRange: function(from, to, lineSep) { | |
var doc = this.view.doc; | |
from = clipPos(doc, from); to = clipPos(doc, to); | |
var l1 = from.line, l2 = to.line; | |
if (l1 == l2) return getLine(doc, l1).text.slice(from.ch, to.ch); | |
var code = [getLine(doc, l1).text.slice(from.ch)]; | |
doc.iter(l1 + 1, l2, function(line) { code.push(line.text); }); | |
code.push(getLine(doc, l2).text.slice(0, to.ch)); | |
return code.join(lineSep || "\n"); | |
}, | |
triggerOnKeyDown: operation(null, onKeyDown), | |
execCommand: function(cmd) {return commands[cmd](this);}, | |
// Stuff used by commands, probably not much use to outside code. | |
moveH: operation(null, function(dir, unit) { | |
var sel = this.view.sel, pos = dir < 0 ? sel.from : sel.to; | |
if (sel.shift || sel.extend || posEq(sel.from, sel.to)) | |
pos = findPosH(this, dir, unit, this.options.rtlMoveVisually); | |
extendSelection(this, pos, pos, dir); | |
}), | |
deleteH: operation(null, function(dir, unit) { | |
var sel = this.view.sel; | |
if (!posEq(sel.from, sel.to)) replaceRange(this, "", sel.from, sel.to, "delete"); | |
else replaceRange(this, "", sel.from, findPosH(this, dir, unit, false), "delete"); | |
this.curOp.userSelChange = true; | |
}), | |
moveV: operation(null, function(dir, unit) { | |
var view = this.view, doc = view.doc, display = this.display; | |
var cur = view.sel.head, pos = cursorCoords(this, cur, "div"); | |
var x = pos.left, y; | |
if (view.goalColumn != null) x = view.goalColumn; | |
if (unit == "page") { | |
var pageSize = Math.min(display.wrapper.clientHeight, window.innerHeight || document.documentElement.clientHeight); | |
y = pos.top + dir * pageSize; | |
} else if (unit == "line") { | |
y = dir > 0 ? pos.bottom + 3 : pos.top - 3; | |
} | |
do { | |
var target = coordsChar(this, x, y); | |
y += dir * 5; | |
} while (target.outside && (dir < 0 ? y > 0 : y < doc.height)); | |
if (unit == "page") display.scrollbarV.scrollTop += charCoords(this, target, "div").top - pos.top; | |
extendSelection(this, target, target, dir); | |
view.goalColumn = x; | |
}), | |
toggleOverwrite: function() { | |
if (this.view.overwrite = !this.view.overwrite) | |
this.display.cursor.className += " CodeMirror-overwrite"; | |
else | |
this.display.cursor.className = this.display.cursor.className.replace(" CodeMirror-overwrite", ""); | |
}, | |
posFromIndex: function(off) { | |
var lineNo = 0, ch, doc = this.view.doc; | |
doc.iter(0, doc.size, function(line) { | |
var sz = line.text.length + 1; | |
if (sz > off) { ch = off; return true; } | |
off -= sz; | |
++lineNo; | |
}); | |
return clipPos(doc, {line: lineNo, ch: ch}); | |
}, | |
indexFromPos: function (coords) { | |
coords = clipPos(this.view.doc, coords); | |
var index = coords.ch; | |
this.view.doc.iter(0, coords.line, function (line) { | |
index += line.text.length + 1; | |
}); | |
return index; | |
}, | |
scrollTo: function(x, y) { | |
if (x != null) this.display.scrollbarH.scrollLeft = this.display.scroller.scrollLeft = x; | |
if (y != null) this.display.scrollbarV.scrollTop = this.display.scroller.scrollTop = y; | |
updateDisplay(this, []); | |
}, | |
getScrollInfo: function() { | |
var scroller = this.display.scroller, co = scrollerCutOff; | |
return {left: scroller.scrollLeft, top: scroller.scrollTop, | |
height: scroller.scrollHeight - co, width: scroller.scrollWidth - co, | |
clientHeight: scroller.clientHeight - co, clientWidth: scroller.clientWidth - co}; | |
}, | |
scrollIntoView: function(pos) { | |
if (typeof pos == "number") pos = {line: pos, ch: 0}; | |
if (!pos || pos.line != null) { | |
pos = pos ? clipPos(this.view.doc, pos) : this.view.sel.head; | |
scrollPosIntoView(this, pos); | |
} else { | |
scrollIntoView(this, pos.left, pos.top, pos.right, pos.bottom); | |
} | |
}, | |
setSize: function(width, height) { | |
function interpret(val) { | |
return typeof val == "number" || /^\d+$/.test(String(val)) ? val + "px" : val; | |
} | |
if (width != null) this.display.wrapper.style.width = interpret(width); | |
if (height != null) this.display.wrapper.style.height = interpret(height); | |
this.refresh(); | |
}, | |
on: function(type, f) {on(this, type, f);}, | |
off: function(type, f) {off(this, type, f);}, | |
operation: function(f){return operation(this, f)();}, | |
refresh: function() { | |
clearCaches(this); | |
var sTop = this.view.scrollTop, sLeft = this.view.scrollLeft; | |
if (this.display.scroller.scrollHeight > sTop) | |
this.display.scrollbarV.scrollTop = this.display.scroller.scrollTop = sTop; | |
if (this.display.scroller.scrollWidth > sLeft) | |
this.display.scrollbarH.scrollLeft = this.display.scroller.scrollLeft = sLeft; | |
updateDisplay(this, true); | |
}, | |
getInputField: function(){return this.display.input;}, | |
getWrapperElement: function(){return this.display.wrapper;}, | |
getScrollerElement: function(){return this.display.scroller;}, | |
getGutterElement: function(){return this.display.gutters;} | |
}; | |
// OPTION DEFAULTS | |
var optionHandlers = CodeMirror.optionHandlers = {}; | |
// The default configuration options. | |
var defaults = CodeMirror.defaults = {}; | |
function option(name, deflt, handle, notOnInit) { | |
CodeMirror.defaults[name] = deflt; | |
if (handle) optionHandlers[name] = | |
notOnInit ? function(cm, val, old) {if (old != Init) handle(cm, val, old);} : handle; | |
} | |
var Init = CodeMirror.Init = {toString: function(){return "CodeMirror.Init";}}; | |
// These two are, on init, called from the constructor because they | |
// have to be initialized before the editor can start at all. | |
option("value", "", function(cm, val) {cm.setValue(val);}, true); | |
option("mode", null, loadMode, true); | |
option("indentUnit", 2, loadMode, true); | |
option("indentWithTabs", false); | |
option("smartIndent", true); | |
option("tabSize", 4, function(cm) { | |
loadMode(cm); | |
clearCaches(cm); | |
updateDisplay(cm, true); | |
}, true); | |
option("electricChars", true); | |
option("rtlMoveVisually", !windows); | |
option("theme", "default", function(cm) { | |
themeChanged(cm); | |
guttersChanged(cm); | |
}, true); | |
option("keyMap", "default", keyMapChanged); | |
option("extraKeys", null); | |
option("onKeyEvent", null); | |
option("onDragEvent", null); | |
option("lineWrapping", false, wrappingChanged, true); | |
option("gutters", [], function(cm) { | |
setGuttersForLineNumbers(cm.options); | |
guttersChanged(cm); | |
}, true); | |
option("fixedGutter", true, function(cm, val) { | |
cm.display.gutters.style.left = val ? compensateForHScroll(cm.display) + "px" : "0"; | |
cm.refresh(); | |
}, true); | |
option("lineNumbers", false, function(cm) { | |
setGuttersForLineNumbers(cm.options); | |
guttersChanged(cm); | |
}, true); | |
option("firstLineNumber", 1, guttersChanged, true); | |
option("lineNumberFormatter", function(integer) {return integer;}, guttersChanged, true); | |
option("showCursorWhenSelecting", false, updateSelection, true); | |
option("readOnly", false, function(cm, val) { | |
if (val == "nocursor") {onBlur(cm); cm.display.input.blur();} | |
else if (!val) resetInput(cm, true); | |
}); | |
option("dragDrop", true); | |
option("cursorBlinkRate", 530); | |
option("cursorHeight", 1); | |
option("workTime", 100); | |
option("workDelay", 100); | |
option("flattenSpans", true); | |
option("pollInterval", 100); | |
option("undoDepth", 40); | |
option("viewportMargin", 10, function(cm){cm.refresh();}, true); | |
option("tabindex", null, function(cm, val) { | |
cm.display.input.tabIndex = val || ""; | |
}); | |
option("autofocus", null); | |
// MODE DEFINITION AND QUERYING | |
// Known modes, by name and by MIME | |
var modes = CodeMirror.modes = {}, mimeModes = CodeMirror.mimeModes = {}; | |
CodeMirror.defineMode = function(name, mode) { | |
if (!CodeMirror.defaults.mode && name != "null") CodeMirror.defaults.mode = name; | |
if (arguments.length > 2) { | |
mode.dependencies = []; | |
for (var i = 2; i < arguments.length; ++i) mode.dependencies.push(arguments[i]); | |
} | |
modes[name] = mode; | |
}; | |
CodeMirror.defineMIME = function(mime, spec) { | |
mimeModes[mime] = spec; | |
}; | |
CodeMirror.resolveMode = function(spec) { | |
if (typeof spec == "string" && mimeModes.hasOwnProperty(spec)) | |
spec = mimeModes[spec]; | |
else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+xml$/.test(spec)) | |
return CodeMirror.resolveMode("application/xml"); | |
if (typeof spec == "string") return {name: spec}; | |
else return spec || {name: "null"}; | |
}; | |
CodeMirror.getMode = function(options, spec) { | |
spec = CodeMirror.resolveMode(spec); | |
var mfactory = modes[spec.name]; | |
if (!mfactory) return CodeMirror.getMode(options, "text/plain"); | |
var modeObj = mfactory(options, spec); | |
if (modeExtensions.hasOwnProperty(spec.name)) { | |
var exts = modeExtensions[spec.name]; | |
for (var prop in exts) { | |
if (!exts.hasOwnProperty(prop)) continue; | |
if (modeObj.hasOwnProperty(prop)) modeObj["_" + prop] = modeObj[prop]; | |
modeObj[prop] = exts[prop]; | |
} | |
} | |
modeObj.name = spec.name; | |
return modeObj; | |
}; | |
CodeMirror.defineMode("null", function() { | |
return {token: function(stream) {stream.skipToEnd();}}; | |
}); | |
CodeMirror.defineMIME("text/plain", "null"); | |
var modeExtensions = CodeMirror.modeExtensions = {}; | |
CodeMirror.extendMode = function(mode, properties) { | |
var exts = modeExtensions.hasOwnProperty(mode) ? modeExtensions[mode] : (modeExtensions[mode] = {}); | |
for (var prop in properties) if (properties.hasOwnProperty(prop)) | |
exts[prop] = properties[prop]; | |
}; | |
// EXTENSIONS | |
CodeMirror.defineExtension = function(name, func) { | |
CodeMirror.prototype[name] = func; | |
}; | |
CodeMirror.defineOption = option; | |
var initHooks = []; | |
CodeMirror.defineInitHook = function(f) {initHooks.push(f);}; | |
// MODE STATE HANDLING | |
// Utility functions for working with state. Exported because modes | |
// sometimes need to do this. | |
function copyState(mode, state) { | |
if (state === true) return state; | |
if (mode.copyState) return mode.copyState(state); | |
var nstate = {}; | |
for (var n in state) { | |
var val = state[n]; | |
if (val instanceof Array) val = val.concat([]); | |
nstate[n] = val; | |
} | |
return nstate; | |
} | |
CodeMirror.copyState = copyState; | |
function startState(mode, a1, a2) { | |
return mode.startState ? mode.startState(a1, a2) : true; | |
} | |
CodeMirror.startState = startState; | |
CodeMirror.innerMode = function(mode, state) { | |
while (mode.innerMode) { | |
var info = mode.innerMode(state); | |
state = info.state; | |
mode = info.mode; | |
} | |
return info || {mode: mode, state: state}; | |
}; | |
// STANDARD COMMANDS | |
var commands = CodeMirror.commands = { | |
selectAll: function(cm) {cm.setSelection({line: 0, ch: 0}, {line: cm.lineCount() - 1});}, | |
killLine: function(cm) { | |
var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to); | |
if (!sel && cm.getLine(from.line).length == from.ch) | |
cm.replaceRange("", from, {line: from.line + 1, ch: 0}, "delete"); | |
else cm.replaceRange("", from, sel ? to : {line: from.line}, "delete"); | |
}, | |
deleteLine: function(cm) { | |
var l = cm.getCursor().line; | |
cm.replaceRange("", {line: l, ch: 0}, {line: l}, "delete"); | |
}, | |
undo: function(cm) {cm.undo();}, | |
redo: function(cm) {cm.redo();}, | |
goDocStart: function(cm) {cm.extendSelection({line: 0, ch: 0});}, | |
goDocEnd: function(cm) {cm.extendSelection({line: cm.lineCount() - 1});}, | |
goLineStart: function(cm) { | |
cm.extendSelection(lineStart(cm, cm.getCursor().line)); | |
}, | |
goLineStartSmart: function(cm) { | |
var cur = cm.getCursor(), start = lineStart(cm, cur.line); | |
var line = cm.getLineHandle(start.line); | |
var order = getOrder(line); | |
if (!order || order[0].level == 0) { | |
var firstNonWS = Math.max(0, line.text.search(/\S/)); | |
var inWS = cur.line == start.line && cur.ch <= firstNonWS && cur.ch; | |
cm.extendSelection({line: start.line, ch: inWS ? 0 : firstNonWS}); | |
} else cm.extendSelection(start); | |
}, | |
goLineEnd: function(cm) { | |
cm.extendSelection(lineEnd(cm, cm.getCursor().line)); | |
}, | |
goLineUp: function(cm) {cm.moveV(-1, "line");}, | |
goLineDown: function(cm) {cm.moveV(1, "line");}, | |
goPageUp: function(cm) {cm.moveV(-1, "page");}, | |
goPageDown: function(cm) {cm.moveV(1, "page");}, | |
goCharLeft: function(cm) {cm.moveH(-1, "char");}, | |
goCharRight: function(cm) {cm.moveH(1, "char");}, | |
goColumnLeft: function(cm) {cm.moveH(-1, "column");}, | |
goColumnRight: function(cm) {cm.moveH(1, "column");}, | |
goWordLeft: function(cm) {cm.moveH(-1, "word");}, | |
goWordRight: function(cm) {cm.moveH(1, "word");}, | |
delCharBefore: function(cm) {cm.deleteH(-1, "char");}, | |
delCharAfter: function(cm) {cm.deleteH(1, "char");}, | |
delWordBefore: function(cm) {cm.deleteH(-1, "word");}, | |
delWordAfter: function(cm) {cm.deleteH(1, "word");}, | |
indentAuto: function(cm) {cm.indentSelection("smart");}, | |
indentMore: function(cm) {cm.indentSelection("add");}, | |
indentLess: function(cm) {cm.indentSelection("subtract");}, | |
insertTab: function(cm) {cm.replaceSelection("\t", "end", "input");}, | |
defaultTab: function(cm) { | |
if (cm.somethingSelected()) cm.indentSelection("add"); | |
else cm.replaceSelection("\t", "end", "input"); | |
}, | |
transposeChars: function(cm) { | |
var cur = cm.getCursor(), line = cm.getLine(cur.line); | |
if (cur.ch > 0 && cur.ch < line.length - 1) | |
cm.replaceRange(line.charAt(cur.ch) + line.charAt(cur.ch - 1), | |
{line: cur.line, ch: cur.ch - 1}, {line: cur.line, ch: cur.ch + 1}); | |
}, | |
newlineAndIndent: function(cm) { | |
operation(cm, function() { | |
cm.replaceSelection("\n", "end", "input"); | |
cm.indentLine(cm.getCursor().line, null, true); | |
})(); | |
}, | |
toggleOverwrite: function(cm) {cm.toggleOverwrite();} | |
}; | |
// STANDARD KEYMAPS | |
var keyMap = CodeMirror.keyMap = {}; | |
keyMap.basic = { | |
"Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goLineDown", | |
"End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageDown": "goPageDown", | |
"Delete": "delCharAfter", "Backspace": "delCharBefore", "Tab": "defaultTab", "Shift-Tab": "indentAuto", | |
"Enter": "newlineAndIndent", "Insert": "toggleOverwrite" | |
}; | |
// Note that the save and find-related commands aren't defined by | |
// default. Unknown commands are simply ignored. | |
keyMap.pcDefault = { | |
"Ctrl-A": "selectAll", "Ctrl-D": "deleteLine", "Ctrl-Z": "undo", "Shift-Ctrl-Z": "redo", "Ctrl-Y": "redo", | |
"Ctrl-Home": "goDocStart", "Alt-Up": "goDocStart", "Ctrl-End": "goDocEnd", "Ctrl-Down": "goDocEnd", | |
"Ctrl-Left": "goWordLeft", "Ctrl-Right": "goWordRight", "Alt-Left": "goLineStart", "Alt-Right": "goLineEnd", | |
"Ctrl-Backspace": "delWordBefore", "Ctrl-Delete": "delWordAfter", "Ctrl-S": "save", "Ctrl-F": "find", | |
"Ctrl-G": "findNext", "Shift-Ctrl-G": "findPrev", "Shift-Ctrl-F": "replace", "Shift-Ctrl-R": "replaceAll", | |
"Ctrl-[": "indentLess", "Ctrl-]": "indentMore", | |
fallthrough: "basic" | |
}; | |
keyMap.macDefault = { | |
"Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z": "redo", "Cmd-Y": "redo", | |
"Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cmd-Down": "goDocEnd", "Alt-Left": "goWordLeft", | |
"Alt-Right": "goWordRight", "Cmd-Left": "goLineStart", "Cmd-Right": "goLineEnd", "Alt-Backspace": "delWordBefore", | |
"Ctrl-Alt-Backspace": "delWordAfter", "Alt-Delete": "delWordAfter", "Cmd-S": "save", "Cmd-F": "find", | |
"Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift-Cmd-Alt-F": "replaceAll", | |
"Cmd-[": "indentLess", "Cmd-]": "indentMore", | |
fallthrough: ["basic", "emacsy"] | |
}; | |
keyMap["default"] = mac ? keyMap.macDefault : keyMap.pcDefault; | |
keyMap.emacsy = { | |
"Ctrl-F": "goCharRight", "Ctrl-B": "goCharLeft", "Ctrl-P": "goLineUp", "Ctrl-N": "goLineDown", | |
"Alt-F": "goWordRight", "Alt-B": "goWordLeft", "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd", | |
"Ctrl-V": "goPageDown", "Shift-Ctrl-V": "goPageUp", "Ctrl-D": "delCharAfter", "Ctrl-H": "delCharBefore", | |
"Alt-D": "delWordAfter", "Alt-Backspace": "delWordBefore", "Ctrl-K": "killLine", "Ctrl-T": "transposeChars" | |
}; | |
// KEYMAP DISPATCH | |
function getKeyMap(val) { | |
if (typeof val == "string") return keyMap[val]; | |
else return val; | |
} | |
function lookupKey(name, maps, handle, stop) { | |
function lookup(map) { | |
map = getKeyMap(map); | |
var found = map[name]; | |
if (found === false) { | |
if (stop) stop(); | |
return true; | |
} | |
if (found != null && handle(found)) return true; | |
if (map.nofallthrough) { | |
if (stop) stop(); | |
return true; | |
} | |
var fallthrough = map.fallthrough; | |
if (fallthrough == null) return false; | |
if (Object.prototype.toString.call(fallthrough) != "[object Array]") | |
return lookup(fallthrough); | |
for (var i = 0, e = fallthrough.length; i < e; ++i) { | |
if (lookup(fallthrough[i])) return true; | |
} | |
return false; | |
} | |
for (var i = 0; i < maps.length; ++i) | |
if (lookup(maps[i])) return true; | |
} | |
function isModifierKey(event) { | |
var name = keyNames[e_prop(event, "keyCode")]; | |
return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod"; | |
} | |
CodeMirror.isModifierKey = isModifierKey; | |
// FROMTEXTAREA | |
CodeMirror.fromTextArea = function(textarea, options) { | |
if (!options) options = {}; | |
options.value = textarea.value; | |
if (!options.tabindex && textarea.tabindex) | |
options.tabindex = textarea.tabindex; | |
// Set autofocus to true if this textarea is focused, or if it has | |
// autofocus and no other element is focused. | |
if (options.autofocus == null) { | |
var hasFocus = document.body; | |
// doc.activeElement occasionally throws on IE | |
try { hasFocus = document.activeElement; } catch(e) {} | |
options.autofocus = hasFocus == textarea || | |
textarea.getAttribute("autofocus") != null && hasFocus == document.body; | |
} | |
function save() {textarea.value = cm.getValue();} | |
if (textarea.form) { | |
// Deplorable hack to make the submit method do the right thing. | |
on(textarea.form, "submit", save); | |
var form = textarea.form, realSubmit = form.submit; | |
try { | |
form.submit = function wrappedSubmit() { | |
save(); | |
form.submit = realSubmit; | |
form.submit(); | |
form.submit = wrappedSubmit; | |
}; | |
} catch(e) {} | |
} | |
textarea.style.display = "none"; | |
var cm = CodeMirror(function(node) { | |
textarea.parentNode.insertBefore(node, textarea.nextSibling); | |
}, options); | |
cm.save = save; | |
cm.getTextArea = function() { return textarea; }; | |
cm.toTextArea = function() { | |
save(); | |
textarea.parentNode.removeChild(cm.getWrapperElement()); | |
textarea.style.display = ""; | |
if (textarea.form) { | |
off(textarea.form, "submit", save); | |
if (typeof textarea.form.submit == "function") | |
textarea.form.submit = realSubmit; | |
} | |
}; | |
return cm; | |
}; | |
// STRING STREAM | |
// Fed to the mode parsers, provides helper functions to make | |
// parsers more succinct. | |
// The character stream used by a mode's parser. | |
function StringStream(string, tabSize) { | |
this.pos = this.start = 0; | |
this.string = string; | |
this.tabSize = tabSize || 8; | |
} | |
StringStream.prototype = { | |
eol: function() {return this.pos >= this.string.length;}, | |
sol: function() {return this.pos == 0;}, | |
peek: function() {return this.string.charAt(this.pos) || undefined;}, | |
next: function() { | |
if (this.pos < this.string.length) | |
return this.string.charAt(this.pos++); | |
}, | |
eat: function(match) { | |
var ch = this.string.charAt(this.pos); | |
if (typeof match == "string") var ok = ch == match; | |
else var ok = ch && (match.test ? match.test(ch) : match(ch)); | |
if (ok) {++this.pos; return ch;} | |
}, | |
eatWhile: function(match) { | |
var start = this.pos; | |
while (this.eat(match)){} | |
return this.pos > start; | |
}, | |
eatSpace: function() { | |
var start = this.pos; | |
while (/[\s\u00a0]/.test(this.string.charAt(this.pos))) ++this.pos; | |
return this.pos > start; | |
}, | |
skipToEnd: function() {this.pos = this.string.length;}, | |
skipTo: function(ch) { | |
var found = this.string.indexOf(ch, this.pos); | |
if (found > -1) {this.pos = found; return true;} | |
}, | |
backUp: function(n) {this.pos -= n;}, | |
column: function() {return countColumn(this.string, this.start, this.tabSize);}, | |
indentation: function() {return countColumn(this.string, null, this.tabSize);}, | |
match: function(pattern, consume, caseInsensitive) { | |
if (typeof pattern == "string") { | |
var cased = function(str) {return caseInsensitive ? str.toLowerCase() : str;}; | |
if (cased(this.string).indexOf(cased(pattern), this.pos) == this.pos) { | |
if (consume !== false) this.pos += pattern.length; | |
return true; | |
} | |
} else { | |
var match = this.string.slice(this.pos).match(pattern); | |
if (match && match.index > 0) return null; | |
if (match && consume !== false) this.pos += match[0].length; | |
return match; | |
} | |
}, | |
current: function(){return this.string.slice(this.start, this.pos);} | |
}; | |
CodeMirror.StringStream = StringStream; | |
// TEXTMARKERS | |
function TextMarker(cm, type) { | |
this.lines = []; | |
this.type = type; | |
this.cm = cm; | |
} | |
CodeMirror.TextMarker = TextMarker; | |
TextMarker.prototype.clear = function() { | |
if (this.explicitlyCleared) return; | |
startOperation(this.cm); | |
var view = this.cm.view, min = null, max = null; | |
for (var i = 0; i < this.lines.length; ++i) { | |
var line = this.lines[i]; | |
var span = getMarkedSpanFor(line.markedSpans, this); | |
if (span.to != null) max = lineNo(line); | |
line.markedSpans = removeMarkedSpan(line.markedSpans, span); | |
if (span.from != null) | |
min = lineNo(line); | |
else if (this.collapsed && !lineIsHidden(line)) | |
updateLineHeight(line, textHeight(this.cm.display)); | |
} | |
if (this.collapsed && !this.cm.options.lineWrapping) for (var i = 0; i < this.lines.length; ++i) { | |
var visual = visualLine(view.doc, this.lines[i]), len = lineLength(view.doc, visual); | |
if (len > view.maxLineLength) { | |
view.maxLine = visual; | |
view.maxLineLength = len; | |
view.maxLineChanged = true; | |
} | |
} | |
if (min != null) regChange(this.cm, min, max + 1); | |
this.lines.length = 0; | |
this.explicitlyCleared = true; | |
if (this.collapsed && this.cm.view.cantEdit) { | |
this.cm.view.cantEdit = false; | |
reCheckSelection(this.cm); | |
} | |
endOperation(this.cm); | |
signalLater(this.cm, this, "clear"); | |
}; | |
TextMarker.prototype.find = function() { | |
var from, to; | |
for (var i = 0; i < this.lines.length; ++i) { | |
var line = this.lines[i]; | |
var span = getMarkedSpanFor(line.markedSpans, this); | |
if (span.from != null || span.to != null) { | |
var found = lineNo(line); | |
if (span.from != null) from = {line: found, ch: span.from}; | |
if (span.to != null) to = {line: found, ch: span.to}; | |
} | |
} | |
if (this.type == "bookmark") return from; | |
return from && {from: from, to: to}; | |
}; | |
TextMarker.prototype.getOptions = function(copyWidget) { | |
var repl = this.replacedWith; | |
return {className: this.className, | |
inclusiveLeft: this.inclusiveLeft, inclusiveRight: this.inclusiveRight, | |
atomic: this.atomic, | |
collapsed: this.collapsed, | |
clearOnEnter: this.clearOnEnter, | |
replacedWith: copyWidget ? repl && repl.cloneNode(true) : repl, | |
readOnly: this.readOnly, | |
startStyle: this.startStyle, endStyle: this.endStyle}; | |
}; | |
function markText(cm, from, to, options, type) { | |
var doc = cm.view.doc; | |
var marker = new TextMarker(cm, type); | |
if (type == "range" && !posLess(from, to)) return marker; | |
if (options) for (var opt in options) if (options.hasOwnProperty(opt)) | |
marker[opt] = options[opt]; | |
if (marker.replacedWith) { | |
marker.collapsed = true; | |
marker.replacedWith = elt("span", [marker.replacedWith], "CodeMirror-widget"); | |
} | |
if (marker.collapsed) sawCollapsedSpans = true; | |
var curLine = from.line, size = 0, collapsedAtStart, collapsedAtEnd; | |
doc.iter(curLine, to.line + 1, function(line) { | |
if (marker.collapsed && !cm.options.lineWrapping && visualLine(doc, line) == cm.view.maxLine) | |
cm.curOp.updateMaxLine = true; | |
var span = {from: null, to: null, marker: marker}; | |
size += line.text.length; | |
if (curLine == from.line) {span.from = from.ch; size -= from.ch;} | |
if (curLine == to.line) {span.to = to.ch; size -= line.text.length - to.ch;} | |
if (marker.collapsed) { | |
if (curLine == to.line) collapsedAtEnd = collapsedSpanAt(line, to.ch); | |
if (curLine == from.line) collapsedAtStart = collapsedSpanAt(line, from.ch); | |
else updateLineHeight(line, 0); | |
} | |
addMarkedSpan(line, span); | |
++curLine; | |
}); | |
if (marker.collapsed) doc.iter(from.line, to.line + 1, function(line) { | |
if (lineIsHidden(line)) updateLineHeight(line, 0); | |
}); | |
if (marker.readOnly) { | |
sawReadOnlySpans = true; | |
if (cm.view.history.done.length || cm.view.history.undone.length) | |
cm.clearHistory(); | |
} | |
if (marker.collapsed) { | |
if (collapsedAtStart != collapsedAtEnd) | |
throw new Error("Inserting collapsed marker overlapping an existing one"); | |
marker.size = size; | |
marker.atomic = true; | |
} | |
if (marker.className || marker.startStyle || marker.endStyle || marker.collapsed) | |
regChange(cm, from.line, to.line + 1); | |
if (marker.atomic) reCheckSelection(cm); | |
return marker; | |
} | |
// TEXTMARKER SPANS | |
function getMarkedSpanFor(spans, marker) { | |
if (spans) for (var i = 0; i < spans.length; ++i) { | |
var span = spans[i]; | |
if (span.marker == marker) return span; | |
} | |
} | |
function removeMarkedSpan(spans, span) { | |
for (var r, i = 0; i < spans.length; ++i) | |
if (spans[i] != span) (r || (r = [])).push(spans[i]); | |
return r; | |
} | |
function addMarkedSpan(line, span) { | |
line.markedSpans = line.markedSpans ? line.markedSpans.concat([span]) : [span]; | |
span.marker.lines.push(line); | |
} | |
function markedSpansBefore(old, startCh) { | |
if (old) for (var i = 0, nw; i < old.length; ++i) { | |
var span = old[i], marker = span.marker; | |
var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= startCh : span.from < startCh); | |
if (startsBefore || marker.type == "bookmark" && span.from == startCh) { | |
var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= startCh : span.to > startCh); | |
(nw || (nw = [])).push({from: span.from, | |
to: endsAfter ? null : span.to, | |
marker: marker}); | |
} | |
} | |
return nw; | |
} | |
function markedSpansAfter(old, startCh, endCh) { | |
if (old) for (var i = 0, nw; i < old.length; ++i) { | |
var span = old[i], marker = span.marker; | |
var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= endCh : span.to > endCh); | |
if (endsAfter || marker.type == "bookmark" && span.from == endCh && span.from != startCh) { | |
var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= endCh : span.from < endCh); | |
(nw || (nw = [])).push({from: startsBefore ? null : span.from - endCh, | |
to: span.to == null ? null : span.to - endCh, | |
marker: marker}); | |
} | |
} | |
return nw; | |
} | |
function updateMarkedSpans(oldFirst, oldLast, startCh, endCh, newText) { | |
if (!oldFirst && !oldLast) return newText; | |
// Get the spans that 'stick out' on both sides | |
var first = markedSpansBefore(oldFirst, startCh); | |
var last = markedSpansAfter(oldLast, startCh, endCh); | |
// Next, merge those two ends | |
var sameLine = newText.length == 1, offset = lst(newText).length + (sameLine ? startCh : 0); | |
if (first) { | |
// Fix up .to properties of first | |
for (var i = 0; i < first.length; ++i) { | |
var span = first[i]; | |
if (span.to == null) { | |
var found = getMarkedSpanFor(last, span.marker); | |
if (!found) span.to = startCh; | |
else if (sameLine) span.to = found.to == null ? null : found.to + offset; | |
} | |
} | |
} | |
if (last) { | |
// Fix up .from in last (or move them into first in case of sameLine) | |
for (var i = 0; i < last.length; ++i) { | |
var span = last[i]; | |
if (span.to != null) span.to += offset; | |
if (span.from == null) { | |
var found = getMarkedSpanFor(first, span.marker); | |
if (!found) { | |
span.from = offset; | |
if (sameLine) (first || (first = [])).push(span); | |
} | |
} else { | |
span.from += offset; | |
if (sameLine) (first || (first = [])).push(span); | |
} | |
} | |
} | |
var newMarkers = [newHL(newText[0], first)]; | |
if (!sameLine) { | |
// Fill gap with whole-line-spans | |
var gap = newText.length - 2, gapMarkers; | |
if (gap > 0 && first) | |
for (var i = 0; i < first.length; ++i) | |
if (first[i].to == null) | |
(gapMarkers || (gapMarkers = [])).push({from: null, to: null, marker: first[i].marker}); | |
for (var i = 0; i < gap; ++i) | |
newMarkers.push(newHL(newText[i+1], gapMarkers)); | |
newMarkers.push(newHL(lst(newText), last)); | |
} | |
return newMarkers; | |
} | |
function removeReadOnlyRanges(doc, from, to) { | |
var markers = null; | |
doc.iter(from.line, to.line + 1, function(line) { | |
if (line.markedSpans) for (var i = 0; i < line.markedSpans.length; ++i) { | |
var mark = line.markedSpans[i].marker; | |
if (mark.readOnly && (!markers || indexOf(markers, mark) == -1)) | |
(markers || (markers = [])).push(mark); | |
} | |
}); | |
if (!markers) return null; | |
var parts = [{from: from, to: to}]; | |
for (var i = 0; i < markers.length; ++i) { | |
var m = markers[i].find(); | |
for (var j = 0; j < parts.length; ++j) { | |
var p = parts[j]; | |
if (!posLess(m.from, p.to) || posLess(m.to, p.from)) continue; | |
var newParts = [j, 1]; | |
if (posLess(p.from, m.from)) newParts.push({from: p.from, to: m.from}); | |
if (posLess(m.to, p.to)) newParts.push({from: m.to, to: p.to}); | |
parts.splice.apply(parts, newParts); | |
j += newParts.length - 1; | |
} | |
} | |
return parts; | |
} | |
function collapsedSpanAt(line, ch) { | |
var sps = sawCollapsedSpans && line.markedSpans, found; | |
if (sps) for (var sp, i = 0; i < sps.length; ++i) { | |
sp = sps[i]; | |
if (!sp.marker.collapsed) continue; | |
if ((sp.from == null || sp.from < ch) && | |
(sp.to == null || sp.to > ch) && | |
(!found || found.width < sp.marker.width)) | |
found = sp.marker; | |
} | |
return found; | |
} | |
function collapsedSpanAtStart(line) { return collapsedSpanAt(line, -1); } | |
function collapsedSpanAtEnd(line) { return collapsedSpanAt(line, line.text.length + 1); } | |
function visualLine(doc, line) { | |
var merged; | |
while (merged = collapsedSpanAtStart(line)) | |
line = getLine(doc, merged.find().from.line); | |
return line; | |
} | |
function lineIsHidden(line) { | |
var sps = sawCollapsedSpans && line.markedSpans; | |
if (sps) for (var sp, i = 0; i < sps.length; ++i) { | |
sp = sps[i]; | |
if (!sp.marker.collapsed) continue; | |
if (sp.from == null) return true; | |
if (sp.from == 0 && sp.marker.inclusiveLeft && lineIsHiddenInner(line, sp)) | |
return true; | |
} | |
} | |
function lineIsHiddenInner(line, span) { | |
if (span.to == null) { | |
var end = span.marker.find().to, endLine = getLine(lineDoc(line), end.line); | |
return lineIsHiddenInner(endLine, getMarkedSpanFor(endLine.markedSpans, span.marker)); | |
} | |
if (span.marker.inclusiveRight && span.to == line.text.length) | |
return true; | |
for (var sp, i = 0; i < line.markedSpans.length; ++i) { | |
sp = line.markedSpans[i]; | |
if (sp.marker.collapsed && sp.from == span.to && | |
(sp.marker.inclusiveLeft || span.marker.inclusiveRight) && | |
lineIsHiddenInner(line, sp)) return true; | |
} | |
} | |
// hl stands for history-line, a data structure that can be either a | |
// string (line without markers) or a {text, markedSpans} object. | |
function hlText(val) { return typeof val == "string" ? val : val.text; } | |
function hlSpans(val) { | |
if (typeof val == "string") return null; | |
var spans = val.markedSpans, out = null; | |
for (var i = 0; i < spans.length; ++i) { | |
if (spans[i].marker.explicitlyCleared) { if (!out) out = spans.slice(0, i); } | |
else if (out) out.push(spans[i]); | |
} | |
return !out ? spans : out.length ? out : null; | |
} | |
function newHL(text, spans) { return spans ? {text: text, markedSpans: spans} : text; } | |
function detachMarkedSpans(line) { | |
var spans = line.markedSpans; | |
if (!spans) return; | |
for (var i = 0; i < spans.length; ++i) { | |
var lines = spans[i].marker.lines; | |
var ix = indexOf(lines, line); | |
lines.splice(ix, 1); | |
} | |
line.markedSpans = null; | |
} | |
function attachMarkedSpans(line, spans) { | |
if (!spans) return; | |
for (var i = 0; i < spans.length; ++i) | |
spans[i].marker.lines.push(line); | |
line.markedSpans = spans; | |
} | |
// LINE WIDGETS | |
var LineWidget = CodeMirror.LineWidget = function(cm, node, options) { | |
for (var opt in options) if (options.hasOwnProperty(opt)) | |
this[opt] = options[opt]; | |
this.cm = cm; | |
this.node = node; | |
}; | |
function widgetOperation(f) { | |
return function() { | |
startOperation(this.cm); | |
try {var result = f.apply(this, arguments);} | |
finally {endOperation(this.cm);} | |
return result; | |
}; | |
} | |
LineWidget.prototype.clear = widgetOperation(function() { | |
var ws = this.line.widgets, no = lineNo(this.line); | |
if (no == null || !ws) return; | |
for (var i = 0; i < ws.length; ++i) if (ws[i] == this) ws.splice(i--, 1); | |
updateLineHeight(this.line, Math.max(0, this.line.height - widgetHeight(this))); | |
regChange(this.cm, no, no + 1); | |
}); | |
LineWidget.prototype.changed = widgetOperation(function() { | |
var oldH = this.height; | |
this.height = null; | |
var diff = widgetHeight(this) - oldH; | |
if (!diff) return; | |
updateLineHeight(this.line, this.line.height + diff); | |
var no = lineNo(this.line); | |
regChange(this.cm, no, no + 1); | |
}); | |
function widgetHeight(widget) { | |
if (widget.height != null) return widget.height; | |
if (!widget.node.parentNode || widget.node.parentNode.nodeType != 1) | |
removeChildrenAndAdd(widget.cm.display.measure, elt("div", [widget.node], null, "position: relative")); | |
return widget.height = widget.node.offsetHeight; | |
} | |
function addLineWidget(cm, handle, node, options) { | |
var widget = new LineWidget(cm, node, options); | |
if (widget.noHScroll) cm.display.alignWidgets = true; | |
changeLine(cm, handle, function(line) { | |
(line.widgets || (line.widgets = [])).push(widget); | |
widget.line = line; | |
if (!lineIsHidden(line) || widget.showIfHidden) { | |
var aboveVisible = heightAtLine(cm, line) < cm.display.scroller.scrollTop; | |
updateLineHeight(line, line.height + widgetHeight(widget)); | |
if (aboveVisible) | |
setTimeout(function() {cm.display.scroller.scrollTop += widget.height;}); | |
} | |
return true; | |
}); | |
return widget; | |
} | |
// LINE DATA STRUCTURE | |
// Line objects. These hold state related to a line, including | |
// highlighting info (the styles array). | |
function makeLine(text, markedSpans, height) { | |
var line = {text: text, height: height}; | |
attachMarkedSpans(line, markedSpans); | |
if (lineIsHidden(line)) line.height = 0; | |
return line; | |
} | |
function updateLine(cm, line, text, markedSpans) { | |
line.text = text; | |
if (line.stateAfter) line.stateAfter = null; | |
if (line.styles) line.styles = null; | |
if (line.order != null) line.order = null; | |
detachMarkedSpans(line); | |
attachMarkedSpans(line, markedSpans); | |
if (lineIsHidden(line)) line.height = 0; | |
else if (!line.height) line.height = textHeight(cm.display); | |
signalLater(cm, line, "change"); | |
} | |
function cleanUpLine(line) { | |
line.parent = null; | |
detachMarkedSpans(line); | |
} | |
// Run the given mode's parser over a line, update the styles | |
// array, which contains alternating fragments of text and CSS | |
// classes. | |
function runMode(cm, text, mode, state, f) { | |
var flattenSpans = cm.options.flattenSpans; | |
var curText = "", curStyle = null; | |
var stream = new StringStream(text, cm.options.tabSize); | |
if (text == "" && mode.blankLine) mode.blankLine(state); | |
while (!stream.eol()) { | |
var style = mode.token(stream, state); | |
if (stream.pos > 5000) { | |
flattenSpans = false; | |
// Webkit seems to refuse to render text nodes longer than 57444 characters | |
stream.pos = Math.min(text.length, stream.start + 50000); | |
style = null; | |
} | |
var substr = stream.current(); | |
stream.start = stream.pos; | |
if (!flattenSpans || curStyle != style) { | |
if (curText) f(curText, curStyle); | |
curText = substr; curStyle = style; | |
} else curText = curText + substr; | |
} | |
if (curText) f(curText, curStyle); | |
} | |
function highlightLine(cm, line, state) { | |
// A styles array always starts with a number identifying the | |
// mode/overlays that it is based on (for easy invalidation). | |
var st = [cm.view.modeGen]; | |
// Compute the base array of styles | |
runMode(cm, line.text, cm.view.mode, state, function(txt, style) {st.push(txt, style);}); | |
// Run overlays, adjust style array. | |
for (var o = 0; o < cm.view.overlays.length; ++o) { | |
var overlay = cm.view.overlays[o], i = 1; | |
runMode(cm, line.text, overlay.mode, true, function(txt, style) { | |
var start = i, len = txt.length; | |
// Ensure there's a token end at the current position, and that i points at it | |
while (len) { | |
var cur = st[i], len_ = cur.length; | |
if (len_ <= len) { | |
len -= len_; | |
} else { | |
st.splice(i, 1, cur.slice(0, len), st[i+1], cur.slice(len)); | |
len = 0; | |
} | |
i += 2; | |
} | |
if (!style) return; | |
if (overlay.opaque) { | |
st.splice(start, i - start, txt, style); | |
i = start + 2; | |
} else { | |
for (; start < i; start += 2) { | |
var cur = st[start+1]; | |
st[start+1] = cur ? cur + " " + style : style; | |
} | |
} | |
}); | |
} | |
return st; | |
} | |
function getLineStyles(cm, line) { | |
if (!line.styles || line.styles[0] != cm.view.modeGen) | |
line.styles = highlightLine(cm, line, line.stateAfter = getStateBefore(cm, lineNo(line))); | |
return line.styles; | |
} | |
// Lightweight form of highlight -- proceed over this line and | |
// update state, but don't save a style array. | |
function processLine(cm, line, state) { | |
var mode = cm.view.mode; | |
var stream = new StringStream(line.text, cm.options.tabSize); | |
if (line.text == "" && mode.blankLine) mode.blankLine(state); | |
while (!stream.eol() && stream.pos <= 5000) { | |
mode.token(stream, state); | |
stream.start = stream.pos; | |
} | |
} | |
var styleToClassCache = {}; | |
function styleToClass(style) { | |
if (!style) return null; | |
return styleToClassCache[style] || | |
(styleToClassCache[style] = "cm-" + style.replace(/ +/g, " cm-")); | |
} | |
function lineContent(cm, realLine, measure) { | |
var merged, line = realLine, lineBefore, sawBefore, simple = true; | |
while (merged = collapsedSpanAtStart(line)) { | |
simple = false; | |
line = getLine(cm.view.doc, merged.find().from.line); | |
if (!lineBefore) lineBefore = line; | |
} | |
var builder = {pre: elt("pre"), col: 0, pos: 0, display: !measure, | |
measure: null, addedOne: false, cm: cm}; | |
if (line.textClass) builder.pre.className = line.textClass; | |
do { | |
builder.measure = line == realLine && measure; | |
builder.pos = 0; | |
builder.addToken = builder.measure ? buildTokenMeasure : buildToken; | |
if (measure && sawBefore && line != realLine && !builder.addedOne) { | |
measure[0] = builder.pre.appendChild(zeroWidthElement(cm.display.measure)); | |
builder.addedOne = true; | |
} | |
var next = insertLineContent(line, builder, getLineStyles(cm, line)); | |
sawBefore = line == lineBefore; | |
if (next) { | |
line = getLine(cm.view.doc, next.to.line); | |
simple = false; | |
} | |
} while (next); | |
if (measure && !builder.addedOne) | |
measure[0] = builder.pre.appendChild(simple ? elt("span", "\u00a0") : zeroWidthElement(cm.display.measure)); | |
if (!builder.pre.firstChild && !lineIsHidden(realLine)) | |
builder.pre.appendChild(document.createTextNode("\u00a0")); | |
return builder.pre; | |
} | |
var tokenSpecialChars = /[\t\u0000-\u0019\u200b\u2028\u2029\uFEFF]/g; | |
function buildToken(builder, text, style, startStyle, endStyle) { | |
if (!text) return; | |
if (!tokenSpecialChars.test(text)) { | |
builder.col += text.length; | |
var content = document.createTextNode(text); | |
} else { | |
var content = document.createDocumentFragment(), pos = 0; | |
while (true) { | |
tokenSpecialChars.lastIndex = pos; | |
var m = tokenSpecialChars.exec(text); | |
var skipped = m ? m.index - pos : text.length - pos; | |
if (skipped) { | |
content.appendChild(document.createTextNode(text.slice(pos, pos + skipped))); | |
builder.col += skipped; | |
} | |
if (!m) break; | |
pos += skipped + 1; | |
if (m[0] == "\t") { | |
var tabSize = builder.cm.options.tabSize, tabWidth = tabSize - builder.col % tabSize; | |
content.appendChild(elt("span", spaceStr(tabWidth), "cm-tab")); | |
builder.col += tabWidth; | |
} else { | |
var token = elt("span", "\u2022", "cm-invalidchar"); | |
token.title = "\\u" + m[0].charCodeAt(0).toString(16); | |
content.appendChild(token); | |
builder.col += 1; | |
} | |
} | |
} | |
if (style || startStyle || endStyle || builder.measure) { | |
var fullStyle = style || ""; | |
if (startStyle) fullStyle += startStyle; | |
if (endStyle) fullStyle += endStyle; | |
return builder.pre.appendChild(elt("span", [content], fullStyle)); | |
} | |
builder.pre.appendChild(content); | |
} | |
function buildTokenMeasure(builder, text, style, startStyle, endStyle) { | |
for (var i = 0; i < text.length; ++i) { | |
if (i && i < text.length && | |
builder.cm.options.lineWrapping && | |
spanAffectsWrapping.test(text.slice(i - 1, i + 1))) | |
builder.pre.appendChild(elt("wbr")); | |
builder.measure[builder.pos++] = | |
buildToken(builder, text.charAt(i), style, | |
i == 0 && startStyle, i == text.length - 1 && endStyle); | |
} | |
if (text.length) builder.addedOne = true; | |
} | |
function buildCollapsedSpan(builder, size, widget) { | |
if (widget) { | |
if (!builder.display) widget = widget.cloneNode(true); | |
builder.pre.appendChild(widget); | |
if (builder.measure && size) { | |
builder.measure[builder.pos] = widget; | |
builder.addedOne = true; | |
} | |
} | |
builder.pos += size; | |
} | |
// Outputs a number of spans to make up a line, taking highlighting | |
// and marked text into account. | |
function insertLineContent(line, builder, styles) { | |
var spans = line.markedSpans; | |
if (!spans) { | |
for (var i = 1; i < styles.length; i+=2) | |
builder.addToken(builder, styles[i], styleToClass(styles[i+1])); | |
return; | |
} | |
var allText = line.text, len = allText.length; | |
var pos = 0, i = 1, text = "", style; | |
var nextChange = 0, spanStyle, spanEndStyle, spanStartStyle, collapsed; | |
for (;;) { | |
if (nextChange == pos) { // Update current marker set | |
spanStyle = spanEndStyle = spanStartStyle = ""; | |
collapsed = null; nextChange = Infinity; | |
var foundBookmark = null; | |
for (var j = 0; j < spans.length; ++j) { | |
var sp = spans[j], m = sp.marker; | |
if (sp.from <= pos && (sp.to == null || sp.to > pos)) { | |
if (sp.to != null && nextChange > sp.to) { nextChange = sp.to; spanEndStyle = ""; } | |
if (m.className) spanStyle += " " + m.className; | |
if (m.startStyle && sp.from == pos) spanStartStyle += " " + m.startStyle; | |
if (m.endStyle && sp.to == nextChange) spanEndStyle += " " + m.endStyle; | |
if (m.collapsed && (!collapsed || collapsed.marker.width < m.width)) | |
collapsed = sp; | |
} else if (sp.from > pos && nextChange > sp.from) { | |
nextChange = sp.from; | |
} | |
if (m.type == "bookmark" && sp.from == pos && m.replacedWith) | |
foundBookmark = m.replacedWith; | |
} | |
if (collapsed && (collapsed.from || 0) == pos) { | |
buildCollapsedSpan(builder, (collapsed.to == null ? len : collapsed.to) - pos, | |
collapsed.from != null && collapsed.marker.replacedWith); | |
if (collapsed.to == null) return collapsed.marker.find(); | |
} | |
if (foundBookmark && !collapsed) buildCollapsedSpan(builder, 0, foundBookmark); | |
} | |
if (pos >= len) break; | |
var upto = Math.min(len, nextChange); | |
while (true) { | |
if (text) { | |
var end = pos + text.length; | |
if (!collapsed) { | |
var tokenText = end > upto ? text.slice(0, upto - pos) : text; | |
builder.addToken(builder, tokenText, style + spanStyle, | |
spanStartStyle, pos + tokenText.length == nextChange ? spanEndStyle : ""); | |
} | |
if (end >= upto) {text = text.slice(upto - pos); pos = upto; break;} | |
pos = end; | |
spanStartStyle = ""; | |
} | |
text = styles[i++]; style = styleToClass(styles[i++]); | |
} | |
} | |
} | |
// DOCUMENT DATA STRUCTURE | |
function LeafChunk(lines) { | |
this.lines = lines; | |
this.parent = null; | |
for (var i = 0, e = lines.length, height = 0; i < e; ++i) { | |
lines[i].parent = this; | |
height += lines[i].height; | |
} | |
this.height = height; | |
} | |
LeafChunk.prototype = { | |
chunkSize: function() { return this.lines.length; }, | |
remove: function(at, n, cm) { | |
for (var i = at, e = at + n; i < e; ++i) { | |
var line = this.lines[i]; | |
this.height -= line.height; | |
cleanUpLine(line); | |
signalLater(cm, line, "delete"); | |
} | |
this.lines.splice(at, n); | |
}, | |
collapse: function(lines) { | |
lines.splice.apply(lines, [lines.length, 0].concat(this.lines)); | |
}, | |
insertHeight: function(at, lines, height) { | |
this.height += height; | |
this.lines = this.lines.slice(0, at).concat(lines).concat(this.lines.slice(at)); | |
for (var i = 0, e = lines.length; i < e; ++i) lines[i].parent = this; | |
}, | |
iterN: function(at, n, op) { | |
for (var e = at + n; at < e; ++at) | |
if (op(this.lines[at])) return true; | |
} | |
}; | |
function BranchChunk(children) { | |
this.children = children; | |
var size = 0, height = 0; | |
for (var i = 0, e = children.length; i < e; ++i) { | |
var ch = children[i]; | |
size += ch.chunkSize(); height += ch.height; | |
ch.parent = this; | |
} | |
this.size = size; | |
this.height = height; | |
this.parent = null; | |
} | |
BranchChunk.prototype = { | |
chunkSize: function() { return this.size; }, | |
remove: function(at, n, callbacks) { | |
this.size -= n; | |
for (var i = 0; i < this.children.length; ++i) { | |
var child = this.children[i], sz = child.chunkSize(); | |
if (at < sz) { | |
var rm = Math.min(n, sz - at), oldHeight = child.height; | |
child.remove(at, rm, callbacks); | |
this.height -= oldHeight - child.height; | |
if (sz == rm) { this.children.splice(i--, 1); child.parent = null; } | |
if ((n -= rm) == 0) break; | |
at = 0; | |
} else at -= sz; | |
} | |
if (this.size - n < 25) { | |
var lines = []; | |
this.collapse(lines); | |
this.children = [new LeafChunk(lines)]; | |
this.children[0].parent = this; | |
} | |
}, | |
collapse: function(lines) { | |
for (var i = 0, e = this.children.length; i < e; ++i) this.children[i].collapse(lines); | |
}, | |
insert: function(at, lines) { | |
var height = 0; | |
for (var i = 0, e = lines.length; i < e; ++i) height += lines[i].height; | |
this.insertHeight(at, lines, height); | |
}, | |
insertHeight: function(at, lines, height) { | |
this.size += lines.length; | |
this.height += height; | |
for (var i = 0, e = this.children.length; i < e; ++i) { | |
var child = this.children[i], sz = child.chunkSize(); | |
if (at <= sz) { | |
child.insertHeight(at, lines, height); | |
if (child.lines && child.lines.length > 50) { | |
while (child.lines.length > 50) { | |
var spilled = child.lines.splice(child.lines.length - 25, 25); | |
var newleaf = new LeafChunk(spilled); | |
child.height -= newleaf.height; | |
this.children.splice(i + 1, 0, newleaf); | |
newleaf.parent = this; | |
} | |
this.maybeSpill(); | |
} | |
break; | |
} | |
at -= sz; | |
} | |
}, | |
maybeSpill: function() { | |
if (this.children.length <= 10) return; | |
var me = this; | |
do { | |
var spilled = me.children.splice(me.children.length - 5, 5); | |
var sibling = new BranchChunk(spilled); | |
if (!me.parent) { // Become the parent node | |
var copy = new BranchChunk(me.children); | |
copy.parent = me; | |
me.children = [copy, sibling]; | |
me = copy; | |
} else { | |
me.size -= sibling.size; | |
me.height -= sibling.height; | |
var myIndex = indexOf(me.parent.children, me); | |
me.parent.children.splice(myIndex + 1, 0, sibling); | |
} | |
sibling.parent = me.parent; | |
} while (me.children.length > 10); | |
me.parent.maybeSpill(); | |
}, | |
iter: function(from, to, op) { this.iterN(from, to - from, op); }, | |
iterN: function(at, n, op) { | |
for (var i = 0, e = this.children.length; i < e; ++i) { | |
var child = this.children[i], sz = child.chunkSize(); | |
if (at < sz) { | |
var used = Math.min(n, sz - at); | |
if (child.iterN(at, used, op)) return true; | |
if ((n -= used) == 0) break; | |
at = 0; | |
} else at -= sz; | |
} | |
} | |
}; | |
// LINE UTILITIES | |
function getLine(chunk, n) { | |
while (!chunk.lines) { | |
for (var i = 0;; ++i) { | |
var child = chunk.children[i], sz = child.chunkSize(); | |
if (n < sz) { chunk = child; break; } | |
n -= sz; | |
} | |
} | |
return chunk.lines[n]; | |
} | |
function updateLineHeight(line, height) { | |
var diff = height - line.height; | |
for (var n = line; n; n = n.parent) n.height += diff; | |
} | |
function lineNo(line) { | |
if (line.parent == null) return null; | |
var cur = line.parent, no = indexOf(cur.lines, line); | |
for (var chunk = cur.parent; chunk; cur = chunk, chunk = chunk.parent) { | |
for (var i = 0;; ++i) { | |
if (chunk.children[i] == cur) break; | |
no += chunk.children[i].chunkSize(); | |
} | |
} | |
return no; | |
} | |
function lineDoc(line) { | |
for (var d = line.parent; d.parent; d = d.parent) {} | |
return d; | |
} | |
function lineAtHeight(chunk, h) { | |
var n = 0; | |
outer: do { | |
for (var i = 0, e = chunk.children.length; i < e; ++i) { | |
var child = chunk.children[i], ch = child.height; | |
if (h < ch) { chunk = child; continue outer; } | |
h -= ch; | |
n += child.chunkSize(); | |
} | |
return n; | |
} while (!chunk.lines); | |
for (var i = 0, e = chunk.lines.length; i < e; ++i) { | |
var line = chunk.lines[i], lh = line.height; | |
if (h < lh) break; | |
h -= lh; | |
} | |
return n + i; | |
} | |
function heightAtLine(cm, lineObj) { | |
lineObj = visualLine(cm.view.doc, lineObj); | |
var h = 0, chunk = lineObj.parent; | |
for (var i = 0; i < chunk.lines.length; ++i) { | |
var line = chunk.lines[i]; | |
if (line == lineObj) break; | |
else h += line.height; | |
} | |
for (var p = chunk.parent; p; chunk = p, p = chunk.parent) { | |
for (var i = 0; i < p.children.length; ++i) { | |
var cur = p.children[i]; | |
if (cur == chunk) break; | |
else h += cur.height; | |
} | |
} | |
return h; | |
} | |
function getOrder(line) { | |
var order = line.order; | |
if (order == null) order = line.order = bidiOrdering(line.text); | |
return order; | |
} | |
// HISTORY | |
function makeHistory() { | |
return { | |
// Arrays of history events. Doing something adds an event to | |
// done and clears undo. Undoing moves events from done to | |
// undone, redoing moves them in the other direction. | |
done: [], undone: [], | |
// Used to track when changes can be merged into a single undo | |
// event | |
lastTime: 0, lastOp: null, lastOrigin: null, | |
// Used by the isClean() method | |
dirtyCounter: 0 | |
}; | |
} | |
function addChange(cm, start, added, old, origin, fromBefore, toBefore, fromAfter, toAfter) { | |
var history = cm.view.history; | |
history.undone.length = 0; | |
var time = +new Date, cur = lst(history.done); | |
if (cur && | |
(history.lastOp == cm.curOp.id || | |
history.lastOrigin == origin && (origin == "input" || origin == "delete") && | |
history.lastTime > time - 600)) { | |
// Merge this change into the last event | |
var last = lst(cur.events); | |
if (last.start > start + old.length || last.start + last.added < start) { | |
// Doesn't intersect with last sub-event, add new sub-event | |
cur.events.push({start: start, added: added, old: old}); | |
} else { | |
// Patch up the last sub-event | |
var startBefore = Math.max(0, last.start - start), | |
endAfter = Math.max(0, (start + old.length) - (last.start + last.added)); | |
for (var i = startBefore; i > 0; --i) last.old.unshift(old[i - 1]); | |
for (var i = endAfter; i > 0; --i) last.old.push(old[old.length - i]); | |
if (startBefore) last.start = start; | |
last.added += added - (old.length - startBefore - endAfter); | |
} | |
cur.fromAfter = fromAfter; cur.toAfter = toAfter; | |
} else { | |
// Can not be merged, start a new event. | |
cur = {events: [{start: start, added: added, old: old}], | |
fromBefore: fromBefore, toBefore: toBefore, fromAfter: fromAfter, toAfter: toAfter}; | |
history.done.push(cur); | |
while (history.done.length > cm.options.undoDepth) | |
history.done.shift(); | |
if (history.dirtyCounter < 0) | |
// The user has made a change after undoing past the last clean state. | |
// We can never get back to a clean state now until markClean() is called. | |
history.dirtyCounter = NaN; | |
else | |
history.dirtyCounter++; | |
} | |
history.lastTime = time; | |
history.lastOp = cm.curOp.id; | |
history.lastOrigin = origin; | |
} | |
// EVENT OPERATORS | |
function stopMethod() {e_stop(this);} | |
// Ensure an event has a stop method. | |
function addStop(event) { | |
if (!event.stop) event.stop = stopMethod; | |
return event; | |
} | |
function e_preventDefault(e) { | |
if (e.preventDefault) e.preventDefault(); | |
else e.returnValue = false; | |
} | |
function e_stopPropagation(e) { | |
if (e.stopPropagation) e.stopPropagation(); | |
else e.cancelBubble = true; | |
} | |
function e_stop(e) {e_preventDefault(e); e_stopPropagation(e);} | |
CodeMirror.e_stop = e_stop; | |
CodeMirror.e_preventDefault = e_preventDefault; | |
CodeMirror.e_stopPropagation = e_stopPropagation; | |
function e_target(e) {return e.target || e.srcElement;} | |
function e_button(e) { | |
var b = e.which; | |
if (b == null) { | |
if (e.button & 1) b = 1; | |
else if (e.button & 2) b = 3; | |
else if (e.button & 4) b = 2; | |
} | |
if (mac && e.ctrlKey && b == 1) b = 3; | |
return b; | |
} | |
// Allow 3rd-party code to override event properties by adding an override | |
// object to an event object. | |
function e_prop(e, prop) { | |
var overridden = e.override && e.override.hasOwnProperty(prop); | |
return overridden ? e.override[prop] : e[prop]; | |
} | |
// EVENT HANDLING | |
function on(emitter, type, f) { | |
if (emitter.addEventListener) | |
emitter.addEventListener(type, f, false); | |
else if (emitter.attachEvent) | |
emitter.attachEvent("on" + type, f); | |
else { | |
var map = emitter._handlers || (emitter._handlers = {}); | |
var arr = map[type] || (map[type] = []); | |
arr.push(f); | |
} | |
} | |
function off(emitter, type, f) { | |
if (emitter.removeEventListener) | |
emitter.removeEventListener(type, f, false); | |
else if (emitter.detachEvent) | |
emitter.detachEvent("on" + type, f); | |
else { | |
var arr = emitter._handlers && emitter._handlers[type]; | |
if (!arr) return; | |
for (var i = 0; i < arr.length; ++i) | |
if (arr[i] == f) { arr.splice(i, 1); break; } | |
} | |
} | |
function signal(emitter, type /*, values...*/) { | |
var arr = emitter._handlers && emitter._handlers[type]; | |
if (!arr) return; | |
var args = Array.prototype.slice.call(arguments, 2); | |
for (var i = 0; i < arr.length; ++i) arr[i].apply(null, args); | |
} | |
function signalLater(cm, emitter, type /*, values...*/) { | |
var arr = emitter._handlers && emitter._handlers[type]; | |
if (!arr) return; | |
var args = Array.prototype.slice.call(arguments, 3), flist = cm.curOp && cm.curOp.delayedCallbacks; | |
function bnd(f) {return function(){f.apply(null, args);};}; | |
for (var i = 0; i < arr.length; ++i) | |
if (flist) flist.push(bnd(arr[i])); | |
else arr[i].apply(null, args); | |
} | |
function hasHandler(emitter, type) { | |
var arr = emitter._handlers && emitter._handlers[type]; | |
return arr && arr.length > 0; | |
} | |
CodeMirror.on = on; CodeMirror.off = off; CodeMirror.signal = signal; | |
// MISC UTILITIES | |
// Number of pixels added to scroller and sizer to hide scrollbar | |
var scrollerCutOff = 30; | |
// Returned or thrown by various protocols to signal 'I'm not | |
// handling this'. | |
var Pass = CodeMirror.Pass = {toString: function(){return "CodeMirror.Pass";}}; | |
function Delayed() {this.id = null;} | |
Delayed.prototype = {set: function(ms, f) {clearTimeout(this.id); this.id = setTimeout(f, ms);}}; | |
// Counts the column offset in a string, taking tabs into account. | |
// Used mostly to find indentation. | |
function countColumn(string, end, tabSize) { | |
if (end == null) { | |
end = string.search(/[^\s\u00a0]/); | |
if (end == -1) end = string.length; | |
} | |
for (var i = 0, n = 0; i < end; ++i) { | |
if (string.charAt(i) == "\t") n += tabSize - (n % tabSize); | |
else ++n; | |
} | |
return n; | |
} | |
CodeMirror.countColumn = countColumn; | |
var spaceStrs = [""]; | |
function spaceStr(n) { | |
while (spaceStrs.length <= n) | |
spaceStrs.push(lst(spaceStrs) + " "); | |
return spaceStrs[n]; | |
} | |
function lst(arr) { return arr[arr.length-1]; } | |
function selectInput(node) { | |
if (ios) { // Mobile Safari apparently has a bug where select() is broken. | |
node.selectionStart = 0; | |
node.selectionEnd = node.value.length; | |
} else node.select(); | |
} | |
function indexOf(collection, elt) { | |
if (collection.indexOf) return collection.indexOf(elt); | |
for (var i = 0, e = collection.length; i < e; ++i) | |
if (collection[i] == elt) return i; | |
return -1; | |
} | |
function emptyArray(size) { | |
for (var a = [], i = 0; i < size; ++i) a.push(undefined); | |
return a; | |
} | |
function bind(f) { | |
var args = Array.prototype.slice.call(arguments, 1); | |
return function(){return f.apply(null, args);}; | |
} | |
var nonASCIISingleCaseWordChar = /[\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc]/; | |
function isWordChar(ch) { | |
return /\w/.test(ch) || ch > "\x80" && | |
(ch.toUpperCase() != ch.toLowerCase() || nonASCIISingleCaseWordChar.test(ch)); | |
} | |
function isEmpty(obj) { | |
var c = 0; | |
for (var n in obj) if (obj.hasOwnProperty(n) && obj[n]) ++c; | |
return !c; | |
} | |
var isExtendingChar = /[\u0300-\u036F\u0483-\u0487\u0488-\u0489\u0591-\u05BD\u05BF\u05C1-\u05C2\u05C4-\u05C5\u05C7\u0610-\u061A\u064B-\u065F\u0670\u06D6-\u06DC\u06DF-\u06E4\u06E7-\u06E8\u06EA-\u06ED\uA66F\uA670-\uA672\uA674-\uA67D\uA69F]/; | |
// DOM UTILITIES | |
function elt(tag, content, className, style) { | |
var e = document.createElement(tag); | |
if (className) e.className = className; | |
if (style) e.style.cssText = style; | |
if (typeof content == "string") setTextContent(e, content); | |
else if (content) for (var i = 0; i < content.length; ++i) e.appendChild(content[i]); | |
return e; | |
} | |
function removeChildren(e) { | |
// IE will break all parent-child relations in subnodes when setting innerHTML | |
if (!ie) e.innerHTML = ""; | |
else while (e.firstChild) e.removeChild(e.firstChild); | |
return e; | |
} | |
function removeChildrenAndAdd(parent, e) { | |
return removeChildren(parent).appendChild(e); | |
} | |
function setTextContent(e, str) { | |
if (ie_lt9) { | |
e.innerHTML = ""; | |
e.appendChild(document.createTextNode(str)); | |
} else e.textContent = str; | |
} | |
// FEATURE DETECTION | |
// Detect drag-and-drop | |
var dragAndDrop = function() { | |
// There is *some* kind of drag-and-drop support in IE6-8, but I | |
// couldn't get it to work yet. | |
if (ie_lt9) return false; | |
var div = elt('div'); | |
return "draggable" in div || "dragDrop" in div; | |
}(); | |
// For a reason I have yet to figure out, some browsers disallow | |
// word wrapping between certain characters *only* if a new inline | |
// element is started between them. This makes it hard to reliably | |
// measure the position of things, since that requires inserting an | |
// extra span. This terribly fragile set of regexps matches the | |
// character combinations that suffer from this phenomenon on the | |
// various browsers. | |
var spanAffectsWrapping = /^$/; // Won't match any two-character string | |
if (gecko) spanAffectsWrapping = /$'/; | |
else if (safari) spanAffectsWrapping = /\-[^ \-?]|\?[^ !'\"\),.\-\/:;\?\]\}]/; | |
else if (chrome) spanAffectsWrapping = /\-[^ \-\.?]|\?[^ \-\.?\]\}:;!'\"\),\/]|[\.!\"#&%\)*+,:;=>\]|\}~][\(\{\[<]|\$'/; | |
var knownScrollbarWidth; | |
function scrollbarWidth(measure) { | |
if (knownScrollbarWidth != null) return knownScrollbarWidth; | |
var test = elt("div", null, null, "width: 50px; height: 50px; overflow-x: scroll"); | |
removeChildrenAndAdd(measure, test); | |
if (test.offsetWidth) | |
knownScrollbarWidth = test.offsetHeight - test.clientHeight; | |
return knownScrollbarWidth || 0; | |
} | |
var zwspSupported; | |
function zeroWidthElement(measure) { | |
if (zwspSupported == null) { | |
var test = elt("span", "\u200b"); | |
removeChildrenAndAdd(measure, elt("span", [test, document.createTextNode("x")])); | |
if (measure.firstChild.offsetHeight != 0) | |
zwspSupported = test.offsetWidth <= 1 && test.offsetHeight > 2 && !ie_lt8; | |
} | |
if (zwspSupported) return elt("span", "\u200b"); | |
else return elt("span", "\u00a0", null, "display: inline-block; width: 1px; margin-right: -1px"); | |
} | |
// See if "".split is the broken IE version, if so, provide an | |
// alternative way to split lines. | |
var splitLines = "\n\nb".split(/\n/).length != 3 ? function(string) { | |
var pos = 0, result = [], l = string.length; | |
while (pos <= l) { | |
var nl = string.indexOf("\n", pos); | |
if (nl == -1) nl = string.length; | |
var line = string.slice(pos, string.charAt(nl - 1) == "\r" ? nl - 1 : nl); | |
var rt = line.indexOf("\r"); | |
if (rt != -1) { | |
result.push(line.slice(0, rt)); | |
pos += rt + 1; | |
} else { | |
result.push(line); | |
pos = nl + 1; | |
} | |
} | |
return result; | |
} : function(string){return string.split(/\r\n?|\n/);}; | |
CodeMirror.splitLines = splitLines; | |
var hasSelection = window.getSelection ? function(te) { | |
try { return te.selectionStart != te.selectionEnd; } | |
catch(e) { return false; } | |
} : function(te) { | |
try {var range = te.ownerDocument.selection.createRange();} | |
catch(e) {} | |
if (!range || range.parentElement() != te) return false; | |
return range.compareEndPoints("StartToEnd", range) != 0; | |
}; | |
var hasCopyEvent = (function() { | |
var e = elt("div"); | |
if ("oncopy" in e) return true; | |
e.setAttribute("oncopy", "return;"); | |
return typeof e.oncopy == 'function'; | |
})(); | |
// KEY NAMING | |
var keyNames = {3: "Enter", 8: "Backspace", 9: "Tab", 13: "Enter", 16: "Shift", 17: "Ctrl", 18: "Alt", | |
19: "Pause", 20: "CapsLock", 27: "Esc", 32: "Space", 33: "PageUp", 34: "PageDown", 35: "End", | |
36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "PrintScrn", 45: "Insert", | |
46: "Delete", 59: ";", 91: "Mod", 92: "Mod", 93: "Mod", 109: "-", 107: "=", 127: "Delete", | |
186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\", | |
221: "]", 222: "'", 63276: "PageUp", 63277: "PageDown", 63275: "End", 63273: "Home", | |
63234: "Left", 63232: "Up", 63235: "Right", 63233: "Down", 63302: "Insert", 63272: "Delete"}; | |
CodeMirror.keyNames = keyNames; | |
(function() { | |
// Number keys | |
for (var i = 0; i < 10; i++) keyNames[i + 48] = String(i); | |
// Alphabetic keys | |
for (var i = 65; i <= 90; i++) keyNames[i] = String.fromCharCode(i); | |
// Function keys | |
for (var i = 1; i <= 12; i++) keyNames[i + 111] = keyNames[i + 63235] = "F" + i; | |
})(); | |
// BIDI HELPERS | |
function iterateBidiSections(order, from, to, f) { | |
if (!order) return f(from, to, "ltr"); | |
for (var i = 0; i < order.length; ++i) { | |
var part = order[i]; | |
if (part.from < to && part.to > from || from == to && part.to == from) | |
f(Math.max(part.from, from), Math.min(part.to, to), part.level == 1 ? "rtl" : "ltr"); | |
} | |
} | |
function bidiLeft(part) { return part.level % 2 ? part.to : part.from; } | |
function bidiRight(part) { return part.level % 2 ? part.from : part.to; } | |
function lineLeft(line) { var order = getOrder(line); return order ? bidiLeft(order[0]) : 0; } | |
function lineRight(line) { | |
var order = getOrder(line); | |
if (!order) return line.text.length; | |
return bidiRight(lst(order)); | |
} | |
function lineStart(cm, lineN) { | |
var line = getLine(cm.view.doc, lineN); | |
var visual = visualLine(cm.view.doc, line); | |
if (visual != line) lineN = lineNo(visual); | |
var order = getOrder(visual); | |
var ch = !order ? 0 : order[0].level % 2 ? lineRight(visual) : lineLeft(visual); | |
return {line: lineN, ch: ch}; | |
} | |
function lineEnd(cm, lineNo) { | |
var merged, line; | |
while (merged = collapsedSpanAtEnd(line = getLine(cm.view.doc, lineNo))) | |
lineNo = merged.find().to.line; | |
var order = getOrder(line); | |
var ch = !order ? line.text.length : order[0].level % 2 ? lineLeft(line) : lineRight(line); | |
return {line: lineNo, ch: ch}; | |
} | |
// This is somewhat involved. It is needed in order to move | |
// 'visually' through bi-directional text -- i.e., pressing left | |
// should make the cursor go left, even when in RTL text. The | |
// tricky part is the 'jumps', where RTL and LTR text touch each | |
// other. This often requires the cursor offset to move more than | |
// one unit, in order to visually move one unit. | |
function moveVisually(line, start, dir, byUnit) { | |
var bidi = getOrder(line); | |
if (!bidi) return moveLogically(line, start, dir, byUnit); | |
var moveOneUnit = byUnit ? function(pos, dir) { | |
do pos += dir; | |
while (pos > 0 && isExtendingChar.test(line.text.charAt(pos))); | |
return pos; | |
} : function(pos, dir) { return pos + dir; }; | |
var linedir = bidi[0].level; | |
for (var i = 0; i < bidi.length; ++i) { | |
var part = bidi[i], sticky = part.level % 2 == linedir; | |
if ((part.from < start && part.to > start) || | |
(sticky && (part.from == start || part.to == start))) break; | |
} | |
var target = moveOneUnit(start, part.level % 2 ? -dir : dir); | |
while (target != null) { | |
if (part.level % 2 == linedir) { | |
if (target < part.from || target > part.to) { | |
part = bidi[i += dir]; | |
target = part && (dir > 0 == part.level % 2 ? moveOneUnit(part.to, -1) : moveOneUnit(part.from, 1)); | |
} else break; | |
} else { | |
if (target == bidiLeft(part)) { | |
part = bidi[--i]; | |
target = part && bidiRight(part); | |
} else if (target == bidiRight(part)) { | |
part = bidi[++i]; | |
target = part && bidiLeft(part); | |
} else break; | |
} | |
} | |
return target < 0 || target > line.text.length ? null : target; | |
} | |
function moveLogically(line, start, dir, byUnit) { | |
var target = start + dir; | |
if (byUnit) while (target > 0 && isExtendingChar.test(line.text.charAt(target))) target += dir; | |
return target < 0 || target > line.text.length ? null : target; | |
} | |
// Bidirectional ordering algorithm | |
// See http://unicode.org/reports/tr9/tr9-13.html for the algorithm | |
// that this (partially) implements. | |
// One-char codes used for character types: | |
// L (L): Left-to-Right | |
// R (R): Right-to-Left | |
// r (AL): Right-to-Left Arabic | |
// 1 (EN): European Number | |
// + (ES): European Number Separator | |
// % (ET): European Number Terminator | |
// n (AN): Arabic Number | |
// , (CS): Common Number Separator | |
// m (NSM): Non-Spacing Mark | |
// b (BN): Boundary Neutral | |
// s (B): Paragraph Separator | |
// t (S): Segment Separator | |
// w (WS): Whitespace | |
// N (ON): Other Neutrals | |
// Returns null if characters are ordered as they appear | |
// (left-to-right), or an array of sections ({from, to, level} | |
// objects) in the order in which they occur visually. | |
var bidiOrdering = (function() { | |
// Character types for codepoints 0 to 0xff | |
var lowTypes = "bbbbbbbbbtstwsbbbbbbbbbbbbbbssstwNN%%%NNNNNN,N,N1111111111NNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNbbbbbbsbbbbbbbbbbbbbbbbbbbbbbbbbb,N%%%%NNNNLNNNNN%%11NLNNN1LNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLL"; | |
// Character types for codepoints 0x600 to 0x6ff | |
var arabicTypes = "rrrrrrrrrrrr,rNNmmmmmmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmrrrrrrrnnnnnnnnnn%nnrrrmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmmmmmmNmmmmrrrrrrrrrrrrrrrrrr"; | |
function charType(code) { | |
if (code <= 0xff) return lowTypes.charAt(code); | |
else if (0x590 <= code && code <= 0x5f4) return "R"; | |
else if (0x600 <= code && code <= 0x6ff) return arabicTypes.charAt(code - 0x600); | |
else if (0x700 <= code && code <= 0x8ac) return "r"; | |
else return "L"; | |
} | |
var bidiRE = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/; | |
var isNeutral = /[stwN]/, isStrong = /[LRr]/, countsAsLeft = /[Lb1n]/, countsAsNum = /[1n]/; | |
// Browsers seem to always treat the boundaries of block elements as being L. | |
var outerType = "L"; | |
return function charOrdering(str) { | |
if (!bidiRE.test(str)) return false; | |
var len = str.length, types = []; | |
for (var i = 0, type; i < len; ++i) | |
types.push(type = charType(str.charCodeAt(i))); | |
// W1. Examine each non-spacing mark (NSM) in the level run, and | |
// change the type of the NSM to the type of the previous | |
// character. If the NSM is at the start of the level run, it will | |
// get the type of sor. | |
for (var i = 0, prev = outerType; i < len; ++i) { | |
var type = types[i]; | |
if (type == "m") types[i] = prev; | |
else prev = type; | |
} | |
// W2. Search backwards from each instance of a European number | |
// until the first strong type (R, L, AL, or sor) is found. If an | |
// AL is found, change the type of the European number to Arabic | |
// number. | |
// W3. Change all ALs to R. | |
for (var i = 0, cur = outerType; i < len; ++i) { | |
var type = types[i]; | |
if (type == "1" && cur == "r") types[i] = "n"; | |
else if (isStrong.test(type)) { cur = type; if (type == "r") types[i] = "R"; } | |
} | |
// W4. A single European separator between two European numbers | |
// changes to a European number. A single common separator between | |
// two numbers of the same type changes to that type. | |
for (var i = 1, prev = types[0]; i < len - 1; ++i) { | |
var type = types[i]; | |
if (type == "+" && prev == "1" && types[i+1] == "1") types[i] = "1"; | |
else if (type == "," && prev == types[i+1] && | |
(prev == "1" || prev == "n")) types[i] = prev; | |
prev = type; | |
} | |
// W5. A sequence of European terminators adjacent to European | |
// numbers changes to all European numbers. | |
// W6. Otherwise, separators and terminators change to Other | |
// Neutral. | |
for (var i = 0; i < len; ++i) { | |
var type = types[i]; | |
if (type == ",") types[i] = "N"; | |
else if (type == "%") { | |
for (var end = i + 1; end < len && types[end] == "%"; ++end) {} | |
var replace = (i && types[i-1] == "!") || (end < len - 1 && types[end] == "1") ? "1" : "N"; | |
for (var j = i; j < end; ++j) types[j] = replace; | |
i = end - 1; | |
} | |
} | |
// W7. Search backwards from each instance of a European number | |
// until the first strong type (R, L, or sor) is found. If an L is | |
// found, then change the type of the European number to L. | |
for (var i = 0, cur = outerType; i < len; ++i) { | |
var type = types[i]; | |
if (cur == "L" && type == "1") types[i] = "L"; | |
else if (isStrong.test(type)) cur = type; | |
} | |
// N1. A sequence of neutrals takes the direction of the | |
// surrounding strong text if the text on both sides has the same | |
// direction. European and Arabic numbers act as if they were R in | |
// terms of their influence on neutrals. Start-of-level-run (sor) | |
// and end-of-level-run (eor) are used at level run boundaries. | |
// N2. Any remaining neutrals take the embedding direction. | |
for (var i = 0; i < len; ++i) { | |
if (isNeutral.test(types[i])) { | |
for (var end = i + 1; end < len && isNeutral.test(types[end]); ++end) {} | |
var before = (i ? types[i-1] : outerType) == "L"; | |
var after = (end < len - 1 ? types[end] : outerType) == "L"; | |
var replace = before || after ? "L" : "R"; | |
for (var j = i; j < end; ++j) types[j] = replace; | |
i = end - 1; | |
} | |
} | |
// Here we depart from the documented algorithm, in order to avoid | |
// building up an actual levels array. Since there are only three | |
// levels (0, 1, 2) in an implementation that doesn't take | |
// explicit embedding into account, we can build up the order on | |
// the fly, without following the level-based algorithm. | |
var order = [], m; | |
for (var i = 0; i < len;) { | |
if (countsAsLeft.test(types[i])) { | |
var start = i; | |
for (++i; i < len && countsAsLeft.test(types[i]); ++i) {} | |
order.push({from: start, to: i, level: 0}); | |
} else { | |
var pos = i, at = order.length; | |
for (++i; i < len && types[i] != "L"; ++i) {} | |
for (var j = pos; j < i;) { | |
if (countsAsNum.test(types[j])) { | |
if (pos < j) order.splice(at, 0, {from: pos, to: j, level: 1}); | |
var nstart = j; | |
for (++j; j < i && countsAsNum.test(types[j]); ++j) {} | |
order.splice(at, 0, {from: nstart, to: j, level: 2}); | |
pos = j; | |
} else ++j; | |
} | |
if (pos < i) order.splice(at, 0, {from: pos, to: i, level: 1}); | |
} | |
} | |
if (order[0].level == 1 && (m = str.match(/^\s+/))) { | |
order[0].from = m[0].length; | |
order.unshift({from: 0, to: m[0].length, level: 0}); | |
} | |
if (lst(order).level == 1 && (m = str.match(/\s+$/))) { | |
lst(order).to -= m[0].length; | |
order.push({from: len - m[0].length, to: len, level: 0}); | |
} | |
if (order[0].level != lst(order).level) | |
order.push({from: len, to: len, level: order[0].level}); | |
return order; | |
}; | |
})(); | |
// THE END | |
CodeMirror.version = "3.02"; | |
return CodeMirror; | |
})(); | |
CodeMirror.defineMode("css", function(config) { | |
var indentUnit = config.indentUnit, type; | |
var atMediaTypes = keySet([ | |
"all", "aural", "braille", "handheld", "print", "projection", "screen", | |
"tty", "tv", "embossed" | |
]); | |
var atMediaFeatures = keySet([ | |
"width", "min-width", "max-width", "height", "min-height", "max-height", | |
"device-width", "min-device-width", "max-device-width", "device-height", | |
"min-device-height", "max-device-height", "aspect-ratio", | |
"min-aspect-ratio", "max-aspect-ratio", "device-aspect-ratio", | |
"min-device-aspect-ratio", "max-device-aspect-ratio", "color", "min-color", | |
"max-color", "color-index", "min-color-index", "max-color-index", | |
"monochrome", "min-monochrome", "max-monochrome", "resolution", | |
"min-resolution", "max-resolution", "scan", "grid" | |
]); | |
var propertyKeywords = keySet([ | |
"align-content", "align-items", "align-self", "alignment-adjust", | |
"alignment-baseline", "anchor-point", "animation", "animation-delay", | |
"animation-direction", "animation-duration", "animation-iteration-count", | |
"animation-name", "animation-play-state", "animation-timing-function", | |
"appearance", "azimuth", "backface-visibility", "background", | |
"background-attachment", "background-clip", "background-color", | |
"background-image", "background-origin", "background-position", | |
"background-repeat", "background-size", "baseline-shift", "binding", | |
"bleed", "bookmark-label", "bookmark-level", "bookmark-state", | |
"bookmark-target", "border", "border-bottom", "border-bottom-color", | |
"border-bottom-left-radius", "border-bottom-right-radius", | |
"border-bottom-style", "border-bottom-width", "border-collapse", | |
"border-color", "border-image", "border-image-outset", | |
"border-image-repeat", "border-image-slice", "border-image-source", | |
"border-image-width", "border-left", "border-left-color", | |
"border-left-style", "border-left-width", "border-radius", "border-right", | |
"border-right-color", "border-right-style", "border-right-width", | |
"border-spacing", "border-style", "border-top", "border-top-color", | |
"border-top-left-radius", "border-top-right-radius", "border-top-style", | |
"border-top-width", "border-width", "bottom", "box-decoration-break", | |
"box-shadow", "box-sizing", "break-after", "break-before", "break-inside", | |
"caption-side", "clear", "clip", "color", "color-profile", "column-count", | |
"column-fill", "column-gap", "column-rule", "column-rule-color", | |
"column-rule-style", "column-rule-width", "column-span", "column-width", | |
"columns", "content", "counter-increment", "counter-reset", "crop", "cue", | |
"cue-after", "cue-before", "cursor", "direction", "display", | |
"dominant-baseline", "drop-initial-after-adjust", | |
"drop-initial-after-align", "drop-initial-before-adjust", | |
"drop-initial-before-align", "drop-initial-size", "drop-initial-value", | |
"elevation", "empty-cells", "fit", "fit-position", "flex", "flex-basis", | |
"flex-direction", "flex-flow", "flex-grow", "flex-shrink", "flex-wrap", | |
"float", "float-offset", "font", "font-feature-settings", "font-family", | |
"font-kerning", "font-language-override", "font-size", "font-size-adjust", | |
"font-stretch", "font-style", "font-synthesis", "font-variant", | |
"font-variant-alternates", "font-variant-caps", "font-variant-east-asian", | |
"font-variant-ligatures", "font-variant-numeric", "font-variant-position", | |
"font-weight", "grid-cell", "grid-column", "grid-column-align", | |
"grid-column-sizing", "grid-column-span", "grid-columns", "grid-flow", | |
"grid-row", "grid-row-align", "grid-row-sizing", "grid-row-span", | |
"grid-rows", "grid-template", "hanging-punctuation", "height", "hyphens", | |
"icon", "image-orientation", "image-rendering", "image-resolution", | |
"inline-box-align", "justify-content", "left", "letter-spacing", | |
"line-break", "line-height", "line-stacking", "line-stacking-ruby", | |
"line-stacking-shift", "line-stacking-strategy", "list-style", | |
"list-style-image", "list-style-position", "list-style-type", "margin", | |
"margin-bottom", "margin-left", "margin-right", "margin-top", | |
"marker-offset", "marks", "marquee-direction", "marquee-loop", | |
"marquee-play-count", "marquee-speed", "marquee-style", "max-height", | |
"max-width", "min-height", "min-width", "move-to", "nav-down", "nav-index", | |
"nav-left", "nav-right", "nav-up", "opacity", "order", "orphans", "outline", | |
"outline-color", "outline-offset", "outline-style", "outline-width", | |
"overflow", "overflow-style", "overflow-wrap", "overflow-x", "overflow-y", | |
"padding", "padding-bottom", "padding-left", "padding-right", "padding-top", | |
"page", "page-break-after", "page-break-before", "page-break-inside", | |
"page-policy", "pause", "pause-after", "pause-before", "perspective", | |
"perspective-origin", "pitch", "pitch-range", "play-during", "position", | |
"presentation-level", "punctuation-trim", "quotes", "rendering-intent", | |
"resize", "rest", "rest-after", "rest-before", "richness", "right", | |
"rotation", "rotation-point", "ruby-align", "ruby-overhang", | |
"ruby-position", "ruby-span", "size", "speak", "speak-as", "speak-header", | |
"speak-numeral", "speak-punctuation", "speech-rate", "stress", "string-set", | |
"tab-size", "table-layout", "target", "target-name", "target-new", | |
"target-position", "text-align", "text-align-last", "text-decoration", | |
"text-decoration-color", "text-decoration-line", "text-decoration-skip", | |
"text-decoration-style", "text-emphasis", "text-emphasis-color", | |
"text-emphasis-position", "text-emphasis-style", "text-height", | |
"text-indent", "text-justify", "text-outline", "text-shadow", | |
"text-space-collapse", "text-transform", "text-underline-position", | |
"text-wrap", "top", "transform", "transform-origin", "transform-style", | |
"transition", "transition-delay", "transition-duration", | |
"transition-property", "transition-timing-function", "unicode-bidi", | |
"vertical-align", "visibility", "voice-balance", "voice-duration", | |
"voice-family", "voice-pitch", "voice-range", "voice-rate", "voice-stress", | |
"voice-volume", "volume", "white-space", "widows", "width", "word-break", | |
"word-spacing", "word-wrap", "z-index" | |
]); | |
var colorKeywords = keySet([ | |
"black", "silver", "gray", "white", "maroon", "red", "purple", "fuchsia", | |
"green", "lime", "olive", "yellow", "navy", "blue", "teal", "aqua" | |
]); | |
var valueKeywords = keySet([ | |
"above", "absolute", "activeborder", "activecaption", "afar", | |
"after-white-space", "ahead", "alias", "all", "all-scroll", "alternate", | |
"always", "amharic", "amharic-abegede", "antialiased", "appworkspace", | |
"arabic-indic", "armenian", "asterisks", "auto", "avoid", "background", | |
"backwards", "baseline", "below", "bidi-override", "binary", "bengali", | |
"blink", "block", "block-axis", "bold", "bolder", "border", "border-box", | |
"both", "bottom", "break-all", "break-word", "button", "button-bevel", | |
"buttonface", "buttonhighlight", "buttonshadow", "buttontext", "cambodian", | |
"capitalize", "caps-lock-indicator", "caption", "captiontext", "caret", | |
"cell", "center", "checkbox", "circle", "cjk-earthly-branch", | |
"cjk-heavenly-stem", "cjk-ideographic", "clear", "clip", "close-quote", | |
"col-resize", "collapse", "compact", "condensed", "contain", "content", | |
"content-box", "context-menu", "continuous", "copy", "cover", "crop", | |
"cross", "crosshair", "currentcolor", "cursive", "dashed", "decimal", | |
"decimal-leading-zero", "default", "default-button", "destination-atop", | |
"destination-in", "destination-out", "destination-over", "devanagari", | |
"disc", "discard", "document", "dot-dash", "dot-dot-dash", "dotted", | |
"double", "down", "e-resize", "ease", "ease-in", "ease-in-out", "ease-out", | |
"element", "ellipsis", "embed", "end", "ethiopic", "ethiopic-abegede", | |
"ethiopic-abegede-am-et", "ethiopic-abegede-gez", "ethiopic-abegede-ti-er", | |
"ethiopic-abegede-ti-et", "ethiopic-halehame-aa-er", | |
"ethiopic-halehame-aa-et", "ethiopic-halehame-am-et", | |
"ethiopic-halehame-gez", "ethiopic-halehame-om-et", | |
"ethiopic-halehame-sid-et", "ethiopic-halehame-so-et", | |
"ethiopic-halehame-ti-er", "ethiopic-halehame-ti-et", | |
"ethiopic-halehame-tig", "ew-resize", "expanded", "extra-condensed", | |
"extra-expanded", "fantasy", "fast", "fill", "fixed", "flat", "footnotes", | |
"forwards", "from", "geometricPrecision", "georgian", "graytext", "groove", | |
"gujarati", "gurmukhi", "hand", "hangul", "hangul-consonant", "hebrew", | |
"help", "hidden", "hide", "higher", "highlight", "highlighttext", | |
"hiragana", "hiragana-iroha", "horizontal", "hsl", "hsla", "icon", "ignore", | |
"inactiveborder", "inactivecaption", "inactivecaptiontext", "infinite", | |
"infobackground", "infotext", "inherit", "initial", "inline", "inline-axis", | |
"inline-block", "inline-table", "inset", "inside", "intrinsic", "invert", | |
"italic", "justify", "kannada", "katakana", "katakana-iroha", "khmer", | |
"landscape", "lao", "large", "larger", "left", "level", "lighter", | |
"line-through", "linear", "lines", "list-item", "listbox", "listitem", | |
"local", "logical", "loud", "lower", "lower-alpha", "lower-armenian", | |
"lower-greek", "lower-hexadecimal", "lower-latin", "lower-norwegian", | |
"lower-roman", "lowercase", "ltr", "malayalam", "match", | |
"media-controls-background", "media-current-time-display", | |
"media-fullscreen-button", "media-mute-button", "media-play-button", | |
"media-return-to-realtime-button", "media-rewind-button", | |
"media-seek-back-button", "media-seek-forward-button", "media-slider", | |
"media-sliderthumb", "media-time-remaining-display", "media-volume-slider", | |
"media-volume-slider-container", "media-volume-sliderthumb", "medium", | |
"menu", "menulist", "menulist-button", "menulist-text", | |
"menulist-textfield", "menutext", "message-box", "middle", "min-intrinsic", | |
"mix", "mongolian", "monospace", "move", "multiple", "myanmar", "n-resize", | |
"narrower", "navy", "ne-resize", "nesw-resize", "no-close-quote", "no-drop", | |
"no-open-quote", "no-repeat", "none", "normal", "not-allowed", "nowrap", | |
"ns-resize", "nw-resize", "nwse-resize", "oblique", "octal", "open-quote", | |
"optimizeLegibility", "optimizeSpeed", "oriya", "oromo", "outset", | |
"outside", "overlay", "overline", "padding", "padding-box", "painted", | |
"paused", "persian", "plus-darker", "plus-lighter", "pointer", "portrait", | |
"pre", "pre-line", "pre-wrap", "preserve-3d", "progress", "push-button", | |
"radio", "read-only", "read-write", "read-write-plaintext-only", "relative", | |
"repeat", "repeat-x", "repeat-y", "reset", "reverse", "rgb", "rgba", | |
"ridge", "right", "round", "row-resize", "rtl", "run-in", "running", | |
"s-resize", "sans-serif", "scroll", "scrollbar", "se-resize", "searchfield", | |
"searchfield-cancel-button", "searchfield-decoration", | |
"searchfield-results-button", "searchfield-results-decoration", | |
"semi-condensed", "semi-expanded", "separate", "serif", "show", "sidama", | |
"single", "skip-white-space", "slide", "slider-horizontal", | |
"slider-vertical", "sliderthumb-horizontal", "sliderthumb-vertical", "slow", | |
"small", "small-caps", "small-caption", "smaller", "solid", "somali", | |
"source-atop", "source-in", "source-out", "source-over", "space", "square", | |
"square-button", "start", "static", "status-bar", "stretch", "stroke", | |
"sub", "subpixel-antialiased", "super", "sw-resize", "table", | |
"table-caption", "table-cell", "table-column", "table-column-group", | |
"table-footer-group", "table-header-group", "table-row", "table-row-group", | |
"telugu", "text", "text-bottom", "text-top", "textarea", "textfield", "thai", | |
"thick", "thin", "threeddarkshadow", "threedface", "threedhighlight", | |
"threedlightshadow", "threedshadow", "tibetan", "tigre", "tigrinya-er", | |
"tigrinya-er-abegede", "tigrinya-et", "tigrinya-et-abegede", "to", "top", | |
"transparent", "ultra-condensed", "ultra-expanded", "underline", "up", | |
"upper-alpha", "upper-armenian", "upper-greek", "upper-hexadecimal", | |
"upper-latin", "upper-norwegian", "upper-roman", "uppercase", "urdu", "url", | |
"vertical", "vertical-text", "visible", "visibleFill", "visiblePainted", | |
"visibleStroke", "visual", "w-resize", "wait", "wave", "white", "wider", | |
"window", "windowframe", "windowtext", "x-large", "x-small", "xor", | |
"xx-large", "xx-small", "yellow" | |
]); | |
function keySet(array) { var keys = {}; for (var i = 0; i < array.length; ++i) keys[array[i]] = true; return keys; } | |
function ret(style, tp) {type = tp; return style;} | |
function tokenBase(stream, state) { | |
var ch = stream.next(); | |
if (ch == "@") {stream.eatWhile(/[\w\\\-]/); return ret("def", stream.current());} | |
else if (ch == "/" && stream.eat("*")) { | |
state.tokenize = tokenCComment; | |
return tokenCComment(stream, state); | |
} | |
else if (ch == "<" && stream.eat("!")) { | |
state.tokenize = tokenSGMLComment; | |
return tokenSGMLComment(stream, state); | |
} | |
else if (ch == "=") ret(null, "compare"); | |
else if ((ch == "~" || ch == "|") && stream.eat("=")) return ret(null, "compare"); | |
else if (ch == "\"" || ch == "'") { | |
state.tokenize = tokenString(ch); | |
return state.tokenize(stream, state); | |
} | |
else if (ch == "#") { | |
stream.eatWhile(/[\w\\\-]/); | |
return ret("atom", "hash"); | |
} | |
else if (ch == "!") { | |
stream.match(/^\s*\w*/); | |
return ret("keyword", "important"); | |
} | |
else if (/\d/.test(ch)) { | |
stream.eatWhile(/[\w.%]/); | |
return ret("number", "unit"); | |
} | |
else if (ch === "-") { | |
if (/\d/.test(stream.peek())) { | |
stream.eatWhile(/[\w.%]/); | |
return ret("number", "unit"); | |
} else if (stream.match(/^[^-]+-/)) { | |
return ret("meta", "meta"); | |
} | |
} | |
else if (/[,+>*\/]/.test(ch)) { | |
return ret(null, "select-op"); | |
} | |
else if (ch == "." && stream.match(/^-?[_a-z][_a-z0-9-]*/i)) { | |
return ret("qualifier", "qualifier"); | |
} | |
else if (ch == ":") { | |
return ret("operator", ch); | |
} | |
else if (/[;{}\[\]\(\)]/.test(ch)) { | |
return ret(null, ch); | |
} | |
else if (ch == "u" && stream.match("rl(")) { | |
stream.backUp(1); | |
state.tokenize = tokenParenthesized; | |
return ret("property", "variable"); | |
} | |
else { | |
stream.eatWhile(/[\w\\\-]/); | |
return ret("property", "variable"); | |
} | |
} | |
function tokenCComment(stream, state) { | |
var maybeEnd = false, ch; | |
while ((ch = stream.next()) != null) { | |
if (maybeEnd && ch == "/") { | |
state.tokenize = tokenBase; | |
break; | |
} | |
maybeEnd = (ch == "*"); | |
} | |
return ret("comment", "comment"); | |
} | |
function tokenSGMLComment(stream, state) { | |
var dashes = 0, ch; | |
while ((ch = stream.next()) != null) { | |
if (dashes >= 2 && ch == ">") { | |
state.tokenize = tokenBase; | |
break; | |
} | |
dashes = (ch == "-") ? dashes + 1 : 0; | |
} | |
return ret("comment", "comment"); | |
} | |
function tokenString(quote, nonInclusive) { | |
return function(stream, state) { | |
var escaped = false, ch; | |
while ((ch = stream.next()) != null) { | |
if (ch == quote && !escaped) | |
break; | |
escaped = !escaped && ch == "\\"; | |
} | |
if (!escaped) { | |
if (nonInclusive) stream.backUp(1); | |
state.tokenize = tokenBase; | |
} | |
return ret("string", "string"); | |
}; | |
} | |
function tokenParenthesized(stream, state) { | |
stream.next(); // Must be '(' | |
if (!stream.match(/\s*[\"\']/, false)) | |
state.tokenize = tokenString(")", true); | |
else | |
state.tokenize = tokenBase; | |
return ret(null, "("); | |
} | |
return { | |
startState: function(base) { | |
return {tokenize: tokenBase, | |
baseIndent: base || 0, | |
stack: []}; | |
}, | |
token: function(stream, state) { | |
// Use these terms when applicable (see http://www.xanthir.com/blog/b4E50) | |
// | |
// rule** or **ruleset: | |
// A selector + braces combo, or an at-rule. | |
// | |
// declaration block: | |
// A sequence of declarations. | |
// | |
// declaration: | |
// A property + colon + value combo. | |
// | |
// property value: | |
// The entire value of a property. | |
// | |
// component value: | |
// A single piece of a property value. Like the 5px in | |
// text-shadow: 0 0 5px blue;. Can also refer to things that are | |
// multiple terms, like the 1-4 terms that make up the background-size | |
// portion of the background shorthand. | |
// | |
// term: | |
// The basic unit of author-facing CSS, like a single number (5), | |
// dimension (5px), string ("foo"), or function. Officially defined | |
// by the CSS 2.1 grammar (look for the 'term' production) | |
// | |
// | |
// simple selector: | |
// A single atomic selector, like a type selector, an attr selector, a | |
// class selector, etc. | |
// | |
// compound selector: | |
// One or more simple selectors without a combinator. div.example is | |
// compound, div > .example is not. | |
// | |
// complex selector: | |
// One or more compound selectors chained with combinators. | |
// | |
// combinator: | |
// The parts of selectors that express relationships. There are four | |
// currently - the space (descendant combinator), the greater-than | |
// bracket (child combinator), the plus sign (next sibling combinator), | |
// and the tilda (following sibling combinator). | |
// | |
// sequence of selectors: | |
// One or more of the named type of selector chained with commas. | |
if (state.tokenize == tokenBase && stream.eatSpace()) return null; | |
var style = state.tokenize(stream, state); | |
// Changing style returned based on context | |
var context = state.stack[state.stack.length-1]; | |
if (style == "property") { | |
if (context == "propertyValue"){ | |
if (valueKeywords[stream.current()]) { | |
style = "string-2"; | |
} else if (colorKeywords[stream.current()]) { | |
style = "keyword"; | |
} else { | |
style = "variable-2"; | |
} | |
} else if (context == "rule") { | |
if (!propertyKeywords[stream.current()]) { | |
style += " error"; | |
} | |
} else if (!context || context == "@media{") { | |
style = "tag"; | |
} else if (context == "@media") { | |
if (atMediaTypes[stream.current()]) { | |
style = "attribute"; // Known attribute | |
} else if (/^(only|not)$/i.test(stream.current())) { | |
style = "keyword"; | |
} else if (stream.current().toLowerCase() == "and") { | |
style = "error"; // "and" is only allowed in @mediaType | |
} else if (atMediaFeatures[stream.current()]) { | |
style = "error"; // Known property, should be in @mediaType( | |
} else { | |
// Unknown, expecting keyword or attribute, assuming attribute | |
style = "attribute error"; | |
} | |
} else if (context == "@mediaType") { | |
if (atMediaTypes[stream.current()]) { | |
style = "attribute"; | |
} else if (stream.current().toLowerCase() == "and") { | |
style = "operator"; | |
} else if (/^(only|not)$/i.test(stream.current())) { | |
style = "error"; // Only allowed in @media | |
} else if (atMediaFeatures[stream.current()]) { | |
style = "error"; // Known property, should be in parentheses | |
} else { | |
// Unknown attribute or property, but expecting property (preceded | |
// by "and"). Should be in parentheses | |
style = "error"; | |
} | |
} else if (context == "@mediaType(") { | |
if (propertyKeywords[stream.current()]) { | |
// do nothing, remains "property" | |
} else if (atMediaTypes[stream.current()]) { | |
style = "error"; // Known property, should be in parentheses | |
} else if (stream.current().toLowerCase() == "and") { | |
style = "operator"; | |
} else if (/^(only|not)$/i.test(stream.current())) { | |
style = "error"; // Only allowed in @media | |
} else { | |
style += " error"; | |
} | |
} else { | |
style = "error"; | |
} | |
} else if (style == "atom") { | |
if(!context || context == "@media{") { | |
style = "builtin"; | |
} else if (context == "propertyValue") { | |
if (!/^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/.test(stream.current())) { | |
style += " error"; | |
} | |
} else { | |
style = "error"; | |
} | |
} else if (context == "@media" && type == "{") { | |
style = "error"; | |
} | |
// Push/pop context stack | |
if (type == "{") { | |
if (context == "@media" || context == "@mediaType") { | |
state.stack.pop(); | |
state.stack[state.stack.length-1] = "@media{"; | |
} | |
else state.stack.push("rule"); | |
} | |
else if (type == "}") { | |
state.stack.pop(); | |
if (context == "propertyValue") state.stack.pop(); | |
} | |
else if (type == "@media") state.stack.push("@media"); | |
else if (context == "@media" && /\b(keyword|attribute)\b/.test(style)) | |
state.stack.push("@mediaType"); | |
else if (context == "@mediaType" && stream.current() == ",") state.stack.pop(); | |
else if (context == "@mediaType" && type == "(") state.stack.push("@mediaType("); | |
else if (context == "@mediaType(" && type == ")") state.stack.pop(); | |
else if (context == "rule" && type == ":") state.stack.push("propertyValue"); | |
else if (context == "propertyValue" && type == ";") state.stack.pop(); | |
return style; | |
}, | |
indent: function(state, textAfter) { | |
var n = state.stack.length; | |
if (/^\}/.test(textAfter)) | |
n -= state.stack[state.stack.length-1] == "propertyValue" ? 2 : 1; | |
return state.baseIndent + n * indentUnit; | |
}, | |
electricChars: "}" | |
}; | |
}); | |
CodeMirror.defineMIME("text/css", "css"); | |
function blockAdminCSS() { | |
this.loadCreationMode = function () { | |
//ui update | |
$("#panelcss").on("keyup.creation change.creation",".liveconfig", function(event){ | |
var nbstyle = document.getElementById("current_stylesheet_nb").value; | |
var nbrule = document.getElementById("current_stylesheet_nb_rule").value; | |
var stylesh = ParsimonyAdmin.currentDocument.styleSheets[nbstyle].cssRules[nbrule]; | |
if(typeof stylesh != "undefined") var rules = stylesh.style.cssText + this.getAttribute("name") + ": " + this.value + ";"; | |
else rules = this.getAttribute("name") + ": " + this.value + ";"; | |
blockAdminCSS.setCss(nbstyle, nbrule, document.getElementById("current_selector_update").value + "{" + rules + "}"); | |
ParsimonyAdmin.$currentDocument.find(".parsimonyDND").parsimonyDND("updatePosition"); | |
}) | |
.on('click.creation',".explorer",function(event){ | |
window.callbackExplorerID = $(this).attr('rel'); | |
window.callbackExplorer = function (file){ | |
$("#" + window.callbackExplorerID).val("url( " + file + ")"); | |
$("#" + window.callbackExplorerID).trigger('keyup'); | |
} | |
ParsimonyAdmin.displayConfBox(BASE_PATH + "admin/explorer","Explorer"); | |
}); | |
$("#right_sidebar").on('click.creation',".cssPickerBTN", function(e){ | |
e.preventDefault(); | |
e.stopPropagation(); | |
$("#threed").show(); | |
function destroyCSSpicker(){ | |
$('#container',ParsimonyAdmin.currentBody).off(".csspicker"); | |
$("#rotatex,#rotatey").val(0); | |
$("#rotatez").val(300); | |
$(".cssPickerBTN").removeClass("active"); | |
} | |
if($(this).hasClass("active")){ | |
destroyCSSpicker(); | |
return false; | |
} | |
ParsimonyAdmin.closeParsiadminMenu(); | |
$('#container',ParsimonyAdmin.currentBody).on('mouseover.csspicker',"*", function(event) { | |
event.stopPropagation(); | |
$(".cssPicker",ParsimonyAdmin.currentBody).removeClass("cssPicker"); | |
this.classList.add("cssPicker"); | |
}); | |
$(".cssPickerBTN").addClass("active"); | |
$('#container',ParsimonyAdmin.currentBody).on('click.csspicker',"*",function(e){ | |
e.preventDefault(); | |
e.stopPropagation(); | |
$("#threed").hide(); | |
ParsimonyAdmin.$currentBody.css('-webkit-transform','initial').removeClass("threed"); | |
$(".cssPicker").removeClass("cssPicker"); | |
$(this).addClass("cssPicker"); | |
blockAdminCSS.getCSSForCSSpicker(); | |
var title = CSSTHEMEPATH; | |
if(this.id.length > 0 && $(".selectorcss[selector='#" + this.id + "']", $("#changecsscode")).length == 0) blockAdminCSS.addNewSelectorCSS( title, "#" + this.id) | |
var forbidClasses = ",selection-block,block,container,selection-container,"; | |
$.each(this.classList, function(index, value) { | |
if($(".selectorcss[selector='." + value + "']").length == 0 && forbidClasses.indexOf("," + value+ ",") == "-1" && value != "parsieditinline"){ blockAdminCSS.addNewSelectorCSS( title, "." + value);} | |
}); | |
var good = false; | |
var selectProp = ""; | |
if(this.id == ""){ | |
if(this.getAttribute('class') != undefined && this.getAttribute('class') != "") selectProp = ("." + this.getAttribute("class").replace(" ",".")); | |
$(this).parentsUntil("body").each(function(){ | |
if(!good){ | |
var selectid = ""; | |
var selectclass = ""; | |
if(this.getAttribute('id') != undefined) selectid = "#" + this.getAttribute('id'); | |
else{ | |
if(this.getAttribute('class') != undefined && this.getAttribute('class') != "") selectclass = "." + this.getAttribute("class").replace(" ","."); | |
} | |
selectProp = selectid + selectclass + " " + selectProp; | |
if(selectid != "") good = true; | |
} | |
}); | |
selectProp = selectProp.replace(".cssPicker","").replace(".clearboth","").replace(".parsieditinline","").replace(/\s\s+/g," "); | |
if($(".selectorcss[selector='" + selectProp + "']", $("#changecsscode")).length == 0) blockAdminCSS.addNewSelectorCSS( title, selectProp); | |
} | |
destroyCSSpicker(); | |
return false; | |
}); | |
}); | |
blockAdminCSS.iframeStyleSheet = ParsimonyAdmin.currentDocument.styleSheets[ParsimonyAdmin.currentDocument.styleSheets.length-1]; | |
/* Shortcut : Save on CTRL+S */ | |
$(document).on("keydown.creation", function(e) { | |
if (e.keyCode == 83 && e.ctrlKey) { | |
e.preventDefault(); | |
$("#savemycss").trigger("click"); | |
} | |
}); | |
} | |
this.unloadCreationMode = function(){ | |
$("#panelcss").off('.creation'); | |
$('.parsimonyDND',ParsimonyAdmin.currentDocument).parsimonyDND('destroy'); | |
$("#colorjack_square").hide(); | |
} | |
} | |
blockAdminCSS.csseditors = []; | |
blockAdminCSS.updateCSSUI = function (cssprop) { | |
$("#current_selector_update,#current_selector_update_prev").val(cssprop.selector); | |
$("#changecsspath").val(cssprop.filePath); | |
$("#changecssform input,select").val(''); | |
$.each(cssprop.values, function(i,item){ | |
$("#panelcss [css=" + i + "]").val(item); | |
}); | |
} | |
blockAdminCSS.setCss = function (nbstyle, nbrule, rule) { | |
if(nbrule == null){ | |
nbRule = ParsimonyAdmin.currentDocument.styleSheets[nbstyle].cssRules.length; | |
if(nbRule > 0) nbrule = nbRule - 1; | |
} | |
if(typeof ParsimonyAdmin.currentDocument.styleSheets[nbstyle].cssRules[nbrule] != "undefined") ParsimonyAdmin.currentDocument.styleSheets[nbstyle].deleteRule(nbrule); | |
ParsimonyAdmin.currentDocument.styleSheets[nbstyle].insertRule(rule,nbrule); | |
} | |
blockAdminCSS.displayCSSConf = function (filePath,selector) { | |
blockAdminCSS.openCSSForm(); | |
ParsimonyAdmin.postData(BASE_PATH + "admin/getCSSSelectorRules" ,{ | |
TOKEN:TOKEN, | |
selector: selector, | |
filePath: filePath | |
} ,function(data){ | |
$("#css_panel input[type!=\"hidden\"]").val(""); | |
blockAdminCSS.updateCSSUI($.parseJSON(data)); | |
}); | |
var selectorPrev = document.getElementById("current_selector_update_prev").value; | |
if(selectorPrev.length > 0){ | |
var nbstyle = document.getElementById("current_stylesheet_nb").value; | |
var nbrule = document.getElementById("current_stylesheet_nb_rule").value; | |
blockAdminCSS.setCss(nbstyle, nbrule, selectorPrev + "{" + (document.getElementById("current_stylesheet_rules").value || " ") + "}"); | |
} | |
document.getElementById("typeofinput").value = "form"; | |
document.getElementById("current_stylesheet_rules").value = ""; | |
document.getElementById("current_stylesheet_nb_rule").value = ""; | |
var styleSheets = ParsimonyAdmin.currentDocument.styleSheets; | |
for (var i = 0; i < styleSheets.length; i++){ | |
if(styleSheets[i].href != null && !!styleSheets[i].href && styleSheets[i].href.match(new RegExp(filePath))){ | |
document.getElementById("current_stylesheet_nb").value = i; | |
$.each(styleSheets[i].cssRules, function(nbrule) { | |
if(this.selectorText == selector){ | |
document.getElementById("current_stylesheet_nb_rule").value = nbrule; | |
document.getElementById("current_stylesheet_rules").value = styleSheets[i].cssRules[nbrule].style.cssText; | |
} | |
}); | |
if(document.getElementById("current_stylesheet_nb_rule").value.length == 0){ | |
var nbRule = ParsimonyAdmin.currentDocument.styleSheets[i].cssRules.length; | |
if(nbRule > 0) nbrule = ParsimonyAdmin.currentDocument.styleSheets[i].cssRules.length - 1; | |
document.getElementById("current_stylesheet_nb_rule").value = nbRule; | |
} | |
} | |
} | |
if($(selector,ParsimonyAdmin.currentBody).length == 1){ | |
$(selector,ParsimonyAdmin.currentBody).parsimonyDND('destroy').parsimonyDND({ | |
stopResizable : function(event, ui) { | |
$("#form_css input[css=width]").val((ui.width() != 'auto' ? ui.width() : '') + "px"); | |
$("#form_css input[css=height]").val((ui.height() != 'auto' ? ui.height() : '') + "px"); | |
$("#form_css input[css=left]").val((ui.css('left') != 'auto' ? ui.css('left') : '')); | |
$("#form_css input[css=top]").val((ui.css('top') != 'auto' ? ui.css('top') : '')); | |
}, | |
stopDraggable: function(event, ui) { | |
$("#form_css input[css=left]").val((ui.css('left') != 'auto' ? ui.css('left') : '')); | |
$("#form_css input[css=top]").val((ui.css('top') != 'auto' ? ui.css('top') : '')); | |
} | |
}); | |
} | |
} | |
blockAdminCSS.CSSeditor = function (id) { | |
editor = CodeMirror(function(node) { | |
document.getElementById(id).parentNode.insertBefore(node, document.getElementById(id).nextSibling); | |
}, { | |
gutters: ["guttercss"], | |
mode:"text/css", | |
value: document.getElementById(id).value, | |
lineNumbers: false, | |
autoClearEmptyLines:true | |
}); | |
editor.id = id; | |
editor.on("change", function(c, change) { | |
var textarea = document.getElementById(c.id); | |
var nbstyle = textarea.getAttribute('data-nbstyle'); | |
var nbrule = textarea.getAttribute('data-nbrule'); | |
var selector = decodeURIComponent(textarea.getAttribute('data-selector')); | |
var code = selector + '{'; | |
var cptL = c.lineCount(); | |
for(var i = 0;i < cptL; i++){ | |
if(c.lineInfo(i).textClass != "barre") code += c.getLine(i); | |
} | |
blockAdminCSS.setCss(nbstyle, nbrule, code + "}"); | |
textarea.value = c.getValue(); | |
}); | |
editor.on("gutterClick", function(c, n) { | |
var info = c.lineInfo(n); | |
if (info.textClass == "barre"){ | |
c.removeLineClass(n, "text", "barre"); | |
c.setGutterMarker(n, "guttercss", null); | |
}else{ | |
var elmt = document.createElement("span"); | |
elmt.classList.add("activebtn"); | |
elmt.innerHTML = "×"; | |
c.setGutterMarker(n, "guttercss", elmt); | |
c.addLineClass(n, "text", "barre"); | |
} | |
c._handlers['change'][0](c); | |
}); | |
blockAdminCSS.csseditors.push(editor); | |
} | |
blockAdminCSS.getCSSForCSSpicker = function () { | |
var matchesSelector = (document.documentElement.webkitMatchesSelector || document.documentElement.mozMatchesSelector || document.documentElement.oMatchesSelector || document.documentElement.matchesSelector); | |
var json = '['; | |
this.openCSSCode(); | |
var elmt = $('.cssPicker',ParsimonyAdmin.currentBody).removeClass('cssPicker').get(0); | |
var styleSheets = ParsimonyAdmin.currentDocument.styleSheets; | |
for (var i = 0; i < styleSheets.length; i++){ | |
if(styleSheets[i].cssRules !== null && styleSheets[i].href != null && !!styleSheets[i].href && styleSheets[i].href.indexOf("iframe.css") == "-1" && styleSheets[i].href.indexOf("/" + window.location.host + BASE_PATH + "lib") == "-1"){ | |
for(j=0; j < styleSheets[i].cssRules.length;j++) { | |
var rule = styleSheets[i].cssRules[j]; | |
if(matchesSelector.call(elmt,rule.selectorText)){ | |
var url = styleSheets[i].href.replace("http://" + window.location.host,"").substring(BASE_PATH.length); | |
blockAdminCSS.addSelectorCSS(url, rule.selectorText, rule.style.cssText.replace(/;[^a-zA-Z\-]+/gm, ";\n"), i , j); | |
json += '{"nbstyle":"' + i + '","nbrule":"' + j + '","url":"' + url + '","selector":"' + rule.selectorText + '"},'; | |
} | |
} | |
} | |
} | |
if(json.length > 1){ | |
json = json.substring(0, json.length - 1) + ']'; | |
$.post(BASE_PATH + "admin/getCSSSelectorsRules", { | |
json: json | |
},function(data) { | |
$.each(data, function(i,item) { | |
var id = 'idcss' + item.nbstyle + item.nbrule; | |
document.getElementById(id).value = item.cssText; | |
}); | |
$.each(blockAdminCSS.csseditors,function(i, el){ | |
el.setValue(document.getElementById(el.id).value); | |
}); | |
}); | |
} | |
} | |
blockAdminCSS.addNewSelectorCSS = function (path, selector) { | |
var code = ''; | |
var nbstyle = ''; | |
var nbrule = ''; | |
var styleSheets = ParsimonyAdmin.currentDocument.styleSheets; | |
for (var i = 0; i < styleSheets.length; i++){ | |
if(styleSheets[i].href != null && !!styleSheets[i].href && styleSheets[i].href.match(new RegExp(path))){ | |
nbstyle = i; | |
$.each(styleSheets[i].cssRules, function(indexRule) { | |
if(this.selectorText == selector){ | |
nbrule = indexRule; | |
code = styleSheets[i].cssRules[nbrule].style.cssText; | |
} | |
}); | |
if(nbrule.length == 0){ | |
nbrule = ParsimonyAdmin.currentDocument.styleSheets[i].cssRules.length; | |
} | |
} | |
} | |
this.addSelectorCSS( path, selector, code, nbstyle, nbrule); | |
this.setCss(nbstyle, nbrule, selector + "{" + code + "}"); | |
} | |
blockAdminCSS.addSelectorCSS = function (url, selector, styleCSS, nbstyle, nbrule) { | |
var id = 'idcss' + nbstyle + nbrule; | |
var code = '<div class="selectorcss" title="' + url + '" selector="' + selector + '"><div class="selectorTitle"><b>' + selector + '</b> <small>in ' + url.replace(/^.*[\/\\]/g, '') + '</small></div><div class="gotoform" onclick="blockAdminCSS.displayCSSConf(\'' + url + '\',\'' + selector + '\')"> '+ t('Visual') +' </div></div>' | |
+ '<input type="hidden" name="selectors[' + id + '][file]" value="' + url + '"><input type="hidden" name="selectors[' + id + '][selector]" value="' + encodeURIComponent(selector) + '">' | |
+ '<textarea class="csscode" id="' + id + '" name="selectors[' + id + '][code]" data-nbstyle="' + nbstyle + '" data-nbrule="' + nbrule + '" data-selector="' + encodeURIComponent(selector) + '">' + styleCSS.replace(/;/,";\n").replace("\n\n","\n") + '</textarea>'; | |
$("#changecsscode").prepend(code); | |
this.CSSeditor(id); | |
} | |
blockAdminCSS.openCSSForm = function () { | |
$("#right_sidebar .active").removeClass("active"); | |
$(".panelcss").addClass("active"); | |
$("#panelcss").removeClass("CSSCode CSSSearch").addClass("CSSForm").show(); | |
document.getElementById("typeofinput").value = "form"; | |
} | |
blockAdminCSS.openCSSCode = function () { | |
$("#right_sidebar .active").removeClass("active"); | |
$(".panelcss").addClass("active"); | |
$("#panelcss").removeClass("CSSForm CSSSearch").addClass("CSSCode"); | |
document.getElementById("typeofinput").value = "code"; | |
$("#changecsscode").empty(); | |
$.each(blockAdminCSS.csseditors,function(i, el){ | |
blockAdminCSS.csseditors.splice(i,i+1); | |
}); | |
} | |
ParsimonyAdmin.setPlugin(new blockAdminCSS()); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Parsimony | |
* | |
* NOTICE OF LICENSE | |
* | |
* This source file is subject to the Open Software License (OSL 3.0) | |
* that is bundled with this package in the file LICENSE.txt. | |
* It is also available through the world-wide-web at this URL: | |
* http://opensource.org/licenses/osl-3.0.php | |
* If you did not receive a copy of the license and are unable to | |
* obtain it through the world-wide-web, please send an email | |
* to contact@parsimony-cms.com so we can send you a copy immediately. | |
* | |
* DISCLAIMER | |
* | |
* Do not edit or add to this file if you wish to upgrade Parsimony to newer | |
* versions in the future. If you wish to customize Parsimony for your | |
* needs please refer to http://www.parsimony.mobi for more information. | |
* | |
* @authors Julien Gras et Benoît Lorillot | |
* @copyright Julien Gras et Benoît Lorillot | |
* @version Release: 1.0 | |
* @category cms.js | |
* @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) | |
*/ | |
var onorientationchange = function(){ | |
$("body").removeClass("landscape portrait"); | |
$("body").addClass(Math.abs(window.orientation == 90)? "landscape" : "portrait"); | |
} | |
function loadBlock(id, params, callback){ | |
if(!params) params = {}; | |
if(!callback) window['callback'] = ''; | |
else window['callback'] = callback; | |
$.get(window.location.href.toLocaleString(), params, function(data) { | |
$('#' + id).html($("<div>").append(data).find("#" + id).html()); | |
if(typeof window['callback'] == 'function') window['callback'].call(); | |
}); | |
} | |
var $lang = new Array; | |
function t(val){ | |
if($lang[val]){ | |
return $lang[val]; | |
}else{ | |
return val; | |
} | |
} | |
$(document).ready(function(){ | |
//autocomplete | |
$("select.autocomplete").each(function(){ | |
$(this).attr('type','text'); | |
var obj = $("option", this); | |
var arr = $.makeArray(obj); | |
$(this).autocomplete({ | |
source: arr | |
}); | |
}); | |
//datepicker | |
/*$(function() { | |
$( ".datepicker" ).datepicker($.datepicker.regional[ "fr" ]); | |
});*/ | |
// Device orientation | |
if(typeof window.orientation != "undefined"){ | |
$("body").addClass(Math.abs(window.orientation == 90)? "landscape" : "portrait"); | |
window.onorientationchange = onorientationchange; | |
} | |
}); | |
$(document).ready(function(){ | |
$(document).on('click','a', function(e){ | |
if($(this).attr("href").substring(0,1) != '#' && $(this).attr("href").substring(0,7) != 'http://'){ | |
e.preventDefault(); | |
loadPage($(this).attr('href')); | |
} | |
}); | |
if((window.history && history.pushState)) history.replaceState({url:document.location.href}, document.title, document.location.href); | |
}); | |
function loadPage(url, isHistory){ | |
$("#content").removeClass("flip"); | |
$.get(url + "?nostructure=yes", function(data) { | |
/*$("#content").fadeOut("speed",function(){ | |
$(this).html(data).fadeIn("speed") | |
});*/ | |
$("#content").html(data).addClass("flip"); | |
if(typeof isHistory == "undefined"){ | |
var hist = new Object() ; | |
hist.url = url; | |
if((window.history && history.pushState)) history.pushState(hist, "", url); | |
} | |
}); | |
} | |
window.onpopstate = function( event ){ | |
var data = event.state; | |
if(data && data.url){ | |
loadPage( data.url, false ); | |
} | |
}/* | |
* Tooltip - jQuery Plugin | |
* | |
* NOTICE OF LICENSE | |
* | |
* This source file is subject to the Open Software License (OSL 3.0) | |
* that is bundled with this package in the file LICENSE.txt. | |
* It is also available through the world-wide-web at this URL: | |
* http://opensource.org/licenses/osl-3.0.php | |
* If you did not receive a copy of the license and are unable to | |
* obtain it through the world-wide-web, please send an email | |
* to contact@parsimony-cms.com so we can send you a copy immediately. | |
* | |
* DISCLAIMER | |
* | |
* Do not edit or add to this file if you wish to upgrade Parsimony to newer | |
* versions in the future. If you wish to customize Parsimony for your | |
* needs please refer to http://www.parsimony.mobi for more information. | |
* | |
* @authors Julien Gras et Benoît Lorillot | |
* @copyright Julien Gras et Benoît Lorillot | |
* @version Release: 1.0 | |
* @category Tooltip - jQuery Plugin | |
* Requires: jQuery v1.4.2+ | |
* @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) | |
*/ | |
(function( $ ){ | |
var methods = { | |
init : function( options ) { | |
params = $.extend( { | |
position: '', | |
triangleWidth: 7 | |
}, options); | |
var tooltip = $("#parsimonyTooltip"); | |
if(tooltip.length ==0){ | |
$('<div id="parsimonyTooltip"><div class="tri"></div><div class="parsimonyTooltipContent"></div></div>').appendTo("body"); | |
tooltip = $("#parsimonyTooltip"); | |
} | |
var tooltipTriangle = $(".tri", tooltip); | |
var tooltipContent = $(".parsimonyTooltipContent", tooltip); | |
/* On mouse enter on the object */ | |
$(this.context).on("mouseenter.parsimonyTooltip", this.selector, function(e){ | |
var $this = $(this); | |
/* Get position of tooltip */ | |
var position = $(this).data("pos"); | |
/* Get offset position of tooltip */ | |
var off = $this.offset(); | |
/* get content to display */ | |
if($(this).data("tooltip").substring(0, 1) == "#") var content = $($(this).data("tooltip")).html(); | |
else var content = $(this).data("tooltip"); | |
/* Set data to display in tooltip */ | |
$(".parsimonyTooltipContent",tooltip).html(content); | |
var left, top = ''; | |
off = $this.offset(); | |
/* If no position set */ | |
if(typeof position == 'undefined'){ | |
if(params.position == ''){ | |
/* If no position set on data attributes */ | |
if(!$(this).data("pos")){ | |
/* If left space lower than right */ | |
var first = 'w'; | |
if( off.left <= $(window).width() - off.left + $this.width()){ | |
first = 'e'; | |
} | |
/* If top space lower than bottom */ | |
var second = 's'; | |
if( off.top <= $(window).height() - off.top + $this.height()){ | |
second = 'n'; | |
} | |
/* Set position of tooltip */ | |
$(this).data("pos",first + second); | |
} | |
}else{ | |
$(this).data("pos",params.position); | |
} | |
position = $(this).data("pos"); | |
} | |
/* Calculate pos of tooltip */ | |
switch(position.substring(0,1)){ | |
case 'n': | |
tooltipContent.css("margin","0 0 " + params.triangleWidth + "px 0"); | |
if(position.length==1) { | |
left = off.left + ($this.outerWidth()/2) - (tooltip.outerWidth()/2); | |
tooltipTriangle.attr('style','').css("left",(tooltip.outerWidth()/2 - params.triangleWidth) + "px"); | |
} | |
top = off.top - tooltip.outerHeight(); | |
break; | |
case 's': | |
tooltipContent.css("margin","" + params.triangleWidth + "px 0 0 0"); | |
if(position.length==1) { | |
left = off.left + ($this.outerWidth()/2) - (tooltip.outerWidth()/2); | |
tooltipTriangle.attr('style','').css("left",(tooltip.outerWidth()/2 - params.triangleWidth) + "px"); | |
} | |
top = off.top + $this.outerHeight(); | |
break; | |
case 'e': | |
tooltipContent.css("margin","0 0 0 " + params.triangleWidth + "px"); | |
left = off.left + $this.outerWidth(); | |
if(position.length==1) { | |
top = (off.top + $this.outerHeight()/2) - tooltip.outerHeight()/2; | |
tooltipTriangle.attr('style','').css("top",(tooltip.outerHeight()/2 - params.triangleWidth) + "px"); | |
} | |
break; | |
case 'w': | |
tooltipContent.css("margin","0 " + params.triangleWidth + "px 0 0"); | |
left = off.left - tooltip.outerWidth(); | |
if(position.length==1) { | |
top = (off.top + $this.outerHeight()/2) - tooltip.outerHeight()/2; | |
tooltipTriangle.attr('style','').css("top",(tooltip.outerHeight()/2 - params.triangleWidth) + "px"); | |
} | |
break; | |
} | |
if(position.length==2){ | |
switch(position.substring(1)){ | |
case 'e': | |
left = off.left + $this.outerWidth() - tooltip.outerWidth(); | |
tooltipTriangle.attr('style','').css("right",params.triangleWidth + "px"); | |
break; | |
case 'w': | |
left = off.left; | |
tooltipTriangle.attr('style','').css("left",params.triangleWidth + "px"); | |
break; | |
case 'n': | |
top = off.top; | |
tooltipTriangle.attr('style','').css("top",params.triangleWidth + "px"); | |
break; | |
case 's': | |
top = off.top + $this.outerHeight() - tooltip.outerHeight(); | |
tooltipTriangle.attr('style','').css("bottom",params.triangleWidth + "px"); | |
break; | |
} | |
} | |
/* Set class to the triangle of the tooltip */ | |
var classes = 'tri-' + position; | |
if(position.length==2)classes += ' tri-' + position.substring(0,1); | |
tooltipTriangle.attr('class','tri ' + classes); | |
/* Set triangle style */ | |
tooltipTriangle.css("border-width", params.triangleWidth + "px"); | |
/* Display tooltip */ | |
tooltip.stop().css({ | |
left:left, | |
top:top, | |
opacity:1, | |
display:"block" | |
}); | |
}); | |
/* On mouse leave the object */ | |
$(this.context).on("mouseleave.parsimonyTooltip", this.selector,function(e){ | |
/* We hide the tooltip */ | |
tooltip.fadeOut("speed"); | |
}); | |
}, | |
destroy : function( ) { | |
return this.each(function(){ | |
$(this).off('.parsimonyTooltip'); | |
}) | |
} | |
}; | |
$.fn.parsimonyTooltip = function( method ) { | |
if ( methods[method] ) { | |
return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 )); | |
} else if ( typeof method === 'object' || ! method ) { | |
return methods.init.apply( this, arguments ); | |
} else { | |
$.error( 'Method ' + method + ' does not exist on jQuery.parsimonyTooltip' ); | |
} | |
}; | |
})( jQuery );/** | |
* Parsimony | |
* | |
* NOTICE OF LICENSE | |
* | |
* This source file is subject to the Open Software License (OSL 3.0) | |
* that is bundled with this package in the file LICENSE.txt. | |
* It is also available through the world-wide-web at this URL: | |
* http://opensource.org/licenses/osl-3.0.php | |
* If you did not receive a copy of the license and are unable to | |
* obtain it through the world-wide-web, please send an email | |
* to contact@parsimony-cms.com so we can send you a copy immediately. | |
* | |
* DISCLAIMER | |
* | |
* Do not edit or add to this file if you wish to upgrade Parsimony to newer | |
* versions in the future. If you wish to customize Parsimony for your | |
* needs please refer to http://www.parsimony.mobi for more information. | |
* | |
* @authors Julien Gras et Benoît Lorillot | |
* @copyright Julien Gras et Benoît Lorillot | |
* @version Release: 1.0 | |
* @category Parsimony | |
* @package admin | |
* Requires: jQuery | |
* @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) | |
*/ | |
var ParsimonyAdmin = { | |
isInit : false, | |
currentWindow : "", | |
currentDocument : "", | |
currentBody : "", | |
currentMode : "", | |
inProgress : "", | |
typeProgress : "", | |
wysiwyg : "", | |
unsavedChanges : false, | |
plugins: [], | |
setPlugin : function(plugin){ | |
this.plugins.push(plugin); | |
}, | |
pluginDispatch : function(methodName){ | |
/* Call this method for all plugins */ | |
for (var i=0; i < this.plugins.length; i++) { | |
var plugin = this.plugins[i]; | |
eval("if(typeof plugin." + methodName + " != 'undefined') plugin." + methodName + "();"); | |
} | |
}, | |
initBefore : function(){ | |
$("#ajaxhack").on("load",function() { | |
var elmt = $(this).contents().find('body').text(); | |
if(elmt != "") ParsimonyAdmin.execResult(elmt); /* Firefox fix */ | |
}); | |
$("#dialog-id").keyup(function(){ | |
this.value = this.value.toLowerCase().replace(/[^a-z_]+/,""); | |
}); | |
$("#conf_box").on('click','#conf_box_wpopup', function(e){ | |
var action = $("#conf_box_form input[name=action]").val(); | |
$("#conf_box_form").attr('target','conf_box_content_popup' + action); | |
ParsimonyAdmin.closeConfBox(); | |
window.open ($("#conf_box_content_iframe").attr('src'),'conf_box_content_popup' + action,"width=" + $("#conf_box_content_iframe").width() + ",height=" + $("#conf_box_content_iframe").height()); | |
$("#conf_box_form").trigger("submit").attr('target','conf_box_content_iframe'); | |
}); | |
this.pluginDispatch("initBefore"); | |
}, | |
initIframe : function(){ | |
ParsimonyAdmin.iframe = document.getElementById("parsiframe"); | |
ParsimonyAdmin.$iframe = $(ParsimonyAdmin.iframe); | |
ParsimonyAdmin.currentWindow = ParsimonyAdmin.iframe.contentWindow; | |
ParsimonyAdmin.currentDocument = ParsimonyAdmin.currentWindow.document; | |
ParsimonyAdmin.$currentDocument = $(ParsimonyAdmin.currentDocument); | |
ParsimonyAdmin.currentBody = ParsimonyAdmin.currentDocument.body; | |
ParsimonyAdmin.$currentBody = $(ParsimonyAdmin.currentBody); | |
ParsimonyAdmin.inProgress = "container"; | |
ParsimonyAdmin.updateUI(); | |
ParsimonyAdmin.changeDeviceUpdate(); | |
/* Add Iframe style */ | |
var iframeStyle = document.createElement("link"); | |
iframeStyle.setAttribute("rel", "stylesheet"); | |
iframeStyle.setAttribute("type", "text/css"); | |
iframeStyle.setAttribute("href", BASE_PATH + "admin/iframe.css"); | |
ParsimonyAdmin.currentBody.insertBefore(iframeStyle, ParsimonyAdmin.currentBody.firstChild); | |
/* Init mode */ | |
var initialMode = ParsimonyAdmin.getCookie("mode"); | |
if(initialMode == 'edit'){ | |
$("#editMode").trigger('click'); | |
}else if(initialMode == 'preview'){ | |
$("#previewMode").trigger('click'); | |
}else{ | |
$("#creationMode").trigger('click'); | |
} | |
//override jQuery ready function to exec them with ajax portions | |
setTimeout('$.fn.ready = function(a) {ParsimonyAdmin.currentWindow.eval(" exec = " + a.toString()+";exec.call(window)");}',4000); | |
//document.getElementById("parsiframe").contentWindow.$.fn.ready = function(a) {a.call(document.getElementById("parsiframe").contentWindow);} | |
this.pluginDispatch("initIframe"); | |
} | |
, | |
loadCreationMode : function(){ | |
ParsimonyAdmin.unloadCreationMode(); | |
ParsimonyAdmin.$currentBody.on('click.creation','.traduction', function(e){ | |
e.trad = true; | |
ParsimonyAdmin.closeParsiadminMenu(); | |
ParsimonyAdmin.addTitleParsiadminMenu(t('Translation')); | |
ParsimonyAdmin.addOptionParsiadminMenu('<span class="ui-icon ui-icon-pencil floatleft"></span><a href="#" class="action" rel="getViewTranslation" params="key=' + $(this).data("key") + '" title="'+ t('Translation') +'">'+ t('Translate') +'</a>'); | |
}) | |
.on('click.creation','a', function(e){ | |
e.link = true; | |
e.preventDefault(); | |
if(e.trad != true) ParsimonyAdmin.closeParsiadminMenu(); | |
ParsimonyAdmin.addTitleParsiadminMenu("Link"); | |
ParsimonyAdmin.addOptionParsiadminMenu('<a href="#" onclick="ParsimonyAdmin.goToPage(\'' + $.trim($(this).text().replace("'","\\'")) + '\',\'' + $(this).attr('href') + '\');return false;"><span class="ui-icon ui-icon-extlink floatleft"></span>'+ t('Go to the link') +'</a>'); | |
}); | |
$(document).on("keypress.creation",'#dialog-id',function(e){ | |
var code = e.keyCode || e.which; | |
if(code == 13) { | |
$("#dialog-ok").trigger("click"); | |
} | |
}); | |
$(".parsieditinline",ParsimonyAdmin.currentBody).removeClass('usereditinline').attr("contenteditable", "false"); | |
this.pluginDispatch("loadCreationMode"); | |
}, | |
unloadCreationMode : function(){ | |
$(".selection-block",ParsimonyAdmin.currentBody).removeClass("selection-block"); | |
$(".selection-container",ParsimonyAdmin.currentBody).removeClass("selection-container"); | |
ParsimonyAdmin.closeParsiadminMenu(); | |
ParsimonyAdmin.$currentBody.off('.creation'); | |
this.pluginDispatch("unloadCreationMode"); | |
}, | |
loadEditMode : function(){ | |
$(".parsieditinline",ParsimonyAdmin.currentBody).addClass('usereditinline').attr("contenteditable", "true"); | |
/* Active edit behavior on WYSIWYG blocks */ | |
$(".wysiwyg",ParsimonyAdmin.currentBody).addClass('activeEdit').attr("contenteditable", "true") | |
/* Shortcut : Save on CTRL+S */ | |
.on("keydown.edit", function(e) { | |
if (e.keyCode == 83 && e.ctrlKey) { | |
e.preventDefault(); | |
ParsimonyAdmin.haveToSave = true; | |
} | |
}); | |
/* Init WYSIWYG editor */ | |
if(typeof ParsimonyAdmin.wysiwyg == "string"){ | |
ParsimonyAdmin.wysiwyg = new wysiwyg(); | |
ParsimonyAdmin.wysiwyg.init(".wysiwyg",["bold","underline","italic","justifyLeft","justifyCenter","justifyRight","justifyFull","strikeThrough","subscript","superscript","orderedList","unOrderedList","outdent","indent","removeFormat","createLink","unlink","formatBlock","fontName","fontSize","foreColor","hiliteColor","insertImage"], document, ParsimonyAdmin.currentDocument); | |
} | |
$(".HTML5editorToolbar").hide(); | |
/* Manage clicks on <a> in edit mode */ | |
ParsimonyAdmin.$currentDocument.on('click.edit','a', function(e){ | |
e.preventDefault(); | |
/*if($(this).attr("href").substring(0,1) != '#' && $(this).attr("href").substring(0,7) != 'http://' && $(".usereditinline",this).length == 0){ | |
ParsimonyAdmin.goToPage( $.trim($(this).text().replace("'","\\'")) , $(this).attr('href') ); | |
}*/ | |
}) | |
/* Hide WYSIWYG editor if focused element isn't a WYSIWYG block */ | |
.on('click.edit','.block',function(e){ | |
if(!$(this).hasClass("wysiwyg")) $(".HTML5editorToolbar").hide(); | |
else $(".HTML5editorToolbar").show(); | |
}); | |
/* Manage undo/redo on save toolbar */ | |
$("#toolbarEditMode").on('click.edit',".toolbarEditModeCommands",function(e){ | |
ParsimonyAdmin.currentDocument.execCommand(this.dataset.command, false, null); | |
}) | |
/* Save all WYSISYG blocks or contenteditable fields */ | |
.on('click.edit',"#toolbarEditModeSave",function(e){ | |
$(this).trigger("focus"); | |
/* We collect fresh data for WYSIWYG blocks */ | |
var changes = {}; | |
$(".wysiwyg.activeEdit",ParsimonyAdmin.currentBody).each(function(){ | |
if(this.dataset.modified) { | |
var module = ParsimonyAdmin.currentWindow.THEMEMODULE; | |
var theme = ParsimonyAdmin.currentWindow.THEME; | |
var idPage = ''; | |
if(ParsimonyAdmin.whereIAm(this.id) == 'page'){ | |
theme = ''; | |
module = ParsimonyAdmin.currentWindow.MODULE; | |
idPage = $(".container_page",ParsimonyAdmin.currentBody).data('page'); | |
} | |
changes[this.id] = {idPage:idPage,theme:theme,module:module,html:this.innerHTML}; | |
} | |
}); | |
/* We collect fresh data for contenteditable fields */ | |
$(".usereditinline",ParsimonyAdmin.currentBody).each(function(){ | |
if(this.dataset.modified) { | |
changes[this.dataset.property+this.dataset.id] = {module:this.dataset.module,entity:this.dataset.entity,fieldName:this.dataset.property,id:this.dataset.id,html:this.innerHTML}; | |
} | |
}); | |
/* We send fresh data to the serve to save it */ | |
$.post(BASE_PATH + 'admin/saveWYSIWYGS',{changes:JSON.stringify(changes)},function(data){ | |
ParsimonyAdmin.unsavedChanges = false | |
$("#toolbarEditMode").slideUp(); | |
$(".wysiwyg.activeEdit, .usereditinline").attr("data-modified","0"); | |
ParsimonyAdmin.execResult(data); | |
}); | |
}); | |
/* Manage save toolbar : show/hide for WYSISYG blocks or contenteditable fields */ | |
$(ParsimonyAdmin.currentBody).on('keyup.edit',".wysiwyg.activeEdit, .usereditinline",function(e){ | |
var undo = ParsimonyAdmin.currentDocument.queryCommandEnabled("undo"); | |
var redo = ParsimonyAdmin.currentDocument.queryCommandEnabled("redo"); | |
if(undo || redo){ | |
$("#toolbarEditMode").slideDown(); | |
if(undo) document.getElementById("toolbarEditModeUndo").style.display = "inline-block"; | |
else document.getElementById("toolbarEditModeUndo").style.display = "none"; | |
if(redo) document.getElementById("toolbarEditModeRedo").style.display = "inline-block"; | |
else document.getElementById("toolbarEditModeRedo").style.display = "none"; | |
}else{ | |
$("#toolbarEditMode").slideUp(); | |
} | |
if(ParsimonyAdmin.haveToSave){ | |
ParsimonyAdmin.haveToSave = false; | |
$("#toolbarEditModeSave").trigger("click"); | |
} | |
ParsimonyAdmin.unsavedChanges = true; | |
}); | |
this.pluginDispatch("loadEditMode"); | |
}, | |
unloadEditMode : function(){ | |
//if(typeof ParsimonyAdmin.wysiwyg == "object") ParsimonyAdmin.wysiwyg.disable(); | |
delete ParsimonyAdmin.wysiwyg; | |
ParsimonyAdmin.wysiwyg = ""; | |
ParsimonyAdmin.$currentDocument.add(ParsimonyAdmin.currentBody).off('.edit'); | |
$(".wysiwyg.activeEdit",ParsimonyAdmin.currentBody).off(); | |
$(".parsieditinline",ParsimonyAdmin.currentBody).removeClass('usereditinline').attr("contenteditable", "false"); | |
$(".wysiwyg",ParsimonyAdmin.currentBody).removeClass('activeEdit').attr("contenteditable", "false"); | |
$(".HTML5editorToolbar").hide(); | |
$("#toolbarEditMode").hide(); | |
this.pluginDispatch("unloadEditMode"); | |
}, | |
loadPreviewMode : function(){ | |
ParsimonyAdmin.$currentBody.on('click.preview','a', function(e){ | |
if($(this).attr("href").substring(0,1) != '#' && $(this).attr("href").substring(0,7) != 'http://'){ | |
e.preventDefault(); | |
ParsimonyAdmin.goToPage( $.trim($(this).text().replace("'","\\'")) , $(this).attr('href') ); | |
} | |
}); | |
ParsimonyAdmin.closeConfBox(); | |
this.pluginDispatch("loadPreviewMode"); | |
}, | |
unloadPreviewMode : function(){ | |
ParsimonyAdmin.$currentBody.off('.preview'); | |
this.pluginDispatch("unloadPreviewMode"); | |
}, | |
init : function(){ | |
ParsimonyAdmin.isInit = true; | |
$(document).add('#config_tree_selector').on('click',".action", function(e){ | |
var parentId = ''; | |
var inProgress = $("#treedom_" + ParsimonyAdmin.inProgress); | |
if(inProgress.length > 0){ | |
if(inProgress.parent().closest(".container").attr("id") == "treedom_content") parentId = inProgress.parent().closest("#treedom_content").data('page'); | |
else parentId = inProgress.parent().closest(".container").attr('id').replace("treedom_",""); | |
} | |
ParsimonyAdmin.displayConfBox(BASE_PATH + "admin/action",($(this).data('title') || $(this).attr('title') || $(this).data('tooltip')),"TOKEN=" + TOKEN + "&idBlock=" + ParsimonyAdmin.inProgress + "&parentBlock=" + parentId + "&typeProgress=" + ParsimonyAdmin.typeProgress + "&action=" + $(this).attr('rel') +"&IDPage=" + $(".container_page",ParsimonyAdmin.currentBody).data('page') +"&" + $(this).attr('params')); | |
e.preventDefault(); | |
}).on('click','#menu a',function(e){ | |
ParsimonyAdmin.closeParsiadminMenu(); | |
$('.cssPicker',ParsimonyAdmin.currentDocument).removeClass('cssPicker'); | |
}); | |
$("#menu").on("mouseenter",".CSSProps a",function(){ | |
$("#" + ParsimonyAdmin.inProgress + " " + this.dataset.css,ParsimonyAdmin.currentDocument).addClass('cssPicker'); | |
}).on("mouseout",".CSSProps a",function(){ | |
$('.cssPicker',ParsimonyAdmin.currentDocument).removeClass('cssPicker'); | |
}); | |
/* Init tooltip */ | |
$(".tooltip").parsimonyTooltip({ | |
triangleWidth:5 | |
}); | |
var timer = setInterval(function resizeIframe() { | |
if(document.getElementById("changeres").value == "max"){ | |
var height = ParsimonyAdmin.currentBody.getBoundingClientRect().bottom; | |
if(screen.height > height) height = screen.height - 35; | |
if(ParsimonyAdmin.iframe.style.height != height + "px"){ | |
ParsimonyAdmin.iframe.style.height = height + "px"; | |
document.getElementById("overlays").style.height = height + "px"; | |
} | |
} | |
}, 1000); | |
/* Shortcut : Save on CTRL+S */ | |
document.addEventListener("keydown", function(e) { | |
if (e.keyCode == 83 && e.ctrlKey) { | |
e.preventDefault(); | |
$("form",$('#conf_box_content_iframe').contents().find("body")).trigger("submit"); | |
} | |
}, false); | |
$(window).bind("beforeunload",function(event) { | |
if(ParsimonyAdmin.unsavedChanges) return t("You have unsaved changes"); | |
}); | |
ParsimonyAdmin.hideOverlay(); | |
ParsimonyAdmin.removeEmptyTextNodes(document.body); | |
this.pluginDispatch("init"); | |
} | |
, | |
goToPage : function (pageTitle,pageUrl, isHistory){ | |
ParsimonyAdmin.unloadCreationMode(); | |
ParsimonyAdmin.unloadEditMode(); | |
ParsimonyAdmin.unloadPreviewMode(); | |
if(pageUrl.substring(0,BASE_PATH.length) != BASE_PATH && pageUrl.substring(0,7) != "http://") pageUrl = BASE_PATH + pageUrl; | |
pageUrl = $.trim(pageUrl); | |
if(pageUrl.indexOf('?') > -1 && pageUrl.indexOf('?parsiframe=ok') == -1) pageUrl += '&parsiframe=ok'; | |
else pageUrl += '?parsiframe=ok'; | |
ParsimonyAdmin.currentDocument.title = pageTitle; | |
$('#parsiframe').attr('src', pageUrl); | |
return false; | |
}, | |
execResult : function (obj){ | |
if (obj.notification == null) | |
var obj = jQuery.parseJSON(obj); | |
if(obj.eval != null) eval(obj.eval); | |
var headParsiFrame = ParsimonyAdmin.$iframe.contents().find("head"); | |
if(obj.jsFiles){ | |
obj.jsFiles = jQuery.parseJSON(obj.jsFiles); | |
$.each(obj.jsFiles, function(index, url) { | |
if (!$("script[scr='" + url + "']",headParsiFrame).length) { | |
ParsimonyAdmin.$currentBody.append('<script type="text/javascript" src="' + url + '"></script>'); | |
} | |
}); | |
} | |
if(obj.CSSFiles){ | |
obj.CSSFiles = jQuery.parseJSON(obj.CSSFiles); | |
$.each(obj.CSSFiles, function(index, url) { | |
if (!$('link[href="' + url + '"]',headParsiFrame).length) { | |
ParsimonyAdmin.$currentBody.append('<link rel="stylesheet" type="text/css" href="' + url + '">'); | |
} | |
}); | |
} | |
if (obj.notification) | |
ParsimonyAdmin.notify(obj.notification,obj.notificationType); | |
}, | |
postData : function (url,params,callBack){ | |
$.post(url,params,function(data){ | |
callBack(data); | |
}); | |
}, | |
destroyBlock : function (){ | |
if(ParsimonyAdmin.inProgress != "container"){ | |
if(confirm(t('Do you really want to remove the block ') + ParsimonyAdmin.inProgress + ' ?')==true){ | |
ParsimonyAdmin.returnToShelter(); | |
if($("#treedom_" + ParsimonyAdmin.inProgress).parent().closest(".container").attr("id")=="treedom_content") var parentId = $("#treedom_" + ParsimonyAdmin.inProgress).parent().closest("#treedom_content").data('page'); | |
else var parentId = $("#treedom_" + ParsimonyAdmin.inProgress).parent().closest(".container").attr('id').replace("treedom_",""); | |
ParsimonyAdmin.postData(BASE_PATH + "admin/removeBlock",{ | |
TOKEN: TOKEN , | |
idBlock:ParsimonyAdmin.inProgress, | |
parentBlock: parentId , | |
typeProgress:ParsimonyAdmin.typeProgress, | |
IDPage:($(".container_page",ParsimonyAdmin.currentBody).data('page') || $(".sublist.selected").attr("id").replace("page_","")) | |
} ,function(data){ | |
ParsimonyAdmin.execResult(data); | |
ParsimonyAdmin.returnToShelter(); | |
ParsimonyAdmin.updateUI(); | |
}); | |
} | |
} | |
}, | |
addBlock : function (idBlock, contentBlock, idBlockAfter){ | |
if($( "#" + idBlockAfter ,ParsimonyAdmin.currentBody).parent().hasClass("container")){ | |
$( "#" + idBlockAfter ,ParsimonyAdmin.currentBody).after(contentBlock); | |
ParsimonyAdmin.returnToShelter(); | |
}else { | |
var block = $( "#" + idBlockAfter ,ParsimonyAdmin.currentBody).closest(".container"); | |
ParsimonyAdmin.returnToShelter(); | |
$(".dropInContainer:first",block).remove(); | |
if(block.get(0).id == 'container' && block.children(".block").length==1) block.prepend(contentBlock); | |
else block.append(contentBlock); | |
} | |
$("#" + idBlock,ParsimonyAdmin.currentBody ).trigger("click"); | |
var offset = $("#" + idBlock,ParsimonyAdmin.currentBody ).offset(); | |
ParsimonyAdmin.openParsiadminMenu(offset.left + 30,offset.top + 30); | |
}, | |
moveMyBlock : function (idBlock, idBlockAfter){ | |
if($( "#" + idBlockAfter ,ParsimonyAdmin.currentBody).parent().hasClass("container")){ | |
$( "#" + idBlockAfter ,ParsimonyAdmin.currentBody).after( $("#" + idBlock,ParsimonyAdmin.currentBody) ); | |
ParsimonyAdmin.returnToShelter(); | |
}else { | |
var block = $( "#" + idBlockAfter ,ParsimonyAdmin.currentBody).parent().parent().parent(); | |
ParsimonyAdmin.returnToShelter(); | |
block.html($("#" + idBlock,ParsimonyAdmin.currentBody ) ); | |
} | |
}, | |
selectBlock : function (idBlock){ | |
var blockTreeObj = document.getElementById("treedom_" + idBlock); | |
var block = ParsimonyAdmin.currentDocument.getElementById(idBlock); | |
var oldSelection = ParsimonyAdmin.currentDocument.querySelector(".selection-block"); | |
var oldSelectionTree = document.querySelector(".currentDOM"); | |
var config_tree_selector = document.getElementById("config_tree_selector"); | |
oldSelection && oldSelection.classList.remove("selection-block"); | |
oldSelectionTree && oldSelectionTree.classList.remove("currentDOM"); | |
ParsimonyAdmin.inProgress = idBlock; | |
ParsimonyAdmin.typeProgress = ParsimonyAdmin.whereIAm(ParsimonyAdmin.inProgress); | |
block && block.classList.add("selection-block"); | |
if(blockTreeObj) blockTreeObj.classList.add("currentDOM"); | |
if(idBlock == "container" || (block && block.classList.contains("container_page"))) config_tree_selector.classList.add("restrict"); | |
else document.getElementById("config_tree_selector").classList.remove("restrict"); | |
config_tree_selector.style.display = "block"; | |
if(blockTreeObj) blockTreeObj.insertBefore(config_tree_selector, blockTreeObj.firstChild); | |
}, | |
whereIAm : function (idBlock){ | |
var where = "theme"; | |
var elmt = ParsimonyAdmin.currentDocument.getElementById(idBlock); | |
if(elmt){ | |
if(elmt.compareDocumentPosition(ParsimonyAdmin.currentDocument.getElementById("content")) == 10){ | |
where = "page"; | |
} | |
}else{ | |
if(idBlock == 'dropInTree') var obj = document.getElementById("dropInTree"); | |
else var obj = document.getElementById("treedom_" + idBlock); | |
if(obj.compareDocumentPosition(document.getElementById("treedom_content")) == 10){ | |
where = "page"; | |
} | |
} | |
return where; | |
}, | |
showOverlay : function (opacity){ | |
if(typeof opacity== "undefined") opacity=1; | |
$( "#conf_box_overlay").css('opacity',opacity).show(); | |
}, | |
hideOverlay : function (){ | |
document.getElementById("conf_box_overlay").style.display = "none"; | |
}, | |
displayConfBox : function (url,title,params,modal){ | |
$("#conf_box_load").show(); | |
$("#conf_box,#conf_box_content" ).removeAttr("style"); | |
$("#conf_box").css("visibility","hidden").show(); | |
if(typeof modal == "undefined" || modal == true) ParsimonyAdmin.showOverlay(); | |
ParsimonyAdmin.setConfBoxTitle(title); | |
if(url.substring(0,1) != "#"){ | |
ParsimonyAdmin.returnToShelter(); | |
$("#conf_box_form").attr("action",url).empty(); | |
if(typeof params != "undefined"){ | |
var vars = params.split(/&/); | |
for (var i=0; i< vars.length; i++) { | |
var myvar = vars[i].split(/=/); | |
$("#conf_box_form").append('<input type="hidden" name="' + myvar[0] + '" value="' + myvar[1] + '">'); | |
} | |
} | |
$("#conf_box_form").append('<input type="hidden" name="popup" value="yes">').trigger("submit"); | |
$("#conf_box_content_iframe").show(); | |
$("#conf_box_content_inline").hide(); | |
}else{ | |
$("#shelter").append($("#conf_box_content_inline").html()); | |
$("#conf_box_content_inline").show().append($(url)); | |
$("#conf_box_content_iframe").hide(); | |
$(url).show(); | |
document.getElementById("conf_box").style.visibility = "visible"; | |
} | |
}, | |
closeConfBox : function (){ | |
$("#conf_box").hide(); | |
ParsimonyAdmin.hideOverlay(); | |
$("#conf_box_title").empty(); | |
$("#conf_box_content_iframe").attr("src","about:blank"); | |
}, | |
resizeConfBox : function(){ | |
var iframe = document.getElementById("conf_box_content_iframe"); | |
iframe.removeAttribute("style"); | |
var doc = iframe.contentDocument; | |
document.getElementById("conf_box_load").style.display = "none"; | |
if(doc.location.href != "about:blank"){ | |
var elmt = $(".adminzone",doc)[0] || $("body",doc)[0] ; | |
var height = $(".adminzonefooter",doc).length > 0 ? ( elmt.scrollHeight + 40) : elmt.scrollHeight ; | |
iframe.style.cssText = "width:" + elmt.scrollWidth + "px;height:" + height + "px"; | |
} | |
document.getElementById("conf_box").style.visibility = "visible"; | |
}, | |
setConfBoxTitle : function (title){ | |
$("#conf_box_title").html(title); | |
}, | |
returnToShelter : function () { | |
$("#dropInPage",ParsimonyAdmin.currentBody).prependTo($("#shelter")); | |
$("#dropInTree").prependTo($("#shelter")); | |
}, | |
changeDevice : function (device) { | |
ParsimonyAdmin.setCookie("device",device,999); | |
THEMETYPE = device; | |
$('#changeres').val('');// to change res. | |
ParsimonyAdmin.changeDeviceUpdate(device); | |
$("#info_themetype").text(device); | |
ParsimonyAdmin.$iframe.attr("src", ParsimonyAdmin.$iframe.attr("src")); | |
ParsimonyAdmin.loadBlock('panelblocks'); | |
}, | |
changeDeviceUpdate : function () { | |
var select = ''; | |
var nb = 0; | |
var changeres = $('#changeres'); | |
$.each($.parseJSON(resultions[THEMETYPE]), function(i,item){ | |
if(changeres[0].value == "" && nb == 0) changeres.val(i).trigger('change'); | |
select += '<li><a href="#" onclick="$(\'#changeres\').val(\'' + i + '\').trigger(\'change\');">' + item + ' (' + i + ')</a></li>'; | |
nb++; | |
}); | |
$("#currentRes").text(changeres[0].value); | |
$('#listres').html(select);$('#currentRes').css("position","relative"); | |
}, | |
changeLocale : function (locale) { | |
ParsimonyAdmin.setCookie("locale",locale,999); | |
window.location.reload(); | |
}, | |
setCookie : function (name,value,days) { | |
if (days) { | |
var date = new Date(); | |
date.setTime(date.getTime()+(days*24*60*60*1000)); | |
var expires = "; expires="+date.toGMTString(); | |
} | |
else var expires = ""; | |
document.cookie = name+"="+value+expires+"; path=/"; | |
}, | |
getCookie: function (name){ | |
var i, x, y, cookies = document.cookie.split(";"); | |
for (i=0; i < cookies.length;i++){ | |
x = cookies[i].substr(0,cookies[i].indexOf("=")); | |
y = cookies[i].substr(cookies[i].indexOf("=") + 1); | |
x = x.replace(/^\s+|\s+$/g,""); | |
if (x==name) return unescape(y); | |
} | |
}, | |
notify : function (message,type) { | |
$("#notify").appendTo("body").attr("class","").addClass(type).html(message).fadeIn("normal").delay(4000).fadeOut("slow"); | |
}, | |
openParsiadminMenu : function (x,y) { | |
var off = ParsimonyAdmin.$iframe.offset(); | |
$("#menu").appendTo("body").css({ | |
"top":(y + off.top), | |
"left":(x + off.left) | |
}); | |
}, | |
closeParsiadminMenu : function () { | |
$("#menu").appendTo($("#shelter")).find(".options").empty(); | |
}, | |
addTitleParsiadminMenu : function (title) { | |
$("#menu .options").append('<h5>' + title + '</h5>'); | |
}, | |
addOptionParsiadminMenu : function (option) { | |
$("#menu .options").append('<div class="option">' + option + '</div>'); | |
}, | |
/*reloadIframe : function (){ | |
$.get("index.html?parsiframe=ok", | |
function(data){ | |
ParsimonyAdmin.$iframe.contents().find("html").replaceWith(data); | |
}); | |
},*/ | |
updateUI : function (tree){ | |
$(".dropInContainer",ParsimonyAdmin.currentBody).remove(); | |
if(tree!=false) { | |
$("#config_tree_selector").hide().prependTo("#right_sidebar"); | |
ParsimonyAdmin.loadBlock('tree'); | |
ParsimonyAdmin.loadBlock('tree',{}, function(){ | |
if(ParsimonyAdmin.inProgress != "container") $("#treedom_" + ParsimonyAdmin.inProgress).trigger("click"); | |
}); | |
} | |
$(".container",ParsimonyAdmin.currentBody).each(function(){ | |
if($(this).find('.block:not("#content")').length==0) { | |
$(this).prepend('<div class="dropInContainer"><div class="dropInContainerChild">Id #' + $(this).get(0).id + ". " + t("Drop the blocks in this space") + '</div></div>'); | |
}else $(".dropInContainerChild:first",this).remove(); | |
}); | |
}, | |
setCreationMode : function (){ | |
$('.sidebar,.panelblocks,.creation').show(); | |
$(".panelblocks").removeClass("active"); | |
$(".panelmodules").addClass("active"); | |
ParsimonyAdmin.setMode("creation"); | |
}, | |
setEditMode : function (){ | |
$('#right_sidebar,.panelblocks,.creation').hide(); | |
$('#left_sidebar').show(); | |
$(".panelmodules").addClass("active"); | |
ParsimonyAdmin.setMode("edit"); | |
}, | |
setPreviewMode : function (){ | |
$('.sidebar').hide(); | |
ParsimonyAdmin.setMode("preview"); | |
}, | |
setMode : function (mode){ | |
$("body").removeClass("previewMode modeMode creationMode").addClass(mode + "Mode"); | |
$(".switchMode").removeClass("selected"); | |
$("#" + mode + "Mode").addClass("selected"); | |
/* Unload current mode if exists */ | |
if(ParsimonyAdmin.currentMode.length > 0){ | |
var captitalizeOldMode = ParsimonyAdmin.currentMode[0].toUpperCase() + ParsimonyAdmin.currentMode.substring(1); | |
ParsimonyAdmin["unload" + captitalizeOldMode + "Mode"](); | |
} | |
ParsimonyAdmin.currentMode = mode; | |
var captitalizeNewMode = mode[0].toUpperCase() + mode.substring(1); | |
/* Load new mode */ | |
ParsimonyAdmin["load" + captitalizeNewMode + "Mode"](); | |
ParsimonyAdmin.setCookie("mode",mode,999); | |
}, | |
loadBlock: function(id, params, func){ | |
if(!params) params = {}; | |
$.get(window.location.href.toLocaleString(),params , function(data) { | |
$('#' + id).html($("<div>").append(data.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, "")).find("#" + id).html()); | |
},func); | |
//$('#' + id).load(window.location.toLocaleString() + " #" + id + " > div"); | |
}, | |
removeEmptyTextNodes: function(elem){ | |
var children = elem.childNodes; | |
var child; | |
var len = children.length; | |
var whitespace = /^\s*$/; | |
for(var i = 0; i < len; i++){ | |
child = children[i]; | |
if(child.nodeType == 3){ | |
if(whitespace.test(child.nodeValue)){ | |
elem.removeChild(child); | |
i--; | |
len--; | |
} | |
}else if(child.nodeType == 1){ | |
this.removeEmptyTextNodes(child); | |
} | |
} | |
} | |
} | |
var $lang = new Array; | |
function t(val){ | |
if($lang[val]){ | |
return $lang[val]; | |
}else{ | |
return val; | |
} | |
} | |
function block_calendar() { | |
block.call(this); | |
this.name = "calendar"; | |
this.stylableElements = { | |
"calendar":".calendar", | |
"calendar inner":".calendarInner", | |
"a day":".day", | |
"a link day":".day a", | |
"a week":".week", | |
"month title":".monthTitle", | |
"week title":".weekTitle", | |
"days which has posts":".day.hasposts", | |
"days link which has post":".day.hasposts a", | |
"days which are in this month":".day.thisMonth", | |
"days which aren't in this month":".day.out", | |
"calendar navigation":".calendarNav", | |
"calendar navigation links":".calendarNav a", | |
"link previous month":".prevMonth", | |
"link next month":".nextMonth" | |
} | |
}function block_categories() { | |
block.call(this); | |
this.name = "categories"; | |
this.stylableElements = { | |
"categories list":"ul", | |
"categories items":"li", | |
"links":"a" | |
} | |
}function block_connexion() { | |
block.call(this); | |
this.name = "connexion"; | |
this.stylableElements = { | |
"My account title":"h3", | |
"user info box":".userInfo", | |
"logout link":".logout", | |
"form":"form", | |
"labels":"label", | |
"login part":".connectLogin", | |
"login input":".connectLogin input", | |
"login label":".connectLogin label", | |
"password part":".connectPassword", | |
"password input":".connectPassword input", | |
"password label":".connectPassword label", | |
"button part":".connectSubmit", | |
"submit button":".connectSubmit input", | |
"error message":".error" | |
} | |
}function block_contactform() { | |
block.call(this); | |
this.name = "contactform"; | |
this.stylableElements = { | |
"form":"form", | |
"form parts":"form div", | |
"labels":"label", | |
"inputs":"input", | |
"textareas":"textarea", | |
"selects":"select", | |
"submit button":".submit", | |
"notification":".notify", | |
"positive notification":".positive", | |
"negative notification":".negative" | |
} | |
}function block_formadd() { | |
block.call(this); | |
this.name = "formadd"; | |
this.stylableElements = { | |
"form":"form", | |
"form parts":"form div", | |
"labels":"label", | |
"inputs":"input", | |
"textareas":"textarea", | |
"selects":"select", | |
"submit button":".submit", | |
"notification":".notify", | |
"positive notification":".positive", | |
"negative notification":".negative" | |
} | |
}function block_gallery() { | |
block.call(this); | |
this.name = "gallery"; | |
this.stylableElements = { | |
"slider":".slider", | |
"sldies container":".slides_container", | |
"each slide":".slide", | |
"slide links":".slide a", | |
"images":".slide img", | |
"captions container":".caption", | |
"caption":".caption p", | |
"previous button":".prev", | |
"next button":".next", | |
"pagination container":".paginationSlides", | |
"pagination buttons":".paginationSlides li", | |
"pagination links":".paginationSlides a", | |
"pagination current slide":".paginationSlides li.current a"} | |
}function block_menu() { | |
block.call(this); | |
this.name = "menu"; | |
this.stylableElements = {"Menu container":".parsimenu", | |
"Menu Items":".parsimenu li", | |
"Menu Links Items":".parsimenu a", | |
"Menu Active Items":".parsimenu .current a", | |
"Menu Items Hover":".parsimenu li:hover > a", | |
"Sub Menu Links Hover":".parsimenu ul a:hover", | |
"Sub Menu":".parsimenu ul", | |
"Sub Menu Items":".parsimenu ul li", | |
"Sub Menu links":".parsimenu ul a", | |
"Sub Sub Menu Items":".parsimenu ul ul"} | |
}function block_query() { | |
block.call(this); | |
this.name = "query"; | |
this.stylableElements = { | |
"each line":".itemscope", | |
"each property":".itemprop", | |
"pagination links":".pagination a", | |
"active pagination links":".pagination a.active", | |
"pagination links hover":".pagination a:hover", | |
"no results message":".noResults" | |
} | |
}function block_recentposts() { | |
block.call(this); | |
this.name = "recentposts"; | |
this.stylableElements = { | |
"posts list":"ul", | |
"posts items":"li", | |
"links":"a" | |
} | |
}function block_tags() { | |
block.call(this); | |
this.name = "tags"; | |
this.stylableElements = { | |
"tags list":"ul", | |
"tags items":"li", | |
"links":"a", | |
"x small tags":".xsmall", | |
"small tags":".small", | |
"medium tags":".medium", | |
"large tags":".large", | |
"x large tags":".xlarge" | |
} | |
}/* | |
DHTML Color Picker : v1.1 : 2010/12/28 | |
--------------------------------------- | |
http://www.colorjack.com/software/dhtml+color+picker.html | |
Native support: Firefox 2+, Safari 3+, Opera 9+, Google Chrome, IE9+ | |
ChromeFrame supprt: IE7+ | |
*/ | |
if(window.Color == undefined) Color = {}; | |
Color.Picker = function (props) { | |
/// loading properties | |
if (typeof(props) == "undefined") props = {}; | |
this.callback = props.callback; // bind custom function | |
this.hue = props.hue || 0; // 0-360 | |
this.sat = props.sat || 0; // 0-100 | |
this.val = props.val || 100; // 0-100 | |
this.element = props.element || document.body; | |
this.size = 165; // size of colorpicker | |
this.margin = 10; // margins on colorpicker | |
this.offset = this.margin / 2; | |
this.hueWidth = 30; | |
/// creating colorpicker (header) | |
var plugin = document.createElement("div"); | |
plugin.id = "colorjack_square"; | |
plugin.style.cssText = "height: " + (this.size + this.margin * 2) + "px"; | |
// shows current selected color as the background of this box | |
var hexBox = document.createElement("div"); | |
hexBox.className = "hexBox"; | |
plugin.appendChild(hexBox); | |
// shows current selected color as HEX string | |
var hexString = document.createElement("div"); | |
hexString.className = "hexString"; | |
plugin.appendChild(hexString); | |
// close the plugin | |
var hexClose = document.createElement("div"); | |
hexClose.className = "hexClose"; | |
hexClose.textContent = "X"; | |
hexClose.onclick = function () { // close colorpicker | |
plugin.style.display = (plugin.style.display == "none") ? "block" : "none"; | |
}; | |
plugin.appendChild(hexClose); | |
plugin.appendChild(document.createElement("br")); | |
/// creating media-resources | |
var arrows = document.createElement("canvas"); | |
arrows.width = 40; | |
arrows.height = 5; | |
(function () { // creating arrows | |
var ctx = arrows.getContext("2d"); | |
var width = 3; | |
var height = 5; | |
var size = 9; | |
var top = -size / 4; | |
var left = 1; | |
for (var n = 0; n < 20; n++) { // multiply anti-aliasing | |
ctx.beginPath(); | |
ctx.fillStyle = "#FFF"; | |
ctx.moveTo(left + size / 4, size / 2 + top); | |
ctx.lineTo(left, size / 4 + top); | |
ctx.lineTo(left, size / 4 * 3 + top); | |
ctx.fill(); | |
} | |
ctx.translate(width, height); | |
ctx.rotate(180 * Math.PI / 180); // rotate arrows | |
ctx.drawImage(arrows, -29, 0); | |
ctx.translate(-width, -height); | |
})(); | |
var circle = document.createElement("canvas"); | |
circle.width = 10; | |
circle.height = 10; | |
(function () { // creating circle-selection | |
var ctx = circle.getContext("2d"); | |
ctx.lineWidth = 1; | |
ctx.beginPath(); | |
var x = circle.width / 2; | |
var y = circle.width / 2; | |
ctx.arc(x, y, 4.5, 0, Math.PI * 2, true); | |
ctx.strokeStyle = '#000'; | |
ctx.stroke(); | |
ctx.beginPath(); | |
ctx.arc(x, y, 3.5, 0, Math.PI * 2, true); | |
ctx.strokeStyle = '#FFF'; | |
ctx.stroke(); | |
})(); | |
/// creating colorpicker sliders | |
var canvas = document.createElement("canvas"); | |
var ctx = canvas.getContext("2d"); | |
canvas.style.cssText = "position: absolute; top: 19px; left: " + (this.offset) + "px;"; | |
canvas.width = this.size + this.hueWidth + this.margin; | |
canvas.height = this.size + this.margin; | |
plugin.appendChild(canvas); | |
plugin.onmousemove = | |
plugin.onmousedown = function (e) { | |
var down = (e.type == "mousedown"); | |
var offset = that.margin / 2; | |
var abs = abPos(canvas); | |
var x0 = (e.pageX - abs.x) - offset; | |
var y0 = (e.pageY - abs.y) - offset; | |
var x = clamp(x0, 0, canvas.width); | |
var y = clamp(y0, 0, that.size); | |
if (e.target.className == "hexString") { | |
plugin.style.cursor = "text"; | |
return; // allow selection of HEX | |
} else if (x != x0 || y != y0) { // move colorpicker | |
plugin.style.cursor = "move"; | |
if (down) dragElement({ | |
type: "difference", | |
event: e, | |
element: plugin, | |
callback: function (coords, state) { | |
plugin.style.left = coords.x + "px"; | |
plugin.style.top = coords.y + "px"; | |
} | |
}); | |
} else if (x <= that.size) { // saturation-value selection | |
plugin.style.cursor = "crosshair"; | |
if (down) dragElement({ | |
type: "relative", | |
event: e, | |
element: canvas, | |
callback: function (coords, state) { | |
var x = clamp(coords.x - that.offset, 0, that.size); | |
var y = clamp(coords.y - that.offset, 0, that.size); | |
that.sat = x / that.size * 100; // scale saturation | |
that.val = 100 - (y / that.size * 100); // scale value | |
that.drawSample(); | |
} | |
}); | |
} else if (x > that.size + that.margin && x <= that.size + that.hueWidth) { // hue selection | |
plugin.style.cursor = "crosshair"; | |
if (down) dragElement({ | |
type: "relative", | |
event: e, | |
element: canvas, | |
callback: function (coords, state) { | |
var y = clamp(coords.y - that.offset, 0, that.size); | |
that.hue = Math.min(1, y / that.size) * 360; | |
that.drawSample(); | |
} | |
}); | |
} else { // margin between hue/saturation-value | |
plugin.style.cursor = "default"; | |
} | |
return false; // prevent selection | |
}; | |
// appending to element | |
this.element.appendChild(plugin); | |
/// helper functions | |
var that = this; | |
this.el = plugin; | |
this.drawSample = function () { | |
// clearing canvas | |
ctx.clearRect(0, 0, canvas.width, canvas.height) | |
that.drawSquare(); | |
that.drawHue(); | |
// retrieving hex-code | |
var hex = Color.HSV_HEX({ | |
H: that.hue, | |
S: that.sat, | |
V: that.val | |
}); | |
// display hex string | |
hexString.textContent = hex.toUpperCase(); | |
// display background color | |
hexBox.style.backgroundColor = "#" + hex; | |
// arrow-selection | |
var y = (that.hue / 362) * that.size - 2; | |
ctx.drawImage(arrows, that.size + that.offset + 4, Math.round(y) + that.offset); | |
// circle-selection | |
var x = that.sat / 100 * that.size; | |
var y = (1 - (that.val / 100)) * that.size; | |
x = x - circle.width / 2; | |
y = y - circle.height / 2; | |
ctx.drawImage(circle, Math.round(x) + that.offset, Math.round(y) + that.offset); | |
// run custom code | |
if (that.callback) that.callback(hex); | |
}; | |
this.drawSquare = function () { | |
// retrieving hex-code | |
var hex = Color.HSV_HEX({ | |
H: that.hue, | |
S: 100, | |
V: 100 | |
}); | |
var offset = that.offset; | |
var size = that.size; | |
// drawing color | |
ctx.fillStyle = "#" + hex; | |
ctx.fillRect(offset, offset, size, size); | |
// overlaying saturation | |
var gradient = ctx.createLinearGradient(offset, offset, size + offset, 0); | |
gradient.addColorStop(0, "rgba(255, 255, 255, 1)"); | |
gradient.addColorStop(1, "rgba(255, 255, 255, 0)"); | |
ctx.fillStyle = gradient; | |
ctx.fillRect(offset, offset, size, size); | |
// overlaying value | |
var gradient = ctx.createLinearGradient(offset, offset, 0, size + offset); | |
gradient.addColorStop(0, "rgba(0, 0, 0, 0)"); | |
gradient.addColorStop(1, "rgba(0, 0, 0, 1)"); | |
ctx.fillStyle = gradient; | |
ctx.fillRect(offset, offset, size, size); | |
// drawing outer bounds | |
ctx.strokeStyle = "rgba(255,255,255,0.15)"; | |
ctx.strokeRect(offset+0.5, offset+0.5, size-1, size-1); | |
}; | |
this.drawHue = function () { | |
// drawing hue selector | |
var left = that.size + that.margin + that.offset; | |
var gradient = ctx.createLinearGradient(0, 0, 0, that.size); | |
gradient.addColorStop(0, "rgba(255, 0, 0, 1)"); | |
gradient.addColorStop(0.15, "rgba(255, 255, 0, 1)"); | |
gradient.addColorStop(0.3, "rgba(0, 255, 0, 1)"); | |
gradient.addColorStop(0.5, "rgba(0, 255, 255, 1)"); | |
gradient.addColorStop(0.65, "rgba(0, 0, 255, 1)"); | |
gradient.addColorStop(0.8, "rgba(255, 0, 255, 1)"); | |
gradient.addColorStop(1, "rgba(255, 0, 0, 1)"); | |
ctx.fillStyle = gradient; | |
ctx.fillRect(left, that.offset, 20, that.size); | |
// drawing outer bounds | |
ctx.strokeStyle = "rgba(255,255,255,0.2)"; | |
ctx.strokeRect(left + 0.5, that.offset + 0.5, 19, that.size-1); | |
}; | |
this.destory = function () { | |
document.body.removeChild(plugin); | |
for (var key in that) delete that[key]; | |
}; | |
// drawing color selection | |
this.drawSample(); | |
return this; | |
}; | |
/* GLOBALS LIBRARY */ | |
var dragElement = function(props) { | |
function mouseMove(e, state) { | |
if (typeof(state) == "undefined") state = "move"; | |
var coord = XY(e); | |
switch (props.type) { | |
case "difference": | |
props.callback({ | |
x: coord.x + oX - eX, | |
y: coord.y + oY - eY | |
}, state); | |
break; | |
case "relative": | |
props.callback({ | |
x: coord.x - oX, | |
y: coord.y - oY | |
}, state); | |
break; | |
default: // "absolute" | |
props.callback({ | |
x: coord.x, | |
y: coord.y | |
}, state); | |
break; | |
} | |
}; | |
function mouseUp(e) { | |
window.removeEventListener("mousemove", mouseMove, false); | |
window.removeEventListener("mouseup", mouseUp, false); | |
mouseMove(e, "up"); | |
}; | |
// current element position | |
var el = props.element; | |
var origin = abPos(el); | |
var oX = origin.x; | |
var oY = origin.y; | |
// current mouse position | |
var e = props.event; | |
var coord = XY(e); | |
var eX = coord.x; | |
var eY = coord.y; | |
// events | |
window.addEventListener("mousemove", mouseMove, false); | |
window.addEventListener("mouseup", mouseUp, false); | |
mouseMove(e, "down"); // run mouse-down | |
}; | |
var clamp = function(n, min, max) { | |
return (n < min) ? min : ((n > max) ? max : n); | |
}; | |
var XY = window.ActiveXObject ? // fix XY to work in various browsers | |
function(event) { | |
return { | |
x: event.clientX + document.documentElement.scrollLeft, | |
y: event.clientY + document.documentElement.scrollTop | |
}; | |
} : function(event) { | |
return { | |
x: event.pageX, | |
y: event.pageY | |
}; | |
}; | |
var abPos = function(o) { | |
o = typeof(o) == 'object' ? o : $(o); | |
var offset = { x: 0, y: 0 }; | |
while(o != null) { | |
offset.x += o.offsetLeft; | |
offset.y += o.offsetTop; | |
o = o.offsetParent; | |
}; | |
return offset; | |
}; | |
/* COLOR LIBRARY */ | |
Color.HEX_STRING = function (o) { | |
var z = o.toString(16); | |
var n = z.length; | |
while (n < 6) { | |
z = '0' + z; | |
n ++; | |
} | |
return z; | |
}; | |
Color.RGB_HEX = function (o) { | |
return o.R << 16 | o.G << 8 | o.B; | |
}; | |
Color.HSV_RGB = function (o) { | |
var H = o.H / 360, | |
S = o.S / 100, | |
V = o.V / 100, | |
R, G, B; | |
var A, B, C, D; | |
if (S == 0) { | |
R = G = B = Math.round(V * 255); | |
} else { | |
if (H >= 1) H = 0; | |
H = 6 * H; | |
D = H - Math.floor(H); | |
A = Math.round(255 * V * (1 - S)); | |
B = Math.round(255 * V * (1 - (S * D))); | |
C = Math.round(255 * V * (1 - (S * (1 - D)))); | |
V = Math.round(255 * V); | |
switch (Math.floor(H)) { | |
case 0: | |
R = V; | |
G = C; | |
B = A; | |
break; | |
case 1: | |
R = B; | |
G = V; | |
B = A; | |
break; | |
case 2: | |
R = A; | |
G = V; | |
B = C; | |
break; | |
case 3: | |
R = A; | |
G = B; | |
B = V; | |
break; | |
case 4: | |
R = C; | |
G = A; | |
B = V; | |
break; | |
case 5: | |
R = V; | |
G = A; | |
B = B; | |
break; | |
} | |
} | |
return { | |
R: R, | |
G: G, | |
B: B | |
}; | |
}; | |
Color.HSV_HEX = function (o) { | |
return Color.HEX_STRING(Color.RGB_HEX(Color.HSV_RGB(o))); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment