diff --git a/src/editor/CSSInlineEditor.js b/src/editor/CSSInlineEditor.js index efbb086249b..2a6ccef28ca 100644 --- a/src/editor/CSSInlineEditor.js +++ b/src/editor/CSSInlineEditor.js @@ -18,7 +18,7 @@ define(function (require, exports, module) { /** * @constructor - * @extends {InlineEditor} + * @extends {InlineWidget} */ function CSSInlineEditor(rules) { InlineTextEditor.call(this); @@ -28,6 +28,9 @@ define(function (require, exports, module) { CSSInlineEditor.prototype = new InlineTextEditor(); CSSInlineEditor.prototype.constructor = CSSInlineEditor; CSSInlineEditor.prototype.parentClass = InlineTextEditor.prototype; + CSSInlineEditor.prototype.$editorsDiv = null; + CSSInlineEditor.prototype.$relatedContainer = null; + CSSInlineEditor.prototype.updateRelatedContainerProxy = null; /** * @override @@ -43,7 +46,7 @@ define(function (require, exports, module) { $location; // Create DOM to hold editors and related list - var $editorsDiv = $(document.createElement('div')).addClass("inlineEditorHolder"); + this.$editorsDiv = $(document.createElement('div')).addClass("inlineEditorHolder"); // Outer container for border-left and scrolling this.$relatedContainer = $(document.createElement("div")).addClass("relatedContainer"); @@ -57,16 +60,6 @@ define(function (require, exports, module) { // Rule list var $ruleList = $(document.createElement("ul")).appendTo($related); - // Keyboard shortcuts - var extraKeys = { - "Alt-Up" : $.proxy(this.previousRule, this), - "Alt-Down" : $.proxy(this.nextRule, this) - }; - - // load first rule - var rule = this._rules[0]; - this.createInlineEditorFromText(rule.document, rule.lineStart, rule.lineEnd, $editorsDiv.get(0), extraKeys); - // create rule list this._ruleItems = []; this._rules.forEach(function (rule, i) { @@ -87,29 +80,76 @@ define(function (require, exports, module) { self.setSelectedRule(0); // attach to main container - this.$htmlContent.append($editorsDiv).append(this.$relatedContainer); + this.$htmlContent.append(this.$editorsDiv).append(this.$relatedContainer); // initialize position based on the main #editorHolder setTimeout(function () { self._updateRelatedContainer(); }, 0); - var updateRelatedContainerProxy = $.proxy(this._updateRelatedContainer, this); + this._updateRelatedContainerProxy = $.proxy(this._updateRelatedContainer, this); // Changes to the host editor should update the relatedContainer - $(this.hostEditor).on("change.CSSInlineEditor", updateRelatedContainerProxy); - - // TODO (jasonsj): install on active inline editor - // Changes in size to the inline editor should update the relatedContainer - $(this.editors[0]).on("change.CSSInlineEditor", updateRelatedContainerProxy); + $(this.hostEditor).on("change.CSSInlineEditor", this._updateRelatedContainerProxy); // Since overflow-y is hidden on the CM scrollerElement, the scroll event is never fired. // Instead, we add a hook to CM's onScroll to reposition the relatedContainer. - this.hostEditor._codeMirror.setOption("onScroll", updateRelatedContainerProxy); + this.hostEditor._codeMirror.setOption("onScroll", this._updateRelatedContainerProxy); return (new $.Deferred()).resolve(); }; + /** + * + * + */ + CSSInlineEditor.prototype.setSelectedRule = function (index) { + var newIndex = Math.min(Math.max(0, index), this._rules.length - 1); + + this._selectedRuleIndex = newIndex; + var $ruleItem = this._ruleItems[this._selectedRuleIndex]; + + this._ruleItems[this._selectedRuleIndex].toggleClass("selected", true); + + // Remove previous editors + this.editors.forEach(function (editor) { + editor.destroy(); //release ref on Document + }); + this.editors = []; + this.$editorsDiv.children().remove(); + $(this.editors[0]).off("change.CSSInlineEditor"); + + // Keyboard shortcuts + var extraKeys = { + "Alt-Up" : $.proxy(this.previousRule, this), + "Alt-Down" : $.proxy(this.nextRule, this) + }; + + + // Add new editor + var rule = this.getSelectedRule(); + this.createInlineEditorFromText(rule.document, rule.lineStart, rule.lineEnd, this.$editorsDiv.get(0), extraKeys); + this.editors[0].focus(); + + // Changes in size to the inline editor should update the relatedContainer + $(this.editors[0]).on("change.CSSInlineEditor", this.updateRelatedContainerProxy); + + this.sizeInlineWidgetToContents(true); + this._updateRelatedContainer(); + + // scroll the selection to the ruleItem, use setTimeout to wait for DOM updates + var self = this; + setTimeout(function () { + var itemTop = $ruleItem.position().top; + self.$selectedMarker.css("top", itemTop); + self.$selectedMarker.height($ruleItem.height()); + + // FUTURE (jasonsj): figure out if rule list should scroll + itemTop -= $ruleItem.parent().css("paddingTop").replace("px", ""); + self.$relatedContainer.scrollTop(itemTop); + }, 0); + }; + /** * Called any time inline was closed, whether manually (via closeThisInline()) or automatically */ @@ -122,47 +162,62 @@ define(function (require, exports, module) { this.hostEditor._codeMirror.setOption("onScroll", null); }; + /** + * + * + */ CSSInlineEditor.prototype._updateRelatedContainer = function () { var borderThickness = (this.$htmlContent.outerHeight() - this.$htmlContent.innerHeight()) / 2; this.$relatedContainer.css("top", this.$htmlContent.offset().top + borderThickness); this.$relatedContainer.height(this.$htmlContent.height()); }; - // TY TODO: part of sprint 6 + /** + * + * + */ CSSInlineEditor.prototype.getRules = function () { + return this._rules; }; - + + /** + * + * + */ CSSInlineEditor.prototype.getSelectedRule = function () { + return this._rules[this._selectedRuleIndex]; }; + - CSSInlineEditor.prototype.setSelectedRule = function (index) { - var newIndex = Math.min(Math.max(0, index), this._rules.length - 1); - - this._selectedRuleIndex = newIndex; - - var $ruleItem = this._ruleItems[this._selectedRuleIndex]; - - // scroll the selection to the ruleItem, use setTimeout to wait for DOM updates - var self = this; - setTimeout(function () { - var itemTop = $ruleItem.position().top; - self.$selectedMarker.css("top", itemTop); - self.$selectedMarker.height($ruleItem.height()); - - // FUTURE (jasonsj): figure out if rule list should scroll - itemTop -= $ruleItem.parent().css("paddingTop").replace("px", ""); - self.$relatedContainer.scrollTop(itemTop); - }, 0); - }; - + /** + * Display the next css rule in the rule list + */ CSSInlineEditor.prototype.nextRule = function () { this.setSelectedRule(this._selectedRuleIndex + 1); }; + /** + * Display the previous css rule in the rule list + */ CSSInlineEditor.prototype.previousRule = function () { this.setSelectedRule(this._selectedRuleIndex - 1); }; + /** + * Sizes the inline widget height to be the maximum between the rule list height and the editor height + * @overide + */ + CSSInlineEditor.prototype.sizeInlineWidgetToContents = function (force) { + // Size the code mirror editors height to the editor content + this.parentClass.sizeInlineWidgetToContents.call(this, force); + // Size the widget height to the max between the editor content and the related rules list + var widgetHeight = Math.max(this.$relatedContainer.find(".related").height(), this.$editorsDiv.height()); + this.hostEditor.setInlineWidgetHeight(this.inlineId, widgetHeight, true); + + // The related rules container size itself based on htmlContent which is set by setInlineWidgetHeight above. + this._updateRelatedContainer(); + }; + /** * Given a position in an HTML editor, returns the relevant selector for the attribute/tag @@ -215,7 +270,7 @@ define(function (require, exports, module) { * * @param {!Editor} editor * @param {!{line:Number, ch:Number}} pos - * @return {$.Promise} a promise that will be resolved with an InlineEditor + * @return {$.Promise} a promise that will be resolved with an InlineWidget * or null if we're not going to provide anything. */ function htmlToCSSProvider(hostEditor, pos) { diff --git a/src/editor/EditorManager.js b/src/editor/EditorManager.js index c323645fb5e..29e2d6e5619 100644 --- a/src/editor/EditorManager.js +++ b/src/editor/EditorManager.js @@ -112,7 +112,7 @@ define(function (require, exports, module) { * @private * Bound to Ctrl+E on outermost editors. * @param {!Editor} editor the candidate host editor - * @return {$.Promise} a promise that will be resolved when an InlineEditor + * @return {$.Promise} a promise that will be resolved when an InlineWidget * is created or rejected when no inline editors are available. */ function _openInlineWidget(editor) { @@ -129,21 +129,21 @@ define(function (require, exports, module) { // If one of them will provide a widget, show it inline once ready if (inlinePromise) { - inlinePromise.done(function (inlineEditor) { - $(inlineEditor.htmlContent).append('
') + inlinePromise.done(function (inlineWidget) { + $(inlineWidget.htmlContent).append('') .append(''); var closeCallback = function () { - inlineEditor.onClosed(); + inlineWidget.onClosed(); }; var parentShowCallback = function () { - inlineEditor.onParentShown(); + inlineWidget.onParentShown(); }; - var inlineId = editor.addInlineWidget(pos, inlineEditor.htmlContent, inlineEditor.height, - parentShowCallback, closeCallback, inlineEditor); + var inlineId = editor.addInlineWidget(pos, inlineWidget.htmlContent, inlineWidget.height, + parentShowCallback, closeCallback, inlineWidget); - inlineEditor.onAdded(inlineId); + inlineWidget.onAdded(inlineId); result.resolve(); }).fail(function () { result.reject(); @@ -190,7 +190,7 @@ define(function (require, exports, module) { * {!Editor} editor, {!{line:Number, ch:Number}} pos * * Returns: - * {$.Promise} a promise that will be resolved with an InlineEditor + * {$.Promise} a promise that will be resolved with an inlineWidget * or null to indicate the provider doesn't create an editor in this case */ function registerInlineEditProvider(provider) { @@ -246,7 +246,7 @@ define(function (require, exports, module) { * @param {?{startLine:Number, endLine:Number}} range If specified, all lines outside the given * range are hidden from the editor. Range is inclusive. Line numbers start at 0. * @param {HTMLDivContainer} inlineContent - * @param {function(InlineEditor)} closeThisInline + * @param {function(inlineWidget)} closeThisInline * * @return {{content:DOMElement, editor:Editor}} */ diff --git a/src/editor/InlineTextEditor.js b/src/editor/InlineTextEditor.js index ddb560802d9..8c890a6dedc 100644 --- a/src/editor/InlineTextEditor.js +++ b/src/editor/InlineTextEditor.js @@ -11,7 +11,7 @@ define(function (require, exports, module) { // Load dependent modules var DocumentManager = require("document/DocumentManager"), EditorManager = require("editor/EditorManager"), - InlineEditor = require("editor/InlineEditor").InlineEditor; + InlineWidget = require("editor/InlineWidget").InlineWidget; /** * Returns editor holder width (not CodeMirror's width). @@ -54,13 +54,14 @@ define(function (require, exports, module) { * */ function InlineTextEditor() { - InlineEditor.call(this); - this._docRangeToEditorMap = {}; + InlineWidget.call(this); + + /* @type {Array.<{Editor}>}*/ this.editors = []; } - InlineTextEditor.prototype = new InlineEditor(); + InlineTextEditor.prototype = new InlineWidget(); InlineTextEditor.prototype.constructor = InlineTextEditor; - InlineTextEditor.prototype.parentClass = InlineEditor.prototype; + InlineTextEditor.prototype.parentClass = InlineWidget.prototype; InlineTextEditor.prototype.editors = null; /** @@ -118,29 +119,32 @@ define(function (require, exports, module) { /** * Update the inline editor's height when the number of lines change + * @param {boolean} force the editor to resize */ - InlineTextEditor.prototype.sizeInlineEditorToContents = function (force) { + InlineTextEditor.prototype.sizeInlineWidgetToContents = function (force) { var i, len = this.editors.length, editor; + // TODO: only handles 1 editor right now. Add multiple editor support when + // the design is finalized + + // Reize the editors to the content for (i = 0; i < len; i++) { + // Only supports 1 editor right now + if (i === 1) { + break; + } + editor = this.editors[i]; if (editor.isFullyVisible()) { - // set inner editor height - var editorHeight = editor.totalHeight(true); - if (force || editorHeight !== this._editorHeight) { - this._editorHeight = editorHeight; - $(editor.getScrollerElement()).height(editorHeight); + var height = editor.totalHeight(true); + if (force || height !== this.height) { + $(editor.getScrollerElement()).height(height); + this.height = height; editor.refresh(); } - - // use outermost wrapper (htmlContent) scrollHeight to prop open the host editor - this.height = this.htmlContent.scrollHeight; - this.hostEditor.setInlineWidgetHeight(this.inlineId, this.height, true); - - break; // there should only be 1 visible editor } } }; @@ -160,7 +164,7 @@ define(function (require, exports, module) { _syncGutterWidths(this.hostEditor); // Set initial size - this.sizeInlineEditorToContents(); + this.sizeInlineWidgetToContents(); this.editors[0].focus(); }; @@ -208,7 +212,7 @@ define(function (require, exports, module) { // Size editor to content whenever it changes (via edits here or any other view of the doc) $(inlineInfo.editor).on("change", function () { - self.sizeInlineEditorToContents(); + self.sizeInlineWidgetToContents(); }); // If Document's file is deleted, or Editor loses sync with Document, just close @@ -242,18 +246,18 @@ define(function (require, exports, module) { InlineTextEditor.prototype.onParentShown = function () { // We need to call this explicitly whenever the host editor is reshown, since // we don't actually resize the inline editor while its host is invisible (see - // isFullyVisible() check in sizeInlineEditorToContents()). - this.sizeInlineEditorToContents(true); + // isFullyVisible() check in sizeInlineWidgetToContents()). + this.sizeInlineWidgetToContents(true); }; - InlineEditor.prototype._editorHasFocus = function () { + InlineTextEditor.prototype._editorHasFocus = function () { return this.editors.some(function (editor) { return editor.hasFocus(); }); }; /** Closes this inline widget and all its contained Editors */ - InlineEditor.prototype.close = function () { + InlineTextEditor.prototype.close = function () { var shouldMoveFocus = this._editorHasFocus(); EditorManager.closeInlineWidget(this.hostEditor, this.inlineId, shouldMoveFocus); // closeInlineWidget() causes our onClosed() to get run diff --git a/src/editor/InlineEditor.js b/src/editor/InlineWidget.js similarity index 73% rename from src/editor/InlineEditor.js rename to src/editor/InlineWidget.js index 8dab61e2ca5..c847b27eedb 100644 --- a/src/editor/InlineEditor.js +++ b/src/editor/InlineWidget.js @@ -15,20 +15,20 @@ define(function (require, exports, module) { * @constructor * */ - function InlineEditor() { + function InlineWidget() { // create the outer wrapper div this.htmlContent = document.createElement("div"); - this.$htmlContent = $(this.htmlContent).addClass("inlineEditor"); + this.$htmlContent = $(this.htmlContent).addClass("InlineWidget"); } - InlineEditor.prototype.htmlContent = null; - InlineEditor.prototype.height = 0; - InlineEditor.prototype.inlineId = null; - InlineEditor.prototype.hostEditor = null; + InlineWidget.prototype.htmlContent = null; + InlineWidget.prototype.height = 0; + InlineWidget.prototype.inlineId = null; + InlineWidget.prototype.hostEditor = null; /** * Called any time inline was closed, whether manually (via close()) or automatically */ - InlineEditor.prototype.onClosed = function () { + InlineWidget.prototype.onClosed = function () { // do nothing - base implementation }; @@ -37,14 +37,14 @@ define(function (require, exports, module) { * @param {string} the inline ID that is generated by CodeMirror after the widget that holds the inline * editor is constructed and added to the DOM */ - InlineEditor.prototype.onAdded = function (inlineId) { + InlineWidget.prototype.onAdded = function (inlineId) { this.inlineId = inlineId; }; /** * @param {Editor} hostEditor */ - InlineEditor.prototype.load = function (hostEditor) { + InlineWidget.prototype.load = function (hostEditor) { this.hostEditor = hostEditor; // TODO: incomplete impelementation. It's not clear yet if InlineTextEditor @@ -56,10 +56,10 @@ define(function (require, exports, module) { /** * Called when the editor containing the inline is made visible. */ - InlineEditor.prototype.onParentShown = function () { + InlineWidget.prototype.onParentShown = function () { // do nothing - base implementation }; - exports.InlineEditor = InlineEditor; + exports.InlineWidget = InlineWidget; }); diff --git a/src/styles/brackets.less b/src/styles/brackets.less index c312841b00c..cf666708938 100644 --- a/src/styles/brackets.less +++ b/src/styles/brackets.less @@ -318,7 +318,7 @@ a, img { } /* Styles for inline editors */ -.inlineEditor { +.InlineWidget { border-top: 1px solid #C0C0C0; border-bottom: 1px solid #C0C0C0; min-width: 100%; @@ -392,7 +392,6 @@ a, img { position: absolute; top: 0; left: 1px; - height: 100%; box-shadow: -1px 0 0 0 #fff; font-size: 12px;