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
2828define ( 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()" ) ;
0 commit comments