diff -r 000000000000 -r 6474c204b198 browser/devtools/markupview/html-editor.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/browser/devtools/markupview/html-editor.js Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,186 @@ +/* vim:set ts=2 sw=2 sts=2 et tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + "use strict"; + +const {Cu} = require("chrome"); +const Editor = require("devtools/sourceeditor/editor"); +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/devtools/event-emitter.js"); + +exports.HTMLEditor = HTMLEditor; + +function ctrl(k) { + return (Services.appinfo.OS == "Darwin" ? "Cmd-" : "Ctrl-") + k; +} +function stopPropagation(e) { + e.stopPropagation(); +} +/** + * A wrapper around the Editor component, that allows editing of HTML. + * + * The main functionality this provides around the Editor is the ability + * to show/hide/position an editor inplace. It only appends once to the + * body, and uses CSS to position the editor. The reason it is done this + * way is that the editor is loaded in an iframe, and calling appendChild + * causes it to reload. + * + * Meant to be embedded inside of an HTML page, as in markup-view.xhtml. + * + * @param HTMLDocument htmlDocument + * The document to attach the editor to. Will also use this + * document as a basis for listening resize events. + */ +function HTMLEditor(htmlDocument) +{ + this.doc = htmlDocument; + this.container = this.doc.createElement("div"); + this.container.className = "html-editor theme-body"; + this.container.style.display = "none"; + this.editorInner = this.doc.createElement("div"); + this.editorInner.className = "html-editor-inner"; + this.container.appendChild(this.editorInner); + + this.doc.body.appendChild(this.container); + this.hide = this.hide.bind(this); + this.refresh = this.refresh.bind(this); + + EventEmitter.decorate(this); + + this.doc.defaultView.addEventListener("resize", + this.refresh, true); + + let config = { + mode: Editor.modes.html, + lineWrapping: true, + styleActiveLine: false, + extraKeys: {}, + theme: "mozilla markup-view" + }; + + config.extraKeys[ctrl("Enter")] = this.hide; + config.extraKeys["F2"] = this.hide; + config.extraKeys["Esc"] = this.hide.bind(this, false); + + this.container.addEventListener("click", this.hide, false); + this.editorInner.addEventListener("click", stopPropagation, false); + this.editor = new Editor(config); + + let iframe = this.editorInner.ownerDocument.createElement("iframe"); + this.editor.appendTo(this.editorInner, iframe).then(() => { + this.hide(false); + }).then(null, (err) => console.log(err.message)); +} + +HTMLEditor.prototype = { + + /** + * Need to refresh position by manually setting CSS values, so this will + * need to be called on resizes and other sizing changes. + */ + refresh: function() { + let element = this._attachedElement; + + if (element) { + this.container.style.top = element.offsetTop + "px"; + this.container.style.left = element.offsetLeft + "px"; + this.container.style.width = element.offsetWidth + "px"; + this.container.style.height = element.parentNode.offsetHeight + "px"; + this.editor.refresh(); + } + }, + + /** + * Anchor the editor to a particular element. + * + * @param DOMNode element + * The element that the editor will be anchored to. + * Should belong to the HTMLDocument passed into the constructor. + */ + _attach: function(element) + { + this._detach(); + this._attachedElement = element; + element.classList.add("html-editor-container"); + this.refresh(); + }, + + /** + * Unanchor the editor from an element. + */ + _detach: function() + { + if (this._attachedElement) { + this._attachedElement.classList.remove("html-editor-container"); + this._attachedElement = undefined; + } + }, + + /** + * Anchor the editor to a particular element, and show the editor. + * + * @param DOMNode element + * The element that the editor will be anchored to. + * Should belong to the HTMLDocument passed into the constructor. + * @param string text + * Value to set the contents of the editor to + * @param function cb + * The function to call when hiding + */ + show: function(element, text) + { + if (this._visible) { + return; + } + + this._originalValue = text; + this.editor.setText(text); + this._attach(element); + this.container.style.display = "flex"; + this._visible = true; + + this.editor.refresh(); + this.editor.focus(); + + this.emit("popupshown"); + }, + + /** + * Hide the editor, optionally committing the changes + * + * @param bool shouldCommit + * A change will be committed by default. If this param + * strictly equals false, no change will occur. + */ + hide: function(shouldCommit) + { + if (!this._visible) { + return; + } + + this.container.style.display = "none"; + this._detach(); + + let newValue = this.editor.getText(); + let valueHasChanged = this._originalValue !== newValue; + let preventCommit = shouldCommit === false || !valueHasChanged; + this._originalValue = undefined; + this._visible = undefined; + this.emit("popuphidden", !preventCommit, newValue); + }, + + /** + * Destroy this object and unbind all event handlers + */ + destroy: function() + { + this.doc.defaultView.removeEventListener("resize", + this.refresh, true); + this.container.removeEventListener("click", this.hide, false); + this.editorInner.removeEventListener("click", stopPropagation, false); + + this.hide(false); + this.container.parentNode.removeChild(this.container); + } +};