diff --git a/.gitmodules b/.gitmodules index c7ae0834a..4961f226c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -76,3 +76,6 @@ [submodule "vendor/packages/Go.tmbundle"] path = vendor/packages/Go.tmbundle url = https://github.com/rsms/Go.tmbundle +[submodule "vendor/bootstrap"] + path = vendor/bootstrap + url = https://github.com/twitter/bootstrap diff --git a/.nakignore b/.nakignore index 660678d6b..0a67982e7 100644 --- a/.nakignore +++ b/.nakignore @@ -1,3 +1,4 @@ tags node_modules vendor/packages +.git diff --git a/docs/getting-started.md b/docs/getting-started.md index d869e9506..75c4f21fd 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -6,12 +6,13 @@ about configuring, theming, and extending Atom. ## The Command Palette -If there's one key-command you learn in Atom, it should be `meta-p`. You can -always hit `meta-p` to bring up a list of commands that are relevant to the -currently focused UI element. If there is a key binding for a given command, it -is also displayed. This is a great way to explore the system and get to know the -key commands interactively. If you'd like to add or change a binding for a -command, refer to the [key bindings](#customizing-key-bindings) section to learn how. +If there's one key-command you learn in Atom, it should be `meta-p` (`meta` is +synonymous with the ⌘ key). You can always hit `meta-p` to bring up a list of +commands that are relevant to the currently focused UI element. If there is a +key binding for a given command, it is also displayed. This is a great way to +explore the system and get to know the key commands interactively. If you'd like +to add or change a binding for a command, refer to the [key +bindings](#customizing-key-bindings) section to learn how.  diff --git a/package.json b/package.json index 80eca4b73..15d64f6ce 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,8 @@ "spellchecker": "0.3.0", "pathwatcher": "0.3.0", "plist": "git://github.com/nathansobo/node-plist.git", - "space-pen": "git://github.com/nathansobo/space-pen.git" + "space-pen": "git://github.com/nathansobo/space-pen.git", + "less": "git://github.com/nathansobo/less.js.git" }, "devDependencies" : { diff --git a/script/compile-less.coffee b/script/compile-less.coffee index 805d477d2..8e95d26d9 100644 --- a/script/compile-less.coffee +++ b/script/compile-less.coffee @@ -5,7 +5,7 @@ global.document = global.location = port: 80 -{less} = require '../vendor/less' +less = require 'less' fs = require 'fs' inputFile = process.argv[2] @@ -19,7 +19,19 @@ unless outputFile?.length > 0 process.exit(1) contents = fs.readFileSync(inputFile)?.toString() ? '' -(new less.Parser).parse contents, (e, tree) -> - console.error(e.stack or e) if e - process.exit(1) if e - fs.writeFileSync(outputFile, tree.toCSS()) + +parser = new less.Parser + syncImport: true + paths: [fs.realpathSync("#{__dirname}/../static"), fs.realpathSync("#{__dirname}/../vendor")] + filename: inputFile + +logErrorAndExit = (e) -> + console.error("Error compiling less file '#{inputFile}':", e.message) + process.exit(1) + +parser.parse contents, (e, tree) -> + logErrorAndExit(e) if e + try + fs.writeFileSync(outputFile, tree.toCSS()) + catch e + logErrorAndExit(e) diff --git a/script/copy-files-to-bundle b/script/copy-files-to-bundle index 586db807f..8aaec6ee2 100755 --- a/script/copy-files-to-bundle +++ b/script/copy-files-to-bundle @@ -10,6 +10,5 @@ RESOUCES_PATH="$BUILT_PRODUCTS_DIR/$UNLOCALIZED_RESOURCES_FOLDER_PATH" rsync --archive --recursive \ --exclude="src/**.coffee" --exclude="src/**.cson" \ --exclude="src/**.less" --exclude="static/**.less" \ - --exclude="node_modules/less" \ node_modules src static vendor spec benchmark themes dot-atom atom.sh \ "${COMPILED_SOURCES_DIR}/" "$RESOUCES_PATH" diff --git a/spec/app/image-view-spec.coffee b/spec/app/image-view-spec.coffee index 896c70f54..d4dda8920 100644 --- a/spec/app/image-view-spec.coffee +++ b/spec/app/image-view-spec.coffee @@ -8,7 +8,7 @@ describe "ImageView", -> path = project.resolve('binary-file.png') view = new ImageView() view.attachToDom() - view.parent().height(100) + view.height(100) it "displays the image for a path", -> view.setModel(new ImageEditSession(path)) diff --git a/spec/app/select-list-spec.coffee b/spec/app/select-list-spec.coffee index 0e3dec287..ea2b1e7da 100644 --- a/spec/app/select-list-spec.coffee +++ b/spec/app/select-list-spec.coffee @@ -113,7 +113,7 @@ describe "SelectList", -> miniEditor.trigger 'core:move-up' miniEditor.trigger 'core:move-up' - expect(list.scrollBottom()).toBe itemHeight * 3 + expect(list.scrollTop()).toBe itemHeight describe "the core:confirm event", -> describe "when there is an item selected (because the list in not empty)", -> diff --git a/spec/atom-reporter.coffee b/spec/atom-reporter.coffee index c02035cc8..5636647ca 100644 --- a/spec/atom-reporter.coffee +++ b/spec/atom-reporter.coffee @@ -8,7 +8,7 @@ class AtomReporter extends View @div id: 'HTMLReporter', class: 'jasmine_reporter', => @div outlet: 'specPopup', class: "spec-popup" @div outlet: "suites" - @ul outlet: "symbolSummary", class: 'symbolSummary' + @ul outlet: "symbolSummary", class: 'symbolSummary list-unstyled' @div outlet: "status", class: 'status', => @div outlet: "time", class: 'time' @div outlet: "specCount", class: 'spec-count' diff --git a/src/app/atom-package.coffee b/src/app/atom-package.coffee index c7f5cfeb3..9013d461a 100644 --- a/src/app/atom-package.coffee +++ b/src/app/atom-package.coffee @@ -28,7 +28,7 @@ class AtomPackage extends Package else @requireMainModule() catch e - console.warn "Failed to load package named '#{@name}'", e.stack + console.warn "Failed to load package named '#{@name}'", e.stack ? e this activate: ({immediate}={}) -> diff --git a/src/app/buffer-marker.coffee b/src/app/buffer-marker.coffee index 6aa013e01..d232a11cd 100644 --- a/src/app/buffer-marker.coffee +++ b/src/app/buffer-marker.coffee @@ -107,14 +107,13 @@ class BufferMarker handleBufferChange: (bufferChange) -> @consolidateObserverNotifications true, => - @setHeadPosition(@updatePosition(@headPosition, bufferChange, false), clip: false, bufferChanged: true) - @setTailPosition(@updatePosition(@tailPosition, bufferChange, true), clip: false, bufferChanged: true) if @tailPosition + @setHeadPosition(@updatePosition(@headPosition, bufferChange, true), clip: false, bufferChanged: true) + @setTailPosition(@updatePosition(@tailPosition, bufferChange, false), clip: false, bufferChanged: true) if @tailPosition - updatePosition: (position, bufferChange, isFirstPoint) -> + updatePosition: (position, bufferChange, isHead) -> { oldRange, newRange } = bufferChange - return position if oldRange.containsPoint(position, exclusive: true) - return position if isFirstPoint and oldRange.start.isEqual(position) + return position if not isHead and oldRange.start.isEqual(position) return position if position.isLessThan(oldRange.end) newRow = newRange.end.row diff --git a/src/app/config.coffee b/src/app/config.coffee index 455901565..ac44ca272 100644 --- a/src/app/config.coffee +++ b/src/app/config.coffee @@ -21,6 +21,7 @@ class Config themeDirPaths: [userThemesDirPath, bundledThemesDirPath, vendoredThemesDirPath] packageDirPaths: [userPackagesDirPath, vendoredPackagesDirPath, bundledPackagesDirPath] userPackagesDirPath: userPackagesDirPath + lessSearchPaths: [fsUtils.join(resourcePath, 'static'), fsUtils.join(resourcePath, 'vendor')] defaultSettings: null settings: null configFileHasErrors: null diff --git a/src/app/cursor-view.coffee b/src/app/cursor-view.coffee index 3cef15ba2..1bfb5d204 100644 --- a/src/app/cursor-view.coffee +++ b/src/app/cursor-view.coffee @@ -7,7 +7,7 @@ module.exports = class CursorView extends View # Internal: @content: -> - @pre class: 'cursor idle', => @raw ' ' + @div class: 'cursor idle', => @raw ' ' blinkPeriod: 800 editor: null diff --git a/src/app/editor.coffee b/src/app/editor.coffee index 5282bb998..75c441f48 100644 --- a/src/app/editor.coffee +++ b/src/app/editor.coffee @@ -730,10 +730,6 @@ class Editor extends View @verticalScrollbar.on 'scroll', => @scrollTop(@verticalScrollbar.scrollTop(), adjustVerticalScrollbar: false) - unless @mini - @gutter.widthChanged = (newWidth) => - @scrollView.css('left', newWidth + 'px') - @scrollView.on 'scroll', => if @scrollView.scrollLeft() == 0 @gutter.removeClass('drop-shadow') @@ -766,7 +762,6 @@ class Editor extends View return if @attached @attached = true @calculateDimensions() - @hiddenInput.width(@charWidth) @setSoftWrapColumn() if @activeEditSession.getSoftWrap() @subscribe $(window), "resize.editor-#{@id}", => @requestDisplayUpdate() @focus() if @isFocused @@ -1108,7 +1103,7 @@ class Editor extends View # Internal: calculateDimensions: -> - fragment = $('
x') + fragment = $('x') @renderedLines.append(fragment) lineRect = fragment[0].getBoundingClientRect() @@ -1116,7 +1111,6 @@ class Editor extends View @lineHeight = lineRect.height @charWidth = charRect.width @charHeight = charRect.height - @height(@lineHeight) if @mini fragment.remove() updateLayerDimensions: -> @@ -1446,7 +1440,7 @@ class Editor extends View attributePairs = [] attributePairs.push "#{attributeName}=\"#{value}\"" for attributeName, value of lineAttributes - line.push("") + line.push("") invisibles = @invisibles if @showInvisibles @@ -1475,7 +1469,7 @@ class Editor extends View line.push("") if fold - line.push('') + line.push('') line.join('') lineElementForScreenRow: (screenRow) -> diff --git a/src/app/gutter.coffee b/src/app/gutter.coffee index aabf890c2..df2dc0dc4 100644 --- a/src/app/gutter.coffee +++ b/src/app/gutter.coffee @@ -16,7 +16,6 @@ class Gutter extends View firstScreenRow: Infinity lastScreenRow: -1 - highestNumberWidth: null # Internal: afterAttach: (onDom) -> diff --git a/src/app/window.coffee b/src/app/window.coffee index 53c2c95b6..a92306629 100644 --- a/src/app/window.coffee +++ b/src/app/window.coffee @@ -2,7 +2,7 @@ fs = require 'fs' fsUtils = require 'fs-utils' $ = require 'jquery' _ = require 'underscore' -{less} = require 'less' +less = require 'less' require 'jquery-extensions' require 'underscore-extensions' require 'space-pen-extensions' @@ -25,12 +25,7 @@ window.setUpEnvironment = -> $(document).on 'keydown', keymap.handleKeyEvent keymap.bindDefaultKeys() - requireStylesheet 'reset' requireStylesheet 'atom' - requireStylesheet 'overlay' - requireStylesheet 'popover-list' - requireStylesheet 'notification' - requireStylesheet 'markdown' if nativeStylesheetPath = fsUtils.resolveOnLoadPath(process.platform, ['css', 'less']) requireStylesheet(nativeStylesheetPath) @@ -140,13 +135,28 @@ window.requireStylesheet = (path) -> throw new Error("Could not find a file at path '#{path}'") window.loadStylesheet = (path) -> - content = fsUtils.read(path) if fsUtils.extension(path) == '.less' - (new less.Parser).parse content, (e, tree) -> - throw new Error(e.message, path, e.line) if e - content = tree.toCSS() + loadLessStylesheet(path) + else + fsUtils.read(path) - content +window.loadLessStylesheet = (path) -> + parser = new less.Parser + syncImport: true + paths: config.lessSearchPaths + filename: path + try + content = null + parser.parse fsUtils.read(path), (e, tree) -> + throw e if e? + content = tree.toCSS() + content + catch e + console.error """ + Error compiling less stylesheet: #{path} + Line number: #{e.line} + #{e.message} + """ window.removeStylesheet = (path) -> unless fullPath = window.resolveStylesheet(path) diff --git a/src/packages/autocomplete/stylesheets/autocomplete.less b/src/packages/autocomplete/stylesheets/autocomplete.less index 1c7a332e7..eb78828b6 100644 --- a/src/packages/autocomplete/stylesheets/autocomplete.less +++ b/src/packages/autocomplete/stylesheets/autocomplete.less @@ -4,6 +4,7 @@ } .autocomplete ol { + box-sizing: content-box; position: relative; overflow-y: scroll; max-height: 200px; diff --git a/src/packages/command-palette/lib/command-palette-view.coffee b/src/packages/command-palette/lib/command-palette-view.coffee index 34988e751..6194abddf 100644 --- a/src/packages/command-palette/lib/command-palette-view.coffee +++ b/src/packages/command-palette/lib/command-palette-view.coffee @@ -49,7 +49,7 @@ class CommandPaletteView extends SelectList keyBindings = @keyBindings $$ -> @li class: 'event', 'data-event-name': eventName, => - @span eventDescription, class: 'label', title: eventName + @span eventDescription, title: eventName @div class: 'right', => for binding in keyBindings[eventName] ? [] @kbd binding, class: 'key-binding' diff --git a/src/packages/command-palette/spec/command-palette-spec.coffee b/src/packages/command-palette/spec/command-palette-spec.coffee index a0de19c61..6d294185a 100644 --- a/src/packages/command-palette/spec/command-palette-spec.coffee +++ b/src/packages/command-palette/spec/command-palette-spec.coffee @@ -24,8 +24,8 @@ describe "CommandPalette", -> eventLi = palette.list.children("[data-event-name='#{eventName}']") if description expect(eventLi).toExist() - expect(eventLi.find('.label')).toHaveText(description) - expect(eventLi.find('.label').attr('title')).toBe(eventName) + expect(eventLi.find('span')).toHaveText(description) + expect(eventLi.find('span').attr('title')).toBe(eventName) for binding in keyBindings[eventName] ? [] expect(eventLi.find(".key-binding:contains(#{binding})")).toExist() else @@ -40,8 +40,8 @@ describe "CommandPalette", -> description = editorEvents[eventName] unless description if description expect(eventLi).toExist() - expect(eventLi.find('.label')).toHaveText(description) - expect(eventLi.find('.label').attr('title')).toBe(eventName) + expect(eventLi.find('span')).toHaveText(description) + expect(eventLi.find('span').attr('title')).toBe(eventName) else expect(eventLi).not.toExist() @@ -92,8 +92,8 @@ describe "CommandPalette", -> eventLi = palette.list.children("[data-event-name='#{eventName}']") if description expect(eventLi).toExist() - expect(eventLi.find('.label')).toHaveText(description) - expect(eventLi.find('.label').attr('title')).toBe(eventName) + expect(eventLi.find('span')).toHaveText(description) + expect(eventLi.find('span').attr('title')).toBe(eventName) for binding in keyBindings[eventName] ? [] expect(eventLi.find(".key-binding:contains(#{binding})")).toExist() else diff --git a/src/packages/command-panel/lib/command-panel-view.coffee b/src/packages/command-panel/lib/command-panel-view.coffee index a5ef8b681..38791ddab 100644 --- a/src/packages/command-panel/lib/command-panel-view.coffee +++ b/src/packages/command-panel/lib/command-panel-view.coffee @@ -13,9 +13,8 @@ class CommandPanelView extends View @div class: 'command-panel tool-panel', => @div class: 'loading is-loading', outlet: 'loadingMessage', 'Searching...' @div class: 'header', outlet: 'previewHeader', => - @ul outlet: 'expandCollapse', class: 'expand-collapse', => - @li class: 'expand', 'Expand All' - @li class: 'collapse', 'Collapse All' + @button outlet: 'collapseAll', class: 'btn btn-mini pull-right', 'Collapse All' + @button outlet: 'expandAll', class: 'btn btn-mini pull-right', 'Expand All' @span outlet: 'previewCount', class: 'preview-count' @subview 'previewList', new PreviewList(rootView) @@ -47,8 +46,8 @@ class CommandPanelView extends View @subscribeToCommand rootView, 'command-panel:repeat-relative-address-in-reverse', => @repeatRelativeAddress(reverse: true) @subscribeToCommand rootView, 'command-panel:set-selection-as-regex-address', => @setSelectionAsLastRelativeAddress() - @on 'click', '.expand', @onExpandAll - @on 'click', '.collapse', @onCollapseAll + @expandAll.on 'click', @onExpandAll + @collapseAll.on 'click', @onCollapseAll @previewList.hide() @previewHeader.hide() diff --git a/src/packages/command-panel/spec/command-panel-spec.coffee b/src/packages/command-panel/spec/command-panel-spec.coffee index 782399da3..be448d0bb 100644 --- a/src/packages/command-panel/spec/command-panel-spec.coffee +++ b/src/packages/command-panel/spec/command-panel-spec.coffee @@ -139,7 +139,8 @@ describe "CommandPanel", -> expect(commandPanel.previewList).toBeVisible() it "shows the expand and collapse all buttons", -> - expect(commandPanel.find('.expand-collapse')).toBeVisible() + expect(commandPanel.collapseAll).toBeVisible() + expect(commandPanel.expandAll).toBeVisible() describe "when the preview list is focused", -> it "hides the command panel", -> diff --git a/src/packages/command-panel/stylesheets/command-panel.less b/src/packages/command-panel/stylesheets/command-panel.less index 15a133ec7..1aa0928bb 100644 --- a/src/packages/command-panel/stylesheets/command-panel.less +++ b/src/packages/command-panel/stylesheets/command-panel.less @@ -92,6 +92,11 @@ padding: 1px; } } + + .matches { + list-style-type: none; + margin: 0; + } } .header:after { @@ -105,6 +110,7 @@ .expand-collapse { float: right; -webkit-user-select: none; + margin: 0; li { display: inline-block; @@ -113,6 +119,7 @@ margin-left: 5px; padding: 5px 10px; border-radius: 3px; + line-height: normal; } } @@ -124,9 +131,15 @@ -webkit-flex: 1; } } -} -.error-messages { - padding: 5px 1em; - color: white; + .error-messages { + list-style-type: none; + margin: 0; + padding: 5px 1em; + color: white; + } + + .btn { + margin-left: 5px; + } } diff --git a/src/packages/fuzzy-finder/lib/fuzzy-finder-view.coffee b/src/packages/fuzzy-finder/lib/fuzzy-finder-view.coffee index 8bdfc5521..0080f7bbb 100644 --- a/src/packages/fuzzy-finder/lib/fuzzy-finder-view.coffee +++ b/src/packages/fuzzy-finder/lib/fuzzy-finder-view.coffee @@ -59,7 +59,7 @@ class FuzzyFinderView extends SelectList else typeClass = 'text-name' - @span fsUtils.base(path), class: "file label #{typeClass}" + @span fsUtils.base(path), class: "file #{typeClass}" if folder = project.relativize(fsUtils.directory(path)) @span " - #{folder}/", class: 'directory' diff --git a/src/packages/symbols-view/lib/symbols-view.coffee b/src/packages/symbols-view/lib/symbols-view.coffee index a6f617139..b5ff72e57 100644 --- a/src/packages/symbols-view/lib/symbols-view.coffee +++ b/src/packages/symbols-view/lib/symbols-view.coffee @@ -26,7 +26,7 @@ class SymbolsView extends SelectList itemForElement: ({position, name, file}) -> $$ -> @li => - @div name, class: 'label' + @span name if position text = "Line #{position.row + 1}" else diff --git a/src/packages/symbols-view/spec/symbols-view-spec.coffee b/src/packages/symbols-view/spec/symbols-view-spec.coffee index 4762ba421..8c43ecf8b 100644 --- a/src/packages/symbols-view/spec/symbols-view-spec.coffee +++ b/src/packages/symbols-view/spec/symbols-view-spec.coffee @@ -30,9 +30,9 @@ describe "SymbolsView", -> expect(symbolsView.find('.loading')).toBeEmpty() expect(rootView.find('.symbols-view')).toExist() expect(symbolsView.list.children('li').length).toBe 2 - expect(symbolsView.list.children('li:first').find('.label')).toHaveText 'quicksort' + expect(symbolsView.list.children('li:first').find('span')).toHaveText 'quicksort' expect(symbolsView.list.children('li:first').find('.function-details')).toHaveText 'Line 1' - expect(symbolsView.list.children('li:last').find('.label')).toHaveText 'quicksort.sort' + expect(symbolsView.list.children('li:last').find('span')).toHaveText 'quicksort.sort' expect(symbolsView.list.children('li:last').find('.function-details')).toHaveText 'Line 2' expect(symbolsView).not.toHaveClass "error" expect(symbolsView.error).not.toBeVisible() @@ -175,7 +175,7 @@ describe "SymbolsView", -> editor.trigger 'symbols-view:go-to-declaration' symbolsView = rootView.find('.symbols-view').view() expect(symbolsView.list.children('li').length).toBe 1 - expect(symbolsView.list.children('li:first').find('.label')).toHaveText 'tagged.js' + expect(symbolsView.list.children('li:first').find('span')).toHaveText 'tagged.js' describe "project symbols", -> it "displays all tags", -> @@ -192,9 +192,9 @@ describe "SymbolsView", -> expect(symbolsView.find('.loading')).toBeEmpty() expect(rootView.find('.symbols-view')).toExist() expect(symbolsView.list.children('li').length).toBe 4 - expect(symbolsView.list.children('li:first').find('.label')).toHaveText 'callMeMaybe' + expect(symbolsView.list.children('li:first').find('span')).toHaveText 'callMeMaybe' expect(symbolsView.list.children('li:first').find('.function-details')).toHaveText 'tagged.js' - expect(symbolsView.list.children('li:last').find('.label')).toHaveText 'thisIsCrazy' + expect(symbolsView.list.children('li:last').find('span')).toHaveText 'thisIsCrazy' expect(symbolsView.list.children('li:last').find('.function-details')).toHaveText 'tagged.js' expect(symbolsView).not.toHaveClass "error" expect(symbolsView.error).not.toBeVisible() diff --git a/src/packages/tabs/lib/tab-bar-view.coffee b/src/packages/tabs/lib/tab-bar-view.coffee index 9bd4227c4..6b524c2a7 100644 --- a/src/packages/tabs/lib/tab-bar-view.coffee +++ b/src/packages/tabs/lib/tab-bar-view.coffee @@ -6,7 +6,7 @@ TabView = require './tab-view' module.exports = class TabBarView extends View @content: -> - @ul tabindex: -1, class: "tabs sortable-list" + @ul tabindex: -1, class: "list-inline tabs" initialize: (@pane) -> @on 'dragstart', '.sortable', @onDragStart diff --git a/src/packages/tabs/lib/tab-view.coffee b/src/packages/tabs/lib/tab-view.coffee index 9c5faab90..ed817af58 100644 --- a/src/packages/tabs/lib/tab-view.coffee +++ b/src/packages/tabs/lib/tab-view.coffee @@ -6,8 +6,8 @@ module.exports = class TabView extends View @content: -> @li class: 'tab sortable', => - @span class: 'title', outlet: 'title' - @span class: 'close-icon' + @div class: 'title', outlet: 'title' + @div class: 'close-icon' initialize: (@item, @pane) -> @item.on? 'title-changed', => @updateTitle() diff --git a/src/packages/tabs/stylesheets/tabs.less b/src/packages/tabs/stylesheets/tabs.less index 120503c5d..efa2119d9 100644 --- a/src/packages/tabs/stylesheets/tabs.less +++ b/src/packages/tabs/stylesheets/tabs.less @@ -1,87 +1,59 @@ +@import "bootstrap/less/variables.less"; +@import "octicon-mixins.less"; + +@close-icon-size: 11px; + .tabs { - font: caption; display: -webkit-flex; - -webkit-box-align: center; -} - -.tab { -webkit-user-select: none; - -webkit-user-drag: element; - cursor: default; - -webkit-flex: 1; - width: 175px; - max-width: 175px; - min-width: 40px; - box-sizing: border-box; - text-shadow: -1px -1px 0 #000; - font-size: 11px; - padding: 5px 10px; - position: relative; -} + margin: 0; -.tab.active { - -webkit-flex: 2; -} + .tab { + line-height: 30px; + font-size: 11px; + position: relative; + padding-left: 10px; + padding-right: 10px + @close-icon-size + 2px; + -webkit-user-drag: element; + -webkit-flex: 1; + max-width: 175px; + min-width: 40px; -.tab .title { - display: block; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - padding: 3px 10px 3px 0; -} + &.active { + -webkit-flex: 2; + } -.tab .close-icon { - font-family: 'Octicons Regular'; - font-size: 12px; - width: 12px; - height: 12px; - cursor: pointer; - position: absolute; - color: rgba(255, 255, 255, 0.5); - right: 8px; - top: 5px; - -webkit-font-smoothing: antialiased; -} + .title { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } -.tab .close-icon:before { - content: "\f081"; -} + .close-icon { + .mini-icon(x, @close-icon-size); + position: absolute; + top: 0px; + right: 10px; + cursor: pointer; + } -.tab .close-icon:hover { - color: #fff; -} + &.modified:hover .close-icon { + color: #66a6ff; + } -.tab.modified .close-icon { - top: 11px; - width: 5px; - height: 5px; - right: 9px; - border: 2px solid #66a6ff; - border-radius: 12px; -} - -.tab.modified .close-icon:before { - content: ""; -} - -.tab.modified:hover .close-icon { - border: none; - width: 12px; - height: 12px; - right: 8px; - top: 5px; -} - -.tab.modified:hover .close-icon:before { - content: "\f081"; - color: #66a6ff; + &.modified:not(:hover) .close-icon { + &:before { content: "" } + top: 11px; + right: 11px; + width: 8px; + height: 8px; + border: 2px solid #66a6ff; + border-radius: 12px; + } + } } /* Drag and Drop */ -.tab.is-dragging { - -} .tab.is-drop-target:before, .tab.drop-target-is-after:after { content: ""; diff --git a/src/packages/tree-view/lib/directory-view.coffee b/src/packages/tree-view/lib/directory-view.coffee index 8d58bcfb2..e1ebb798f 100644 --- a/src/packages/tree-view/lib/directory-view.coffee +++ b/src/packages/tree-view/lib/directory-view.coffee @@ -8,10 +8,10 @@ module.exports = class DirectoryView extends View @content: ({directory, isExpanded} = {}) -> @li class: 'directory entry', => + @span class: 'highlight' @div outlet: 'header', class: 'header', => @span class: 'disclosure-arrow', outlet: 'disclosureArrow' @span directory.getBaseName(), class: 'name', outlet: 'directoryName' - @span class: 'highlight' directory: null entries: null @@ -60,7 +60,7 @@ class DirectoryView extends View buildEntries: -> @unwatchDescendantEntries() @entries?.remove() - @entries = $$ -> @ol class: 'entries' + @entries = $$ -> @ol class: 'entries list-unstyled' for entry in @directory.getEntries() continue if @isPathIgnored(entry.path) if entry instanceof Directory diff --git a/src/packages/tree-view/lib/file-view.coffee b/src/packages/tree-view/lib/file-view.coffee index 1aea8286b..fb1790fa4 100644 --- a/src/packages/tree-view/lib/file-view.coffee +++ b/src/packages/tree-view/lib/file-view.coffee @@ -8,8 +8,8 @@ class FileView extends View @content: ({file} = {}) -> @li class: 'file entry', => - @span file.getBaseName(), class: 'name', outlet: 'fileName' @span class: 'highlight' + @span file.getBaseName(), class: 'name', outlet: 'fileName' file: null diff --git a/src/packages/tree-view/lib/tree-view.coffee b/src/packages/tree-view/lib/tree-view.coffee index 6e0aa25b2..535f87e88 100644 --- a/src/packages/tree-view/lib/tree-view.coffee +++ b/src/packages/tree-view/lib/tree-view.coffee @@ -11,9 +11,10 @@ _ = require 'underscore' module.exports = class TreeView extends ScrollView @content: (rootView) -> - @div class: 'tree-view-wrapper', => - @ol class: 'tree-view tool-panel', tabindex: -1, outlet: 'treeViewList' - @div class: 'tree-view-resizer', outlet: 'resizer' + @div class: 'tree-view-resizer', => + @div class: 'tree-view-scroller', outlet: 'scroller', => + @ol class: 'list-unstyled tree-view tool-panel', tabindex: -1, outlet: 'list' + @div class: 'tree-view-resize-handle', outlet: 'resizeHandle' root: null focusAfterAttach: false @@ -23,7 +24,7 @@ class TreeView extends ScrollView initialize: (state) -> super @on 'click', '.entry', (e) => @entryClicked(e) - @on 'mousedown', '.tree-view-resizer', (e) => @resizeStarted(e) + @on 'mousedown', '.tree-view-resize-handle', (e) => @resizeStarted(e) @command 'core:move-up', => @moveUp() @command 'core:move-down', => @moveDown() @command 'core:close', => @detach(); false @@ -56,7 +57,6 @@ class TreeView extends ScrollView afterAttach: (onDom) -> @focus() if @focusAfterAttach @scrollTop(@scrollTopAfterAttach) if @scrollTopAfterAttach > 0 - @find('.selected > .highlight').width(@treeViewList[0].scrollWidth) serialize: -> directoryExpansionStates: @root?.serializeEntryExpansionStates() @@ -89,10 +89,10 @@ class TreeView extends ScrollView rootView.focus() focus: -> - @treeViewList.focus() + @list.focus() hasFocus: -> - @treeViewList.is(':focus') + @list.is(':focus') entryClicked: (e) -> entry = $(e.currentTarget).view() @@ -111,12 +111,10 @@ class TreeView extends ScrollView resizeStarted: (e) => $(document.body).on('mousemove', @resizeTreeView) $(document.body).on('mouseup', @resizeStopped) - @css(overflow: 'hidden') resizeStopped: (e) => $(document.body).off('mousemove', @resizeTreeView) $(document.body).off('mouseup', @resizeStopped) - @css(overflow: 'auto') resizeTreeView: (e) => @css(width: e.pageX) @@ -126,7 +124,7 @@ class TreeView extends ScrollView if rootDirectory = project.getRootDirectory() @root = new DirectoryView(directory: rootDirectory, isExpanded: true, project: project) - @treeViewList.append(@root) + @list.append(@root) else @root = null @@ -162,7 +160,7 @@ class TreeView extends ScrollView else bestMatchEntry - @treeViewList.find(".entry").toArray().reduce(fn, @root) + @list.find(".entry").toArray().reduce(fn, @root) selectEntryForPath: (path) -> @selectEntry(@entryForPath(path)) @@ -189,7 +187,7 @@ class TreeView extends ScrollView else @selectEntry(selectedEntry.parents('.directory').first()) else - @selectEntry(@treeViewList.find('.entry').last()) + @selectEntry(@list.find('.entry').last()) @scrollToEntry(@selectedEntry()) @@ -298,47 +296,43 @@ class TreeView extends ScrollView rootView.append(dialog) selectedEntry: -> - @treeViewList.find('.selected')?.view() + @list.find('.selected')?.view() selectEntry: (entry) -> return false unless entry.get(0) entry = entry.view() unless entry instanceof View @selectedPath = entry.getPath() @deselect() - entry.children('.highlight').width(@treeViewList[0].scrollWidth) entry.addClass('selected') deselect: -> - @treeViewList.find('.selected').removeClass('selected').children('.highlight').width('') + @list.find('.selected').removeClass('selected') scrollTop: (top) -> - if top - @treeViewList.scrollTop(top) + if top? + @scroller.scrollTop(top) else - @treeViewList.scrollTop() + @scroller.scrollTop() scrollBottom: (bottom) -> - if bottom - @treeViewList.scrollBottom(bottom) + if bottom? + @scroller.scrollBottom(bottom) else - @treeViewList.scrollBottom() + @scroller.scrollBottom() scrollToEntry: (entry) -> displayElement = if entry instanceof DirectoryView then entry.header else entry - top = @scrollTop() + displayElement.position().top + top = displayElement.position().top bottom = top + displayElement.outerHeight() if bottom > @scrollBottom() - @treeViewList.scrollBottom(bottom) + @scrollBottom(bottom) if top < @scrollTop() - @treeViewList.scrollTop(top) + @scrollTop(top) scrollToBottom: -> - super() - @selectEntry(@root.find('.entry:last')) if @root @scrollToEntry(@root.find('.entry:last')) if @root scrollToTop: -> - super() @selectEntry(@root) if @root - @treeViewList.scrollTop(0) + @scrollTop(0) diff --git a/src/packages/tree-view/spec/tree-view-spec.coffee b/src/packages/tree-view/spec/tree-view-spec.coffee index a79b4f116..53cbca5c0 100644 --- a/src/packages/tree-view/spec/tree-view-spec.coffee +++ b/src/packages/tree-view/spec/tree-view-spec.coffee @@ -101,11 +101,11 @@ describe "TreeView", -> it "restores the focus state of the tree view", -> rootView.attachToDom() treeView.focus() - expect(treeView.find(".tree-view")).toMatchSelector ':focus' + expect(treeView.list).toMatchSelector ':focus' atom.deactivatePackage("tree-view") atom.activatePackage("tree-view") treeView = rootView.find(".tree-view").view() - expect(treeView.find(".tree-view")).toMatchSelector ':focus' + expect(treeView.list).toMatchSelector ':focus' it "restores the scroll top when toggled", -> rootView.height(5) @@ -142,14 +142,14 @@ describe "TreeView", -> rootView.focus() rootView.trigger 'tree-view:toggle' expect(treeView).toBeVisible() - expect(treeView.find(".tree-view")).toMatchSelector(':focus') + expect(treeView.list).toMatchSelector(':focus') describe "when the tree view is hidden", -> it "shows and focuses the tree view", -> treeView.detach() rootView.trigger 'tree-view:toggle' expect(treeView.hasParent()).toBeTruthy() - expect(treeView.find(".tree-view")).toMatchSelector(':focus') + expect(treeView.list).toMatchSelector(':focus') describe "when tree-view:reveal-current-file is triggered on the root view", -> beforeEach -> @@ -184,10 +184,10 @@ describe "TreeView", -> rootView.open() # When we trigger 'tool-panel:unfocus' below, we want an editor to become focused rootView.attachToDom() treeView.focus() - expect(treeView.find(".tree-view")).toMatchSelector(':focus') + expect(treeView.list).toMatchSelector(':focus') treeView.trigger 'tool-panel:unfocus' expect(treeView).toBeVisible() - expect(treeView.find(".tree-view")).not.toMatchSelector(':focus') + expect(treeView.list).not.toMatchSelector(':focus') expect(rootView.getActiveView().isFocused).toBeTruthy() describe "when core:close is triggered on the tree view", -> @@ -428,7 +428,7 @@ describe "TreeView", -> treeView.height(100) treeView.attachToDom() $(element).view().expand() for element in treeView.find('.directory') - expect(treeView.find(".tree-view").prop('scrollHeight')).toBeGreaterThan treeView.find(".tree-view").outerHeight() + expect(treeView.list.outerHeight()).toBeGreaterThan treeView.scroller.outerHeight() expect(treeView.scrollTop()).toBe 0 @@ -452,11 +452,11 @@ describe "TreeView", -> treeView.height(100) treeView.attachToDom() $(element).view().expand() for element in treeView.find('.directory') - expect(treeView.find(".tree-view").prop('scrollHeight')).toBeGreaterThan treeView.find(".tree-view").outerHeight() + expect(treeView.list.outerHeight()).toBeGreaterThan treeView.scroller.outerHeight() expect(treeView.scrollTop()).toBe 0 treeView.trigger 'core:move-to-bottom' - expect(treeView.scrollBottom()).toBe treeView.find(".tree-view").prop('scrollHeight') + expect(treeView.scrollBottom()).toBe treeView.root.outerHeight() it "selects the last entry", -> expect(treeView.root).toHaveClass 'selected' @@ -468,7 +468,7 @@ describe "TreeView", -> treeView.height(5) treeView.attachToDom() $(element).view().expand() for element in treeView.find('.directory') - expect(treeView.find(".tree-view").prop('scrollHeight')).toBeGreaterThan treeView.find(".tree-view").outerHeight() + expect(treeView.list.outerHeight()).toBeGreaterThan treeView.scroller.outerHeight() expect(treeView.scrollTop()).toBe 0 treeView.scrollToBottom() @@ -483,7 +483,7 @@ describe "TreeView", -> treeView.height(5) treeView.attachToDom() $(element).view().expand() for element in treeView.find('.directory') - expect(treeView.find(".tree-view").prop('scrollHeight')).toBeGreaterThan treeView.find(".tree-view").outerHeight() + expect(treeView.list.outerHeight()).toBeGreaterThan treeView.scroller.outerHeight() expect(treeView.scrollTop()).toBe 0 treeView.trigger 'core:page-down' @@ -494,14 +494,16 @@ describe "TreeView", -> treeView.height(100) treeView.attachToDom() $(element).view().expand() for element in treeView.find('.directory') - expect(treeView.find(".tree-view").prop('scrollHeight')).toBeGreaterThan treeView.find(".tree-view").outerHeight() + expect(treeView.list.outerHeight()).toBeGreaterThan treeView.scroller.outerHeight() treeView.moveDown() expect(treeView.scrollTop()).toBe 0 entryCount = treeView.find(".entry").length + entryHeight = treeView.find('.file').height() + _.times entryCount, -> treeView.moveDown() - expect(treeView.scrollBottom()).toBe treeView.find(".tree-view").prop('scrollHeight') + expect(treeView.scrollBottom()).toBeGreaterThan (entryCount * entryHeight) - 1 _.times entryCount, -> treeView.moveUp() expect(treeView.scrollTop()).toBe 0 diff --git a/src/packages/tree-view/stylesheets/tree-view.less b/src/packages/tree-view/stylesheets/tree-view.less index d082dead4..4e02304b3 100644 --- a/src/packages/tree-view/stylesheets/tree-view.less +++ b/src/packages/tree-view/stylesheets/tree-view.less @@ -1,153 +1,16 @@ -.tree-view .entries { - margin-left: 12px; -} +@import "bootstrap/less/variables.less"; +@import "octicon-mixins.less"; -.tree-view .entries .file .name { - margin-left: 20px; -} - -.tree-view .file .name, -.tree-view .directory .header { - padding-top: 4px; - padding-bottom: 4px; - padding-right: 10px; -} - -.tree-view .directory .header { - padding-left: 5px; -} - -.tree-view-dialog { - padding: 5px; -} - -.tree-view-dialog .prompt { - padding-bottom: 3px; - font-size: 12px; - line-height: 16px; -} - -.tree-view-dialog .prompt span { - position: relative; - top: -1px; -} - -.tree-view-dialog .prompt:before { - font-family: 'Octicons Regular'; - font-size: 16px; - width: 16px; - height: 16px; - margin-right: 3px; - -webkit-font-smoothing: antialiased; -} - -.tree-view-dialog .prompt.add-file:before { - content: "\f086"; -} - -.tree-view-dialog .prompt.add-directory:before { - content: "\f095"; -} - -.tree-view-dialog .prompt.move:before { - content: "\f03e"; -} - -.tree-view .directory .header .name, -.tree-view .file .name { - position: relative; - padding-left: 21px; -} - -.tree-view .directory .header .name:before, -.tree-view .file .name:before { - font-family: 'Octicons Regular'; - font-size: 16px; - width: 16px; - height: 16px; - margin-right: 5px; - -webkit-font-smoothing: antialiased; - position: absolute; - left: 0; -} - -.tree-view .disclosure-arrow:before { - font-family: 'Octicons Regular'; - font-size: 12px; - width: 12px; - height: 12px; - line-height: 16px; - margin-right: 3px; - -webkit-font-smoothing: antialiased; -} - -.tree-view .directory .header .directory-icon:before { - content: "\f016"; - top: -5px; -} - -.tree-view .directory .header .repository-icon:before { - content: "\f001"; - top: -4px; -} - -.tree-view .directory .header .submodule-icon:before { - content: "\f017"; - top: -5px; -} - -.tree-view .file .text-icon:before { - content: "\f011"; - top: -2px; -} - -.tree-view .file .image-icon:before { - content: "\f012"; - top: -2px; -} - -.tree-view .file .compressed-icon:before { - content: "\f013"; - top: -2px; -} - -.tree-view .file .pdf-icon:before { - content: "\f014"; - top: -2px; -} - -.tree-view .file .readme-icon:before { - content: "\f007"; - top: -2px; -} - -.tree-view .file .binary-icon:before { - content: "\f094"; - top: -2px; -} - -.tree-view .file .symlink-icon:before { - content: "\f09b"; - top: -2px; -} - -.tree-view .directory > .header .disclosure-arrow:before { - content: "\f05a"; -} - -.tree-view .directory.expanded > .header .disclosure-arrow:before { - content: "\f05b"; -} - -.tree-view-wrapper { +.tree-view-resizer { position: relative; height: 100%; + overflow: hidden; cursor: default; -webkit-user-select: none; min-width: 50px; z-index: 2; - .tree-view-resizer { + .tree-view-resize-handle { position: absolute; top: 0; right: 0; @@ -158,41 +21,108 @@ } } -.tree-view { - position: relative; - cursor: default; - -webkit-user-select: none; - overflow: auto; +.tree-view-scroller { height: 100%; + width: 100%; + overflow: scroll; +} + +.tree-view { + min-width: -webkit-min-content; + min-height: 100%; + @item-line-height: @line-height-base * 1.25; + @disclosure-arrow-size: 12px; + @icon-margin: @line-height-base / 4; + + position: relative; + padding: 0 @icon-margin; + margin-bottom: 0; + cursor: default; .entry { text-wrap: none; white-space: nowrap; - & > .header, - > .name { - z-index: 1; + .name { position: relative; - display: inline-block; + z-index: 1; + } + + &.selected > .highlight { + position: absolute; + left: 0; + right: 0; + height: @item-line-height; } } - .selected > .highlight { - position: absolute; - left: 0; - right: 0; - height: 24px; + .directory { + > .header { + line-height: @item-line-height; + .disclosure-arrow { margin-right: @icon-margin; } + } + + .entries { + margin-left: 12px; + } } - .disclosure-arrow { - display: inline-block; + .file { + line-height: @item-line-height; + + .name { + margin-left: @disclosure-arrow-size + @icon-margin; + } + } + + // icons + .name:before { + margin-right: @icon-margin; + position: relative; + top: 1px; + } + + .directory > .header { + .disclosure-arrow { + .mini-icon(arr-collapsed, @disclosure-arrow-size); + position: relative; + } + .directory-icon { .mini-icon(directory); } + .repository-icon { .mini-icon(public-repo); } + .submodule-icon { .mini-icon(submodule); } + } + + .directory.expanded > .header { + .disclosure-arrow { .mini-icon(arr-expanded, @disclosure-arrow-size); } + } + + .file { + .text-icon { .mini-icon(text-file); } + .image-icon { .mini-icon(download-media); } + .compressed-icon { .mini-icon(download-zip); } + .pdf-icon { .mini-icon(download-pdf); } + .readme-icon { .mini-icon(readme); } + .binary-icon { .mini-icon(binary-file); } + .symlink-icon { .mini-icon(symlink); } } } + .tree-view-dialog { + padding: 5px; position: absolute; bottom: 0; left: 0; right: 0; z-index: 99; + + .prompt { + padding-bottom: 3px; + font-size: 12px; + line-height: 16px; + &:before { margin-right: @line-height-base / 4; } + &.add-file { .mini-icon(new-file); } + &.add-directory { .mini-icon(create-directory); } + &.prompt.move { .mini-icon(arr-right); } + } } diff --git a/src/packages/whitespace/lib/whitespace.coffee b/src/packages/whitespace/lib/whitespace.coffee index f5c954ba5..a16f9877b 100644 --- a/src/packages/whitespace/lib/whitespace.coffee +++ b/src/packages/whitespace/lib/whitespace.coffee @@ -1,11 +1,12 @@ module.exports = activate: -> - rootView.eachBuffer (buffer) => @whitespaceBeforeSave(buffer) + rootView.eachEditSession (editSession) => @whitespaceBeforeSave(editSession) configDefaults: ensureSingleTrailingNewline: true - whitespaceBeforeSave: (buffer) -> + whitespaceBeforeSave: (editSession) -> + buffer = editSession.buffer buffer.on 'will-be-saved', -> buffer.transact -> buffer.scan /[ \t]+$/g, ({replace}) -> replace('') @@ -16,4 +17,6 @@ module.exports = while row and buffer.lineForRow(row) is '' buffer.deleteRow(row--) else + selectedBufferRanges = editSession.getSelectedBufferRanges() buffer.append('\n') + editSession.setSelectedBufferRanges(selectedBufferRanges) diff --git a/src/packages/whitespace/spec/whitespace-spec.coffee b/src/packages/whitespace/spec/whitespace-spec.coffee index 1a0e54f44..35fe6a8ee 100644 --- a/src/packages/whitespace/spec/whitespace-spec.coffee +++ b/src/packages/whitespace/spec/whitespace-spec.coffee @@ -79,3 +79,10 @@ describe "Whitespace", -> editor.insertText "no trailing newline" editor.getBuffer().save() expect(editor.getText()).toBe "no trailing newline" + + it "does not move the cursor when the new line is added", -> + editor.insertText "foo" + expect(editor.getCursorBufferPosition()).toEqual([0,3]) + editor.getBuffer().save() + expect(editor.getText()).toBe "foo\n" + expect(editor.getCursorBufferPosition()).toEqual([0,3]) diff --git a/src/stdlib/cson.coffee b/src/stdlib/cson.coffee index 6d331483d..5b8527d75 100644 --- a/src/stdlib/cson.coffee +++ b/src/stdlib/cson.coffee @@ -14,7 +14,8 @@ module.exports = readObjectAsync: (path, done) -> fs.readFile path, 'utf8', (err, contents) => return done(err) if err? - try done(null, @parseObject(path, contents)) + try + done(null, @parseObject(path, contents)) catch err done(err) diff --git a/static/atom.less b/static/atom.less index f58c62d76..8435756b4 100644 --- a/static/atom.less +++ b/static/atom.less @@ -1,86 +1,10 @@ -html, body { - width: 100%; - height: 100%; - overflow: hidden; -} - -#root-view { - height: 100%; - overflow: hidden; - position: relative; - - #horizontal { - display: -webkit-flex; - height: 100%; - } - - #vertical { - display: -webkit-flex; - -webkit-flex: 1; - -webkit-flex-flow: column; - } -} - -#panes { - position: relative; - -webkit-flex: 1; - - .column { - position: absolute; - top: 0; - bottom: 0; - left: 0; - right: 0; - overflow-y: hidden; - } - - .row { - position: absolute; - top: 0; - bottom: 0; - left: 0; - right: 0; - overflow-x: hidden; - } - - .pane { - position: absolute; - display: -webkit-flex; - -webkit-flex-flow: column; - top: 0; - bottom: 0; - left: 0; - right: 0; - box-sizing: border-box; - } - - .pane .item-views { - -webkit-flex: 1; - display: -webkit-flex; - -webkit-flex-flow: column; - } - - .pane .item-views > * { - -webkit-flex: 1; - min-height: 0; - } -} - -@font-face { - font-family: 'Octicons Regular'; - src: url("octicons-regular-webfont.woff") format("woff"); - font-weight: normal; - font-style: normal; -} - -.is-loading { - background-image: url(images/spinner.svg); - background-repeat: no-repeat; - width: 14px; - height: 14px; - opacity: 0.5; - background-size: contain; - position: relative; - display: inline-block; - padding-left: 19px; +@import "bootstrap/less/bootstrap.less"; +@import "root-view.less"; +@import "overlay.less"; +@import "popover-list.less"; +@import "notification.less"; +@import "markdown.less"; + +:focus { + outline: none; } diff --git a/static/editor.less b/static/editor.less index 4ac5cb832..61ed9ac1b 100644 --- a/static/editor.less +++ b/static/editor.less @@ -30,7 +30,6 @@ cursor: default; min-width: 1em; box-sizing: border-box; - text-align: right; } .editor .gutter .line-number { @@ -139,6 +138,10 @@ position: absolute; } +.editor .line { + white-space: pre; +} + .editor .line span { vertical-align: top; } @@ -158,11 +161,14 @@ } .editor .hidden-input { + padding: 0; + border: 0; position: absolute; z-index: -1; top: 0; left: 0; opacity: 0; + width: 1px; } .editor .selection .region { diff --git a/static/image-view.less b/static/image-view.less index 49c39fbda..ef2deb767 100644 --- a/static/image-view.less +++ b/static/image-view.less @@ -6,6 +6,7 @@ overflow: auto; img { + box-sizing: content-box; padding: 25px; border: 2px solid; background-image: url(images/transparent-background.gif); diff --git a/static/jasmine.less b/static/jasmine.less index efcd29038..d90335ad9 100644 --- a/static/jasmine.less +++ b/static/jasmine.less @@ -25,7 +25,7 @@ body { #HTMLReporter .symbolSummary { background-color: #222; overflow: hidden; - *zoom: 1 + margin: 0; } #HTMLReporter .symbolSummary li { diff --git a/static/mega-octicon-utf-codes.less b/static/mega-octicon-utf-codes.less new file mode 100644 index 000000000..7086023c7 --- /dev/null +++ b/static/mega-octicon-utf-codes.less @@ -0,0 +1,171 @@ +@private-repo: "\f26a"; +@public-repo: "\f201"; +@repo-forked: "\f202"; +@create: "\f203"; +@delete: "\f204"; +@push: "\f205"; +@pull: "\f206"; +@readme: "\f207"; +@wiki: "\f207"; +@octocat: "\f208"; +@site-message: "\f209"; +@blacktocat: "\f209"; +@invertocat: "\f20a"; +@download: "\f20b"; +@upload: "\f20c"; +@keyboard: "\f20d"; +@gist: "\f20e"; +@gist-private: "\f20f"; +@code-file: "\f210"; +@download-unknown: "\f210"; +@download-text: "\f211"; +@text-file: "\f211"; +@download-media: "\f212"; +@download-zip: "\f213"; +@download-pdf: "\f214"; +@download-tag: "\f215"; +@tag: "\f215"; +@directory: "\f216"; +@submodule: "\f217"; +@person: "\f218"; +@team: "\f219"; +@member-added: "\f21a"; +@member-removed: "\f21b"; +@follow: "\f21c"; +@watching: "\f21d"; +@unwatch: "\f21e"; +@commit: "\f21f"; +@public-fork: "\f220"; +@fork: "\f220"; +@branch: "\f220"; +@tree: "\f220"; +@private-fork: "\f221"; +@pull-request: "\f222"; +@merge: "\f223"; +@public-mirror: "\f224"; +@private-mirror: "\f225"; +@issue-opened: "\f226"; +@issue-reopened: "\f227"; +@issue-closed: "\f228"; +@issue-comment: "\f229"; +@star: "\f22a"; +@commit-comment: "\f22b"; +@help: "\f22c"; +@exclamation: "\f22d"; +@search-input: "\f22e"; +@advanced-search: "\f22f"; +@notifications: "\f230"; +@account-settings: "\f231"; +@logout: "\f232"; +@admin-tools: "\f233"; +@feed: "\f234"; +@clipboard: "\f235"; +@apple: "\f236"; +@windows: "\f237"; +@ios: "\f238"; +@download-android: "\f239"; +@android: "\f239"; +@confirm: "\f23a"; +@unread-note: "\f23b"; +@read-note: "\f23c"; +@arr-up: "\f23d"; +@arr-right: "\f23e"; +@arr-down: "\f23f"; +@arr-left: "\f240"; +@pin: "\f241"; +@gift: "\f242"; +@graph: "\f243"; +@wrench: "\f244"; +@credit-card: "\f245"; +@time: "\f246"; +@ruby: "\f247"; +@podcast: "\f248"; +@key: "\f249"; +@force-push: "\f24a"; +@sync: "\f24b"; +@clone: "\f24c"; +@diff: "\f24d"; +@watchers: "\f24e"; +@discussion: "\f24f"; +@delete-note: "\f250"; +@remove-close: "\f250"; +@reply: "\f251"; +@mail-status: "\f252"; +@block: "\f253"; +@tag-create: "\f254"; +@tab-delete: "\f255"; +@branch-create: "\f256"; +@branch-delete: "\f257"; +@edit: "\f258"; +@info: "\f259"; +@arr-collapsed: "\f25a"; +@arr-expanded: "\f25b"; +@link: "\f25c"; +@add: "\f25d"; +@reorder: "\f25e"; +@code: "\f25f"; +@location: "\f260"; +@u-list: "\f261"; +@o-list: "\f262"; +@quotemark: "\f263"; +@version: "\f264"; +@brightness: "\f265"; +@fullscreen: "\f266"; +@normalscreen: "\f267"; +@calendar: "\f268"; +@beer: "\f269"; +@secure: "\f26a"; +@lock: "\f26a"; +@added: "\f26b"; +@removed: "\f26c"; +@modified: "\f26d"; +@moved: "\f26e"; +@renamed: "\f26e"; +@add-comment: "\f26f"; +@horizontal-rule: "\f270"; +@arr-right-mini: "\f271"; +@jump-down: "\f272"; +@jump-up: "\f273"; +@reference: "\f274"; +@milestone: "\f275"; +@save-document: "\f276"; +@megaphone: "\f277"; +@chevron: "\f278"; +@gist-forked: "\f279"; +@gist-add: "\f27a"; +@bookmark: "\f27b"; +@filters: "\f27c"; +@dashboard: "\f27d"; +@history: "\f27e"; +@external-link: "\f27f"; +@mute: "\f280"; +@x: "\f281"; +@add-star: "\f282"; +@remove-star: "\f283"; +@circle-slash: "\f284"; +@pulse: "\f285"; +@new-file: "\f286"; +@refresh: "\f287"; +@telescope: "\f288"; +@microscope: "\f289"; +@align: "\f28a"; +@unalign: "\f28b"; +@gist-secret: "\f28c"; +@home: "\f28d"; +@aligned-to: "\f28e"; +@stop-spam: "\f28f"; +@zap: "\f290"; +@bug: "\f291"; +@loading-inner: "\f292"; +@loading-outer: "\f293"; +@binary-file: "\f294"; +@create-directory: "\f295"; +@database: "\f296"; +@server: "\f297"; +@ignored-file: "\f298"; +@ignore: "\f299"; +@ellipsis: "\f29a"; +@symlink: "\f29b"; +@no-newline: "\f29c"; +@hubot: "\f29d"; +@hourglass: "\f29e"; diff --git a/static/mini-octicon-utf-codes.less b/static/mini-octicon-utf-codes.less new file mode 100644 index 000000000..5a20d3d0a --- /dev/null +++ b/static/mini-octicon-utf-codes.less @@ -0,0 +1,171 @@ +@private-repo: "\f06a"; +@public-repo: "\f001"; +@repo-forked: "\f002"; +@create: "\f003"; +@delete: "\f004"; +@push: "\f005"; +@pull: "\f006"; +@readme: "\f007"; +@wiki: "\f007"; +@octocat: "\f008"; +@site-message: "\f009"; +@blacktocat: "\f009"; +@invertocat: "\f00a"; +@download: "\f00b"; +@upload: "\f00c"; +@keyboard: "\f00d"; +@gist: "\f00e"; +@gist-private: "\f00f"; +@code-file: "\f010"; +@download-unknown: "\f010"; +@download-text: "\f011"; +@text-file: "\f011"; +@download-media: "\f012"; +@download-zip: "\f013"; +@download-pdf: "\f014"; +@download-tag: "\f015"; +@tag: "\f015"; +@directory: "\f016"; +@submodule: "\f017"; +@person: "\f018"; +@team: "\f019"; +@member-added: "\f01a"; +@member-removed: "\f01b"; +@follow: "\f01c"; +@watching: "\f01d"; +@unwatch: "\f01e"; +@commit: "\f01f"; +@public-fork: "\f020"; +@fork: "\f020"; +@branch: "\f020"; +@tree: "\f020"; +@private-fork: "\f021"; +@pull-request: "\f022"; +@merge: "\f023"; +@public-mirror: "\f024"; +@private-mirror: "\f025"; +@issue-opened: "\f026"; +@issue-reopened: "\f027"; +@issue-closed: "\f028"; +@issue-comment: "\f029"; +@star: "\f02a"; +@commit-comment: "\f02b"; +@help: "\f02c"; +@exclamation: "\f02d"; +@search-input: "\f02e"; +@advanced-search: "\f02f"; +@notifications: "\f030"; +@account-settings: "\f031"; +@logout: "\f032"; +@admin-tools: "\f033"; +@feed: "\f034"; +@clipboard: "\f035"; +@apple: "\f036"; +@windows: "\f037"; +@ios: "\f038"; +@download-android: "\f039"; +@android: "\f039"; +@confirm: "\f03a"; +@unread-note: "\f03b"; +@read-note: "\f03c"; +@arr-up: "\f03d"; +@arr-right: "\f03e"; +@arr-down: "\f03f"; +@arr-left: "\f040"; +@pin: "\f041"; +@gift: "\f042"; +@graph: "\f043"; +@wrench: "\f044"; +@credit-card: "\f045"; +@time: "\f046"; +@ruby: "\f047"; +@podcast: "\f048"; +@key: "\f049"; +@force-push: "\f04a"; +@sync: "\f04b"; +@clone: "\f04c"; +@diff: "\f04d"; +@watchers: "\f04e"; +@discussion: "\f04f"; +@delete-note: "\f050"; +@remove-close: "\f050"; +@reply: "\f051"; +@mail-status: "\f052"; +@block: "\f053"; +@tag-create: "\f054"; +@tab-delete: "\f055"; +@branch-create: "\f056"; +@branch-delete: "\f057"; +@edit: "\f058"; +@info: "\f059"; +@arr-collapsed: "\f05a"; +@arr-expanded: "\f05b"; +@link: "\f05c"; +@add: "\f05d"; +@reorder: "\f05e"; +@code: "\f05f"; +@location: "\f060"; +@u-list: "\f061"; +@o-list: "\f062"; +@quotemark: "\f063"; +@version: "\f064"; +@brightness: "\f065"; +@fullscreen: "\f066"; +@normalscreen: "\f067"; +@calendar: "\f068"; +@beer: "\f069"; +@secure: "\f06a"; +@lock: "\f06a"; +@added: "\f06b"; +@removed: "\f06c"; +@modified: "\f06d"; +@moved: "\f06e"; +@renamed: "\f06e"; +@add-comment: "\f06f"; +@horizontal-rule: "\f070"; +@arr-right-mini: "\f071"; +@jump-down: "\f072"; +@jump-up: "\f073"; +@reference: "\f074"; +@milestone: "\f075"; +@save-document: "\f076"; +@megaphone: "\f077"; +@chevron: "\f078"; +@gist-forked: "\f079"; +@gist-add: "\f07a"; +@bookmark: "\f07b"; +@filters: "\f07c"; +@dashboard: "\f07d"; +@history: "\f07e"; +@external-link: "\f07f"; +@mute: "\f080"; +@x: "\f081"; +@add-star: "\f082"; +@remove-star: "\f083"; +@circle-slash: "\f084"; +@pulse: "\f085"; +@new-file: "\f086"; +@refresh: "\f087"; +@telescope: "\f088"; +@microscope: "\f089"; +@align: "\f08a"; +@unalign: "\f08b"; +@gist-secret: "\f08c"; +@home: "\f08d"; +@aligned-to: "\f08e"; +@stop-spam: "\f08f"; +@zap: "\f090"; +@bug: "\f091"; +@loading-inner: "\f092"; +@loading-outer: "\f093"; +@binary-file: "\f094"; +@create-directory: "\f095"; +@database: "\f096"; +@server: "\f097"; +@ignored-file: "\f098"; +@ignore: "\f099"; +@ellipsis: "\f09a"; +@symlink: "\f09b"; +@no-newline: "\f09c"; +@hubot: "\f09d"; +@hourglass: "\f09e"; diff --git a/static/octicon-mixins.less b/static/octicon-mixins.less new file mode 100644 index 000000000..fa660a6e2 --- /dev/null +++ b/static/octicon-mixins.less @@ -0,0 +1,33 @@ +.icon() { + font-family: 'Octicons Regular'; + font-weight: normal; + font-style: normal; + display: inline-block; + line-height: 1; + -webkit-font-smoothing: antialiased; + line-height: 1; + text-decoration: none; +} + +.icon(@size) { + .icon; + font-size: @size; + width: @size; + height: @size; +} + +.mini-icon(@name, @size: 16px) { + @import "mini-octicon-utf-codes.less"; + &:before { + .icon(@size); + content: @@name + } +} + +.mega-icon(@name, @size: 32px) { + @import "mega-octicon-utf-codes.less"; + &:before { + .icon(@size); + content: @@name + } +} diff --git a/static/reset.less b/static/reset.less deleted file mode 100644 index 44497b27d..000000000 --- a/static/reset.less +++ /dev/null @@ -1,68 +0,0 @@ -/* -------------------------------------------------------------- - - reset.css - * Resets default browser CSS. - --------------------------------------------------------------- */ - -html { - margin:0; - padding:0; - border:0; -} - -body, div, span, object, iframe, -h1, h2, h3, h4, h5, h6, p, blockquote, pre, -a, abbr, acronym, address, code, -del, dfn, em, img, q, dl, dt, dd, ol, ul, li, -fieldset, form, label, legend, -table, caption, tbody, tfoot, thead, tr, th, td, -article, aside, dialog, figure, footer, header, -hgroup, nav, section { - margin: 0; - padding: 0; - border: 0; - font-size: 100%; - font: inherit; - vertical-align: baseline; -} - -li { - list-style-type: none; -} - -/* This helps to make newer HTML5 elements behave like DIVs in older browers */ -article, aside, details, figcaption, figure, dialog, -footer, header, hgroup, menu, nav, section { - display:block; -} - -/* Line-height should always be unitless! */ -body { - line-height: 1.5; -} - -/* Tables still need 'cellspacing="0"' in the markup. */ -table { - border-collapse: separate; - border-spacing: 0; -} -/* float:none prevents the span-x classes from breaking table-cell display */ -caption, th, td { - text-align: left; - font-weight: normal; - float:none !important; -} -table, th, td { - vertical-align: middle; -} - -/* Remove possible quote marks (") from,. */ -blockquote:before, blockquote:after, q:before, q:after { content: ''; } -blockquote, q { quotes: "" ""; } - -/* Remove annoying border on linked images. */ -a img { border: none; } - -/* Remember to define your own focus styles! */ -:focus { outline: 0; } diff --git a/static/root-view.less b/static/root-view.less new file mode 100644 index 000000000..f58c62d76 --- /dev/null +++ b/static/root-view.less @@ -0,0 +1,86 @@ +html, body { + width: 100%; + height: 100%; + overflow: hidden; +} + +#root-view { + height: 100%; + overflow: hidden; + position: relative; + + #horizontal { + display: -webkit-flex; + height: 100%; + } + + #vertical { + display: -webkit-flex; + -webkit-flex: 1; + -webkit-flex-flow: column; + } +} + +#panes { + position: relative; + -webkit-flex: 1; + + .column { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + overflow-y: hidden; + } + + .row { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + overflow-x: hidden; + } + + .pane { + position: absolute; + display: -webkit-flex; + -webkit-flex-flow: column; + top: 0; + bottom: 0; + left: 0; + right: 0; + box-sizing: border-box; + } + + .pane .item-views { + -webkit-flex: 1; + display: -webkit-flex; + -webkit-flex-flow: column; + } + + .pane .item-views > * { + -webkit-flex: 1; + min-height: 0; + } +} + +@font-face { + font-family: 'Octicons Regular'; + src: url("octicons-regular-webfont.woff") format("woff"); + font-weight: normal; + font-style: normal; +} + +.is-loading { + background-image: url(images/spinner.svg); + background-repeat: no-repeat; + width: 14px; + height: 14px; + opacity: 0.5; + background-size: contain; + position: relative; + display: inline-block; + padding-left: 19px; +} diff --git a/static/select-list.less b/static/select-list.less index 24676a9c2..affeec33c 100644 --- a/static/select-list.less +++ b/static/select-list.less @@ -1,61 +1,51 @@ -.select-list ol { - border: 1px solid #212121; - position: relative; - overflow-y: auto; - max-height: 312px; - margin: 0; - padding: 0; - line-height: 100%; - -webkit-user-select: none; - cursor: default; -} +@import "octicon-mixins.less"; -.select-list ol:empty { - border: none; -} +.select-list { + .error { + font-weight: bold; + } -.select-list ol li { - padding: 10px; - box-sizing: border-box; - display: block; -} + ol { + position: relative; + overflow-y: auto; + max-height: 312px; + margin: 0; + padding: 0; -.select-list li .label { - display: inline-block; -} + &:empty { + border: none; + } -.select-list li .right { - float: right; -} + li { + padding: 10px; + display: block; + } -.select-list .key-binding { - border-radius: 2px; - margin-left: 5px; - padding: 3px; - font-size: 11px; -} + li:last-child { + border-bottom: none; + } -.select-list ol li:last-child { - border-bottom: none; -} + li.active-item { + .mini-icon(confirm, 14px); -.select-list .error { - font-weight: bold; - color: white; - text-shadow: 0 1px 0 #4E0000; -} + &:before { + margin-right: 5px; + } + } -.select-list li.active-item:before { - font-family: 'Octicons Regular'; - font-size: 14px; - width: 14px; - height: 14px; - margin-right: 5px; - -webkit-font-smoothing: antialiased; - color: #9d9d9d; - content: '\f03a'; -} + li.inactive-item { + padding-left: 29px; + } -.select-list li.inactive-item { - padding-left: 29px; + li .right { + float: right; + } + + .key-binding { + border-radius: 2px; + margin-left: 5px; + padding: 3px; + font-size: 11px; + } + } } diff --git a/themes/atom-dark-ui/atom.less b/themes/atom-dark-ui/atom.less index 22afd1c83..915e12106 100644 --- a/themes/atom-dark-ui/atom.less +++ b/themes/atom-dark-ui/atom.less @@ -1,3 +1,5 @@ +@import "bootstrap/less/mixins.less"; + html, body, #root-view { font: caption; @@ -23,4 +25,9 @@ html, body, .wrap-guide { background: rgba(150, 150, 150, 0.1); -} \ No newline at end of file +} + +.btn { + .btn-pseudo-states(#19191a, #1a1b1c); + color: #969696; +} diff --git a/themes/atom-dark-ui/select-list.less b/themes/atom-dark-ui/select-list.less index bbe192765..0413d192a 100644 --- a/themes/atom-dark-ui/select-list.less +++ b/themes/atom-dark-ui/select-list.less @@ -1,48 +1,60 @@ -.select-list ol li { - background-color: #27292b; - border-bottom: 1px solid #1e1e1e; -} +.select-list { + .error { + color: white; + text-shadow: 0 1px 0 #4E0000; + } -.select-list li .label { - color: #bbb; -} + ol { + border: 1px solid #212121; -.select-list .key-binding { - background: -webkit-linear-gradient( - rgba(100, 100, 100, 0.5), - rgba(70,70,70, 0.5)); - color: #ccc; - -webkit-box-shadow: inset 1px 1px 0 rgba(255, 255, 255, 0.1); - display: inline-block; - line-height: 100%; -} + li { + background-color: #27292b; + border-bottom: 1px solid #1e1e1e; + color: #bbb; + } -.select-list li:hover .label { - color: #fff; -} + .key-binding { + background: -webkit-linear-gradient( + rgba(100, 100, 100, 0.5), + rgba(70,70,70, 0.5)); + color: #ccc; + -webkit-box-shadow: inset 1px 1px 0 rgba(255, 255, 255, 0.1); + display: inline-block; + line-height: 100%; + } -.select-list ol .selected { - background-image: -webkit-linear-gradient(#7e7e7e, #737373); -} + li.active-item:before { + color: #9d9d9d; + } -.select-list .right, -.select-list .directory { - color: #777; -} + li:hover { + color: #fff; + } -.select-list ol .selected .label { - color: #fff; -} + .selected { + background-image: -webkit-linear-gradient(#7e7e7e, #737373); + } -.select-list ol .selected .right, -.select-list ol .selected .directory { - color: #ccc; -} + .right, + .directory { + color: #777; + } -.select-list .modified { - color: #f78a46; -} + .selected { + color: #fff; + } -.select-list .new { - color: #5293d8; + .selected .right, + .selected .directory { + color: #ccc; + } + + .modified { + color: #f78a46; + } + + .new { + color: #5293d8; + } + } } diff --git a/themes/atom-light-ui/atom.less b/themes/atom-light-ui/atom.less index 76f2e989b..38c6e453d 100644 --- a/themes/atom-light-ui/atom.less +++ b/themes/atom-light-ui/atom.less @@ -1,3 +1,5 @@ +@import "bootstrap/less/mixins.less"; + html, body, #root-view { font: caption; @@ -23,4 +25,14 @@ html, body, .wrap-guide { background: rgba(150, 150, 150, 0.1); -} \ No newline at end of file +} + +.btn { + .btn-pseudo-states(#cbcbcb, #b3b3b3); + color: #444; + + &:hover, + &:focus { + color: #222; + } +} diff --git a/themes/atom-light-ui/select-list.less b/themes/atom-light-ui/select-list.less index 54a8934aa..1864cd06c 100644 --- a/themes/atom-light-ui/select-list.less +++ b/themes/atom-light-ui/select-list.less @@ -3,39 +3,48 @@ border: 1px solid #c6c6c6; color: #323232; box-shadow: 0 0 10px #555; -} -.select-list ol { - border: 1px solid #d2d2d2; -} + .error { + color: white; + text-shadow: 0 1px 0 #4E0000; + } -.select-list ol li { - background-color: #f5f5f5; - border-bottom: 1px solid #ccc; -} + ol { + border: 1px solid #d2d2d2; -.select-list li:hover { - background-color: #f9f9f9; -} + li { + background-color: #f5f5f5; + border-bottom: 1px solid #ccc; + } -.select-list ol .selected { - background-color: #e0e0e0; -} + li:hover { + background-color: #f9f9f9; + } -.select-list .right, -.select-list .directory { - color: #777; -} + li.active-item:before { + color: #9d9d9d; + } -.select-list ol .selected .right, -.select-list ol .selected .directory { - color: #333; -} + .selected { + background-color: #e0e0e0; + } -.select-list .modified { - color: #f78a46; -} + .selected .right, + .selected .directory { + color: #333; + } -.select-list .new { - color: #5293d8; + .right, + .directory { + color: #777; + } + + .modified { + color: #f78a46; + } + + .new { + color: #5293d8; + } + } } diff --git a/vendor/bootstrap b/vendor/bootstrap new file mode 160000 index 000000000..991a353a2 --- /dev/null +++ b/vendor/bootstrap @@ -0,0 +1 @@ +Subproject commit 991a353a26f9f7a7399591985af0afb191eee6ee diff --git a/vendor/less.js b/vendor/less.js deleted file mode 100644 index 59629068d..000000000 --- a/vendor/less.js +++ /dev/null @@ -1,5078 +0,0 @@ -// Modified -// -// Added -// module.exports.less = window.less = less = {} -// less.tree = tree = {} -// less.mode = 'browser' -// -// LESS - Leaner CSS v1.4.0 -// http://lesscss.org -// -// Copyright (c) 2009-2013, Alexis Sellier -// Licensed under the Apache 2.0 License. -// -(function (window, undefined) { -// -// Stub out `require` in the browser -// -function require(arg) { - return window.less[arg.split('/')[1]]; -}; - -// ecma-5.js -// -// -- kriskowal Kris Kowal Copyright (C) 2009-2010 MIT License -// -- tlrobinson Tom Robinson -// dantman Daniel Friesen - -// -// Array -// -if (!Array.isArray) { - Array.isArray = function(obj) { - return Object.prototype.toString.call(obj) === "[object Array]" || - (obj instanceof Array); - }; -} -if (!Array.prototype.forEach) { - Array.prototype.forEach = function(block, thisObject) { - var len = this.length >>> 0; - for (var i = 0; i < len; i++) { - if (i in this) { - block.call(thisObject, this[i], i, this); - } - } - }; -} -if (!Array.prototype.map) { - Array.prototype.map = function(fun /*, thisp*/) { - var len = this.length >>> 0; - var res = new Array(len); - var thisp = arguments[1]; - - for (var i = 0; i < len; i++) { - if (i in this) { - res[i] = fun.call(thisp, this[i], i, this); - } - } - return res; - }; -} -if (!Array.prototype.filter) { - Array.prototype.filter = function (block /*, thisp */) { - var values = []; - var thisp = arguments[1]; - for (var i = 0; i < this.length; i++) { - if (block.call(thisp, this[i])) { - values.push(this[i]); - } - } - return values; - }; -} -if (!Array.prototype.reduce) { - Array.prototype.reduce = function(fun /*, initial*/) { - var len = this.length >>> 0; - var i = 0; - - // no value to return if no initial value and an empty array - if (len === 0 && arguments.length === 1) throw new TypeError(); - - if (arguments.length >= 2) { - var rv = arguments[1]; - } else { - do { - if (i in this) { - rv = this[i++]; - break; - } - // if array contains no values, no initial value to return - if (++i >= len) throw new TypeError(); - } while (true); - } - for (; i < len; i++) { - if (i in this) { - rv = fun.call(null, rv, this[i], i, this); - } - } - return rv; - }; -} -if (!Array.prototype.indexOf) { - Array.prototype.indexOf = function (value /*, fromIndex */ ) { - var length = this.length; - var i = arguments[1] || 0; - - if (!length) return -1; - if (i >= length) return -1; - if (i < 0) i += length; - - for (; i < length; i++) { - if (!Object.prototype.hasOwnProperty.call(this, i)) { continue } - if (value === this[i]) return i; - } - return -1; - }; -} - -// -// Object -// -if (!Object.keys) { - Object.keys = function (object) { - var keys = []; - for (var name in object) { - if (Object.prototype.hasOwnProperty.call(object, name)) { - keys.push(name); - } - } - return keys; - }; -} - -// -// String -// -if (!String.prototype.trim) { - String.prototype.trim = function () { - return String(this).replace(/^\s\s*/, '').replace(/\s\s*$/, ''); - }; -} -var less, tree, charset; - -module.exports.less = window.less = less = {} -less.tree = tree = {} -less.mode = 'browser' -// -// less.js - parser -// -// A relatively straight-forward predictive parser. -// There is no tokenization/lexing stage, the input is parsed -// in one sweep. -// -// To make the parser fast enough to run in the browser, several -// optimization had to be made: -// -// - Matching and slicing on a huge input is often cause of slowdowns. -// The solution is to chunkify the input into smaller strings. -// The chunks are stored in the `chunks` var, -// `j` holds the current chunk index, and `current` holds -// the index of the current chunk in relation to `input`. -// This gives us an almost 4x speed-up. -// -// - In many cases, we don't need to match individual tokens; -// for example, if a value doesn't hold any variables, operations -// or dynamic references, the parser can effectively 'skip' it, -// treating it as a literal. -// An example would be '1px solid #000' - which evaluates to itself, -// we don't need to know what the individual components are. -// The drawback, of course is that you don't get the benefits of -// syntax-checking on the CSS. This gives us a 50% speed-up in the parser, -// and a smaller speed-up in the code-gen. -// -// -// Token matching is done with the `$` function, which either takes -// a terminal string or regexp, or a non-terminal function to call. -// It also takes care of moving all the indices forwards. -// -// -less.Parser = function Parser(env) { - var input, // LeSS input string - i, // current index in `input` - j, // current chunk - temp, // temporarily holds a chunk's state, for backtracking - memo, // temporarily holds `i`, when backtracking - furthest, // furthest index the parser has gone to - chunks, // chunkified input - current, // index of current chunk, in `input` - parser; - - var that = this; - - // Top parser on an import tree must be sure there is one "env" - // which will then be passed around by reference. - if (!(env instanceof tree.parseEnv)) { - env = new tree.parseEnv(env); - } - - if (!env.currentDirectory && env.filename) { - // only works for node, only used for node - env.currentDirectory = env.filename.replace(/[^\/\\]*$/, ""); - } - - // This function is called after all files - // have been imported through `@import`. - var finish = function () {}; - - var imports = this.imports = { - paths: env.paths || [], // Search paths, when importing - queue: [], // Files which haven't been imported yet - files: env.files, // Holds the imported parse trees - contents: env.contents, // Holds the imported file contents - mime: env.mime, // MIME type of .less files - error: null, // Error in parsing/evaluating an import - push: function (path, callback) { - var that = this; - this.queue.push(path); - - // - // Import a file asynchronously - // - less.Parser.importer(path, this.paths, function (e, root, fullPath) { - that.queue.splice(that.queue.indexOf(path), 1); // Remove the path from the queue - - var imported = fullPath in that.files; - - that.files[fullPath] = root; // Store the root - - if (e && !that.error) { that.error = e; } - - callback(e, root, imported); - - if (that.queue.length === 0) { finish(that.error); } // Call `finish` if we're done importing - }, env); - } - }; - - function save() { temp = chunks[j], memo = i, current = i; } - function restore() { chunks[j] = temp, i = memo, current = i; } - - function sync() { - if (i > current) { - chunks[j] = chunks[j].slice(i - current); - current = i; - } - } - function isWhitespace(c) { - // Could change to \s? - var code = c.charCodeAt(0); - return code === 32 || code === 10 || code === 9; - } - // - // Parse from a token, regexp or string, and move forward if match - // - function $(tok) { - var match, args, length, index, k; - - // - // Non-terminal - // - if (tok instanceof Function) { - return tok.call(parser.parsers); - // - // Terminal - // - // Either match a single character in the input, - // or match a regexp in the current chunk (chunk[j]). - // - } else if (typeof(tok) === 'string') { - match = input.charAt(i) === tok ? tok : null; - length = 1; - sync (); - } else { - sync (); - - if (match = tok.exec(chunks[j])) { - length = match[0].length; - } else { - return null; - } - } - - // The match is confirmed, add the match length to `i`, - // and consume any extra white-space characters (' ' || '\n') - // which come after that. The reason for this is that LeSS's - // grammar is mostly white-space insensitive. - // - if (match) { - skipWhitespace(length); - - if(typeof(match) === 'string') { - return match; - } else { - return match.length === 1 ? match[0] : match; - } - } - } - - function skipWhitespace(length) { - var oldi = i, oldj = j, - endIndex = i + chunks[j].length, - mem = i += length; - - while (i < endIndex) { - if (! isWhitespace(input.charAt(i))) { break } - i++; - } - chunks[j] = chunks[j].slice(length + (i - mem)); - current = i; - - if (chunks[j].length === 0 && j < chunks.length - 1) { j++ } - - return oldi !== i || oldj !== j; - } - - function expect(arg, msg) { - var result = $(arg); - if (! result) { - error(msg || (typeof(arg) === 'string' ? "expected '" + arg + "' got '" + input.charAt(i) + "'" - : "unexpected token")); - } else { - return result; - } - } - - function error(msg, type) { - var e = new Error(msg); - e.index = i; - e.type = type || 'Syntax'; - throw e; - } - - // Same as $(), but don't change the state of the parser, - // just return the match. - function peek(tok) { - if (typeof(tok) === 'string') { - return input.charAt(i) === tok; - } else { - if (tok.test(chunks[j])) { - return true; - } else { - return false; - } - } - } - - function getInput(e, env) { - if (e.filename && env.filename && (e.filename !== env.filename)) { - return parser.imports.contents[e.filename]; - } else { - return input; - } - } - - function getLocation(index, input) { - for (var n = index, column = -1; - n >= 0 && input.charAt(n) !== '\n'; - n--) { column++ } - - return { line: typeof(index) === 'number' ? (input.slice(0, index).match(/\n/g) || "").length : null, - column: column }; - } - - function getFileName(e) { - if(less.mode === 'browser' || less.mode === 'rhino') - return e.filename; - else - return require('path').resolve(e.filename); - } - - function getDebugInfo(index, inputStream, e) { - return { - lineNumber: getLocation(index, inputStream).line + 1, - fileName: getFileName(e) - }; - } - - function LessError(e, env) { - var input = getInput(e, env), - loc = getLocation(e.index, input), - line = loc.line, - col = loc.column, - lines = input.split('\n'); - - this.type = e.type || 'Syntax'; - this.message = e.message; - this.filename = e.filename || env.filename; - this.index = e.index; - this.line = typeof(line) === 'number' ? line + 1 : null; - this.callLine = e.call && (getLocation(e.call, input).line + 1); - this.callExtract = lines[getLocation(e.call, input).line]; - this.stack = e.stack; - this.column = col; - this.extract = [ - lines[line - 1], - lines[line], - lines[line + 1] - ]; - } - - this.env = env = env || {}; - - // The optimization level dictates the thoroughness of the parser, - // the lower the number, the less nodes it will create in the tree. - // This could matter for debugging, or if you want to access - // the individual nodes in the tree. - this.optimization = ('optimization' in this.env) ? this.env.optimization : 1; - - this.env.filename = this.env.filename || null; - - // - // The Parser - // - return parser = { - - imports: imports, - // - // Parse an input string into an abstract syntax tree, - // call `callback` when done. - // - parse: function (str, callback) { - var root, start, end, zone, line, lines, buff = [], c, error = null; - - i = j = current = furthest = 0; - input = str.replace(/\r\n/g, '\n'); - - // Remove potential UTF Byte Order Mark - input = input.replace(/^\uFEFF/, ''); - - // Split the input into chunks. - chunks = (function (chunks) { - var j = 0, - skip = /(?:@\{[\w-]+\}|[^"'`\{\}\/\(\)\\])+/g, - comment = /\/\*(?:[^*]|\*+[^\/*])*\*+\/|\/\/.*/g, - string = /"((?:[^"\\\r\n]|\\.)*)"|'((?:[^'\\\r\n]|\\.)*)'|`((?:[^`]|\\.)*)`/g, - level = 0, - match, - chunk = chunks[0], - inParam; - - for (var i = 0, c, cc; i < input.length;) { - skip.lastIndex = i; - if (match = skip.exec(input)) { - if (match.index === i) { - i += match[0].length; - chunk.push(match[0]); - } - } - c = input.charAt(i); - comment.lastIndex = string.lastIndex = i; - - if (match = string.exec(input)) { - if (match.index === i) { - i += match[0].length; - chunk.push(match[0]); - continue; - } - } - - if (!inParam && c === '/') { - cc = input.charAt(i + 1); - if (cc === '/' || cc === '*') { - if (match = comment.exec(input)) { - if (match.index === i) { - i += match[0].length; - chunk.push(match[0]); - continue; - } - } - } - } - - switch (c) { - case '{': if (! inParam) { level ++; chunk.push(c); break } - case '}': if (! inParam) { level --; chunk.push(c); chunks[++j] = chunk = []; break } - case '(': if (! inParam) { inParam = true; chunk.push(c); break } - case ')': if ( inParam) { inParam = false; chunk.push(c); break } - default: chunk.push(c); - } - - i++; - } - if (level != 0) { - error = new(LessError)({ - index: i-1, - type: 'Parse', - message: (level > 0) ? "missing closing `}`" : "missing opening `{`", - filename: env.filename - }, env); - } - - return chunks.map(function (c) { return c.join('') });; - })([[]]); - - if (error) { - return callback(new(LessError)(error, env)); - } - - // Start with the primary rule. - // The whole syntax tree is held under a Ruleset node, - // with the `root` property set to true, so no `{}` are - // output. The callback is called when the input is parsed. - try { - root = new(tree.Ruleset)([], $(this.parsers.primary)); - root.root = true; - } catch (e) { - return callback(new(LessError)(e, env)); - } - - root.toCSS = (function (evaluate) { - var line, lines, column; - - return function (options, variables) { - options = options || {}; - var importError, - evalEnv = new tree.evalEnv(options); - - // - // Allows setting variables with a hash, so: - // - // `{ color: new(tree.Color)('#f01') }` will become: - // - // new(tree.Rule)('@color', - // new(tree.Value)([ - // new(tree.Expression)([ - // new(tree.Color)('#f01') - // ]) - // ]) - // ) - // - if (typeof(variables) === 'object' && !Array.isArray(variables)) { - variables = Object.keys(variables).map(function (k) { - var value = variables[k]; - - if (! (value instanceof tree.Value)) { - if (! (value instanceof tree.Expression)) { - value = new(tree.Expression)([value]); - } - value = new(tree.Value)([value]); - } - return new(tree.Rule)('@' + k, value, false, 0); - }); - evalEnv.frames = [new(tree.Ruleset)(null, variables)]; - } - - try { - var css = evaluate.call(this, evalEnv) - .toCSS([], { - compress: options.compress || false, - dumpLineNumbers: env.dumpLineNumbers, - strictUnits: options.strictUnits === false ? false : true}); - } catch (e) { - throw new(LessError)(e, env); - } - - if (options.yuicompress && less.mode === 'node') { - return require('ycssmin').cssmin(css); - } else if (options.compress) { - return css.replace(/(\s)+/g, "$1"); - } else { - return css; - } - }; - })(root.eval); - - // If `i` is smaller than the `input.length - 1`, - // it means the parser wasn't able to parse the whole - // string, so we've got a parsing error. - // - // We try to extract a \n delimited string, - // showing the line where the parse error occured. - // We split it up into two parts (the part which parsed, - // and the part which didn't), so we can color them differently. - if (i < input.length - 1) { - i = furthest; - lines = input.split('\n'); - line = (input.slice(0, i).match(/\n/g) || "").length + 1; - - for (var n = i, column = -1; n >= 0 && input.charAt(n) !== '\n'; n--) { column++ } - - error = { - type: "Parse", - message: "Unrecognised input", - index: i, - filename: env.filename, - line: line, - column: column, - extract: [ - lines[line - 2], - lines[line - 1], - lines[line] - ] - }; - } - - finish = function (e) { - e = error || e || parser.imports.error; - - if (e) { - if (!(e instanceof LessError)) { - e = new(LessError)(e, env); - } - - callback(e); - } - else { - callback(null, root); - } - }; - - if (this.imports.queue.length === 0) { - finish(); - } - }, - - // - // Here in, the parsing rules/functions - // - // The basic structure of the syntax tree generated is as follows: - // - // Ruleset -> Rule -> Value -> Expression -> Entity - // - // Here's some LESS code: - // - // .class { - // color: #fff; - // border: 1px solid #000; - // width: @w + 4px; - // > .child {...} - // } - // - // And here's what the parse tree might look like: - // - // Ruleset (Selector '.class', [ - // Rule ("color", Value ([Expression [Color #fff]])) - // Rule ("border", Value ([Expression [Dimension 1px][Keyword "solid"][Color #000]])) - // Rule ("width", Value ([Expression [Operation "+" [Variable "@w"][Dimension 4px]]])) - // Ruleset (Selector [Element '>', '.child'], [...]) - // ]) - // - // In general, most rules will try to parse a token with the `$()` function, and if the return - // value is truly, will return a new node, of the relevant type. Sometimes, we need to check - // first, before parsing, that's when we use `peek()`. - // - parsers: { - // - // The `primary` rule is the *entry* and *exit* point of the parser. - // The rules here can appear at any level of the parse tree. - // - // The recursive nature of the grammar is an interplay between the `block` - // rule, which represents `{ ... }`, the `ruleset` rule, and this `primary` rule, - // as represented by this simplified grammar: - // - // primary → (ruleset | rule)+ - // ruleset → selector+ block - // block → '{' primary '}' - // - // Only at one point is the primary rule not called from the - // block rule: at the root level. - // - primary: function () { - var node, root = []; - - while ((node = $(this.extendRule) || $(this.mixin.definition) || $(this.rule) || $(this.ruleset) || - $(this.mixin.call) || $(this.comment) || $(this.directive)) - || $(/^[\s\n]+/) || $(/^;+/)) { - node && root.push(node); - } - return root; - }, - - // We create a Comment node for CSS comments `/* */`, - // but keep the LeSS comments `//` silent, by just skipping - // over them. - comment: function () { - var comment; - - if (input.charAt(i) !== '/') return; - - if (input.charAt(i + 1) === '/') { - return new(tree.Comment)($(/^\/\/.*/), true); - } else if (comment = $(/^\/\*(?:[^*]|\*+[^\/*])*\*+\/\n?/)) { - return new(tree.Comment)(comment); - } - }, - - // - // Entities are tokens which can be found inside an Expression - // - entities: { - // - // A string, which supports escaping " and ' - // - // "milky way" 'he\'s the one!' - // - quoted: function () { - var str, j = i, e; - - if (input.charAt(j) === '~') { j++, e = true } // Escaped strings - if (input.charAt(j) !== '"' && input.charAt(j) !== "'") return; - - e && $('~'); - - if (str = $(/^"((?:[^"\\\r\n]|\\.)*)"|'((?:[^'\\\r\n]|\\.)*)'/)) { - return new(tree.Quoted)(str[0], str[1] || str[2], e); - } - }, - - // - // A catch-all word, such as: - // - // black border-collapse - // - keyword: function () { - var k; - - if (k = $(/^[_A-Za-z-][_A-Za-z0-9-]*/)) { - if (tree.colors.hasOwnProperty(k)) { - // detect named color - return new(tree.Color)(tree.colors[k].slice(1)); - } else { - return new(tree.Keyword)(k); - } - } - }, - - // - // A function call - // - // rgb(255, 0, 255) - // - // We also try to catch IE's `alpha()`, but let the `alpha` parser - // deal with the details. - // - // The arguments are parsed with the `entities.arguments` parser. - // - call: function () { - var name, nameLC, args, alpha_ret, index = i; - - if (! (name = /^([\w-]+|%|progid:[\w\.]+)\(/.exec(chunks[j]))) return; - - name = name[1]; - nameLC = name.toLowerCase(); - - if (nameLC === 'url') { return null } - else { i += name.length } - - if (nameLC === 'alpha') { - alpha_ret = $(this.alpha); - if(typeof alpha_ret !== 'undefined') { - return alpha_ret; - } - } - - $('('); // Parse the '(' and consume whitespace. - - args = $(this.entities.arguments); - - if (! $(')')) { - return; - } - - if (name) { return new(tree.Call)(name, args, index, env.filename, env.rootpath, env.currentDirectory); } - }, - arguments: function () { - var args = [], arg; - - while (arg = $(this.entities.assignment) || $(this.expression)) { - args.push(arg); - if (! $(',')) { break } - } - return args; - }, - literal: function () { - return $(this.entities.dimension) || - $(this.entities.color) || - $(this.entities.quoted) || - $(this.entities.unicodeDescriptor); - }, - - // Assignments are argument entities for calls. - // They are present in ie filter properties as shown below. - // - // filter: progid:DXImageTransform.Microsoft.Alpha( *opacity=50* ) - // - - assignment: function () { - var key, value; - if ((key = $(/^\w+(?=\s?=)/i)) && $('=') && (value = $(this.entity))) { - return new(tree.Assignment)(key, value); - } - }, - - // - // Parse url() tokens - // - // We use a specific rule for urls, because they don't really behave like - // standard function calls. The difference is that the argument doesn't have - // to be enclosed within a string, so it can't be parsed as an Expression. - // - url: function () { - var value; - - if (input.charAt(i) !== 'u' || !$(/^url\(/)) return; - value = $(this.entities.quoted) || $(this.entities.variable) || - $(/^(?:(?:\\[\(\)'"])|[^\(\)'"])+/) || ""; - - expect(')'); - - return new(tree.URL)((value.value != null || value instanceof tree.Variable) - ? value : new(tree.Anonymous)(value), env.rootpath); - }, - - // - // A Variable entity, such as `@fink`, in - // - // width: @fink + 2px - // - // We use a different parser for variable definitions, - // see `parsers.variable`. - // - variable: function () { - var name, index = i; - - if (input.charAt(i) === '@' && (name = $(/^@@?[\w-]+/))) { - return new(tree.Variable)(name, index, env.filename); - } - }, - - // A variable entity useing the protective {} e.g. @{var} - variableCurly: function () { - var name, curly, index = i; - - if (input.charAt(i) === '@' && (curly = $(/^@\{([\w-]+)\}/))) { - return new(tree.Variable)("@" + curly[1], index, env.filename); - } - }, - - // - // A Hexadecimal color - // - // #4F3C2F - // - // `rgb` and `hsl` colors are parsed through the `entities.call` parser. - // - color: function () { - var rgb; - - if (input.charAt(i) === '#' && (rgb = $(/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})/))) { - return new(tree.Color)(rgb[1]); - } - }, - - // - // A Dimension, that is, a number and a unit - // - // 0.5em 95% - // - dimension: function () { - var value, c = input.charCodeAt(i); - //Is the first char of the dimension 0-9, '.', '+' or '-' - if ((c > 57 || c < 43) || c === 47 || c == 44) return; - - if (value = $(/^([+-]?\d*\.?\d+)(%|[a-z]+)?/)) { - return new(tree.Dimension)(value[1], value[2]); - } - }, - - // - // A unicode descriptor, as is used in unicode-range - // - // U+0?? or U+00A1-00A9 - // - unicodeDescriptor: function () { - var ud; - - if (ud = $(/^U\+[0-9a-fA-F?]+(\-[0-9a-fA-F?]+)?/)) { - return new(tree.UnicodeDescriptor)(ud[0]); - } - }, - - // - // JavaScript code to be evaluated - // - // `window.location.href` - // - javascript: function () { - var str, j = i, e; - - if (input.charAt(j) === '~') { j++, e = true } // Escaped strings - if (input.charAt(j) !== '`') { return } - - e && $('~'); - - if (str = $(/^`([^`]*)`/)) { - return new(tree.JavaScript)(str[1], i, e); - } - } - }, - - // - // The variable part of a variable definition. Used in the `rule` parser - // - // @fink: - // - variable: function () { - var name; - - if (input.charAt(i) === '@' && (name = $(/^(@[\w-]+)\s*:/))) { return name[1] } - }, - - // - // extend syntax - used to extend selectors - // - extend: function(isRule) { - var elements = [], e, args, index = i; - - if (!$(isRule ? /^&:extend\(/ : /^:extend\(/)) { return; } - - while (e = $(/^[#.](?:[\w-]|\\(?:[a-fA-F0-9]{1,6} ?|[^a-fA-F0-9]))+/)) { - elements.push(new(tree.Element)(null, e, i)); - } - - expect(/^\)/); - - if (isRule) { - expect(/^;/); - } - - return new(tree.Extend)(elements, index); - }, - - // - // extendRule - used in a rule to extend all the parent selectors - // - extendRule: function() { - return this.extend(true); - }, - - // - // Mixins - // - mixin: { - // - // A Mixin call, with an optional argument list - // - // #mixins > .square(#fff); - // .rounded(4px, black); - // .button; - // - // The `while` loop is there because mixins can be - // namespaced, but we only support the child and descendant - // selector for now. - // - call: function () { - var elements = [], e, c, argsSemiColon = [], argsComma = [], args, delim, arg, nameLoop, expressions, isSemiColonSeperated, expressionContainsNamed, index = i, s = input.charAt(i), name, value, important = false; - - if (s !== '.' && s !== '#') { return } - - save(); // stop us absorbing part of an invalid selector - - while (e = $(/^[#.](?:[\w-]|\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+/)) { - elements.push(new(tree.Element)(c, e, i)); - c = $('>'); - } - if ($('(')) { - expressions = []; - while (arg = $(this.expression)) { - nameLoop = null; - arg.throwAwayComments(); - value = arg; - - // Variable - if (arg.value.length == 1) { - var val = arg.value[0]; - if (val instanceof tree.Variable) { - if ($(':')) { - if (expressions.length > 0) { - if (isSemiColonSeperated) { - error("Cannot mix ; and , as delimiter types"); - } - expressionContainsNamed = true; - } - value = expect(this.expression); - nameLoop = (name = val.name); - } - } - } - - expressions.push(value); - - argsComma.push({ name: nameLoop, value: value }); - - if ($(',')) { - continue; - } - - if ($(';') || isSemiColonSeperated) { - - if (expressionContainsNamed) { - error("Cannot mix ; and , as delimiter types"); - } - - isSemiColonSeperated = true; - - if (expressions.length > 1) { - value = new(tree.Value)(expressions); - } - argsSemiColon.push({ name: name, value: value }); - - name = null; - expressions = []; - expressionContainsNamed = false; - } - } - - expect(')'); - } - - args = isSemiColonSeperated ? argsSemiColon : argsComma; - - if ($(this.important)) { - important = true; - } - - if (elements.length > 0 && ($(';') || peek('}'))) { - return new(tree.mixin.Call)(elements, args, index, env.filename, important); - } - - restore(); - }, - - // - // A Mixin definition, with a list of parameters - // - // .rounded (@radius: 2px, @color) { - // ... - // } - // - // Until we have a finer grained state-machine, we have to - // do a look-ahead, to make sure we don't have a mixin call. - // See the `rule` function for more information. - // - // We start by matching `.rounded (`, and then proceed on to - // the argument list, which has optional default values. - // We store the parameters in `params`, with a `value` key, - // if there is a value, such as in the case of `@radius`. - // - // Once we've got our params list, and a closing `)`, we parse - // the `{...}` block. - // - definition: function () { - var name, params = [], match, ruleset, param, value, cond, variadic = false; - if ((input.charAt(i) !== '.' && input.charAt(i) !== '#') || - peek(/^[^{]*\}/)) return; - - save(); - - if (match = $(/^([#.](?:[\w-]|\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+)\s*\(/)) { - name = match[1]; - - do { - $(this.comment); - if (input.charAt(i) === '.' && $(/^\.{3}/)) { - variadic = true; - params.push({ variadic: true }); - break; - } else if (param = $(this.entities.variable) || $(this.entities.literal) - || $(this.entities.keyword)) { - // Variable - if (param instanceof tree.Variable) { - if ($(':')) { - value = expect(this.expression, 'expected expression'); - params.push({ name: param.name, value: value }); - } else if ($(/^\.{3}/)) { - params.push({ name: param.name, variadic: true }); - variadic = true; - break; - } else { - params.push({ name: param.name }); - } - } else { - params.push({ value: param }); - } - } else { - break; - } - } while ($(',') || $(';')) - - // .mixincall("@{a}"); - // looks a bit like a mixin definition.. so we have to be nice and restore - if (!$(')')) { - furthest = i; - restore(); - } - - $(this.comment); - - if ($(/^when/)) { // Guard - cond = expect(this.conditions, 'expected condition'); - } - - ruleset = $(this.block); - - if (ruleset) { - return new(tree.mixin.Definition)(name, params, ruleset, cond, variadic); - } else { - restore(); - } - } - } - }, - - // - // Entities are the smallest recognized token, - // and can be found inside a rule's value. - // - entity: function () { - return $(this.entities.literal) || $(this.entities.variable) || $(this.entities.url) || - $(this.entities.call) || $(this.entities.keyword) ||$(this.entities.javascript) || - $(this.comment); - }, - - // - // A Rule terminator. Note that we use `peek()` to check for '}', - // because the `block` rule will be expecting it, but we still need to make sure - // it's there, if ';' was ommitted. - // - end: function () { - return $(';') || peek('}'); - }, - - // - // IE's alpha function - // - // alpha(opacity=88) - // - alpha: function () { - var value; - - if (! $(/^\(opacity=/i)) return; - if (value = $(/^\d+/) || $(this.entities.variable)) { - expect(')'); - return new(tree.Alpha)(value); - } - }, - - // - // A Selector Element - // - // div - // + h1 - // #socks - // input[type="text"] - // - // Elements are the building blocks for Selectors, - // they are made out of a `Combinator` (see combinator rule), - // and an element name, such as a tag a class, or `*`. - // - element: function () { - var e, t, c, v; - - c = $(this.combinator); - - e = $(/^(?:\d+\.\d+|\d+)%/) || $(/^(?:[.#]?|:*)(?:[\w-]|[^\x00-\x9f]|\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+/) || - $('*') || $('&') || $(this.attribute) || $(/^\([^()@]+\)/) || $(/^[\.#](?=@)/) || $(this.entities.variableCurly); - - if (! e) { - if ($('(')) { - if ((v = (//$(this.entities.variableCurly) || - $(this.selector))) && - $(')')) { - e = new(tree.Paren)(v); - } - } - } - - if (e) { return new(tree.Element)(c, e, i) } - }, - - // - // Combinators combine elements together, in a Selector. - // - // Because our parser isn't white-space sensitive, special care - // has to be taken, when parsing the descendant combinator, ` `, - // as it's an empty space. We have to check the previous character - // in the input, to see if it's a ` ` character. More info on how - // we deal with this in *combinator.js*. - // - combinator: function () { - var match, c = input.charAt(i); - - if (c === '>' || c === '+' || c === '~' || c === '|') { - i++; - while (input.charAt(i).match(/\s/)) { i++ } - return new(tree.Combinator)(c); - } else if (input.charAt(i - 1).match(/\s/)) { - return new(tree.Combinator)(" "); - } else { - return new(tree.Combinator)(null); - } - }, - - // - // A CSS Selector - // - // .class > div + h1 - // li a:hover - // - // Selectors are made out of one or more Elements, see above. - // - selector: function () { - var sel, e, elements = [], c, match, extend; - - while ((extend = $(this.extend)) || (e = $(this.element))) { - if (!e) { - break; - } - c = input.charAt(i); - elements.push(e) - e = null; - if (c === '{' || c === '}' || c === ';' || c === ',' || c === ')') { break } - } - - if (elements.length > 0) { return new(tree.Selector)(elements, extend) } - if (extend) { error("Extend must be used to extend a selector"); } - }, - attribute: function () { - var attr = '', key, val, op; - - if (! $('[')) return; - - if (key = $(/^(?:[_A-Za-z0-9-]|\\.)+/) || $(this.entities.quoted)) { - if ((op = $(/^[|~*$^]?=/)) && - (val = $(this.entities.quoted) || $(/^[\w-]+/))) { - attr = [key, op, val.toCSS ? val.toCSS() : val].join(''); - } else { attr = key } - } - - if (! $(']')) return; - - if (attr) { return "[" + attr + "]" } - }, - - // - // The `block` rule is used by `ruleset` and `mixin.definition`. - // It's a wrapper around the `primary` rule, with added `{}`. - // - block: function () { - var content; - if ($('{') && (content = $(this.primary)) && $('}')) { - return content; - } - }, - - // - // div, .class, body > p {...} - // - ruleset: function () { - var selectors = [], s, rules, match, debugInfo; - - save(); - - if (env.dumpLineNumbers) - debugInfo = getDebugInfo(i, input, env); - - while (s = $(this.selector)) { - selectors.push(s); - $(this.comment); - if (! $(',')) { break } - $(this.comment); - } - - if (selectors.length > 0 && (rules = $(this.block))) { - var ruleset = new(tree.Ruleset)(selectors, rules, env.strictImports); - if (env.dumpLineNumbers) - ruleset.debugInfo = debugInfo; - return ruleset; - } else { - // Backtrack - furthest = i; - restore(); - } - }, - rule: function () { - var name, value, c = input.charAt(i), important, match; - save(); - - if (c === '.' || c === '#' || c === '&') { return } - - if (name = $(this.variable) || $(this.property)) { - if (!env.compress && (name.charAt(0) != '@') && (match = /^([^@+\/'"*`(;{}-]*);/.exec(chunks[j]))) { - i += match[0].length - 1; - value = new(tree.Anonymous)(match[1]); - } else { - value = $(this.value); - } - important = $(this.important); - - if (value && $(this.end)) { - return new(tree.Rule)(name, value, important, memo); - } else { - furthest = i; - restore(); - } - } - }, - - // - // An @import directive - // - // @import "lib"; - // - // Depending on our environemnt, importing is done differently: - // In the browser, it's an XHR request, in Node, it would be a - // file-system operation. The function used for importing is - // stored in `import`, which we pass to the Import constructor. - // - "import": function () { - var path, features, index = i; - - save(); - - var dir = $(/^@import(?:-(once|multiple))?\s+/); - - if (dir && (path = $(this.entities.quoted) || $(this.entities.url))) { - features = $(this.mediaFeatures); - if ($(';')) { - features = features && new(tree.Value)(features); - var importOnce = dir[1] !== 'multiple'; - return new(tree.Import)(path, imports, features, importOnce, index, env.rootpath); - } - } - - restore(); - }, - - mediaFeature: function () { - var e, p, nodes = []; - - do { - if (e = $(this.entities.keyword)) { - nodes.push(e); - } else if ($('(')) { - p = $(this.property); - e = $(this.value); - if ($(')')) { - if (p && e) { - nodes.push(new(tree.Paren)(new(tree.Rule)(p, e, null, i, true))); - } else if (e) { - nodes.push(new(tree.Paren)(e)); - } else { - return null; - } - } else { return null } - } - } while (e); - - if (nodes.length > 0) { - return new(tree.Expression)(nodes); - } - }, - - mediaFeatures: function () { - var e, features = []; - - do { - if (e = $(this.mediaFeature)) { - features.push(e); - if (! $(',')) { break } - } else if (e = $(this.entities.variable)) { - features.push(e); - if (! $(',')) { break } - } - } while (e); - - return features.length > 0 ? features : null; - }, - - media: function () { - var features, rules, media, debugInfo; - - if (env.dumpLineNumbers) - debugInfo = getDebugInfo(i, input, env); - - if ($(/^@media/)) { - features = $(this.mediaFeatures); - - if (rules = $(this.block)) { - media = new(tree.Media)(rules, features); - if(env.dumpLineNumbers) - media.debugInfo = debugInfo; - return media; - } - } - }, - - // - // A CSS Directive - // - // @charset "utf-8"; - // - directive: function () { - var name, value, rules, identifier, e, nodes, nonVendorSpecificName, - hasBlock, hasIdentifier, hasExpression; - - if (input.charAt(i) !== '@') return; - - if (value = $(this['import']) || $(this.media)) { - return value; - } - - save(); - - name = $(/^@[a-z-]+/); - - if (!name) return; - - nonVendorSpecificName = name; - if (name.charAt(1) == '-' && name.indexOf('-', 2) > 0) { - nonVendorSpecificName = "@" + name.slice(name.indexOf('-', 2) + 1); - } - - switch(nonVendorSpecificName) { - case "@font-face": - hasBlock = true; - break; - case "@viewport": - case "@top-left": - case "@top-left-corner": - case "@top-center": - case "@top-right": - case "@top-right-corner": - case "@bottom-left": - case "@bottom-left-corner": - case "@bottom-center": - case "@bottom-right": - case "@bottom-right-corner": - case "@left-top": - case "@left-middle": - case "@left-bottom": - case "@right-top": - case "@right-middle": - case "@right-bottom": - hasBlock = true; - break; - case "@page": - case "@document": - case "@supports": - case "@keyframes": - hasBlock = true; - hasIdentifier = true; - break; - case "@namespace": - hasExpression = true; - break; - } - - if (hasIdentifier) { - name += " " + ($(/^[^{]+/) || '').trim(); - } - - if (hasBlock) - { - if (rules = $(this.block)) { - return new(tree.Directive)(name, rules); - } - } else { - if ((value = hasExpression ? $(this.expression) : $(this.entity)) && $(';')) { - var directive = new(tree.Directive)(name, value); - if (env.dumpLineNumbers) { - directive.debugInfo = getDebugInfo(i, input, env); - } - return directive; - } - } - - restore(); - }, - - // - // A Value is a comma-delimited list of Expressions - // - // font-family: Baskerville, Georgia, serif; - // - // In a Rule, a Value represents everything after the `:`, - // and before the `;`. - // - value: function () { - var e, expressions = [], important; - - while (e = $(this.expression)) { - expressions.push(e); - if (! $(',')) { break } - } - - if (expressions.length > 0) { - return new(tree.Value)(expressions); - } - }, - important: function () { - if (input.charAt(i) === '!') { - return $(/^! *important/); - } - }, - sub: function () { - var a, e; - - if ($('(')) { - if (a = $(this.addition)) { - e = new(tree.Expression)([a]); - expect(')'); - e.parens = true; - return e; - } - } - }, - multiplication: function () { - var m, a, op, operation, isSpaced, expression = []; - if (m = $(this.operand)) { - isSpaced = isWhitespace(input.charAt(i - 1)); - while (!peek(/^\/[*\/]/) && (op = ($('/') || $('*')))) { - if (a = $(this.operand)) { - m.parensInOp = true; - a.parensInOp = true; - operation = new(tree.Operation)(op, [operation || m, a], isSpaced); - isSpaced = isWhitespace(input.charAt(i - 1)); - } else { - break; - } - } - return operation || m; - } - }, - addition: function () { - var m, a, op, operation, isSpaced; - if (m = $(this.multiplication)) { - isSpaced = isWhitespace(input.charAt(i - 1)); - while ((op = $(/^[-+]\s+/) || (!isSpaced && ($('+') || $('-')))) && - (a = $(this.multiplication))) { - m.parensInOp = true; - a.parensInOp = true; - operation = new(tree.Operation)(op, [operation || m, a], isSpaced); - isSpaced = isWhitespace(input.charAt(i - 1)); - } - return operation || m; - } - }, - conditions: function () { - var a, b, index = i, condition; - - if (a = $(this.condition)) { - while ($(',') && (b = $(this.condition))) { - condition = new(tree.Condition)('or', condition || a, b, index); - } - return condition || a; - } - }, - condition: function () { - var a, b, c, op, index = i, negate = false; - - if ($(/^not/)) { negate = true } - expect('('); - if (a = $(this.addition) || $(this.entities.keyword) || $(this.entities.quoted)) { - if (op = $(/^(?:>=|=<|[<=>])/)) { - if (b = $(this.addition) || $(this.entities.keyword) || $(this.entities.quoted)) { - c = new(tree.Condition)(op, a, b, index, negate); - } else { - error('expected expression'); - } - } else { - c = new(tree.Condition)('=', a, new(tree.Keyword)('true'), index, negate); - } - expect(')'); - return $(/^and/) ? new(tree.Condition)('and', c, $(this.condition)) : c; - } - }, - - // - // An operand is anything that can be part of an operation, - // such as a Color, or a Variable - // - operand: function () { - var negate, p = input.charAt(i + 1); - - if (input.charAt(i) === '-' && (p === '@' || p === '(')) { negate = $('-') } - var o = $(this.sub) || $(this.entities.dimension) || - $(this.entities.color) || $(this.entities.variable) || - $(this.entities.call); - - if (negate) { - o.parensInOp = true; - o = new(tree.Negative)(o); - } - - return o; - }, - - // - // Expressions either represent mathematical operations, - // or white-space delimited Entities. - // - // 1px solid black - // @var * 2 - // - expression: function () { - var e, delim, entities = [], d; - - while (e = $(this.addition) || $(this.entity)) { - entities.push(e); - // operations do not allow keyword "/" dimension (e.g. small/20px) so we support that here - if (!peek(/^\/[\/*]/) && (delim = $('/'))) { - entities.push(new(tree.Anonymous)(delim)); - } - } - if (entities.length > 0) { - return new(tree.Expression)(entities); - } - }, - property: function () { - var name; - - if (name = $(/^(\*?-?[_a-z0-9-]+)\s*:/)) { - return name[1]; - } - } - } - }; -}; - -if (less.mode === 'browser' || less.mode === 'rhino') { - // - // Used by `@import` directives - // - less.Parser.importer = function (path, paths, callback, env) { - if (!/^([a-z-]+:)?\//.test(path) && paths.length > 0) { - path = paths[0] + path; - } - // We pass `true` as 3rd argument, to force the reload of the import. - // This is so we can get the syntax tree as opposed to just the CSS output, - // as we need this to evaluate the current stylesheet. - loadStyleSheet(env.toSheet(path), - function (e, root, data, sheet, _, path) { - callback.call(null, e, root, path); - }, true); - }; -} - -(function (tree) { - -tree.functions = { - rgb: function (r, g, b) { - return this.rgba(r, g, b, 1.0); - }, - rgba: function (r, g, b, a) { - var rgb = [r, g, b].map(function (c) { return scaled(c, 256); }); - a = number(a); - return new(tree.Color)(rgb, a); - }, - hsl: function (h, s, l) { - return this.hsla(h, s, l, 1.0); - }, - hsla: function (h, s, l, a) { - h = (number(h) % 360) / 360; - s = number(s); l = number(l); a = number(a); - - var m2 = l <= 0.5 ? l * (s + 1) : l + s - l * s; - var m1 = l * 2 - m2; - - return this.rgba(hue(h + 1/3) * 255, - hue(h) * 255, - hue(h - 1/3) * 255, - a); - - function hue(h) { - h = h < 0 ? h + 1 : (h > 1 ? h - 1 : h); - if (h * 6 < 1) return m1 + (m2 - m1) * h * 6; - else if (h * 2 < 1) return m2; - else if (h * 3 < 2) return m1 + (m2 - m1) * (2/3 - h) * 6; - else return m1; - } - }, - - hsv: function(h, s, v) { - return this.hsva(h, s, v, 1.0); - }, - - hsva: function(h, s, v, a) { - h = ((number(h) % 360) / 360) * 360; - s = number(s); v = number(v); a = number(a); - - var i, f; - i = Math.floor((h / 60) % 6); - f = (h / 60) - i; - - var vs = [v, - v * (1 - s), - v * (1 - f * s), - v * (1 - (1 - f) * s)]; - var perm = [[0, 3, 1], - [2, 0, 1], - [1, 0, 3], - [1, 2, 0], - [3, 1, 0], - [0, 1, 2]]; - - return this.rgba(vs[perm[i][0]] * 255, - vs[perm[i][1]] * 255, - vs[perm[i][2]] * 255, - a); - }, - - hue: function (color) { - return new(tree.Dimension)(Math.round(color.toHSL().h)); - }, - saturation: function (color) { - return new(tree.Dimension)(Math.round(color.toHSL().s * 100), '%'); - }, - lightness: function (color) { - return new(tree.Dimension)(Math.round(color.toHSL().l * 100), '%'); - }, - hsvhue: function(color) { - return new(tree.Dimension)(Math.round(color.toHSV().h)); - }, - hsvsaturation: function (color) { - return new(tree.Dimension)(Math.round(color.toHSV().s * 100), '%'); - }, - hsvvalue: function (color) { - return new(tree.Dimension)(Math.round(color.toHSV().v * 100), '%'); - }, - red: function (color) { - return new(tree.Dimension)(color.rgb[0]); - }, - green: function (color) { - return new(tree.Dimension)(color.rgb[1]); - }, - blue: function (color) { - return new(tree.Dimension)(color.rgb[2]); - }, - alpha: function (color) { - return new(tree.Dimension)(color.toHSL().a); - }, - luma: function (color) { - return new(tree.Dimension)(Math.round(color.luma() * color.alpha * 100), '%'); - }, - saturate: function (color, amount) { - var hsl = color.toHSL(); - - hsl.s += amount.value / 100; - hsl.s = clamp(hsl.s); - return hsla(hsl); - }, - desaturate: function (color, amount) { - var hsl = color.toHSL(); - - hsl.s -= amount.value / 100; - hsl.s = clamp(hsl.s); - return hsla(hsl); - }, - lighten: function (color, amount) { - var hsl = color.toHSL(); - - hsl.l += amount.value / 100; - hsl.l = clamp(hsl.l); - return hsla(hsl); - }, - darken: function (color, amount) { - var hsl = color.toHSL(); - - hsl.l -= amount.value / 100; - hsl.l = clamp(hsl.l); - return hsla(hsl); - }, - fadein: function (color, amount) { - var hsl = color.toHSL(); - - hsl.a += amount.value / 100; - hsl.a = clamp(hsl.a); - return hsla(hsl); - }, - fadeout: function (color, amount) { - var hsl = color.toHSL(); - - hsl.a -= amount.value / 100; - hsl.a = clamp(hsl.a); - return hsla(hsl); - }, - fade: function (color, amount) { - var hsl = color.toHSL(); - - hsl.a = amount.value / 100; - hsl.a = clamp(hsl.a); - return hsla(hsl); - }, - spin: function (color, amount) { - var hsl = color.toHSL(); - var hue = (hsl.h + amount.value) % 360; - - hsl.h = hue < 0 ? 360 + hue : hue; - - return hsla(hsl); - }, - // - // Copyright (c) 2006-2009 Hampton Catlin, Nathan Weizenbaum, and Chris Eppstein - // http://sass-lang.com - // - mix: function (color1, color2, weight) { - if (!weight) { - weight = new(tree.Dimension)(50); - } - var p = weight.value / 100.0; - var w = p * 2 - 1; - var a = color1.toHSL().a - color2.toHSL().a; - - var w1 = (((w * a == -1) ? w : (w + a) / (1 + w * a)) + 1) / 2.0; - var w2 = 1 - w1; - - var rgb = [color1.rgb[0] * w1 + color2.rgb[0] * w2, - color1.rgb[1] * w1 + color2.rgb[1] * w2, - color1.rgb[2] * w1 + color2.rgb[2] * w2]; - - var alpha = color1.alpha * p + color2.alpha * (1 - p); - - return new(tree.Color)(rgb, alpha); - }, - greyscale: function (color) { - return this.desaturate(color, new(tree.Dimension)(100)); - }, - contrast: function (color, dark, light, threshold) { - // filter: contrast(3.2); - // should be kept as is, so check for color - if (!color.rgb) { - return null; - } - if (typeof light === 'undefined') { - light = this.rgba(255, 255, 255, 1.0); - } - if (typeof dark === 'undefined') { - dark = this.rgba(0, 0, 0, 1.0); - } - //Figure out which is actually light and dark! - if (dark.luma() > light.luma()) { - var t = light; - light = dark; - dark = t; - } - if (typeof threshold === 'undefined') { - threshold = 0.43; - } else { - threshold = number(threshold); - } - if ((color.luma() * color.alpha) < threshold) { - return light; - } else { - return dark; - } - }, - e: function (str) { - return new(tree.Anonymous)(str instanceof tree.JavaScript ? str.evaluated : str); - }, - escape: function (str) { - return new(tree.Anonymous)(encodeURI(str.value).replace(/=/g, "%3D").replace(/:/g, "%3A").replace(/#/g, "%23").replace(/;/g, "%3B").replace(/\(/g, "%28").replace(/\)/g, "%29")); - }, - '%': function (quoted /* arg, arg, ...*/) { - var args = Array.prototype.slice.call(arguments, 1), - str = quoted.value; - - for (var i = 0; i < args.length; i++) { - str = str.replace(/%[sda]/i, function(token) { - var value = token.match(/s/i) ? args[i].value : args[i].toCSS(); - return token.match(/[A-Z]$/) ? encodeURIComponent(value) : value; - }); - } - str = str.replace(/%%/g, '%'); - return new(tree.Quoted)('"' + str + '"', str); - }, - unit: function (val, unit) { - return new(tree.Dimension)(val.value, unit ? unit.toCSS() : ""); - }, - convert: function (val, unit) { - return val.convertTo(unit.value); - }, - round: function (n, f) { - var fraction = typeof(f) === "undefined" ? 0 : f.value; - return this._math(function(num) { return num.toFixed(fraction); }, null, n); - }, - pi: function () { - return new(tree.Dimension)(Math.PI); - }, - mod: function(a, b) { - return new(tree.Dimension)(a.value % b.value, a.unit); - }, - pow: function(x, y) { - if (typeof x === "number" && typeof y === "number") { - x = new(tree.Dimension)(x); - y = new(tree.Dimension)(y); - } else if (!(x instanceof tree.Dimension) || !(y instanceof tree.Dimension)) { - throw { type: "Argument", message: "arguments must be numbers" }; - } - - return new(tree.Dimension)(Math.pow(x.value, y.value), x.unit); - }, - _math: function (fn, unit, n) { - if (n instanceof tree.Dimension) { - return new(tree.Dimension)(fn(parseFloat(n.value)), unit == null ? n.unit : unit); - } else if (typeof(n) === 'number') { - return fn(n); - } else { - throw { type: "Argument", message: "argument must be a number" }; - } - }, - argb: function (color) { - return new(tree.Anonymous)(color.toARGB()); - - }, - percentage: function (n) { - return new(tree.Dimension)(n.value * 100, '%'); - }, - color: function (n) { - if (n instanceof tree.Quoted) { - return new(tree.Color)(n.value.slice(1)); - } else { - throw { type: "Argument", message: "argument must be a string" }; - } - }, - iscolor: function (n) { - return this._isa(n, tree.Color); - }, - isnumber: function (n) { - return this._isa(n, tree.Dimension); - }, - isstring: function (n) { - return this._isa(n, tree.Quoted); - }, - iskeyword: function (n) { - return this._isa(n, tree.Keyword); - }, - isurl: function (n) { - return this._isa(n, tree.URL); - }, - ispixel: function (n) { - return (n instanceof tree.Dimension) && n.unit.is('px') ? tree.True : tree.False; - }, - ispercentage: function (n) { - return (n instanceof tree.Dimension) && n.unit.is('%') ? tree.True : tree.False; - }, - isem: function (n) { - return (n instanceof tree.Dimension) && n.unit.is('em') ? tree.True : tree.False; - }, - _isa: function (n, Type) { - return (n instanceof Type) ? tree.True : tree.False; - }, - - /* Blending modes */ - - multiply: function(color1, color2) { - var r = color1.rgb[0] * color2.rgb[0] / 255; - var g = color1.rgb[1] * color2.rgb[1] / 255; - var b = color1.rgb[2] * color2.rgb[2] / 255; - return this.rgb(r, g, b); - }, - screen: function(color1, color2) { - var r = 255 - (255 - color1.rgb[0]) * (255 - color2.rgb[0]) / 255; - var g = 255 - (255 - color1.rgb[1]) * (255 - color2.rgb[1]) / 255; - var b = 255 - (255 - color1.rgb[2]) * (255 - color2.rgb[2]) / 255; - return this.rgb(r, g, b); - }, - overlay: function(color1, color2) { - var r = color1.rgb[0] < 128 ? 2 * color1.rgb[0] * color2.rgb[0] / 255 : 255 - 2 * (255 - color1.rgb[0]) * (255 - color2.rgb[0]) / 255; - var g = color1.rgb[1] < 128 ? 2 * color1.rgb[1] * color2.rgb[1] / 255 : 255 - 2 * (255 - color1.rgb[1]) * (255 - color2.rgb[1]) / 255; - var b = color1.rgb[2] < 128 ? 2 * color1.rgb[2] * color2.rgb[2] / 255 : 255 - 2 * (255 - color1.rgb[2]) * (255 - color2.rgb[2]) / 255; - return this.rgb(r, g, b); - }, - softlight: function(color1, color2) { - var t = color2.rgb[0] * color1.rgb[0] / 255; - var r = t + color1.rgb[0] * (255 - (255 - color1.rgb[0]) * (255 - color2.rgb[0]) / 255 - t) / 255; - t = color2.rgb[1] * color1.rgb[1] / 255; - var g = t + color1.rgb[1] * (255 - (255 - color1.rgb[1]) * (255 - color2.rgb[1]) / 255 - t) / 255; - t = color2.rgb[2] * color1.rgb[2] / 255; - var b = t + color1.rgb[2] * (255 - (255 - color1.rgb[2]) * (255 - color2.rgb[2]) / 255 - t) / 255; - return this.rgb(r, g, b); - }, - hardlight: function(color1, color2) { - var r = color2.rgb[0] < 128 ? 2 * color2.rgb[0] * color1.rgb[0] / 255 : 255 - 2 * (255 - color2.rgb[0]) * (255 - color1.rgb[0]) / 255; - var g = color2.rgb[1] < 128 ? 2 * color2.rgb[1] * color1.rgb[1] / 255 : 255 - 2 * (255 - color2.rgb[1]) * (255 - color1.rgb[1]) / 255; - var b = color2.rgb[2] < 128 ? 2 * color2.rgb[2] * color1.rgb[2] / 255 : 255 - 2 * (255 - color2.rgb[2]) * (255 - color1.rgb[2]) / 255; - return this.rgb(r, g, b); - }, - difference: function(color1, color2) { - var r = Math.abs(color1.rgb[0] - color2.rgb[0]); - var g = Math.abs(color1.rgb[1] - color2.rgb[1]); - var b = Math.abs(color1.rgb[2] - color2.rgb[2]); - return this.rgb(r, g, b); - }, - exclusion: function(color1, color2) { - var r = color1.rgb[0] + color2.rgb[0] * (255 - color1.rgb[0] - color1.rgb[0]) / 255; - var g = color1.rgb[1] + color2.rgb[1] * (255 - color1.rgb[1] - color1.rgb[1]) / 255; - var b = color1.rgb[2] + color2.rgb[2] * (255 - color1.rgb[2] - color1.rgb[2]) / 255; - return this.rgb(r, g, b); - }, - average: function(color1, color2) { - var r = (color1.rgb[0] + color2.rgb[0]) / 2; - var g = (color1.rgb[1] + color2.rgb[1]) / 2; - var b = (color1.rgb[2] + color2.rgb[2]) / 2; - return this.rgb(r, g, b); - }, - negation: function(color1, color2) { - var r = 255 - Math.abs(255 - color2.rgb[0] - color1.rgb[0]); - var g = 255 - Math.abs(255 - color2.rgb[1] - color1.rgb[1]); - var b = 255 - Math.abs(255 - color2.rgb[2] - color1.rgb[2]); - return this.rgb(r, g, b); - }, - tint: function(color, amount) { - return this.mix(this.rgb(255,255,255), color, amount); - }, - shade: function(color, amount) { - return this.mix(this.rgb(0, 0, 0), color, amount); - }, - extract: function(values, index) { - index = index.value - 1; // (1-based index) - return values.value[index]; - }, - - "data-uri": function(mimetypeNode, filePathNode) { - - if (typeof window !== 'undefined') { - return new tree.URL(filePathNode || mimetypeNode, this.rootpath).eval(this.env); - } - - var mimetype = mimetypeNode.value; - var filePath = (filePathNode && filePathNode.value); - - var fs = require("fs"), - path = require("path"), - useBase64 = false; - - if (arguments.length < 2) { - filePath = mimetype; - } - - if (this.currentDirectory && this.env.isPathRelative(filePath)) { - filePath = path.join(this.currentDirectory, filePath); - } - - // detect the mimetype if not given - if (arguments.length < 2) { - var mime; - try { - mime = require('mime'); - } catch (ex) { - mime = tree._mime; - } - - mimetype = mime.lookup(filePath); - - // use base 64 unless it's an ASCII or UTF-8 format - var charset = mime.charsets.lookup(mimetype); - useBase64 = ['US-ASCII', 'UTF-8'].indexOf(charset) < 0; - if (useBase64) mimetype += ';base64'; - } - else { - useBase64 = /;base64$/.test(mimetype) - } - - var buf = fs.readFileSync(filePath); - - // IE8 cannot handle a data-uri larger than 32KB. If this is exceeded - // and the --ieCompat flag is enabled, return a normal url() instead. - var DATA_URI_MAX_KB = 32, - fileSizeInKB = parseInt((buf.length / 1024), 10); - if (fileSizeInKB >= DATA_URI_MAX_KB) { - // the url() must be relative, not an absolute file path - filePath = path.relative(this.currentDirectory, filePath); - - if (this.env.ieCompat !== false) { - if (!this.env.silent) { - console.warn("Skipped data-uri embedding of %s because its size (%dKB) exceeds IE8-safe %dKB!", filePath, fileSizeInKB, DATA_URI_MAX_KB); - } - - return new tree.URL(filePathNode || mimetypeNode, this.rootpath).eval(this.env); - } else if (!this.env.silent) { - // if explicitly disabled (via --no-ie-compat on CLI, or env.ieCompat === false), merely warn - console.warn("WARNING: Embedding %s (%dKB) exceeds IE8's data-uri size limit of %dKB!", filePath, fileSizeInKB, DATA_URI_MAX_KB); - } - } - - buf = useBase64 ? buf.toString('base64') - : encodeURIComponent(buf); - - var uri = "'data:" + mimetype + ',' + buf + "'"; - return new(tree.URL)(new(tree.Anonymous)(uri)); - } -}; - -// these static methods are used as a fallback when the optional 'mime' dependency is missing -tree._mime = { - // this map is intentionally incomplete - // if you want more, install 'mime' dep - _types: { - '.htm' : 'text/html', - '.html': 'text/html', - '.gif' : 'image/gif', - '.jpg' : 'image/jpeg', - '.jpeg': 'image/jpeg', - '.png' : 'image/png' - }, - lookup: function (filepath) { - var ext = require('path').extname(filepath), - type = tree._mime._types[ext]; - if (type === undefined) { - throw new Error('Optional dependency "mime" is required for ' + ext); - } - return type; - }, - charsets: { - lookup: function (type) { - // assumes all text types are UTF-8 - return type && (/^text\//).test(type) ? 'UTF-8' : ''; - } - } -}; - -var mathFunctions = [{name:"ceil"}, {name:"floor"}, {name: "sqrt"}, {name:"abs"}, - {name:"tan", unit: ""}, {name:"sin", unit: ""}, {name:"cos", unit: ""}, - {name:"atan", unit: "rad"}, {name:"asin", unit: "rad"}, {name:"acos", unit: "rad"}], - createMathFunction = function(name, unit) { - return function(n) { - if (unit != null) { - n = n.unify(); - } - return this._math(Math[name], unit, n); - }; - }; - -for(var i = 0; i < mathFunctions.length; i++) { - tree.functions[mathFunctions[i].name] = createMathFunction(mathFunctions[i].name, mathFunctions[i].unit); -} - -function hsla(color) { - return tree.functions.hsla(color.h, color.s, color.l, color.a); -} - -function scaled(n, size) { - if (n instanceof tree.Dimension && n.unit.is('%')) { - return parseFloat(n.value * size / 100); - } else { - return number(n); - } -} - -function number(n) { - if (n instanceof tree.Dimension) { - return parseFloat(n.unit.is('%') ? n.value / 100 : n.value); - } else if (typeof(n) === 'number') { - return n; - } else { - throw { - error: "RuntimeError", - message: "color functions take numbers as parameters" - }; - } -} - -function clamp(val) { - return Math.min(1, Math.max(0, val)); -} - -tree.functionCall = function(env, rootpath, currentDirectory) { - this.env = env; - this.rootpath = rootpath; - this.currentDirectory = currentDirectory; -}; - -tree.functionCall.prototype = tree.functions; - -})(require('./tree')); -(function (tree) { - tree.colors = { - 'aliceblue':'#f0f8ff', - 'antiquewhite':'#faebd7', - 'aqua':'#00ffff', - 'aquamarine':'#7fffd4', - 'azure':'#f0ffff', - 'beige':'#f5f5dc', - 'bisque':'#ffe4c4', - 'black':'#000000', - 'blanchedalmond':'#ffebcd', - 'blue':'#0000ff', - 'blueviolet':'#8a2be2', - 'brown':'#a52a2a', - 'burlywood':'#deb887', - 'cadetblue':'#5f9ea0', - 'chartreuse':'#7fff00', - 'chocolate':'#d2691e', - 'coral':'#ff7f50', - 'cornflowerblue':'#6495ed', - 'cornsilk':'#fff8dc', - 'crimson':'#dc143c', - 'cyan':'#00ffff', - 'darkblue':'#00008b', - 'darkcyan':'#008b8b', - 'darkgoldenrod':'#b8860b', - 'darkgray':'#a9a9a9', - 'darkgrey':'#a9a9a9', - 'darkgreen':'#006400', - 'darkkhaki':'#bdb76b', - 'darkmagenta':'#8b008b', - 'darkolivegreen':'#556b2f', - 'darkorange':'#ff8c00', - 'darkorchid':'#9932cc', - 'darkred':'#8b0000', - 'darksalmon':'#e9967a', - 'darkseagreen':'#8fbc8f', - 'darkslateblue':'#483d8b', - 'darkslategray':'#2f4f4f', - 'darkslategrey':'#2f4f4f', - 'darkturquoise':'#00ced1', - 'darkviolet':'#9400d3', - 'deeppink':'#ff1493', - 'deepskyblue':'#00bfff', - 'dimgray':'#696969', - 'dimgrey':'#696969', - 'dodgerblue':'#1e90ff', - 'firebrick':'#b22222', - 'floralwhite':'#fffaf0', - 'forestgreen':'#228b22', - 'fuchsia':'#ff00ff', - 'gainsboro':'#dcdcdc', - 'ghostwhite':'#f8f8ff', - 'gold':'#ffd700', - 'goldenrod':'#daa520', - 'gray':'#808080', - 'grey':'#808080', - 'green':'#008000', - 'greenyellow':'#adff2f', - 'honeydew':'#f0fff0', - 'hotpink':'#ff69b4', - 'indianred':'#cd5c5c', - 'indigo':'#4b0082', - 'ivory':'#fffff0', - 'khaki':'#f0e68c', - 'lavender':'#e6e6fa', - 'lavenderblush':'#fff0f5', - 'lawngreen':'#7cfc00', - 'lemonchiffon':'#fffacd', - 'lightblue':'#add8e6', - 'lightcoral':'#f08080', - 'lightcyan':'#e0ffff', - 'lightgoldenrodyellow':'#fafad2', - 'lightgray':'#d3d3d3', - 'lightgrey':'#d3d3d3', - 'lightgreen':'#90ee90', - 'lightpink':'#ffb6c1', - 'lightsalmon':'#ffa07a', - 'lightseagreen':'#20b2aa', - 'lightskyblue':'#87cefa', - 'lightslategray':'#778899', - 'lightslategrey':'#778899', - 'lightsteelblue':'#b0c4de', - 'lightyellow':'#ffffe0', - 'lime':'#00ff00', - 'limegreen':'#32cd32', - 'linen':'#faf0e6', - 'magenta':'#ff00ff', - 'maroon':'#800000', - 'mediumaquamarine':'#66cdaa', - 'mediumblue':'#0000cd', - 'mediumorchid':'#ba55d3', - 'mediumpurple':'#9370d8', - 'mediumseagreen':'#3cb371', - 'mediumslateblue':'#7b68ee', - 'mediumspringgreen':'#00fa9a', - 'mediumturquoise':'#48d1cc', - 'mediumvioletred':'#c71585', - 'midnightblue':'#191970', - 'mintcream':'#f5fffa', - 'mistyrose':'#ffe4e1', - 'moccasin':'#ffe4b5', - 'navajowhite':'#ffdead', - 'navy':'#000080', - 'oldlace':'#fdf5e6', - 'olive':'#808000', - 'olivedrab':'#6b8e23', - 'orange':'#ffa500', - 'orangered':'#ff4500', - 'orchid':'#da70d6', - 'palegoldenrod':'#eee8aa', - 'palegreen':'#98fb98', - 'paleturquoise':'#afeeee', - 'palevioletred':'#d87093', - 'papayawhip':'#ffefd5', - 'peachpuff':'#ffdab9', - 'peru':'#cd853f', - 'pink':'#ffc0cb', - 'plum':'#dda0dd', - 'powderblue':'#b0e0e6', - 'purple':'#800080', - 'red':'#ff0000', - 'rosybrown':'#bc8f8f', - 'royalblue':'#4169e1', - 'saddlebrown':'#8b4513', - 'salmon':'#fa8072', - 'sandybrown':'#f4a460', - 'seagreen':'#2e8b57', - 'seashell':'#fff5ee', - 'sienna':'#a0522d', - 'silver':'#c0c0c0', - 'skyblue':'#87ceeb', - 'slateblue':'#6a5acd', - 'slategray':'#708090', - 'slategrey':'#708090', - 'snow':'#fffafa', - 'springgreen':'#00ff7f', - 'steelblue':'#4682b4', - 'tan':'#d2b48c', - 'teal':'#008080', - 'thistle':'#d8bfd8', - 'tomato':'#ff6347', - // 'transparent':'rgba(0,0,0,0)', - 'turquoise':'#40e0d0', - 'violet':'#ee82ee', - 'wheat':'#f5deb3', - 'white':'#ffffff', - 'whitesmoke':'#f5f5f5', - 'yellow':'#ffff00', - 'yellowgreen':'#9acd32' - }; -})(require('./tree')); -(function (tree) { - -tree.Alpha = function (val) { - this.value = val; -}; -tree.Alpha.prototype = { - toCSS: function () { - return "alpha(opacity=" + - (this.value.toCSS ? this.value.toCSS() : this.value) + ")"; - }, - eval: function (env) { - if (this.value.eval) { this.value = this.value.eval(env) } - return this; - } -}; - -})(require('../tree')); -(function (tree) { - -tree.Anonymous = function (string) { - this.value = string.value || string; -}; -tree.Anonymous.prototype = { - toCSS: function () { - return this.value; - }, - eval: function () { return this }, - compare: function (x) { - if (!x.toCSS) { - return -1; - } - - var left = this.toCSS(), - right = x.toCSS(); - - if (left === right) { - return 0; - } - - return left < right ? -1 : 1; - } -}; - -})(require('../tree')); -(function (tree) { - -tree.Assignment = function (key, val) { - this.key = key; - this.value = val; -}; -tree.Assignment.prototype = { - toCSS: function () { - return this.key + '=' + (this.value.toCSS ? this.value.toCSS() : this.value); - }, - eval: function (env) { - if (this.value.eval) { - return new(tree.Assignment)(this.key, this.value.eval(env)); - } - return this; - } -}; - -})(require('../tree'));(function (tree) { - -// -// A function call node. -// -tree.Call = function (name, args, index, filename, rootpath, currentDirectory) { - this.name = name; - this.args = args; - this.index = index; - this.filename = filename; - this.rootpath = rootpath; - this.currentDirectory = currentDirectory; -}; -tree.Call.prototype = { - // - // When evaluating a function call, - // we either find the function in `tree.functions` [1], - // in which case we call it, passing the evaluated arguments, - // if this returns null or we cannot find the function, we - // simply print it out as it appeared originally [2]. - // - // The *functions.js* file contains the built-in functions. - // - // The reason why we evaluate the arguments, is in the case where - // we try to pass a variable to a function, like: `saturate(@color)`. - // The function should receive the value, not the variable. - // - eval: function (env) { - var args = this.args.map(function (a) { return a.eval(env); }), - nameLC = this.name.toLowerCase(), - result, func; - - if (nameLC in tree.functions) { // 1. - try { - func = new tree.functionCall(env, this.rootpath, this.currentDirectory); - result = func[nameLC].apply(func, args); - if (result != null) { - return result; - } - } catch (e) { - throw { type: e.type || "Runtime", - message: "error evaluating function `" + this.name + "`" + - (e.message ? ': ' + e.message : ''), - index: this.index, filename: this.filename }; - } - } - - // 2. - return new(tree.Anonymous)(this.name + - "(" + args.map(function (a) { return a.toCSS(env); }).join(', ') + ")"); - }, - - toCSS: function (env) { - return this.eval(env).toCSS(); - } -}; - -})(require('../tree')); -(function (tree) { -// -// RGB Colors - #ff0014, #eee -// -tree.Color = function (rgb, a) { - // - // The end goal here, is to parse the arguments - // into an integer triplet, such as `128, 255, 0` - // - // This facilitates operations and conversions. - // - if (Array.isArray(rgb)) { - this.rgb = rgb; - } else if (rgb.length == 6) { - this.rgb = rgb.match(/.{2}/g).map(function (c) { - return parseInt(c, 16); - }); - } else { - this.rgb = rgb.split('').map(function (c) { - return parseInt(c + c, 16); - }); - } - this.alpha = typeof(a) === 'number' ? a : 1; -}; -tree.Color.prototype = { - eval: function () { return this }, - luma: function () { return (0.2126 * this.rgb[0] / 255) + (0.7152 * this.rgb[1] / 255) + (0.0722 * this.rgb[2] / 255); }, - - // - // If we have some transparency, the only way to represent it - // is via `rgba`. Otherwise, we use the hex representation, - // which has better compatibility with older browsers. - // Values are capped between `0` and `255`, rounded and zero-padded. - // - toCSS: function (env, doNotCompress) { - var compress = env && env.compress && !doNotCompress; - if (this.alpha < 1.0) { - return "rgba(" + this.rgb.map(function (c) { - return Math.round(c); - }).concat(this.alpha).join(',' + (compress ? '' : ' ')) + ")"; - } else { - var color = this.rgb.map(function (i) { - i = Math.round(i); - i = (i > 255 ? 255 : (i < 0 ? 0 : i)).toString(16); - return i.length === 1 ? '0' + i : i; - }).join(''); - - if (compress) { - color = color.split(''); - - // Convert color to short format - if (color[0] == color[1] && color[2] == color[3] && color[4] == color[5]) { - color = color[0] + color[2] + color[4]; - } else { - color = color.join(''); - } - } - - return '#' + color; - } - }, - - // - // Operations have to be done per-channel, if not, - // channels will spill onto each other. Once we have - // our result, in the form of an integer triplet, - // we create a new Color node to hold the result. - // - operate: function (env, op, other) { - var result = []; - - if (! (other instanceof tree.Color)) { - other = other.toColor(); - } - - for (var c = 0; c < 3; c++) { - result[c] = tree.operate(env, op, this.rgb[c], other.rgb[c]); - } - return new(tree.Color)(result, this.alpha + other.alpha); - }, - - toHSL: function () { - var r = this.rgb[0] / 255, - g = this.rgb[1] / 255, - b = this.rgb[2] / 255, - a = this.alpha; - - var max = Math.max(r, g, b), min = Math.min(r, g, b); - var h, s, l = (max + min) / 2, d = max - min; - - if (max === min) { - h = s = 0; - } else { - s = l > 0.5 ? d / (2 - max - min) : d / (max + min); - - switch (max) { - case r: h = (g - b) / d + (g < b ? 6 : 0); break; - case g: h = (b - r) / d + 2; break; - case b: h = (r - g) / d + 4; break; - } - h /= 6; - } - return { h: h * 360, s: s, l: l, a: a }; - }, - //Adapted from http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript - toHSV: function () { - var r = this.rgb[0] / 255, - g = this.rgb[1] / 255, - b = this.rgb[2] / 255, - a = this.alpha; - - var max = Math.max(r, g, b), min = Math.min(r, g, b); - var h, s, v = max; - - var d = max - min; - if (max === 0) { - s = 0; - } else { - s = d / max; - } - - if (max === min) { - h = 0; - } else { - switch(max){ - case r: h = (g - b) / d + (g < b ? 6 : 0); break; - case g: h = (b - r) / d + 2; break; - case b: h = (r - g) / d + 4; break; - } - h /= 6; - } - return { h: h * 360, s: s, v: v, a: a }; - }, - toARGB: function () { - var argb = [Math.round(this.alpha * 255)].concat(this.rgb); - return '#' + argb.map(function (i) { - i = Math.round(i); - i = (i > 255 ? 255 : (i < 0 ? 0 : i)).toString(16); - return i.length === 1 ? '0' + i : i; - }).join(''); - }, - compare: function (x) { - if (!x.rgb) { - return -1; - } - - return (x.rgb[0] === this.rgb[0] && - x.rgb[1] === this.rgb[1] && - x.rgb[2] === this.rgb[2] && - x.alpha === this.alpha) ? 0 : -1; - } -}; - - -})(require('../tree')); -(function (tree) { - -tree.Comment = function (value, silent) { - this.value = value; - this.silent = !!silent; -}; -tree.Comment.prototype = { - toCSS: function (env) { - return env.compress ? '' : this.value; - }, - eval: function () { return this } -}; - -})(require('../tree')); -(function (tree) { - -tree.Condition = function (op, l, r, i, negate) { - this.op = op.trim(); - this.lvalue = l; - this.rvalue = r; - this.index = i; - this.negate = negate; -}; -tree.Condition.prototype.eval = function (env) { - var a = this.lvalue.eval(env), - b = this.rvalue.eval(env); - - var i = this.index, result; - - var result = (function (op) { - switch (op) { - case 'and': - return a && b; - case 'or': - return a || b; - default: - if (a.compare) { - result = a.compare(b); - } else if (b.compare) { - result = b.compare(a); - } else { - throw { type: "Type", - message: "Unable to perform comparison", - index: i }; - } - switch (result) { - case -1: return op === '<' || op === '=<'; - case 0: return op === '=' || op === '>=' || op === '=<'; - case 1: return op === '>' || op === '>='; - } - } - })(this.op); - return this.negate ? !result : result; -}; - -})(require('../tree')); -(function (tree) { - -// -// A number with a unit -// -tree.Dimension = function (value, unit) { - this.value = parseFloat(value); - this.unit = (unit && unit instanceof tree.Unit) ? unit : - new(tree.Unit)(unit ? [unit] : undefined); -}; - -tree.Dimension.prototype = { - eval: function (env) { - return this; - }, - toColor: function () { - return new(tree.Color)([this.value, this.value, this.value]); - }, - toCSS: function (env) { - if ((!env || env.strictUnits !== false) && !this.unit.isSingular()) { - throw new Error("Multiple units in dimension. Correct the units or use the unit function. Bad unit: "+this.unit.toString()); - } - - var value = this.value, - strValue = String(value); - - if (value !== 0 && value < 0.000001 && value > -0.000001) { - // would be output 1e-6 etc. - strValue = value.toFixed(20).replace(/0+$/, ""); - } - - if (env && env.compress) { - // Zero values doesn't need a unit - if (value === 0 && !this.unit.isAngle()) { - return strValue; - } - - // Float values doesn't need a leading zero - if (value > 0 && value < 1) { - strValue = (strValue).substr(1); - } - } - - return this.unit.isEmpty() ? strValue : (strValue + this.unit.toCSS()); - }, - - // In an operation between two Dimensions, - // we default to the first Dimension's unit, - // so `1px + 2` will yield `3px`. - operate: function (env, op, other) { - var value = tree.operate(env, op, this.value, other.value), - unit = this.unit.clone(); - - if (op === '+' || op === '-') { - if (unit.numerator.length === 0 && unit.denominator.length === 0) { - unit.numerator = other.unit.numerator.slice(0); - unit.denominator = other.unit.denominator.slice(0); - } else if (other.unit.numerator.length == 0 && unit.denominator.length == 0) { - // do nothing - } else { - other = other.convertTo(this.unit.usedUnits()); - - if(env.strictUnits !== false && other.unit.toString() !== unit.toString()) { - throw new Error("Incompatible units. Change the units or use the unit function. Bad units: '" + unit.toString() + - "' and '" + other.unit.toString() + "'."); - } - - value = tree.operate(env, op, this.value, other.value); - } - } else if (op === '*') { - unit.numerator = unit.numerator.concat(other.unit.numerator).sort(); - unit.denominator = unit.denominator.concat(other.unit.denominator).sort(); - unit.cancel(); - } else if (op === '/') { - unit.numerator = unit.numerator.concat(other.unit.denominator).sort(); - unit.denominator = unit.denominator.concat(other.unit.numerator).sort(); - unit.cancel(); - } - return new(tree.Dimension)(value, unit); - }, - - compare: function (other) { - if (other instanceof tree.Dimension) { - var a = this.unify(), b = other.unify(), - aValue = a.value, bValue = b.value; - - if (bValue > aValue) { - return -1; - } else if (bValue < aValue) { - return 1; - } else { - if (!b.unit.isEmpty() && a.unit.compare(b) !== 0) { - return -1; - } - return 0; - } - } else { - return -1; - } - }, - - unify: function () { - return this.convertTo({ length: 'm', duration: 's', angle: 'rad' }); - }, - - convertTo: function (conversions) { - var value = this.value, unit = this.unit.clone(), - i, groupName, group, conversion, targetUnit, derivedConversions = {}; - - if (typeof conversions === 'string') { - for(i in tree.UnitConversions) { - if (tree.UnitConversions[i].hasOwnProperty(conversions)) { - derivedConversions = {}; - derivedConversions[i] = conversions; - } - } - conversions = derivedConversions; - } - - for (groupName in conversions) { - if (conversions.hasOwnProperty(groupName)) { - targetUnit = conversions[groupName]; - group = tree.UnitConversions[groupName]; - - unit.map(function (atomicUnit, denominator) { - if (group.hasOwnProperty(atomicUnit)) { - if (denominator) { - value = value / (group[atomicUnit] / group[targetUnit]); - } else { - value = value * (group[atomicUnit] / group[targetUnit]); - } - - return targetUnit; - } - - return atomicUnit; - }); - } - } - - unit.cancel(); - - return new(tree.Dimension)(value, unit); - } -}; - -// http://www.w3.org/TR/css3-values/#absolute-lengths -tree.UnitConversions = { - length: { - 'm': 1, - 'cm': 0.01, - 'mm': 0.001, - 'in': 0.0254, - 'pt': 0.0254 / 72, - 'pc': 0.0254 / 72 * 12 - }, - duration: { - 's': 1, - 'ms': 0.001 - }, - angle: { - 'rad': 1/(2*Math.PI), - 'deg': 1/360, - 'grad': 1/400, - 'turn': 1 - } -}; - -tree.Unit = function (numerator, denominator) { - this.numerator = numerator ? numerator.slice(0).sort() : []; - this.denominator = denominator ? denominator.slice(0).sort() : []; -}; - -tree.Unit.prototype = { - clone: function () { - return new tree.Unit(this.numerator.slice(0), this.denominator.slice(0)); - }, - - toCSS: function () { - if (this.numerator.length >= 1) { - return this.numerator[0]; - } - if (this.denominator.length >= 1) { - return this.denominator[0]; - } - return ""; - }, - - toString: function () { - var i, returnStr = this.numerator.join("*"); - for (i = 0; i < this.denominator.length; i++) { - returnStr += "/" + this.denominator[i]; - } - return returnStr; - }, - - compare: function (other) { - return this.is(other.toCSS()) ? 0 : -1; - }, - - is: function (unitString) { - return this.toCSS() === unitString; - }, - - isAngle: function () { - return tree.UnitConversions.angle.hasOwnProperty(this.toCSS()); - }, - - isEmpty: function () { - return this.numerator.length == 0 && this.denominator.length == 0; - }, - - isSingular: function() { - return this.numerator.length <= 1 && this.denominator.length == 0; - }, - - map: function(callback) { - var i; - - for (i = 0; i < this.numerator.length; i++) { - this.numerator[i] = callback(this.numerator[i], false); - } - - for (i = 0; i < this.denominator.length; i++) { - this.denominator[i] = callback(this.denominator[i], true); - } - }, - - usedUnits: function() { - var group, groupName, result = {}; - - for (groupName in tree.UnitConversions) { - if (tree.UnitConversions.hasOwnProperty(groupName)) { - group = tree.UnitConversions[groupName]; - - this.map(function (atomicUnit) { - if (group.hasOwnProperty(atomicUnit) && !result[groupName]) { - result[groupName] = atomicUnit; - } - - return atomicUnit; - }); - } - } - - return result; - }, - - cancel: function () { - var counter = {}, atomicUnit, i; - - for (i = 0; i < this.numerator.length; i++) { - atomicUnit = this.numerator[i]; - counter[atomicUnit] = (counter[atomicUnit] || 0) + 1; - } - - for (i = 0; i < this.denominator.length; i++) { - atomicUnit = this.denominator[i]; - counter[atomicUnit] = (counter[atomicUnit] || 0) - 1; - } - - this.numerator = []; - this.denominator = []; - - for (atomicUnit in counter) { - if (counter.hasOwnProperty(atomicUnit)) { - var count = counter[atomicUnit]; - - if (count > 0) { - for (i = 0; i < count; i++) { - this.numerator.push(atomicUnit); - } - } else if (count < 0) { - for (i = 0; i < -count; i++) { - this.denominator.push(atomicUnit); - } - } - } - } - - this.numerator.sort(); - this.denominator.sort(); - } -}; - -})(require('../tree')); -(function (tree) { - -tree.Directive = function (name, value) { - this.name = name; - - if (Array.isArray(value)) { - this.ruleset = new(tree.Ruleset)([], value); - this.ruleset.allowImports = true; - } else { - this.value = value; - } -}; -tree.Directive.prototype = { - toCSS: function (ctx, env) { - if (this.ruleset) { - this.ruleset.root = true; - return this.name + (env.compress ? '{' : ' {\n ') + - this.ruleset.toCSS(ctx, env).trim().replace(/\n/g, '\n ') + - (env.compress ? '}': '\n}\n'); - } else { - return this.name + ' ' + this.value.toCSS() + ';\n'; - } - }, - eval: function (env) { - var evaldDirective = this; - if (this.ruleset) { - env.frames.unshift(this); - evaldDirective = new(tree.Directive)(this.name); - evaldDirective.ruleset = this.ruleset.eval(env); - env.frames.shift(); - } - return evaldDirective; - }, - variable: function (name) { return tree.Ruleset.prototype.variable.call(this.ruleset, name) }, - find: function () { return tree.Ruleset.prototype.find.apply(this.ruleset, arguments) }, - rulesets: function () { return tree.Ruleset.prototype.rulesets.apply(this.ruleset) } -}; - -})(require('../tree')); -(function (tree) { - -tree.Element = function (combinator, value, index) { - this.combinator = combinator instanceof tree.Combinator ? - combinator : new(tree.Combinator)(combinator); - - if (typeof(value) === 'string') { - this.value = value.trim(); - } else if (value) { - this.value = value; - } else { - this.value = ""; - } - this.index = index; -}; -tree.Element.prototype.eval = function (env) { - return new(tree.Element)(this.combinator, - this.value.eval ? this.value.eval(env) : this.value, - this.index); -}; -tree.Element.prototype.toCSS = function (env) { - var value = (this.value.toCSS ? this.value.toCSS(env) : this.value); - if (value == '' && this.combinator.value.charAt(0) == '&') { - return ''; - } else { - return this.combinator.toCSS(env || {}) + value; - } -}; - -tree.Combinator = function (value) { - if (value === ' ') { - this.value = ' '; - } else { - this.value = value ? value.trim() : ""; - } -}; -tree.Combinator.prototype.toCSS = function (env) { - return { - '' : '', - ' ' : ' ', - ':' : ' :', - '+' : env.compress ? '+' : ' + ', - '~' : env.compress ? '~' : ' ~ ', - '>' : env.compress ? '>' : ' > ', - '|' : env.compress ? '|' : ' | ' - }[this.value]; -}; - -})(require('../tree')); -(function (tree) { - -tree.Expression = function (value) { this.value = value; }; -tree.Expression.prototype = { - eval: function (env) { - var returnValue, - inParenthesis = this.parens && !this.parensInOp, - doubleParen = false; - if (inParenthesis) { - env.inParenthesis(); - } - if (this.value.length > 1) { - returnValue = new(tree.Expression)(this.value.map(function (e) { - return e.eval(env); - })); - } else if (this.value.length === 1) { - if (this.value[0].parens && !this.value[0].parensInOp) { - doubleParen = true; - } - returnValue = this.value[0].eval(env); - } else { - returnValue = this; - } - if (inParenthesis) { - env.outOfParenthesis(); - } - if (this.parens && this.parensInOp && !(env.isMathsOn()) && !doubleParen) { - returnValue = new(tree.Paren)(returnValue); - } - return returnValue; - }, - toCSS: function (env) { - return this.value.map(function (e) { - return e.toCSS ? e.toCSS(env) : ''; - }).join(' '); - }, - throwAwayComments: function () { - this.value = this.value.filter(function(v) { - return !(v instanceof tree.Comment); - }); - } -}; - -})(require('../tree')); -(function (tree) { - -tree.Extend = function Extend(elements, index) { - this.selector = new(tree.Selector)(elements); - this.index = index; -}; - -tree.Extend.prototype.eval = function Extend_eval(env, selectors) { - var selfSelectors = findSelfSelectors(selectors || env.selectors), - targetValue = this.selector.elements[0].value; - - env.frames.forEach(function(frame) { - frame.rulesets().forEach(function(rule) { - rule.selectors.forEach(function(selector) { - selector.elements.forEach(function(element, idx) { - if (element.value === targetValue) { - selfSelectors.forEach(function(_selector) { - _selector.elements[0] = new tree.Element( - element.combinator, - _selector.elements[0].value, - _selector.elements[0].index - ); - rule.selectors.push(new tree.Selector( - selector.elements - .slice(0, idx) - .concat(_selector.elements) - .concat(selector.elements.slice(idx + 1)) - )); - }); - } - }); - }); - }); - }); - return this; -}; - -function findSelfSelectors(selectors) { - var ret = []; - - (function loop(elem, i) { - if (selectors[i] && selectors[i].length) { - selectors[i].forEach(function(s) { - loop(s.elements.concat(elem), i + 1); - }); - } - else { - ret.push({ elements: elem }); - } - })([], 0); - - return ret; -} - - -})(require('../tree')); -(function (tree) { -// -// CSS @import node -// -// The general strategy here is that we don't want to wait -// for the parsing to be completed, before we start importing -// the file. That's because in the context of a browser, -// most of the time will be spent waiting for the server to respond. -// -// On creation, we push the import path to our import queue, though -// `import,push`, we also pass it a callback, which it'll call once -// the file has been fetched, and parsed. -// -tree.Import = function (path, imports, features, once, index, rootpath) { - var that = this; - - this.once = once; - this.index = index; - this._path = path; - this.features = features; - this.rootpath = rootpath; - - // The '.less' extension is optional - if (path instanceof tree.Quoted) { - this.path = /(\.[a-z]*$)|([\?;].*)$/.test(path.value) ? path.value : path.value + '.less'; - } else { - this.path = path.value.value || path.value; - } - - this.css = /css([\?;].*)?$/.test(this.path); - - // Only pre-compile .less files - if (! this.css) { - imports.push(this.path, function (e, root, imported) { - if (e) { e.index = index; } - if (imported && that.once) { that.skip = imported; } - that.root = root || new(tree.Ruleset)([], []); - }); - } -}; - -// -// The actual import node doesn't return anything, when converted to CSS. -// The reason is that it's used at the evaluation stage, so that the rules -// it imports can be treated like any other rules. -// -// In `eval`, we make sure all Import nodes get evaluated, recursively, so -// we end up with a flat structure, which can easily be imported in the parent -// ruleset. -// -tree.Import.prototype = { - toCSS: function (env) { - var features = this.features ? ' ' + this.features.toCSS(env) : ''; - - if (this.css) { - // Add the base path if the import is relative - if (typeof this._path.value === "string" && !/^(?:[a-z-]+:|\/)/.test(this._path.value)) { - this._path.value = this.rootpath + this._path.value; - } - return "@import " + this._path.toCSS() + features + ';\n'; - } else { - return ""; - } - }, - eval: function (env) { - var ruleset, features = this.features && this.features.eval(env); - - if (this.skip) { return []; } - - if (this.css) { - return new(tree.Import)(this._path, null, features, this.once, this.index, this.rootpath); - } else { - ruleset = new(tree.Ruleset)([], this.root.rules.slice(0)); - - ruleset.evalImports(env); - - return this.features ? new(tree.Media)(ruleset.rules, this.features.value) : ruleset.rules; - } - } -}; - -})(require('../tree')); -(function (tree) { - -tree.JavaScript = function (string, index, escaped) { - this.escaped = escaped; - this.expression = string; - this.index = index; -}; -tree.JavaScript.prototype = { - eval: function (env) { - var result, - that = this, - context = {}; - - var expression = this.expression.replace(/@\{([\w-]+)\}/g, function (_, name) { - return tree.jsify(new(tree.Variable)('@' + name, that.index).eval(env)); - }); - - try { - expression = new(Function)('return (' + expression + ')'); - } catch (e) { - throw { message: "JavaScript evaluation error: `" + expression + "`" , - index: this.index }; - } - - for (var k in env.frames[0].variables()) { - context[k.slice(1)] = { - value: env.frames[0].variables()[k].value, - toJS: function () { - return this.value.eval(env).toCSS(); - } - }; - } - - try { - result = expression.call(context); - } catch (e) { - throw { message: "JavaScript evaluation error: '" + e.name + ': ' + e.message + "'" , - index: this.index }; - } - if (typeof(result) === 'string') { - return new(tree.Quoted)('"' + result + '"', result, this.escaped, this.index); - } else if (Array.isArray(result)) { - return new(tree.Anonymous)(result.join(', ')); - } else { - return new(tree.Anonymous)(result); - } - } -}; - -})(require('../tree')); - -(function (tree) { - -tree.Keyword = function (value) { this.value = value }; -tree.Keyword.prototype = { - eval: function () { return this }, - toCSS: function () { return this.value }, - compare: function (other) { - if (other instanceof tree.Keyword) { - return other.value === this.value ? 0 : 1; - } else { - return -1; - } - } -}; - -tree.True = new(tree.Keyword)('true'); -tree.False = new(tree.Keyword)('false'); - -})(require('../tree')); -(function (tree) { - -tree.Media = function (value, features) { - var selectors = this.emptySelectors(); - - this.features = new(tree.Value)(features); - this.ruleset = new(tree.Ruleset)(selectors, value); - this.ruleset.allowImports = true; -}; -tree.Media.prototype = { - toCSS: function (ctx, env) { - var features = this.features.toCSS(env); - - this.ruleset.root = (ctx.length === 0 || ctx[0].multiMedia); - return '@media ' + features + (env.compress ? '{' : ' {\n ') + - this.ruleset.toCSS(ctx, env).trim().replace(/\n/g, '\n ') + - (env.compress ? '}': '\n}\n'); - }, - eval: function (env) { - if (!env.mediaBlocks) { - env.mediaBlocks = []; - env.mediaPath = []; - } - - var media = new(tree.Media)([], []); - if(this.debugInfo) { - this.ruleset.debugInfo = this.debugInfo; - media.debugInfo = this.debugInfo; - } - var strictMathsBypass = false; - if (env.strictMaths === false) { - strictMathsBypass = true; - env.strictMaths = true; - } - try { - media.features = this.features.eval(env); - } - finally { - if (strictMathsBypass) { - env.strictMaths = false; - } - } - - env.mediaPath.push(media); - env.mediaBlocks.push(media); - - env.frames.unshift(this.ruleset); - media.ruleset = this.ruleset.eval(env); - env.frames.shift(); - - env.mediaPath.pop(); - - return env.mediaPath.length === 0 ? media.evalTop(env) : - media.evalNested(env) - }, - variable: function (name) { return tree.Ruleset.prototype.variable.call(this.ruleset, name) }, - find: function () { return tree.Ruleset.prototype.find.apply(this.ruleset, arguments) }, - rulesets: function () { return tree.Ruleset.prototype.rulesets.apply(this.ruleset) }, - emptySelectors: function() { - var el = new(tree.Element)('', '&', 0); - return [new(tree.Selector)([el])]; - }, - - evalTop: function (env) { - var result = this; - - // Render all dependent Media blocks. - if (env.mediaBlocks.length > 1) { - var selectors = this.emptySelectors(); - result = new(tree.Ruleset)(selectors, env.mediaBlocks); - result.multiMedia = true; - } - - delete env.mediaBlocks; - delete env.mediaPath; - - return result; - }, - evalNested: function (env) { - var i, value, - path = env.mediaPath.concat([this]); - - // Extract the media-query conditions separated with `,` (OR). - for (i = 0; i < path.length; i++) { - value = path[i].features instanceof tree.Value ? - path[i].features.value : path[i].features; - path[i] = Array.isArray(value) ? value : [value]; - } - - // Trace all permutations to generate the resulting media-query. - // - // (a, b and c) with nested (d, e) -> - // a and d - // a and e - // b and c and d - // b and c and e - this.features = new(tree.Value)(this.permute(path).map(function (path) { - path = path.map(function (fragment) { - return fragment.toCSS ? fragment : new(tree.Anonymous)(fragment); - }); - - for(i = path.length - 1; i > 0; i--) { - path.splice(i, 0, new(tree.Anonymous)("and")); - } - - return new(tree.Expression)(path); - })); - - // Fake a tree-node that doesn't output anything. - return new(tree.Ruleset)([], []); - }, - permute: function (arr) { - if (arr.length === 0) { - return []; - } else if (arr.length === 1) { - return arr[0]; - } else { - var result = []; - var rest = this.permute(arr.slice(1)); - for (var i = 0; i < rest.length; i++) { - for (var j = 0; j < arr[0].length; j++) { - result.push([arr[0][j]].concat(rest[i])); - } - } - return result; - } - }, - bubbleSelectors: function (selectors) { - this.ruleset = new(tree.Ruleset)(selectors.slice(0), [this.ruleset]); - } -}; - -})(require('../tree')); -(function (tree) { - -tree.mixin = {}; -tree.mixin.Call = function (elements, args, index, filename, important) { - this.selector = new(tree.Selector)(elements); - this.arguments = args; - this.index = index; - this.filename = filename; - this.important = important; -}; -tree.mixin.Call.prototype = { - eval: function (env) { - var mixins, mixin, args, rules = [], match = false, i, m, f, isRecursive, isOneFound; - - args = this.arguments && this.arguments.map(function (a) { - return { name: a.name, value: a.value.eval(env) }; - }); - - for (i = 0; i < env.frames.length; i++) { - if ((mixins = env.frames[i].find(this.selector)).length > 0) { - isOneFound = true; - for (m = 0; m < mixins.length; m++) { - mixin = mixins[m]; - isRecursive = false; - for(f = 0; f < env.frames.length; f++) { - if ((!(mixin instanceof tree.mixin.Definition)) && mixin === (env.frames[f].originalRuleset || env.frames[f])) { - isRecursive = true; - break; - } - } - if (isRecursive) { - continue; - } - if (mixin.matchArgs(args, env)) { - if (!mixin.matchCondition || mixin.matchCondition(args, env)) { - try { - Array.prototype.push.apply( - rules, mixin.eval(env, args, this.important).rules); - } catch (e) { - throw { message: e.message, index: this.index, filename: this.filename, stack: e.stack }; - } - } - match = true; - } - } - if (match) { - return rules; - } - } - } - if (isOneFound) { - throw { type: 'Runtime', - message: 'No matching definition was found for `' + - this.selector.toCSS().trim() + '(' + - (args ? args.map(function (a) { - var argValue = ""; - if (a.name) { - argValue += a.name + ":"; - } - if (a.value.toCSS) { - argValue += a.value.toCSS(); - } else { - argValue += "???"; - } - return argValue; - }).join(', ') : "") + ")`", - index: this.index, filename: this.filename }; - } else { - throw { type: 'Name', - message: this.selector.toCSS().trim() + " is undefined", - index: this.index, filename: this.filename }; - } - } -}; - -tree.mixin.Definition = function (name, params, rules, condition, variadic) { - this.name = name; - this.selectors = [new(tree.Selector)([new(tree.Element)(null, name)])]; - this.params = params; - this.condition = condition; - this.variadic = variadic; - this.arity = params.length; - this.rules = rules; - this._lookups = {}; - this.required = params.reduce(function (count, p) { - if (!p.name || (p.name && !p.value)) { return count + 1 } - else { return count } - }, 0); - this.parent = tree.Ruleset.prototype; - this.frames = []; -}; -tree.mixin.Definition.prototype = { - toCSS: function () { return "" }, - variable: function (name) { return this.parent.variable.call(this, name) }, - variables: function () { return this.parent.variables.call(this) }, - find: function () { return this.parent.find.apply(this, arguments) }, - rulesets: function () { return this.parent.rulesets.apply(this) }, - - evalParams: function (env, mixinEnv, args, evaldArguments) { - var frame = new(tree.Ruleset)(null, []), - varargs, arg, - params = this.params.slice(0), - i, j, val, name, isNamedFound, argIndex; - - mixinEnv = new tree.evalEnv(mixinEnv, [frame].concat(mixinEnv.frames)); - - if (args) { - args = args.slice(0); - - for(i = 0; i < args.length; i++) { - arg = args[i]; - if (name = (arg && arg.name)) { - isNamedFound = false; - for(j = 0; j < params.length; j++) { - if (!evaldArguments[j] && name === params[j].name) { - evaldArguments[j] = arg.value.eval(env); - frame.rules.unshift(new(tree.Rule)(name, arg.value.eval(env))); - isNamedFound = true; - break; - } - } - if (isNamedFound) { - args.splice(i, 1); - i--; - continue; - } else { - throw { type: 'Runtime', message: "Named argument for " + this.name + - ' ' + args[i].name + ' not found' }; - } - } - } - } - argIndex = 0; - for (i = 0; i < params.length; i++) { - if (evaldArguments[i]) continue; - - arg = args && args[argIndex]; - - if (name = params[i].name) { - if (params[i].variadic && args) { - varargs = []; - for (j = argIndex; j < args.length; j++) { - varargs.push(args[j].value.eval(env)); - } - frame.rules.unshift(new(tree.Rule)(name, new(tree.Expression)(varargs).eval(env))); - } else { - val = arg && arg.value; - if (val) { - val = val.eval(env); - } else if (params[i].value) { - val = params[i].value.eval(mixinEnv); - frame.resetCache(); - } else { - throw { type: 'Runtime', message: "wrong number of arguments for " + this.name + - ' (' + args.length + ' for ' + this.arity + ')' }; - } - - frame.rules.unshift(new(tree.Rule)(name, val)); - evaldArguments[i] = val; - } - } - - if (params[i].variadic && args) { - for (j = argIndex; j < args.length; j++) { - evaldArguments[j] = args[j].value.eval(env); - } - } - argIndex++; - } - - return frame; - }, - eval: function (env, args, important) { - var _arguments = [], - mixinFrames = this.frames.concat(env.frames), - frame = this.evalParams(env, new(tree.evalEnv)(env, mixinFrames), args, _arguments), - context, rules, start, ruleset; - - frame.rules.unshift(new(tree.Rule)('@arguments', new(tree.Expression)(_arguments).eval(env))); - - rules = important ? - this.parent.makeImportant.apply(this).rules : this.rules.slice(0); - - ruleset = new(tree.Ruleset)(null, rules).eval(new(tree.evalEnv)(env, - [this, frame].concat(mixinFrames))); - ruleset.originalRuleset = this; - return ruleset; - }, - matchCondition: function (args, env) { - - if (this.condition && !this.condition.eval( - new(tree.evalEnv)(env, - [this.evalParams(env, new(tree.evalEnv)(env, this.frames.concat(env.frames)), args, [])] - .concat(env.frames)))) { - return false; - } - return true; - }, - matchArgs: function (args, env) { - var argsLength = (args && args.length) || 0, len, frame; - - if (! this.variadic) { - if (argsLength < this.required) { return false } - if (argsLength > this.params.length) { return false } - if ((this.required > 0) && (argsLength > this.params.length)) { return false } - } - - len = Math.min(argsLength, this.arity); - - for (var i = 0; i < len; i++) { - if (!this.params[i].name && !this.params[i].variadic) { - if (args[i].value.eval(env).toCSS() != this.params[i].value.eval(env).toCSS()) { - return false; - } - } - } - return true; - } -}; - -})(require('../tree')); -(function (tree) { - -tree.Negative = function (node) { - this.value = node; -}; -tree.Negative.prototype = { - toCSS: function (env) { - return '-' + this.value.toCSS(env); - }, - eval: function (env) { - if (env.isMathsOn()) { - return (new(tree.Operation)('*', [new(tree.Dimension)(-1), this.value])).eval(env); - } - return new(tree.Negative)(this.value.eval(env)); - } -}; - -})(require('../tree')); -(function (tree) { - -tree.Operation = function (op, operands, isSpaced) { - this.op = op.trim(); - this.operands = operands; - this.isSpaced = isSpaced; -}; -tree.Operation.prototype.eval = function (env) { - var a = this.operands[0].eval(env), - b = this.operands[1].eval(env), - temp; - - if (env.isMathsOn()) { - if (a instanceof tree.Dimension && b instanceof tree.Color) { - if (this.op === '*' || this.op === '+') { - temp = b, b = a, a = temp; - } else { - throw { type: "Operation", - message: "Can't substract or divide a color from a number" }; - } - } - if (!a.operate) { - throw { type: "Operation", - message: "Operation on an invalid type" }; - } - - return a.operate(env, this.op, b); - } else { - return new(tree.Operation)(this.op, [a, b], this.isSpaced); - } -}; -tree.Operation.prototype.toCSS = function (env) { - var separator = this.isSpaced ? " " : ""; - return this.operands[0].toCSS() + separator + this.op + separator + this.operands[1].toCSS(); -}; - -tree.operate = function (env, op, a, b) { - switch (op) { - case '+': return a + b; - case '-': return a - b; - case '*': return a * b; - case '/': return a / b; - } -}; - -})(require('../tree')); - -(function (tree) { - -tree.Paren = function (node) { - this.value = node; -}; -tree.Paren.prototype = { - toCSS: function (env) { - return '(' + this.value.toCSS(env).trim() + ')'; - }, - eval: function (env) { - return new(tree.Paren)(this.value.eval(env)); - } -}; - -})(require('../tree')); -(function (tree) { - -tree.Quoted = function (str, content, escaped, i) { - this.escaped = escaped; - this.value = content || ''; - this.quote = str.charAt(0); - this.index = i; -}; -tree.Quoted.prototype = { - toCSS: function () { - if (this.escaped) { - return this.value; - } else { - return this.quote + this.value + this.quote; - } - }, - eval: function (env) { - var that = this; - var value = this.value.replace(/`([^`]+)`/g, function (_, exp) { - return new(tree.JavaScript)(exp, that.index, true).eval(env).value; - }).replace(/@\{([\w-]+)\}/g, function (_, name) { - var v = new(tree.Variable)('@' + name, that.index).eval(env, true); - return (v instanceof tree.Quoted) ? v.value : v.toCSS(); - }); - return new(tree.Quoted)(this.quote + value + this.quote, value, this.escaped, this.index); - }, - compare: function (x) { - if (!x.toCSS) { - return -1; - } - - var left = this.toCSS(), - right = x.toCSS(); - - if (left === right) { - return 0; - } - - return left < right ? -1 : 1; - } -}; - -})(require('../tree')); -(function (tree) { - -tree.Rule = function (name, value, important, index, inline) { - this.name = name; - this.value = (value instanceof tree.Value) ? value : new(tree.Value)([value]); - this.important = important ? ' ' + important.trim() : ''; - this.index = index; - this.inline = inline || false; - - if (name.charAt(0) === '@') { - this.variable = true; - } else { this.variable = false } -}; -tree.Rule.prototype.toCSS = function (env) { - if (this.variable) { return "" } - else { - return this.name + (env.compress ? ':' : ': ') + - this.value.toCSS(env) + - this.important + (this.inline ? "" : ";"); - } -}; - -tree.Rule.prototype.eval = function (env) { - var strictMathsBypass = false; - if (this.name === "font" && env.strictMaths === false) { - strictMathsBypass = true; - env.strictMaths = true; - } - try { - return new(tree.Rule)(this.name, - this.value.eval(env), - this.important, - this.index, this.inline); - } - finally { - if (strictMathsBypass) { - env.strictMaths = false; - } - } -}; - -tree.Rule.prototype.makeImportant = function () { - return new(tree.Rule)(this.name, - this.value, - "!important", - this.index, this.inline); -}; - -})(require('../tree')); -(function (tree) { - -tree.Ruleset = function (selectors, rules, strictImports) { - this.selectors = selectors; - this.rules = rules; - this._lookups = {}; - this.strictImports = strictImports; -}; -tree.Ruleset.prototype = { - eval: function (env) { - var selectors = this.selectors && this.selectors.map(function (s) { return s.eval(env) }); - var ruleset = new(tree.Ruleset)(selectors, this.rules.slice(0), this.strictImports); - var rules; - - ruleset.originalRuleset = this; - ruleset.root = this.root; - ruleset.allowImports = this.allowImports; - - if(this.debugInfo) { - ruleset.debugInfo = this.debugInfo; - } - - // push the current ruleset to the frames stack - env.frames.unshift(ruleset); - - // currrent selectors - if (!env.selectors) { - env.selectors = []; - } - env.selectors.unshift(this.selectors); - - // Evaluate imports - if (ruleset.root || ruleset.allowImports || !ruleset.strictImports) { - ruleset.evalImports(env); - } - - // Store the frames around mixin definitions, - // so they can be evaluated like closures when the time comes. - for (var i = 0; i < ruleset.rules.length; i++) { - if (ruleset.rules[i] instanceof tree.mixin.Definition) { - ruleset.rules[i].frames = env.frames.slice(0); - } - } - - var mediaBlockCount = (env.mediaBlocks && env.mediaBlocks.length) || 0; - - // Evaluate mixin calls. - for (var i = 0; i < ruleset.rules.length; i++) { - if (ruleset.rules[i] instanceof tree.mixin.Call) { - rules = ruleset.rules[i].eval(env).filter(function(r) { - if ((r instanceof tree.Rule) && r.variable) { - // do not pollute the scope if the variable is - // already there. consider returning false here - // but we need a way to "return" variable from mixins - return !(ruleset.variable(r.name)); - } - return true; - }); - ruleset.rules.splice.apply(ruleset.rules, [i, 1].concat(rules)); - i += rules.length-1; - ruleset.resetCache(); - } - } - - if (this.selectors) { - for (var i = 0; i < this.selectors.length; i++) { - if (this.selectors[i].extend) { - this.selectors[i].extend.eval(env, [[this.selectors[i]]].concat(env.selectors.slice(1))); - } - } - } - - // Evaluate everything else - for (var i = 0, rule; i < ruleset.rules.length; i++) { - rule = ruleset.rules[i]; - - if (! (rule instanceof tree.mixin.Definition)) { - ruleset.rules[i] = rule.eval ? rule.eval(env) : rule; - } - } - - // Pop the stack - env.frames.shift(); - env.selectors.shift(); - - if (env.mediaBlocks) { - for(var i = mediaBlockCount; i < env.mediaBlocks.length; i++) { - env.mediaBlocks[i].bubbleSelectors(selectors); - } - } - - return ruleset; - }, - evalImports: function(env) { - var i, rules; - for (i = 0; i < this.rules.length; i++) { - if (this.rules[i] instanceof tree.Import) { - rules = this.rules[i].eval(env); - if (typeof rules.length === "number") { - this.rules.splice.apply(this.rules, [i, 1].concat(rules)); - i+= rules.length-1; - } else { - this.rules.splice(i, 1, rules); - } - this.resetCache(); - } - } - }, - makeImportant: function() { - return new tree.Ruleset(this.selectors, this.rules.map(function (r) { - if (r.makeImportant) { - return r.makeImportant(); - } else { - return r; - } - }), this.strictImports); - }, - matchArgs: function (args) { - return !args || args.length === 0; - }, - resetCache: function () { - this._rulesets = null; - this._variables = null; - this._lookups = {}; - }, - variables: function () { - if (this._variables) { return this._variables } - else { - return this._variables = this.rules.reduce(function (hash, r) { - if (r instanceof tree.Rule && r.variable === true) { - hash[r.name] = r; - } - return hash; - }, {}); - } - }, - variable: function (name) { - return this.variables()[name]; - }, - rulesets: function () { - return this.rules.filter(function (r) { - return (r instanceof tree.Ruleset) || (r instanceof tree.mixin.Definition); - }); - }, - find: function (selector, self) { - self = self || this; - var rules = [], rule, match, - key = selector.toCSS(); - - if (key in this._lookups) { return this._lookups[key] } - - this.rulesets().forEach(function (rule) { - if (rule !== self) { - for (var j = 0; j < rule.selectors.length; j++) { - if (match = selector.match(rule.selectors[j])) { - if (selector.elements.length > rule.selectors[j].elements.length) { - Array.prototype.push.apply(rules, rule.find( - new(tree.Selector)(selector.elements.slice(1)), self)); - } else { - rules.push(rule); - } - break; - } - } - } - }); - return this._lookups[key] = rules; - }, - // - // Entry point for code generation - // - // `context` holds an array of arrays. - // - toCSS: function (context, env) { - var css = [], // The CSS output - rules = [], // node.Rule instances - _rules = [], // - rulesets = [], // node.Ruleset instances - paths = [], // Current selectors - selector, // The fully rendered selector - debugInfo, // Line number debugging - rule; - - if (! this.root) { - this.joinSelectors(paths, context, this.selectors); - } - - // Compile rules and rulesets - for (var i = 0; i < this.rules.length; i++) { - rule = this.rules[i]; - - if (rule.rules || (rule instanceof tree.Media)) { - rulesets.push(rule.toCSS(paths, env)); - } else if (rule instanceof tree.Directive) { - var cssValue = rule.toCSS(paths, env); - // Output only the first @charset definition as such - convert the others - // to comments in case debug is enabled - if (rule.name === "@charset") { - // Only output the debug info together with subsequent @charset definitions - // a comment (or @media statement) before the actual @charset directive would - // be considered illegal css as it has to be on the first line - if (env.charset) { - if (rule.debugInfo) { - rulesets.push(tree.debugInfo(env, rule)); - rulesets.push(new tree.Comment("/* "+cssValue.replace(/\n/g, "")+" */\n").toCSS(env)); - } - continue; - } - env.charset = true; - } - rulesets.push(cssValue); - } else if (rule instanceof tree.Comment) { - if (!rule.silent) { - if (this.root) { - rulesets.push(rule.toCSS(env)); - } else { - rules.push(rule.toCSS(env)); - } - } - } else { - if (rule.toCSS && !rule.variable) { - rules.push(rule.toCSS(env)); - } else if (rule.value && !rule.variable) { - rules.push(rule.value.toString()); - } - } - } - - // Remove last semicolon - if (env.compress && rules.length) { - rule = rules[rules.length - 1]; - if (rule.charAt(rule.length - 1) === ';') { - rules[rules.length - 1] = rule.substring(0, rule.length - 1); - } - } - - rulesets = rulesets.join(''); - - // If this is the root node, we don't render - // a selector, or {}. - // Otherwise, only output if this ruleset has rules. - if (this.root) { - css.push(rules.join(env.compress ? '' : '\n')); - } else { - if (rules.length > 0) { - debugInfo = tree.debugInfo(env, this); - selector = paths.map(function (p) { - return p.map(function (s) { - return s.toCSS(env); - }).join('').trim(); - }).join(env.compress ? ',' : ',\n'); - - // Remove duplicates - for (var i = rules.length - 1; i >= 0; i--) { - if (_rules.indexOf(rules[i]) === -1) { - _rules.unshift(rules[i]); - } - } - rules = _rules; - - css.push(debugInfo + selector + - (env.compress ? '{' : ' {\n ') + - rules.join(env.compress ? '' : '\n ') + - (env.compress ? '}' : '\n}\n')); - } - } - css.push(rulesets); - - return css.join('') + (env.compress ? '\n' : ''); - }, - - joinSelectors: function (paths, context, selectors) { - for (var s = 0; s < selectors.length; s++) { - this.joinSelector(paths, context, selectors[s]); - } - }, - - joinSelector: function (paths, context, selector) { - - var i, j, k, - hasParentSelector, newSelectors, el, sel, parentSel, - newSelectorPath, afterParentJoin, newJoinedSelector, - newJoinedSelectorEmpty, lastSelector, currentElements, - selectorsMultiplied; - - for (i = 0; i < selector.elements.length; i++) { - el = selector.elements[i]; - if (el.value === '&') { - hasParentSelector = true; - } - } - - if (!hasParentSelector) { - if (context.length > 0) { - for(i = 0; i < context.length; i++) { - paths.push(context[i].concat(selector)); - } - } - else { - paths.push([selector]); - } - return; - } - - // The paths are [[Selector]] - // The first list is a list of comma seperated selectors - // The inner list is a list of inheritance seperated selectors - // e.g. - // .a, .b { - // .c { - // } - // } - // == [[.a] [.c]] [[.b] [.c]] - // - - // the elements from the current selector so far - currentElements = []; - // the current list of new selectors to add to the path. - // We will build it up. We initiate it with one empty selector as we "multiply" the new selectors - // by the parents - newSelectors = [[]]; - - for (i = 0; i < selector.elements.length; i++) { - el = selector.elements[i]; - // non parent reference elements just get added - if (el.value !== "&") { - currentElements.push(el); - } else { - // the new list of selectors to add - selectorsMultiplied = []; - - // merge the current list of non parent selector elements - // on to the current list of selectors to add - if (currentElements.length > 0) { - this.mergeElementsOnToSelectors(currentElements, newSelectors); - } - - // loop through our current selectors - for(j = 0; j < newSelectors.length; j++) { - sel = newSelectors[j]; - // if we don't have any parent paths, the & might be in a mixin so that it can be used - // whether there are parents or not - if (context.length == 0) { - // the combinator used on el should now be applied to the next element instead so that - // it is not lost - if (sel.length > 0) { - sel[0].elements = sel[0].elements.slice(0); - sel[0].elements.push(new(tree.Element)(el.combinator, '', 0)); //new Element(el.Combinator, "")); - } - selectorsMultiplied.push(sel); - } - else { - // and the parent selectors - for(k = 0; k < context.length; k++) { - parentSel = context[k]; - // We need to put the current selectors - // then join the last selector's elements on to the parents selectors - - // our new selector path - newSelectorPath = []; - // selectors from the parent after the join - afterParentJoin = []; - newJoinedSelectorEmpty = true; - - //construct the joined selector - if & is the first thing this will be empty, - // if not newJoinedSelector will be the last set of elements in the selector - if (sel.length > 0) { - newSelectorPath = sel.slice(0); - lastSelector = newSelectorPath.pop(); - newJoinedSelector = new(tree.Selector)(lastSelector.elements.slice(0)); - newJoinedSelectorEmpty = false; - } - else { - newJoinedSelector = new(tree.Selector)([]); - } - - //put together the parent selectors after the join - if (parentSel.length > 1) { - afterParentJoin = afterParentJoin.concat(parentSel.slice(1)); - } - - if (parentSel.length > 0) { - newJoinedSelectorEmpty = false; - - // join the elements so far with the first part of the parent - newJoinedSelector.elements.push(new(tree.Element)(el.combinator, parentSel[0].elements[0].value, 0)); - newJoinedSelector.elements = newJoinedSelector.elements.concat(parentSel[0].elements.slice(1)); - } - - if (!newJoinedSelectorEmpty) { - // now add the joined selector - newSelectorPath.push(newJoinedSelector); - } - - // and the rest of the parent - newSelectorPath = newSelectorPath.concat(afterParentJoin); - - // add that to our new set of selectors - selectorsMultiplied.push(newSelectorPath); - } - } - } - - // our new selectors has been multiplied, so reset the state - newSelectors = selectorsMultiplied; - currentElements = []; - } - } - - // if we have any elements left over (e.g. .a& .b == .b) - // add them on to all the current selectors - if (currentElements.length > 0) { - this.mergeElementsOnToSelectors(currentElements, newSelectors); - } - - for(i = 0; i < newSelectors.length; i++) { - if (newSelectors[i].length > 0) { - paths.push(newSelectors[i]); - } - } - }, - - mergeElementsOnToSelectors: function(elements, selectors) { - var i, sel; - - if (selectors.length == 0) { - selectors.push([ new(tree.Selector)(elements) ]); - return; - } - - for(i = 0; i < selectors.length; i++) { - sel = selectors[i]; - - // if the previous thing in sel is a parent this needs to join on to it - if (sel.length > 0) { - sel[sel.length - 1] = new(tree.Selector)(sel[sel.length - 1].elements.concat(elements)); - } - else { - sel.push(new(tree.Selector)(elements)); - } - } - } -}; -})(require('../tree')); -(function (tree) { - -tree.Selector = function (elements, extend) { - this.elements = elements; - this.extend = extend; -}; -tree.Selector.prototype.match = function (other) { - var elements = this.elements, - len = elements.length, - oelements, olen, max, i; - - oelements = other.elements.slice( - (other.elements.length && other.elements[0].value === "&") ? 1 : 0); - olen = oelements.length; - max = Math.min(len, olen) - - if (olen === 0 || len < olen) { - return false; - } else { - for (i = 0; i < max; i++) { - if (elements[i].value !== oelements[i].value) { - return false; - } - } - } - return true; -}; -tree.Selector.prototype.eval = function (env) { - return new(tree.Selector)(this.elements.map(function (e) { - return e.eval(env); - }), this.extend); -}; -tree.Selector.prototype.toCSS = function (env) { - if (this._css) { return this._css } - - if (this.elements[0].combinator.value === "") { - this._css = ' '; - } else { - this._css = ''; - } - - this._css += this.elements.map(function (e) { - if (typeof(e) === 'string') { - return ' ' + e.trim(); - } else { - return e.toCSS(env); - } - }).join(''); - - return this._css; -}; - -})(require('../tree')); -(function (tree) { - -tree.UnicodeDescriptor = function (value) { - this.value = value; -}; -tree.UnicodeDescriptor.prototype = { - toCSS: function (env) { - return this.value; - }, - eval: function () { return this } -}; - -})(require('../tree')); -(function (tree) { - -tree.URL = function (val, rootpath) { - this.value = val; - this.rootpath = rootpath; -}; -tree.URL.prototype = { - toCSS: function () { - return "url(" + this.value.toCSS() + ")"; - }, - eval: function (ctx) { - var val = this.value.eval(ctx), rootpath; - - // Add the base path if the URL is relative - if (this.rootpath && typeof val.value === "string" && ctx.isPathRelative(val.value)) { - rootpath = this.rootpath; - if (!val.quote) { - rootpath = rootpath.replace(/[\(\)'"\s]/g, function(match) { return "\\"+match; }); - } - val.value = rootpath + val.value; - } - - return new(tree.URL)(val, null); - } -}; - -})(require('../tree')); -(function (tree) { - -tree.Value = function (value) { - this.value = value; - this.is = 'value'; -}; -tree.Value.prototype = { - eval: function (env) { - if (this.value.length === 1) { - return this.value[0].eval(env); - } else { - return new(tree.Value)(this.value.map(function (v) { - return v.eval(env); - })); - } - }, - toCSS: function (env) { - return this.value.map(function (e) { - return e.toCSS(env); - }).join(env.compress ? ',' : ', '); - } -}; - -})(require('../tree')); -(function (tree) { - -tree.Variable = function (name, index, file) { this.name = name, this.index = index, this.file = file }; -tree.Variable.prototype = { - eval: function (env) { - var variable, v, name = this.name; - - if (name.indexOf('@@') == 0) { - name = '@' + new(tree.Variable)(name.slice(1)).eval(env).value; - } - - if (this.evaluating) { - throw { type: 'Name', - message: "Recursive variable definition for " + name, - filename: this.file, - index: this.index }; - } - - this.evaluating = true; - - if (variable = tree.find(env.frames, function (frame) { - if (v = frame.variable(name)) { - return v.value.eval(env); - } - })) { - this.evaluating = false; - return variable; - } - else { - throw { type: 'Name', - message: "variable " + name + " is undefined", - filename: this.file, - index: this.index }; - } - } -}; - -})(require('../tree')); -(function (tree) { - -tree.debugInfo = function(env, ctx) { - var result=""; - if (env.dumpLineNumbers && !env.compress) { - switch(env.dumpLineNumbers) { - case 'comments': - result = tree.debugInfo.asComment(ctx); - break; - case 'mediaquery': - result = tree.debugInfo.asMediaQuery(ctx); - break; - case 'all': - result = tree.debugInfo.asComment(ctx)+tree.debugInfo.asMediaQuery(ctx); - break; - } - } - return result; -}; - -tree.debugInfo.asComment = function(ctx) { - return '/* line ' + ctx.debugInfo.lineNumber + ', ' + ctx.debugInfo.fileName + ' */\n'; -}; - -tree.debugInfo.asMediaQuery = function(ctx) { - return '@media -sass-debug-info{filename{font-family:' + - ('file://' + ctx.debugInfo.fileName).replace(/[\/:.]/g, '\\$&') + - '}line{font-family:\\00003' + ctx.debugInfo.lineNumber + '}}\n'; -}; - -tree.find = function (obj, fun) { - for (var i = 0, r; i < obj.length; i++) { - if (r = fun.call(obj, obj[i])) { return r } - } - return null; -}; -tree.jsify = function (obj) { - if (Array.isArray(obj.value) && (obj.value.length > 1)) { - return '[' + obj.value.map(function (v) { return v.toCSS(false) }).join(', ') + ']'; - } else { - return obj.toCSS(false); - } -}; - -})(require('./tree')); -(function (tree) { - - var parseCopyProperties = [ - 'paths', // paths to search for imports on - 'optimization', // option - optimization level (for the chunker) - 'filename', // current filename, used for error reporting - 'files', // list of files that have been imported, used for import-once - 'contents', // browser-only, contents of all the files - 'rootpath', // current rootpath to append to all url's - 'relativeUrls', // option - whether to adjust URL's to be relative - 'strictImports', // option - - 'dumpLineNumbers', // option - whether to dump line numbers - 'compress', // option - whether to compress - 'mime', // browser only - mime type for sheet import - 'entryPath', // browser only - path of entry less file - 'rootFilename', // browser only - href of the entry less file - 'currentDirectory' // node only - the current directory - ]; - - tree.parseEnv = function(options) { - copyFromOriginal(options, this, parseCopyProperties); - - if (!this.contents) { this.contents = {}; } - if (!this.rootpath) { this.rootpath = ''; } - if (!this.files) { this.files = {}; } - }; - - tree.parseEnv.prototype.toSheet = function (path) { - var env = new tree.parseEnv(this); - env.href = path; - //env.title = path; - env.type = this.mime; - return env; - }; - - var evalCopyProperties = [ - 'silent', // whether to swallow errors and warnings - 'verbose', // whether to log more activity - 'compress', // whether to compress - 'ieCompat', // whether to enforce IE compatibility (IE8 data-uri) - 'strictMaths', // whether maths has to be within parenthesis - 'strictUnits' // whether units need to evaluate correctly - ]; - - tree.evalEnv = function(options, frames) { - copyFromOriginal(options, this, evalCopyProperties); - - this.frames = frames || []; - }; - - tree.evalEnv.prototype.inParenthesis = function () { - if (!this.parensStack) { - this.parensStack = []; - } - this.parensStack.push(true); - }; - - tree.evalEnv.prototype.outOfParenthesis = function () { - this.parensStack.pop(); - }; - - tree.evalEnv.prototype.isMathsOn = function () { - return this.strictMaths === false ? true : (this.parensStack && this.parensStack.length); - }; - - tree.evalEnv.prototype.isPathRelative = function (path) { - return !/^(?:[a-z-]+:|\/)/.test(path); - }; - - //todo - do the same for the toCSS env - //tree.toCSSEnv = function (options) { - //}; - - var copyFromOriginal = function(original, destination, propertiesToCopy) { - if (!original) { return; } - - for(var i = 0; i < propertiesToCopy.length; i++) { - if (original.hasOwnProperty(propertiesToCopy[i])) { - destination[propertiesToCopy[i]] = original[propertiesToCopy[i]]; - } - } - } -})(require('./tree'));// -// browser.js - client-side engine -// - -var isFileProtocol = /^(file|chrome(-extension)?|resource|qrc|app):/.test(location.protocol); - -less.env = less.env || (location.hostname == '127.0.0.1' || - location.hostname == '0.0.0.0' || - location.hostname == 'localhost' || - location.port.length > 0 || - isFileProtocol ? 'development' - : 'production'); - -// Load styles asynchronously (default: false) -// -// This is set to `false` by default, so that the body -// doesn't start loading before the stylesheets are parsed. -// Setting this to `true` can result in flickering. -// -less.async = less.async || false; -less.fileAsync = less.fileAsync || false; - -// Interval between watch polls -less.poll = less.poll || (isFileProtocol ? 1000 : 1500); - -//Setup user functions -if (less.functions) { - for(var func in less.functions) { - less.tree.functions[func] = less.functions[func]; - } -} - -var dumpLineNumbers = /!dumpLineNumbers:(comments|mediaquery|all)/.exec(location.hash); -if (dumpLineNumbers) { - less.dumpLineNumbers = dumpLineNumbers[1]; -} - -// -// Watch mode -// -less.watch = function () { - if (!less.watchMode ){ - less.env = 'development'; - initRunningMode(); - } - return this.watchMode = true -}; - -less.unwatch = function () {clearInterval(less.watchTimer); return this.watchMode = false; }; - -function initRunningMode(){ - if (less.env === 'development') { - less.optimization = 0; - less.watchTimer = setInterval(function () { - if (less.watchMode) { - loadStyleSheets(function (e, root, _, sheet, env) { - if (e) { - error(e, sheet.href); - } else if (root) { - createCSS(root.toCSS(less), sheet, env.lastModified); - } - }); - } - }, less.poll); - } else { - less.optimization = 3; - } -} - -if (/!watch/.test(location.hash)) { - less.watch(); -} - -var cache = null; - -if (less.env != 'development') { - try { - cache = (typeof(window.localStorage) === 'undefined') ? null : window.localStorage; - } catch (_) {} -} - -// -// Get all tags with the 'rel' attribute set to "stylesheet/less" -// -var links = document.getElementsByTagName('link'); -var typePattern = /^text\/(x-)?less$/; - -less.sheets = []; - -for (var i = 0; i < links.length; i++) { - if (links[i].rel === 'stylesheet/less' || (links[i].rel.match(/stylesheet/) && - (links[i].type.match(typePattern)))) { - less.sheets.push(links[i]); - } -} - -// -// With this function, it's possible to alter variables and re-render -// CSS without reloading less-files -// -var session_cache = ''; -less.modifyVars = function(record) { - var str = session_cache; - for (name in record) { - str += ((name.slice(0,1) === '@')? '' : '@') + name +': '+ - ((record[name].slice(-1) === ';')? record[name] : record[name] +';'); - } - new(less.Parser)(new less.tree.parseEnv(less)).parse(str, function (e, root) { - if (e) { - error(e, "session_cache"); - } else { - createCSS(root.toCSS(less), less.sheets[less.sheets.length - 1]); - } - }); -}; - -less.refresh = function (reload) { - var startTime, endTime; - startTime = endTime = new(Date); - - loadStyleSheets(function (e, root, _, sheet, env) { - if (e) { - return error(e, sheet.href); - } - if (env.local) { - log("loading " + sheet.href + " from cache."); - } else { - log("parsed " + sheet.href + " successfully."); - createCSS(root.toCSS(less), sheet, env.lastModified); - } - log("css for " + sheet.href + " generated in " + (new(Date) - endTime) + 'ms'); - (env.remaining === 0) && log("css generated in " + (new(Date) - startTime) + 'ms'); - endTime = new(Date); - }, reload); - - loadStyles(); -}; -less.refreshStyles = loadStyles; - -less.refresh(less.env === 'development'); - -function loadStyles() { - var styles = document.getElementsByTagName('style'); - for (var i = 0; i < styles.length; i++) { - if (styles[i].type.match(typePattern)) { - var env = new less.tree.parseEnv(less); - env.filename = document.location.href.replace(/#.*$/, ''); - - new(less.Parser)(env).parse(styles[i].innerHTML || '', function (e, cssAST) { - if (e) { - return error(e, "inline"); - } - var css = cssAST.toCSS(less); - var style = styles[i]; - style.type = 'text/css'; - if (style.styleSheet) { - style.styleSheet.cssText = css; - } else { - style.innerHTML = css; - } - }); - } - } -} - -function loadStyleSheets(callback, reload) { - for (var i = 0; i < less.sheets.length; i++) { - loadStyleSheet(less.sheets[i], callback, reload, less.sheets.length - (i + 1)); - } -} - -function pathDiff(url, baseUrl) { - // diff between two paths to create a relative path - - var urlParts = extractUrlParts(url), - baseUrlParts = extractUrlParts(baseUrl), - i, max, urlDirectories, baseUrlDirectories, diff = ""; - if (urlParts.hostPart !== baseUrlParts.hostPart) { - return ""; - } - max = Math.max(baseUrlParts.directories.length, urlParts.directories.length); - for(i = 0; i < max; i++) { - if (baseUrlParts.directories[i] !== urlParts.directories[i]) { break; } - } - baseUrlDirectories = baseUrlParts.directories.slice(i); - urlDirectories = urlParts.directories.slice(i); - for(i = 0; i < baseUrlDirectories.length-1; i++) { - diff += "../"; - } - for(i = 0; i < urlDirectories.length-1; i++) { - diff += urlDirectories[i] + "/"; - } - return diff; -} - -function extractUrlParts(url, baseUrl) { - // urlParts[1] = protocol&hostname || / - // urlParts[2] = / if path relative to host base - // urlParts[3] = directories - // urlParts[4] = filename - // urlParts[5] = parameters - - var urlPartsRegex = /^((?:[a-z-]+:)?\/+?(?:[^\/\?#]*\/)|([\/\\]))?((?:[^\/\\\?#]*[\/\\])*)([^\/\\\?#]*)([#\?].*)?$/, - urlParts = url.match(urlPartsRegex), - returner = {}, directories = [], i, baseUrlParts; - - if (!urlParts) { - throw new Error("Could not parse sheet href - '"+url+"'"); - } - - // Stylesheets in IE don't always return the full path - if (!urlParts[1] || urlParts[2]) { - baseUrlParts = baseUrl.match(urlPartsRegex); - if (!baseUrlParts) { - throw new Error("Could not parse page url - '"+baseUrl+"'"); - } - urlParts[1] = baseUrlParts[1]; - if (!urlParts[2]) { - urlParts[3] = baseUrlParts[3] + urlParts[3]; - } - } - - if (urlParts[3]) { - directories = urlParts[3].replace("\\", "/").split("/"); - - for(i = 0; i < directories.length; i++) { - if (directories[i] === ".." && i > 0) { - directories.splice(i-1, 2); - i -= 2; - } - } - } - - returner.hostPart = urlParts[1]; - returner.directories = directories; - returner.path = urlParts[1] + directories.join("/"); - returner.fileUrl = returner.path + (urlParts[4] || ""); - returner.url = returner.fileUrl + (urlParts[5] || ""); - return returner; -} - -function loadStyleSheet(sheet, callback, reload, remaining) { - - // sheet may be set to the stylesheet for the initial load or a collection of properties including - // some env variables for imports - var hrefParts = extractUrlParts(sheet.href, window.location.href); - var href = hrefParts.url; - var css = cache && cache.getItem(href); - var timestamp = cache && cache.getItem(href + ':timestamp'); - var styles = { css: css, timestamp: timestamp }; - var env; - - if (sheet instanceof less.tree.parseEnv) { - env = new less.tree.parseEnv(sheet); - } else { - env = new less.tree.parseEnv(less); - env.entryPath = hrefParts.path; - env.mime = sheet.type; - } - - if (env.relativeUrls) { - //todo - this relies on option being set on less object rather than being passed in as an option - // - need an originalRootpath - if (less.rootpath) { - env.rootpath = extractUrlParts(less.rootpath + pathDiff(hrefParts.path, env.entryPath)).path; - } else { - env.rootpath = hrefParts.path; - } - } else { - if (!less.rootpath) { - env.rootpath = env.entryPath; - } - } - - xhr(href, sheet.type, function (data, lastModified) { - // Store data this session - session_cache += data.replace(/@import .+?;/ig, ''); - - if (!reload && styles && lastModified && - (new(Date)(lastModified).valueOf() === - new(Date)(styles.timestamp).valueOf())) { - // Use local copy - createCSS(styles.css, sheet); - callback(null, null, data, sheet, { local: true, remaining: remaining }, href); - } else { - // Use remote copy (re-parse) - try { - env.contents[href] = data; // Updating content cache - env.paths = [hrefParts.path]; - env.filename = href; - env.rootFilename = env.rootFilename || href; - new(less.Parser)(env).parse(data, function (e, root) { - if (e) { return callback(e, null, null, sheet); } - try { - callback(e, root, data, sheet, { local: false, lastModified: lastModified, remaining: remaining }, href); - //TODO - there must be a better way? A generic less-to-css function that can both call error - //and removeNode where appropriate - //should also add tests - if (env.rootFilename === href) { - removeNode(document.getElementById('less-error-message:' + extractId(href))); - } - } catch (e) { - callback(e, null, null, sheet); - } - }); - } catch (e) { - callback(e, null, null, sheet); - } - } - }, function (status, url) { - callback({ type: 'File', message: "'" + url + "' wasn't found (" + status + ")" }, null, null, sheet); - }); -} - -function extractId(href) { - return href.replace(/^[a-z-]+:\/+?[^\/]+/, '' ) // Remove protocol & domain - .replace(/^\//, '' ) // Remove root / - .replace(/\.[a-zA-Z]+$/, '' ) // Remove simple extension - .replace(/[^\.\w-]+/g, '-') // Replace illegal characters - .replace(/\./g, ':'); // Replace dots with colons(for valid id) -} - -function createCSS(styles, sheet, lastModified) { - // Strip the query-string - var href = sheet.href || ''; - - // If there is no title set, use the filename, minus the extension - var id = 'less:' + (sheet.title || extractId(href)); - - // If this has already been inserted into the DOM, we may need to replace it - var oldCss = document.getElementById(id); - var keepOldCss = false; - - // Create a new stylesheet node for insertion or (if necessary) replacement - var css = document.createElement('style'); - css.setAttribute('type', 'text/css'); - if (sheet.media) { - css.setAttribute('media', sheet.media); - } - css.id = id; - - if (css.styleSheet) { // IE - try { - css.styleSheet.cssText = styles; - } catch (e) { - throw new(Error)("Couldn't reassign styleSheet.cssText."); - } - } else { - css.appendChild(document.createTextNode(styles)); - - // If new contents match contents of oldCss, don't replace oldCss - keepOldCss = (oldCss !== null && oldCss.childNodes.length > 0 && css.childNodes.length > 0 && - oldCss.firstChild.nodeValue === css.firstChild.nodeValue); - } - - var head = document.getElementsByTagName('head')[0]; - - // If there is no oldCss, just append; otherwise, only append if we need - // to replace oldCss with an updated stylesheet - if (oldCss == null || keepOldCss === false) { - var nextEl = sheet && sheet.nextSibling || null; - (nextEl || document.getElementsByTagName('head')[0]).parentNode.insertBefore(css, nextEl); - } - if (oldCss && keepOldCss === false) { - head.removeChild(oldCss); - } - - // Don't update the local store if the file wasn't modified - if (lastModified && cache) { - log('saving ' + href + ' to cache.'); - try { - cache.setItem(href, styles); - cache.setItem(href + ':timestamp', lastModified); - } catch(e) { - //TODO - could do with adding more robust error handling - log('failed to save'); - } - } -} - -function xhr(url, type, callback, errback) { - var xhr = getXMLHttpRequest(); - var async = isFileProtocol ? less.fileAsync : less.async; - - if (typeof(xhr.overrideMimeType) === 'function') { - xhr.overrideMimeType('text/css'); - } - xhr.open('GET', url, async); - xhr.setRequestHeader('Accept', type || 'text/x-less, text/css; q=0.9, */*; q=0.5'); - xhr.send(null); - - if (isFileProtocol && !less.fileAsync) { - if (xhr.status === 0 || (xhr.status >= 200 && xhr.status < 300)) { - callback(xhr.responseText); - } else { - errback(xhr.status, url); - } - } else if (async) { - xhr.onreadystatechange = function () { - if (xhr.readyState == 4) { - handleResponse(xhr, callback, errback); - } - }; - } else { - handleResponse(xhr, callback, errback); - } - - function handleResponse(xhr, callback, errback) { - if (xhr.status >= 200 && xhr.status < 300) { - callback(xhr.responseText, - xhr.getResponseHeader("Last-Modified")); - } else if (typeof(errback) === 'function') { - errback(xhr.status, url); - } - } -} - -function getXMLHttpRequest() { - if (window.XMLHttpRequest) { - return new(XMLHttpRequest); - } else { - try { - return new(ActiveXObject)("MSXML2.XMLHTTP.3.0"); - } catch (e) { - log("browser doesn't support AJAX."); - return null; - } - } -} - -function removeNode(node) { - return node && node.parentNode.removeChild(node); -} - -function log(str) { - if (less.env == 'development' && typeof(console) !== "undefined") { console.log('less: ' + str) } -} - -function error(e, rootHref) { - var id = 'less-error-message:' + extractId(rootHref || ""); - var template = ''; - var elem = document.createElement('div'), timer, content, error = []; - var filename = e.filename || rootHref; - var filenameNoPath = filename.match(/([^\/]+(\?.*)?)$/)[1]; - - elem.id = id; - elem.className = "less-error-message"; - - content = ' {content}' + (e.type || "Syntax") + "Error: " + (e.message || 'There is an error in your .less file') + - '
' + 'in ' + filenameNoPath + " "; - - var errorline = function (e, i, classname) { - if (e.extract[i] != undefined) { - error.push(template.replace(/\{line\}/, (parseInt(e.line) || 0) + (i - 1)) - .replace(/\{class\}/, classname) - .replace(/\{content\}/, e.extract[i])); - } - }; - - if (e.extract) { - errorline(e, 0, ''); - errorline(e, 1, 'line'); - errorline(e, 2, ''); - content += 'on line ' + e.line + ', column ' + (e.column + 1) + ':
' + - '' + error.join('') + '
'; - } else if (e.stack) { - content += '
' + e.stack.split('\n').slice(1).join('
'); - } - elem.innerHTML = content; - - // CSS for error messages - createCSS([ - '.less-error-message ul, .less-error-message li {', - 'list-style-type: none;', - 'margin-right: 15px;', - 'padding: 4px 0;', - 'margin: 0;', - '}', - '.less-error-message label {', - 'font-size: 12px;', - 'margin-right: 15px;', - 'padding: 4px 0;', - 'color: #cc7777;', - '}', - '.less-error-message pre {', - 'color: #dd6666;', - 'padding: 4px 0;', - 'margin: 0;', - 'display: inline-block;', - '}', - '.less-error-message pre.line {', - 'color: #ff0000;', - '}', - '.less-error-message h3 {', - 'font-size: 20px;', - 'font-weight: bold;', - 'padding: 15px 0 5px 0;', - 'margin: 0;', - '}', - '.less-error-message a {', - 'color: #10a', - '}', - '.less-error-message .error {', - 'color: red;', - 'font-weight: bold;', - 'padding-bottom: 2px;', - 'border-bottom: 1px dashed red;', - '}' - ].join('\n'), { title: 'error-message' }); - - elem.style.cssText = [ - "font-family: Arial, sans-serif", - "border: 1px solid #e00", - "background-color: #eee", - "border-radius: 5px", - "-webkit-border-radius: 5px", - "-moz-border-radius: 5px", - "color: #e00", - "padding: 15px", - "margin-bottom: 15px" - ].join(';'); - - if (less.env == 'development') { - timer = setInterval(function () { - if (document.body) { - if (document.getElementById(id)) { - document.body.replaceChild(elem, document.getElementById(id)); - } else { - document.body.insertBefore(elem, document.body.firstChild); - } - clearInterval(timer); - } - }, 10); - } -} -// amd.js -// -// Define Less as an AMD module. -if (typeof define === "function" && define.amd) { - define(function () { return less; } ); -} -})(window); \ No newline at end of file