1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/browser/devtools/markupview/html-editor.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,186 @@ 1.4 +/* vim:set ts=2 sw=2 sts=2 et tw=80: 1.5 + * This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + "use strict"; 1.9 + 1.10 +const {Cu} = require("chrome"); 1.11 +const Editor = require("devtools/sourceeditor/editor"); 1.12 +Cu.import("resource://gre/modules/Services.jsm"); 1.13 +Cu.import("resource://gre/modules/devtools/event-emitter.js"); 1.14 + 1.15 +exports.HTMLEditor = HTMLEditor; 1.16 + 1.17 +function ctrl(k) { 1.18 + return (Services.appinfo.OS == "Darwin" ? "Cmd-" : "Ctrl-") + k; 1.19 +} 1.20 +function stopPropagation(e) { 1.21 + e.stopPropagation(); 1.22 +} 1.23 +/** 1.24 + * A wrapper around the Editor component, that allows editing of HTML. 1.25 + * 1.26 + * The main functionality this provides around the Editor is the ability 1.27 + * to show/hide/position an editor inplace. It only appends once to the 1.28 + * body, and uses CSS to position the editor. The reason it is done this 1.29 + * way is that the editor is loaded in an iframe, and calling appendChild 1.30 + * causes it to reload. 1.31 + * 1.32 + * Meant to be embedded inside of an HTML page, as in markup-view.xhtml. 1.33 + * 1.34 + * @param HTMLDocument htmlDocument 1.35 + * The document to attach the editor to. Will also use this 1.36 + * document as a basis for listening resize events. 1.37 + */ 1.38 +function HTMLEditor(htmlDocument) 1.39 +{ 1.40 + this.doc = htmlDocument; 1.41 + this.container = this.doc.createElement("div"); 1.42 + this.container.className = "html-editor theme-body"; 1.43 + this.container.style.display = "none"; 1.44 + this.editorInner = this.doc.createElement("div"); 1.45 + this.editorInner.className = "html-editor-inner"; 1.46 + this.container.appendChild(this.editorInner); 1.47 + 1.48 + this.doc.body.appendChild(this.container); 1.49 + this.hide = this.hide.bind(this); 1.50 + this.refresh = this.refresh.bind(this); 1.51 + 1.52 + EventEmitter.decorate(this); 1.53 + 1.54 + this.doc.defaultView.addEventListener("resize", 1.55 + this.refresh, true); 1.56 + 1.57 + let config = { 1.58 + mode: Editor.modes.html, 1.59 + lineWrapping: true, 1.60 + styleActiveLine: false, 1.61 + extraKeys: {}, 1.62 + theme: "mozilla markup-view" 1.63 + }; 1.64 + 1.65 + config.extraKeys[ctrl("Enter")] = this.hide; 1.66 + config.extraKeys["F2"] = this.hide; 1.67 + config.extraKeys["Esc"] = this.hide.bind(this, false); 1.68 + 1.69 + this.container.addEventListener("click", this.hide, false); 1.70 + this.editorInner.addEventListener("click", stopPropagation, false); 1.71 + this.editor = new Editor(config); 1.72 + 1.73 + let iframe = this.editorInner.ownerDocument.createElement("iframe"); 1.74 + this.editor.appendTo(this.editorInner, iframe).then(() => { 1.75 + this.hide(false); 1.76 + }).then(null, (err) => console.log(err.message)); 1.77 +} 1.78 + 1.79 +HTMLEditor.prototype = { 1.80 + 1.81 + /** 1.82 + * Need to refresh position by manually setting CSS values, so this will 1.83 + * need to be called on resizes and other sizing changes. 1.84 + */ 1.85 + refresh: function() { 1.86 + let element = this._attachedElement; 1.87 + 1.88 + if (element) { 1.89 + this.container.style.top = element.offsetTop + "px"; 1.90 + this.container.style.left = element.offsetLeft + "px"; 1.91 + this.container.style.width = element.offsetWidth + "px"; 1.92 + this.container.style.height = element.parentNode.offsetHeight + "px"; 1.93 + this.editor.refresh(); 1.94 + } 1.95 + }, 1.96 + 1.97 + /** 1.98 + * Anchor the editor to a particular element. 1.99 + * 1.100 + * @param DOMNode element 1.101 + * The element that the editor will be anchored to. 1.102 + * Should belong to the HTMLDocument passed into the constructor. 1.103 + */ 1.104 + _attach: function(element) 1.105 + { 1.106 + this._detach(); 1.107 + this._attachedElement = element; 1.108 + element.classList.add("html-editor-container"); 1.109 + this.refresh(); 1.110 + }, 1.111 + 1.112 + /** 1.113 + * Unanchor the editor from an element. 1.114 + */ 1.115 + _detach: function() 1.116 + { 1.117 + if (this._attachedElement) { 1.118 + this._attachedElement.classList.remove("html-editor-container"); 1.119 + this._attachedElement = undefined; 1.120 + } 1.121 + }, 1.122 + 1.123 + /** 1.124 + * Anchor the editor to a particular element, and show the editor. 1.125 + * 1.126 + * @param DOMNode element 1.127 + * The element that the editor will be anchored to. 1.128 + * Should belong to the HTMLDocument passed into the constructor. 1.129 + * @param string text 1.130 + * Value to set the contents of the editor to 1.131 + * @param function cb 1.132 + * The function to call when hiding 1.133 + */ 1.134 + show: function(element, text) 1.135 + { 1.136 + if (this._visible) { 1.137 + return; 1.138 + } 1.139 + 1.140 + this._originalValue = text; 1.141 + this.editor.setText(text); 1.142 + this._attach(element); 1.143 + this.container.style.display = "flex"; 1.144 + this._visible = true; 1.145 + 1.146 + this.editor.refresh(); 1.147 + this.editor.focus(); 1.148 + 1.149 + this.emit("popupshown"); 1.150 + }, 1.151 + 1.152 + /** 1.153 + * Hide the editor, optionally committing the changes 1.154 + * 1.155 + * @param bool shouldCommit 1.156 + * A change will be committed by default. If this param 1.157 + * strictly equals false, no change will occur. 1.158 + */ 1.159 + hide: function(shouldCommit) 1.160 + { 1.161 + if (!this._visible) { 1.162 + return; 1.163 + } 1.164 + 1.165 + this.container.style.display = "none"; 1.166 + this._detach(); 1.167 + 1.168 + let newValue = this.editor.getText(); 1.169 + let valueHasChanged = this._originalValue !== newValue; 1.170 + let preventCommit = shouldCommit === false || !valueHasChanged; 1.171 + this._originalValue = undefined; 1.172 + this._visible = undefined; 1.173 + this.emit("popuphidden", !preventCommit, newValue); 1.174 + }, 1.175 + 1.176 + /** 1.177 + * Destroy this object and unbind all event handlers 1.178 + */ 1.179 + destroy: function() 1.180 + { 1.181 + this.doc.defaultView.removeEventListener("resize", 1.182 + this.refresh, true); 1.183 + this.container.removeEventListener("click", this.hide, false); 1.184 + this.editorInner.removeEventListener("click", stopPropagation, false); 1.185 + 1.186 + this.hide(false); 1.187 + this.container.parentNode.removeChild(this.container); 1.188 + } 1.189 +};