diff --git a/src/editor/CSSInlineEditor.js b/src/editor/CSSInlineEditor.js index 01b2dca766d..644a94a2fe0 100644 --- a/src/editor/CSSInlineEditor.js +++ b/src/editor/CSSInlineEditor.js @@ -23,16 +23,25 @@ /*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50 */ -/*global define, $, CodeMirror, window */ +/*global define, $, CodeMirror, window, Mustache */ define(function (require, exports, module) { "use strict"; // Load dependent modules var CSSUtils = require("language/CSSUtils"), + DocumentManager = require("document/DocumentManager"), + DropdownEventHandler = require("utils/DropdownEventHandler").DropdownEventHandler, EditorManager = require("editor/EditorManager"), + Editor = require("editor/Editor").Editor, + FileIndexManager = require("project/FileIndexManager"), HTMLUtils = require("language/HTMLUtils"), - MultiRangeInlineEditor = require("editor/MultiRangeInlineEditor").MultiRangeInlineEditor; + Menus = require("command/Menus"), + MultiRangeInlineEditor = require("editor/MultiRangeInlineEditor").MultiRangeInlineEditor, + PopUpManager = require("widgets/PopUpManager"), + Strings = require("strings"); + + var StylesheetsMenuTemplate = require("text!htmlContent/stylesheets-menu.html"); /** * Given a position in an HTML editor, returns the relevant selector for the attribute/tag @@ -78,6 +87,33 @@ define(function (require, exports, module) { return selectorName; } + /** + * @private + * Create the list of stylesheets in the dropdown menu. + * @return {string} The html content + */ + function _renderList(cssFileInfos) { + var templateVars = { + styleSheetList : cssFileInfos + }; + + return Mustache.render(StylesheetsMenuTemplate, templateVars); + } + + /** + * @private + * Add a new rule for the given selector to the given document, then add the rule to the + * given inline editor. + * @param {string} selectorName The selector to create a rule for. + * @param {MultiRangeInlineEditor} inlineEditor The inline editor to display the new rule in. + * @param {Document} styleDoc The document the rule should be inserted in. + */ + function _addRule(selectorName, inlineEditor, styleDoc) { + var newRuleInfo = CSSUtils.addRuleToDocument(styleDoc, selectorName, Editor.getUseTabChar(), Editor.getSpaceUnits()); + inlineEditor.addAndSelectRange(selectorName, styleDoc, newRuleInfo.range.from.line, newRuleInfo.range.to.line); + inlineEditor.editor.setCursorPos(newRuleInfo.pos.line, newRuleInfo.pos.ch); + } + /** * This function is registered with EditManager as an inline editor provider. It creates a CSSInlineEditor * when cursor is on an HTML tag name, class attribute, or id attribute, find associated @@ -89,6 +125,7 @@ define(function (require, exports, module) { * or null if we're not going to provide anything. */ function htmlToCSSProvider(hostEditor, pos) { + // Only provide a CSS editor when cursor is in HTML content if (hostEditor.getLanguageForSelection().getId() !== "html") { return null; @@ -107,19 +144,126 @@ define(function (require, exports, module) { return null; } - var result = new $.Deferred(); + var result = new $.Deferred(), + cssInlineEditor, + cssFileInfos = [], + $newRuleButton, + $dropdown, + $dropdownItem, + dropdownEventHandler; + + /** + * @private + * Close the dropdown externally to dropdown, which ultimately calls the + * _cleanupDropdown callback. + */ + function _closeDropdown() { + if (dropdownEventHandler) { + dropdownEventHandler.close(); + } + } + + /** + * @private + * Remove the various event handlers that close the dropdown. This is called by the + * PopUpManager when the dropdown is closed. + */ + function _cleanupDropdown() { + $("html").off("click", _closeDropdown); + dropdownEventHandler = null; + $dropdown = null; + + EditorManager.focusEditor(); + } + + /** + * @private + * Callback when item from dropdown list is selected + * @param {jQueryObject} $link The `a` element selected with mouse or keyboard + */ + function _onSelect($link) { + var path = $link.data("path"); + + if (path) { + DocumentManager.getDocumentForPath(path).done(function (styleDoc) { + _addRule(selectorName, cssInlineEditor, styleDoc); + }); + } + } + + /** + * @private + * Show or hide the stylesheets dropdown. + */ + function _showDropdown() { + Menus.closeAll(); + + $dropdown = $(_renderList(cssFileInfos)); + + var toggleOffset = $newRuleButton.offset(); + $dropdown + .css({ + left: toggleOffset.left, + top: toggleOffset.top + $newRuleButton.outerHeight() + }) + .appendTo($("body")); + + $("html").on("click", _closeDropdown); + + dropdownEventHandler = new DropdownEventHandler($dropdown, _onSelect, _cleanupDropdown); + dropdownEventHandler.open(); + + $dropdown.focus(); + } + + /** + * @private + * Checks to see if there are any stylesheets in the project, and returns the appropriate + * "no rules"/"no stylesheets" message accordingly. + * @return {$.Promise} a promise that is resolved with the message to show. Never rejected. + */ + function _getNoRulesMsg() { + var result = new $.Deferred(); + FileIndexManager.getFileInfoList("css").done(function (fileInfos) { + result.resolve(fileInfos.length ? Strings.CSS_QUICK_EDIT_NO_MATCHES : Strings.CSS_QUICK_EDIT_NO_STYLESHEETS); + }); + return result; + } CSSUtils.findMatchingRules(selectorName, hostEditor.document) .done(function (rules) { - if (rules && rules.length > 0) { - var cssInlineEditor = new MultiRangeInlineEditor(rules); - cssInlineEditor.load(hostEditor); - - result.resolve(cssInlineEditor); - } else { - // No matching rules were found. - result.reject(); - } + cssInlineEditor = new MultiRangeInlineEditor(rules || [], _getNoRulesMsg); + cssInlineEditor.load(hostEditor); + + var $header = $(".inline-editor-header", cssInlineEditor.$htmlContent); + $newRuleButton = $("