Skip to content
This repository was archived by the owner on Sep 6, 2021. It is now read-only.

Commit 4ef64fd

Browse files
committed
Merge pull request #5532 from adobe/css-quick-edit-new-selector
[Initial review] Create new rule from CSS quick edit
2 parents b5eccf8 + 167e419 commit 4ef64fd

20 files changed

Lines changed: 1114 additions & 194 deletions

src/editor/CSSInlineEditor.js

Lines changed: 156 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,25 @@
2323

2424

2525
/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50 */
26-
/*global define, $, CodeMirror, window */
26+
/*global define, $, CodeMirror, window, Mustache */
2727

2828
define(function (require, exports, module) {
2929
"use strict";
3030

3131
// Load dependent modules
3232
var CSSUtils = require("language/CSSUtils"),
33+
DocumentManager = require("document/DocumentManager"),
34+
DropdownEventHandler = require("utils/DropdownEventHandler").DropdownEventHandler,
3335
EditorManager = require("editor/EditorManager"),
36+
Editor = require("editor/Editor").Editor,
37+
FileIndexManager = require("project/FileIndexManager"),
3438
HTMLUtils = require("language/HTMLUtils"),
35-
MultiRangeInlineEditor = require("editor/MultiRangeInlineEditor").MultiRangeInlineEditor;
39+
Menus = require("command/Menus"),
40+
MultiRangeInlineEditor = require("editor/MultiRangeInlineEditor").MultiRangeInlineEditor,
41+
PopUpManager = require("widgets/PopUpManager"),
42+
Strings = require("strings");
43+
44+
var StylesheetsMenuTemplate = require("text!htmlContent/stylesheets-menu.html");
3645

3746
/**
3847
* Given a position in an HTML editor, returns the relevant selector for the attribute/tag
@@ -78,6 +87,33 @@ define(function (require, exports, module) {
7887
return selectorName;
7988
}
8089

90+
/**
91+
* @private
92+
* Create the list of stylesheets in the dropdown menu.
93+
* @return {string} The html content
94+
*/
95+
function _renderList(cssFileInfos) {
96+
var templateVars = {
97+
styleSheetList : cssFileInfos
98+
};
99+
100+
return Mustache.render(StylesheetsMenuTemplate, templateVars);
101+
}
102+
103+
/**
104+
* @private
105+
* Add a new rule for the given selector to the given document, then add the rule to the
106+
* given inline editor.
107+
* @param {string} selectorName The selector to create a rule for.
108+
* @param {MultiRangeInlineEditor} inlineEditor The inline editor to display the new rule in.
109+
* @param {Document} styleDoc The document the rule should be inserted in.
110+
*/
111+
function _addRule(selectorName, inlineEditor, styleDoc) {
112+
var newRuleInfo = CSSUtils.addRuleToDocument(styleDoc, selectorName, Editor.getUseTabChar(), Editor.getSpaceUnits());
113+
inlineEditor.addAndSelectRange(selectorName, styleDoc, newRuleInfo.range.from.line, newRuleInfo.range.to.line);
114+
inlineEditor.editor.setCursorPos(newRuleInfo.pos.line, newRuleInfo.pos.ch);
115+
}
116+
81117
/**
82118
* This function is registered with EditManager as an inline editor provider. It creates a CSSInlineEditor
83119
* when cursor is on an HTML tag name, class attribute, or id attribute, find associated
@@ -89,6 +125,7 @@ define(function (require, exports, module) {
89125
* or null if we're not going to provide anything.
90126
*/
91127
function htmlToCSSProvider(hostEditor, pos) {
128+
92129
// Only provide a CSS editor when cursor is in HTML content
93130
if (hostEditor.getLanguageForSelection().getId() !== "html") {
94131
return null;
@@ -107,19 +144,126 @@ define(function (require, exports, module) {
107144
return null;
108145
}
109146

110-
var result = new $.Deferred();
147+
var result = new $.Deferred(),
148+
cssInlineEditor,
149+
cssFileInfos = [],
150+
$newRuleButton,
151+
$dropdown,
152+
$dropdownItem,
153+
dropdownEventHandler;
154+
155+
/**
156+
* @private
157+
* Close the dropdown externally to dropdown, which ultimately calls the
158+
* _cleanupDropdown callback.
159+
*/
160+
function _closeDropdown() {
161+
if (dropdownEventHandler) {
162+
dropdownEventHandler.close();
163+
}
164+
}
165+
166+
/**
167+
* @private
168+
* Remove the various event handlers that close the dropdown. This is called by the
169+
* PopUpManager when the dropdown is closed.
170+
*/
171+
function _cleanupDropdown() {
172+
$("html").off("click", _closeDropdown);
173+
dropdownEventHandler = null;
174+
$dropdown = null;
175+
176+
EditorManager.focusEditor();
177+
}
178+
179+
/**
180+
* @private
181+
* Callback when item from dropdown list is selected
182+
* @param {jQueryObject} $link The `a` element selected with mouse or keyboard
183+
*/
184+
function _onSelect($link) {
185+
var path = $link.data("path");
186+
187+
if (path) {
188+
DocumentManager.getDocumentForPath(path).done(function (styleDoc) {
189+
_addRule(selectorName, cssInlineEditor, styleDoc);
190+
});
191+
}
192+
}
193+
194+
/**
195+
* @private
196+
* Show or hide the stylesheets dropdown.
197+
*/
198+
function _showDropdown() {
199+
Menus.closeAll();
200+
201+
$dropdown = $(_renderList(cssFileInfos));
202+
203+
var toggleOffset = $newRuleButton.offset();
204+
$dropdown
205+
.css({
206+
left: toggleOffset.left,
207+
top: toggleOffset.top + $newRuleButton.outerHeight()
208+
})
209+
.appendTo($("body"));
210+
211+
$("html").on("click", _closeDropdown);
212+
213+
dropdownEventHandler = new DropdownEventHandler($dropdown, _onSelect, _cleanupDropdown);
214+
dropdownEventHandler.open();
215+
216+
$dropdown.focus();
217+
}
218+
219+
/**
220+
* @private
221+
* Checks to see if there are any stylesheets in the project, and returns the appropriate
222+
* "no rules"/"no stylesheets" message accordingly.
223+
* @return {$.Promise} a promise that is resolved with the message to show. Never rejected.
224+
*/
225+
function _getNoRulesMsg() {
226+
var result = new $.Deferred();
227+
FileIndexManager.getFileInfoList("css").done(function (fileInfos) {
228+
result.resolve(fileInfos.length ? Strings.CSS_QUICK_EDIT_NO_MATCHES : Strings.CSS_QUICK_EDIT_NO_STYLESHEETS);
229+
});
230+
return result;
231+
}
111232

112233
CSSUtils.findMatchingRules(selectorName, hostEditor.document)
113234
.done(function (rules) {
114-
if (rules && rules.length > 0) {
115-
var cssInlineEditor = new MultiRangeInlineEditor(rules);
116-
cssInlineEditor.load(hostEditor);
117-
118-
result.resolve(cssInlineEditor);
119-
} else {
120-
// No matching rules were found.
121-
result.reject();
122-
}
235+
cssInlineEditor = new MultiRangeInlineEditor(rules || [], _getNoRulesMsg);
236+
cssInlineEditor.load(hostEditor);
237+
238+
var $header = $(".inline-editor-header", cssInlineEditor.$htmlContent);
239+
$newRuleButton = $("<button class='stylesheet-button btn btn-mini disabled'/>")
240+
.text(Strings.BUTTON_NEW_RULE)
241+
.on("click", function (e) {
242+
if (!$newRuleButton.hasClass("disabled")) {
243+
// toggle dropdown
244+
if ($dropdown) {
245+
_closeDropdown();
246+
} else {
247+
_showDropdown();
248+
}
249+
}
250+
e.stopPropagation();
251+
});
252+
$header.append($newRuleButton);
253+
254+
result.resolve(cssInlineEditor);
255+
256+
// Now that dialog has been built, collect list of stylesheets
257+
FileIndexManager.getFileInfoList("css")
258+
.done(function (fileInfos) {
259+
cssFileInfos = fileInfos;
260+
261+
// "New Rule" button is disabled by default and gets enabled
262+
// here if there are any stylesheets in project
263+
if (cssFileInfos.length > 0) {
264+
$newRuleButton.removeClass("disabled");
265+
}
266+
});
123267
})
124268
.fail(function () {
125269
console.log("Error in findMatchingRules()");

src/editor/EditorManager.js

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -298,8 +298,8 @@ define(function (require, exports, module) {
298298

299299
if (hostEditor) {
300300
hostEditor.getInlineWidgets().forEach(function (widget) {
301-
if (widget instanceof InlineTextEditor) {
302-
inlineEditors = inlineEditors.concat(widget.editors);
301+
if (widget instanceof InlineTextEditor && widget.editor) {
302+
inlineEditors.push(widget.editor);
303303
}
304304
});
305305
}
@@ -343,8 +343,14 @@ define(function (require, exports, module) {
343343
* @return {{content:DOMElement, editor:Editor}}
344344
*/
345345
function createInlineEditorForDocument(doc, range, inlineContent) {
346-
// Create the Editor
346+
// Hide the container for the editor before creating it so that CodeMirror doesn't do extra work
347+
// when initializing the document. When we construct the editor, we have to set its text and then
348+
// set the (small) visible range that we show in the editor. If the editor is visible, CM has to
349+
// render a large portion of the document before setting the visible range. By hiding the editor
350+
// first and showing it after the visible range is set, we avoid that initial render.
351+
$(inlineContent).hide();
347352
var inlineEditor = _createEditorForDocument(doc, false, inlineContent, range);
353+
$(inlineContent).show();
348354

349355
return { content: inlineContent, editor: inlineEditor };
350356
}

0 commit comments

Comments
 (0)