diff --git a/src/style-manager.coffee b/src/style-manager.coffee deleted file mode 100644 index 8f932d229..000000000 --- a/src/style-manager.coffee +++ /dev/null @@ -1,177 +0,0 @@ -fs = require 'fs-plus' -path = require 'path' -{Emitter, Disposable} = require 'event-kit' -StylesElement = require './styles-element' - -# Extended: A singleton instance of this class available via `atom.styles`, -# which you can use to globally query and observe the set of active style -# sheets. The `StyleManager` doesn't add any style elements to the DOM on its -# own, but is instead subscribed to by individual `` elements, -# which clone and attach style elements in different contexts. -module.exports = -class StyleManager - constructor: ({@configDirPath}) -> - @emitter = new Emitter - @styleElements = [] - @styleElementsBySourcePath = {} - - ### - Section: Event Subscription - ### - - # Extended: Invoke `callback` for all current and future style elements. - # - # * `callback` {Function} that is called with style elements. - # * `styleElement` An `HTMLStyleElement` instance. The `.sheet` property - # will be null because this element isn't attached to the DOM. If you want - # to attach this element to the DOM, be sure to clone it first by calling - # `.cloneNode(true)` on it. The style element will also have the following - # non-standard properties: - # * `sourcePath` A {String} containing the path from which the style - # element was loaded. - # * `context` A {String} indicating the target context of the style - # element. - # - # Returns a {Disposable} on which `.dispose()` can be called to cancel the - # subscription. - observeStyleElements: (callback) -> - callback(styleElement) for styleElement in @getStyleElements() - @onDidAddStyleElement(callback) - - # Extended: Invoke `callback` when a style element is added. - # - # * `callback` {Function} that is called with style elements. - # * `styleElement` An `HTMLStyleElement` instance. The `.sheet` property - # will be null because this element isn't attached to the DOM. If you want - # to attach this element to the DOM, be sure to clone it first by calling - # `.cloneNode(true)` on it. The style element will also have the following - # non-standard properties: - # * `sourcePath` A {String} containing the path from which the style - # element was loaded. - # * `context` A {String} indicating the target context of the style - # element. - # - # Returns a {Disposable} on which `.dispose()` can be called to cancel the - # subscription. - onDidAddStyleElement: (callback) -> - @emitter.on 'did-add-style-element', callback - - # Extended: Invoke `callback` when a style element is removed. - # - # * `callback` {Function} that is called with style elements. - # * `styleElement` An `HTMLStyleElement` instance. - # - # Returns a {Disposable} on which `.dispose()` can be called to cancel the - # subscription. - onDidRemoveStyleElement: (callback) -> - @emitter.on 'did-remove-style-element', callback - - # Extended: Invoke `callback` when an existing style element is updated. - # - # * `callback` {Function} that is called with style elements. - # * `styleElement` An `HTMLStyleElement` instance. The `.sheet` property - # will be null because this element isn't attached to the DOM. The style - # element will also have the following non-standard properties: - # * `sourcePath` A {String} containing the path from which the style - # element was loaded. - # * `context` A {String} indicating the target context of the style - # element. - # - # Returns a {Disposable} on which `.dispose()` can be called to cancel the - # subscription. - onDidUpdateStyleElement: (callback) -> - @emitter.on 'did-update-style-element', callback - - ### - Section: Reading Style Elements - ### - - # Extended: Get all loaded style elements. - getStyleElements: -> - @styleElements.slice() - - addStyleSheet: (source, params) -> - sourcePath = params?.sourcePath - context = params?.context - priority = params?.priority - - if sourcePath? and styleElement = @styleElementsBySourcePath[sourcePath] - updated = true - else - styleElement = document.createElement('style') - if sourcePath? - styleElement.sourcePath = sourcePath - styleElement.setAttribute('source-path', sourcePath) - - if context? - styleElement.context = context - styleElement.setAttribute('context', context) - - if priority? - styleElement.priority = priority - styleElement.setAttribute('priority', priority) - - styleElement.textContent = source - - if updated - @emitter.emit 'did-update-style-element', styleElement - else - @addStyleElement(styleElement) - - new Disposable => @removeStyleElement(styleElement) - - addStyleElement: (styleElement) -> - {sourcePath, priority} = styleElement - - if priority? - for existingElement, index in @styleElements - if existingElement.priority > priority - insertIndex = index - break - - insertIndex ?= @styleElements.length - - @styleElements.splice(insertIndex, 0, styleElement) - @styleElementsBySourcePath[sourcePath] ?= styleElement if sourcePath? - @emitter.emit 'did-add-style-element', styleElement - - removeStyleElement: (styleElement) -> - index = @styleElements.indexOf(styleElement) - unless index is -1 - @styleElements.splice(index, 1) - delete @styleElementsBySourcePath[styleElement.sourcePath] if styleElement.sourcePath? - @emitter.emit 'did-remove-style-element', styleElement - - getSnapshot: -> - @styleElements.slice() - - restoreSnapshot: (styleElementsToRestore) -> - for styleElement in @getStyleElements() - @removeStyleElement(styleElement) unless styleElement in styleElementsToRestore - - existingStyleElements = @getStyleElements() - for styleElement in styleElementsToRestore - @addStyleElement(styleElement) unless styleElement in existingStyleElements - - return - - buildStylesElement: -> - stylesElement = new StylesElement - stylesElement.initialize(this) - stylesElement - - ### - Section: Paths - ### - - # Extended: Get the path of the user style sheet in `~/.atom`. - # - # Returns a {String}. - getUserStyleSheetPath: -> - return "" unless @configDirPath? - - stylesheetPath = fs.resolve(path.join(@configDirPath, 'styles'), ['css', 'less']) - if fs.isFileSync(stylesheetPath) - stylesheetPath - else - path.join(@configDirPath, 'styles.less') diff --git a/src/style-manager.js b/src/style-manager.js new file mode 100644 index 000000000..ae06f9060 --- /dev/null +++ b/src/style-manager.js @@ -0,0 +1,207 @@ +const fs = require('fs-plus') +const path = require('path') +const {Emitter, Disposable} = require('event-kit') +const StylesElement = require('./styles-element') + +// Extended: A singleton instance of this class available via `atom.styles`, +// which you can use to globally query and observe the set of active style +// sheets. The `StyleManager` doesn't add any style elements to the DOM on its +// own, but is instead subscribed to by individual `` elements, +// which clone and attach style elements in different contexts. +module.exports = class StyleManager { + constructor ({configDirPath}) { + this.configDirPath = configDirPath + this.emitter = new Emitter() + this.styleElements = [] + this.styleElementsBySourcePath = {} + } + + /* + Section: Event Subscription + */ + + // Extended: Invoke `callback` for all current and future style elements. + // + // * `callback` {Function} that is called with style elements. + // * `styleElement` An `HTMLStyleElement` instance. The `.sheet` property + // will be null because this element isn't attached to the DOM. If you want + // to attach this element to the DOM, be sure to clone it first by calling + // `.cloneNode(true)` on it. The style element will also have the following + // non-standard properties: + // * `sourcePath` A {String} containing the path from which the style + // element was loaded. + // * `context` A {String} indicating the target context of the style + // element. + // + // Returns a {Disposable} on which `.dispose()` can be called to cancel the + // subscription. + observeStyleElements (callback) { + for (let styleElement of this.getStyleElements()) { + callback(styleElement) + } + + return this.onDidAddStyleElement(callback) + } + + // Extended: Invoke `callback` when a style element is added. + // + // * `callback` {Function} that is called with style elements. + // * `styleElement` An `HTMLStyleElement` instance. The `.sheet` property + // will be null because this element isn't attached to the DOM. If you want + // to attach this element to the DOM, be sure to clone it first by calling + // `.cloneNode(true)` on it. The style element will also have the following + // non-standard properties: + // * `sourcePath` A {String} containing the path from which the style + // element was loaded. + // * `context` A {String} indicating the target context of the style + // element. + // + // Returns a {Disposable} on which `.dispose()` can be called to cancel the + // subscription. + onDidAddStyleElement (callback) { + return this.emitter.on('did-add-style-element', callback) + } + + // Extended: Invoke `callback` when a style element is removed. + // + // * `callback` {Function} that is called with style elements. + // * `styleElement` An `HTMLStyleElement` instance. + // + // Returns a {Disposable} on which `.dispose()` can be called to cancel the + // subscription. + onDidRemoveStyleElement (callback) { + return this.emitter.on('did-remove-style-element', callback) + } + + // Extended: Invoke `callback` when an existing style element is updated. + // + // * `callback` {Function} that is called with style elements. + // * `styleElement` An `HTMLStyleElement` instance. The `.sheet` property + // will be null because this element isn't attached to the DOM. The style + // element will also have the following non-standard properties: + // * `sourcePath` A {String} containing the path from which the style + // element was loaded. + // * `context` A {String} indicating the target context of the style + // element. + // + // Returns a {Disposable} on which `.dispose()` can be called to cancel the + // subscription. + onDidUpdateStyleElement (callback) { + return this.emitter.on('did-update-style-element', callback) + } + + /* + Section: Reading Style Elements + */ + + // Extended: Get all loaded style elements. + getStyleElements () { + return this.styleElements.slice() + } + + addStyleSheet (source, params = {}) { + let styleElement + let updated + if (params.sourcePath != null && this.styleElementsBySourcePath[params.sourcePath] != null) { + updated = true + styleElement = this.styleElementsBySourcePath[params.sourcePath] + } else { + updated = false + styleElement = document.createElement('style') + if (params.sourcePath != null) { + styleElement.sourcePath = params.sourcePath + styleElement.setAttribute('source-path', params.sourcePath) + } + if (params.context != null) { + styleElement.context = params.context + styleElement.setAttribute('context', params.context) + } + if (params.priority != null) { + styleElement.priority = params.priority + styleElement.setAttribute('priority', params.priority) + } + } + + styleElement.textContent = source + if (updated) { + this.emitter.emit('did-update-style-element', styleElement) + } else { + this.addStyleElement(styleElement) + } + return new Disposable(() => { this.removeStyleElement(styleElement) }) + } + + addStyleElement (styleElement) { + let insertIndex = this.styleElements.length + if (styleElement.priority != null) { + for (let [index, existingElement] of this.styleElements.entries()) { + if (existingElement.priority > styleElement.priority) { + insertIndex = index + break + } + } + } + + this.styleElements.splice(insertIndex, 0, styleElement) + if (styleElement.sourcePath != null && this.styleElementsBySourcePath[styleElement.sourcePath] == null) { + this.styleElementsBySourcePath[styleElement.sourcePath] = styleElement + } + this.emitter.emit('did-add-style-element', styleElement) + } + + removeStyleElement (styleElement) { + const index = this.styleElements.indexOf(styleElement) + if (index !== -1) { + this.styleElements.splice(index, 1) + if (styleElement.sourcePath != null) { + delete this.styleElementsBySourcePath[styleElement.sourcePath] + } + this.emitter.emit('did-remove-style-element', styleElement) + } + } + + getSnapshot () { + return this.styleElements.slice() + } + + restoreSnapshot (styleElementsToRestore) { + for (let styleElement of this.getStyleElements()) { + if (!styleElementsToRestore.includes(styleElement)) { + this.removeStyleElement(styleElement) + } + } + + const existingStyleElements = this.getStyleElements() + for (let styleElement of styleElementsToRestore) { + if (!existingStyleElements.includes(styleElement)) { + this.addStyleElement(styleElement) + } + } + } + + buildStylesElement () { + var stylesElement = new StylesElement() + stylesElement.initialize(this) + return stylesElement + } + + /* + Section: Paths + */ + + // Extended: Get the path of the user style sheet in `~/.atom`. + // + // Returns a {String}. + getUserStyleSheetPath () { + if (this.configDirPath == null) { + return '' + } else { + const stylesheetPath = fs.resolve(path.join(this.configDirPath, 'styles'), ['css', 'less']) + if (fs.isFileSync(stylesheetPath)) { + return stylesheetPath + } else { + return path.join(this.configDirPath, 'styles.less') + } + } + } +}