├── .coffeelintignore ├── .github ├── no-response.yml └── workflows │ └── ci.yml ├── .gitignore ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE.md ├── LICENSE.md ├── PULL_REQUEST_TEMPLATE.md ├── README.md ├── coffeelint.json ├── lib ├── default-file-icons.js ├── get-icon-services.js ├── layout.js ├── main.js ├── mru-item-view.js ├── mru-list-view.js ├── tab-bar-view.coffee └── tab-view.coffee ├── menus └── tabs.cson ├── package-lock.json ├── package.json ├── spec ├── async-spec-helpers.js ├── event-helpers.coffee ├── event-helpers.js ├── fixtures │ ├── sample.js │ └── sample.png ├── icon-services-spec.js ├── mru-list-spec.js └── tabs-spec.coffee └── styles ├── layout.less ├── tabs-mru-switcher.less └── tabs.less /.coffeelintignore: -------------------------------------------------------------------------------- 1 | spec/fixtures 2 | -------------------------------------------------------------------------------- /.github/no-response.yml: -------------------------------------------------------------------------------- 1 | # Configuration for probot-no-response - https://github.com/probot/no-response 2 | 3 | # Number of days of inactivity before an issue is closed for lack of response 4 | daysUntilClose: 28 5 | 6 | # Label requiring a response 7 | responseRequiredLabel: more-information-needed 8 | 9 | # Comment to post when closing an issue for lack of response. Set to `false` to disable. 10 | closeComment: > 11 | This issue has been automatically closed because there has been no response 12 | to our request for more information from the original author. With only the 13 | information that is currently in the issue, we don't have enough information 14 | to take action. Please reach out if you have or find the answers we need so 15 | that we can investigate further. 16 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push] 4 | 5 | env: 6 | CI: true 7 | 8 | jobs: 9 | Test: 10 | strategy: 11 | matrix: 12 | os: [ubuntu-latest, macos-latest, windows-latest] 13 | channel: [stable, beta] 14 | runs-on: ${{ matrix.os }} 15 | steps: 16 | - uses: actions/checkout@v1 17 | - uses: UziTech/action-setup-atom@v2 18 | with: 19 | version: ${{ matrix.channel }} 20 | - name: Install dependencies 21 | run: apm install 22 | - name: Run tests 23 | run: atom --test spec 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | See the [Atom contributing guide](https://github.com/atom/atom/blob/master/CONTRIBUTING.md) 2 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 8 | 9 | ### Prerequisites 10 | 11 | * [ ] Put an X between the brackets on this line if you have done all of the following: 12 | * Reproduced the problem in Safe Mode: http://flight-manual.atom.io/hacking-atom/sections/debugging/#using-safe-mode 13 | * Followed all applicable steps in the debugging guide: http://flight-manual.atom.io/hacking-atom/sections/debugging/ 14 | * Checked the FAQs on the message board for common solutions: https://discuss.atom.io/c/faq 15 | * Checked that your issue isn't already filed: https://github.com/issues?utf8=✓&q=is%3Aissue+user%3Aatom 16 | * Checked that there is not already an Atom package that provides the described functionality: https://atom.io/packages 17 | 18 | ### Description 19 | 20 | [Description of the issue] 21 | 22 | ### Steps to Reproduce 23 | 24 | 1. [First Step] 25 | 2. [Second Step] 26 | 3. [and so on...] 27 | 28 | **Expected behavior:** [What you expect to happen] 29 | 30 | **Actual behavior:** [What actually happens] 31 | 32 | **Reproduces how often:** [What percentage of the time does it reproduce?] 33 | 34 | ### Versions 35 | 36 | You can get this information from copy and pasting the output of `atom --version` and `apm --version` from the command line. Also, please include the OS and what version of the OS you're running. 37 | 38 | ### Additional Information 39 | 40 | Any additional information, configuration or data that might be necessary to reproduce the issue. 41 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 GitHub Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Requirements 2 | 3 | * Filling out the template is required. Any pull request that does not include enough information to be reviewed in a timely manner may be closed at the maintainers' discretion. 4 | * All new code requires tests to ensure against regressions 5 | 6 | ### Description of the Change 7 | 8 | 13 | 14 | ### Alternate Designs 15 | 16 | 17 | 18 | ### Benefits 19 | 20 | 21 | 22 | ### Possible Drawbacks 23 | 24 | 25 | 26 | ### Applicable Issues 27 | 28 | 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ##### Atom and all repositories under Atom will be archived on December 15, 2022. Learn more in our [official announcement](https://github.blog/2022-06-08-sunsetting-atom/) 2 | # Tabs package 3 | [![OS X Build Status](https://travis-ci.org/atom/tabs.svg?branch=master)](https://travis-ci.org/atom/tabs) [![Windows Build Status](https://ci.appveyor.com/api/projects/status/nf4hdmuk4i9xkfmb/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/tabs/branch/master) [![Dependency Status](https://david-dm.org/atom/tabs.svg)](https://david-dm.org/atom/tabs) 4 | 5 | Display selectable tabs above the editor. 6 | 7 | ![](https://cloud.githubusercontent.com/assets/18362/10862852/c6de2de0-800d-11e5-8158-284f30aaf5d2.png) 8 | 9 | ## API 10 | 11 | Tabs can display icons next to file names. These icons are customizable by installing a package that provides an `atom.file-icons` service. 12 | 13 | The `atom.file-icons` service must provide the following methods: 14 | 15 | * `iconClassForPath(path)` - Returns a CSS class name to add to the tab view 16 | -------------------------------------------------------------------------------- /coffeelint.json: -------------------------------------------------------------------------------- 1 | { 2 | "max_line_length": { 3 | "level": "ignore" 4 | }, 5 | "no_empty_param_list": { 6 | "level": "error" 7 | }, 8 | "arrow_spacing": { 9 | "level": "error" 10 | }, 11 | "no_interpolation_in_single_quotes": { 12 | "level": "error" 13 | }, 14 | "no_debugger": { 15 | "level": "error" 16 | }, 17 | "prefer_english_operator": { 18 | "level": "error" 19 | }, 20 | "colon_assignment_spacing": { 21 | "spacing": { 22 | "left": 0, 23 | "right": 1 24 | }, 25 | "level": "error" 26 | }, 27 | "braces_spacing": { 28 | "spaces": 0, 29 | "level": "error" 30 | }, 31 | "spacing_after_comma": { 32 | "level": "error" 33 | }, 34 | "no_stand_alone_at": { 35 | "level": "error" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lib/default-file-icons.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs-plus') 2 | const path = require('path') 3 | 4 | class DefaultFileIcons { 5 | iconClassForPath (filePath, caller) { 6 | if (caller !== 'tabs-mru-switcher') return '' 7 | if (typeof filePath !== 'string') return 'icon-file-text' 8 | 9 | const extension = path.extname(filePath) 10 | 11 | if (fs.isSymbolicLinkSync(filePath)) { 12 | return 'icon-file-symlink-file' 13 | } else if (fs.isReadmePath(filePath)) { 14 | return 'icon-book' 15 | } else if (fs.isCompressedExtension(extension)) { 16 | return 'icon-file-zip' 17 | } else if (fs.isImageExtension(extension)) { 18 | return 'icon-file-media' 19 | } else if (fs.isPdfExtension(extension)) { 20 | return 'icon-file-pdf' 21 | } else if (fs.isBinaryExtension(extension)) { 22 | return 'icon-file-binary' 23 | } else { 24 | return 'icon-file-text' 25 | } 26 | } 27 | } 28 | 29 | module.exports = new DefaultFileIcons() 30 | -------------------------------------------------------------------------------- /lib/get-icon-services.js: -------------------------------------------------------------------------------- 1 | const DefaultFileIcons = require('./default-file-icons') 2 | const {Emitter, CompositeDisposable} = require('atom') 3 | 4 | let iconServices 5 | module.exports = function () { 6 | if (!iconServices) iconServices = new IconServices() 7 | return iconServices 8 | } 9 | 10 | class IconServices { 11 | constructor () { 12 | this.emitter = new Emitter() 13 | this.elementIcons = null 14 | this.elementIconDisposables = new CompositeDisposable() 15 | this.fileIcons = DefaultFileIcons 16 | } 17 | 18 | onDidChange (callback) { 19 | return this.emitter.on('did-change', callback) 20 | } 21 | 22 | resetElementIcons () { 23 | this.setElementIcons(null) 24 | } 25 | 26 | resetFileIcons () { 27 | this.setFileIcons(DefaultFileIcons) 28 | } 29 | 30 | setElementIcons (service) { 31 | if (service !== this.elementIcons) { 32 | if (this.elementIconDisposables != null) { 33 | this.elementIconDisposables.dispose() 34 | } 35 | if (service) { this.elementIconDisposables = new CompositeDisposable() } 36 | this.elementIcons = service 37 | return this.emitter.emit('did-change') 38 | } 39 | } 40 | 41 | setFileIcons (service) { 42 | if (service !== this.fileIcons) { 43 | this.fileIcons = service 44 | return this.emitter.emit('did-change') 45 | } 46 | } 47 | 48 | updateMRUIcon (view) { 49 | if (this.elementIcons) { 50 | view.firstLineDiv.classList.add('icon') 51 | this.elementIconDisposables.add(this.elementIcons(view.firstLineDiv, view.itemPath)) 52 | } else { 53 | let typeClasses = this.fileIcons.iconClassForPath(view.itemPath, 'tabs-mru-switcher') 54 | if (typeClasses) { 55 | if (!Array.isArray(typeClasses)) typeClasses = typeClasses.split(/\s+/g) 56 | if (typeClasses) view.firstLineDiv.classList.add(...typeClasses) 57 | } 58 | } 59 | } 60 | 61 | updateTabIcon (view) { 62 | if (view.iconElement && !view.iconElement.disposed) return 63 | if (view.iconName) { 64 | const names = !Array.isArray(view.iconName) 65 | ? view.iconName.split(/\s+/g) 66 | : view.iconName 67 | view.itemTitle.classList.remove('icon', `icon-${names[0]}`, ...names) 68 | } 69 | if (typeof view.item.getIconName === 'function') { 70 | view.iconName = view.item.getIconName() 71 | } else { 72 | view.iconName = null 73 | } if (view.iconName) { 74 | return view.itemTitle.classList.add('icon', `icon-${view.iconName}`) 75 | } else if (view.path != null) { 76 | if (this.elementIcons) { 77 | view.itemTitle.classList.add('icon') 78 | view.iconElement = this.elementIcons(view.itemTitle, view.path, {isTabIcon: true}) 79 | view.subscriptions.add(view.iconElement) 80 | } else { 81 | view.iconName = this.fileIcons.iconClassForPath(view.path, 'tabs') 82 | if (view.iconName) { 83 | let names = view.iconName 84 | if (!Array.isArray(names)) { 85 | names = names.toString().split(/\s+/g) 86 | } 87 | return view.itemTitle.classList.add('icon', ...names) 88 | } 89 | } 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /lib/layout.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | activate () { 3 | this.view = document.createElement('div') 4 | atom.workspace.getElement().appendChild(this.view) 5 | this.view.classList.add('tabs-layout-overlay') 6 | }, 7 | 8 | deactivate () { 9 | if (this.view.parentElement) { 10 | this.view.parentElement.removeChild(this.view) 11 | } 12 | }, 13 | 14 | test: {}, 15 | 16 | drag (e) { 17 | this.lastCoords = e 18 | const pane = this.getPaneAt(e) 19 | const itemView = this.getItemViewAt(e) 20 | const {item} = e.target 21 | if (pane && itemView && item && this.itemIsAllowedInPane(item, pane)) { 22 | let coords 23 | if (!this.isOnlyTabInPane(pane, e.target) && pane.getItems().length !== 0) { 24 | coords = [e.clientX, e.clientY] 25 | } 26 | this.lastSplit = this.updateView(itemView, coords) 27 | } else { 28 | this.disableView() 29 | } 30 | }, 31 | 32 | end (e) { 33 | this.disableView() 34 | if (this.lastCoords == null || !this.getItemViewAt(this.lastCoords)) { 35 | return 36 | } 37 | 38 | const target = this.getPaneAt(this.lastCoords) 39 | if (target == null) { 40 | return 41 | } 42 | 43 | const tab = e.target 44 | const fromPane = tab.pane 45 | const {item} = tab 46 | 47 | if (!this.itemIsAllowedInPane(item, target)) { 48 | return 49 | } 50 | 51 | let toPane 52 | switch (this.lastSplit) { 53 | case 'left': 54 | toPane = target.splitLeft() 55 | break 56 | case 'right': 57 | toPane = target.splitRight() 58 | break 59 | case 'up': 60 | toPane = target.splitUp() 61 | break 62 | case 'down': 63 | toPane = target.splitDown() 64 | break 65 | default: 66 | toPane = target 67 | } 68 | 69 | if (toPane === fromPane) { 70 | return 71 | } 72 | 73 | fromPane.moveItemToPane(item, toPane) 74 | toPane.activateItem(item) 75 | toPane.activate() 76 | }, 77 | 78 | getElement ({clientX, clientY}, selector) { 79 | if (selector == null) { 80 | selector = '*' 81 | } 82 | return document.elementFromPoint(clientX, clientY).closest(selector) 83 | }, 84 | 85 | getItemViewAt (coords) { 86 | return this.test.itemView || this.getElement(coords, '.item-views') 87 | }, 88 | 89 | getPaneAt () { 90 | if (this.test.pane) { 91 | return this.test.pane 92 | } else { 93 | const element = this.getElement(this.lastCoords, 'atom-pane') 94 | if (element) { 95 | return element.getModel() 96 | } 97 | } 98 | }, 99 | 100 | isOnlyTabInPane (pane, tab) { 101 | return pane.getItems().length === 1 && pane === tab.pane 102 | }, 103 | 104 | normalizeCoords ({left, top, width, height}, [x, y]) { 105 | return [(x - left) / width, (y - top) / height] 106 | }, 107 | 108 | splitType ([x, y]) { 109 | if (x < (1 / 3)) { 110 | return 'left' 111 | } else if (x > (2 / 3)) { 112 | return 'right' 113 | } else if (y < (1 / 3)) { 114 | return 'up' 115 | } else if (y > (2 / 3)) { 116 | return 'down' 117 | } 118 | }, 119 | 120 | boundsForSplit (split) { 121 | switch (split) { 122 | case 'left': return [0, 0, 0.5, 1] 123 | case 'right': return [0.5, 0, 0.5, 1] 124 | case 'up': return [0, 0, 1, 0.5] 125 | case 'down': return [0, 0.5, 1, 0.5] 126 | default: return [0, 0, 1, 1] 127 | } 128 | }, 129 | 130 | innerBounds ({left, top, width, height}, [x, y, w, h]) { 131 | left += x * width 132 | top += y * height 133 | width *= w 134 | height *= h 135 | return {left, top, width, height} 136 | }, 137 | 138 | updateViewBounds ({left, top, width, height}) { 139 | this.view.style.left = `${left}px` 140 | this.view.style.top = `${top}px` 141 | this.view.style.width = `${width}px` 142 | this.view.style.height = `${height}px` 143 | }, 144 | 145 | updateView (pane, coords) { 146 | this.view.classList.add('visible') 147 | const rect = this.test.rect || pane.getBoundingClientRect() 148 | const split = coords ? this.splitType(this.normalizeCoords(rect, coords)) : undefined 149 | this.updateViewBounds(this.innerBounds(rect, this.boundsForSplit(split))) 150 | return split 151 | }, 152 | 153 | disableView () { 154 | this.view.classList.remove('visible') 155 | }, 156 | 157 | itemIsAllowedInPane (item, pane) { 158 | if (typeof item.getAllowedLocations === 'function') { 159 | const allowedLocations = item.getAllowedLocations() 160 | if (allowedLocations == null) { 161 | return true 162 | } 163 | 164 | const container = pane.getContainer() 165 | let location = 'center' 166 | if (typeof container.getLocation === 'function') { 167 | location = container.getLocation() || 'center' 168 | } 169 | 170 | return allowedLocations.includes(location) 171 | } 172 | return true 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /lib/main.js: -------------------------------------------------------------------------------- 1 | const {CompositeDisposable, Disposable} = require('atom') 2 | const getIconServices = require('./get-icon-services') 3 | const layout = require('./layout') 4 | const TabBarView = require('./tab-bar-view') 5 | const MRUListView = require('./mru-list-view') 6 | const _ = require('underscore-plus') 7 | 8 | module.exports = { 9 | activate () { 10 | this.subscriptions = new CompositeDisposable() 11 | layout.activate() 12 | this.tabBarViews = [] 13 | this.mruListViews = [] 14 | 15 | const keyBindSource = 'tabs package' 16 | const enableMruConfigKey = 'tabs.enableMruTabSwitching' 17 | 18 | this.updateTraversalKeybinds = () => { 19 | // We don't modify keybindings based on our setting if the user has already tweaked them. 20 | let bindings = atom.keymaps.findKeyBindings({ 21 | target: document.body, 22 | keystrokes: 'ctrl-tab' 23 | }) 24 | 25 | if (bindings.length > 1 && bindings[0].source !== keyBindSource) { 26 | return 27 | } 28 | 29 | bindings = atom.keymaps.findKeyBindings({ 30 | target: document.body, 31 | keystrokes: 'ctrl-shift-tab' 32 | }) 33 | 34 | if (bindings.length > 1 && bindings[0].source !== keyBindSource) { 35 | return 36 | } 37 | 38 | if (atom.config.get(enableMruConfigKey)) { 39 | atom.keymaps.removeBindingsFromSource(keyBindSource) 40 | } else { 41 | const disabledBindings = { 42 | 'body': { 43 | 'ctrl-tab': 'pane:show-next-item', 44 | 'ctrl-tab ^ctrl': 'unset!', 45 | 'ctrl-shift-tab': 'pane:show-previous-item', 46 | 'ctrl-shift-tab ^ctrl': 'unset!' 47 | } 48 | } 49 | atom.keymaps.add(keyBindSource, disabledBindings, 0) 50 | } 51 | } 52 | 53 | this.subscriptions.add(atom.config.observe(enableMruConfigKey, () => this.updateTraversalKeybinds())) 54 | this.subscriptions.add(atom.keymaps.onDidLoadUserKeymap(() => this.updateTraversalKeybinds())) 55 | 56 | // If the command bubbles up without being handled by a particular pane, 57 | // close all tabs in all panes 58 | this.subscriptions.add(atom.commands.add('atom-workspace', { 59 | 'tabs:close-all-tabs': () => { 60 | // We loop backwards because the panes are 61 | // removed from the array as we go 62 | for (let i = this.tabBarViews.length - 1; i >= 0; i--) { 63 | this.tabBarViews[i].closeAllTabs() 64 | } 65 | } 66 | })) 67 | 68 | const paneContainers = { 69 | center: atom.workspace.getCenter(), 70 | left: atom.workspace.getLeftDock(), 71 | right: atom.workspace.getRightDock(), 72 | bottom: atom.workspace.getBottomDock() 73 | } 74 | 75 | Object.keys(paneContainers).forEach(location => { 76 | const container = paneContainers[location] 77 | if (!container) { 78 | return 79 | } 80 | 81 | this.subscriptions.add(container.observePanes(pane => { 82 | const tabBarView = new TabBarView(pane, location) 83 | const mruListView = new MRUListView() 84 | mruListView.initialize(pane) 85 | 86 | const paneElement = pane.getElement() 87 | paneElement.insertBefore(tabBarView.element, paneElement.firstChild) 88 | 89 | this.tabBarViews.push(tabBarView) 90 | pane.onDidDestroy(() => _.remove(this.tabBarViews, tabBarView)) 91 | this.mruListViews.push(mruListView) 92 | pane.onDidDestroy(() => _.remove(this.mruListViews, mruListView)) 93 | })) 94 | }) 95 | }, 96 | 97 | deactivate () { 98 | layout.deactivate() 99 | this.subscriptions.dispose() 100 | if (this.fileIconsDisposable != null) { 101 | this.fileIconsDisposable.dispose() 102 | } 103 | 104 | for (let tabBarView of this.tabBarViews) { 105 | tabBarView.destroy() 106 | } 107 | for (let mruListView of this.mruListViews) { 108 | mruListView.destroy() 109 | } 110 | }, 111 | 112 | consumeElementIcons (service) { 113 | getIconServices().setElementIcons(service) 114 | this.updateFileIcons() 115 | return new Disposable(() => { 116 | getIconServices().resetElementIcons() 117 | this.updateFileIcons() 118 | }) 119 | }, 120 | 121 | consumeFileIcons (service) { 122 | getIconServices().setFileIcons(service) 123 | this.updateFileIcons() 124 | return new Disposable(() => { 125 | getIconServices().resetFileIcons() 126 | this.updateFileIcons() 127 | }) 128 | }, 129 | 130 | updateFileIcons () { 131 | for (let tabBarView of this.tabBarViews) { 132 | for (let tabView of tabBarView.getTabs()) { 133 | tabView.updateIcon() 134 | } 135 | } 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /lib/mru-item-view.js: -------------------------------------------------------------------------------- 1 | 'use babel' 2 | 3 | import getIconServices from './get-icon-services' 4 | import path from 'path' 5 | 6 | export default class MRUItemView { 7 | initialize (listView, item) { 8 | this.listView = listView 9 | this.item = item 10 | 11 | this.element = document.createElement('li') 12 | this.element.itemViewData = this 13 | this.element.classList.add('two-lines') 14 | 15 | this.itemPath = null 16 | if (item.getPath && typeof item.getPath === 'function') { 17 | this.itemPath = item.getPath() 18 | } 19 | 20 | const repo = MRUItemView.repositoryForPath(this.itemPath) 21 | if (repo != null) { 22 | const statusIconDiv = document.createElement('div') 23 | const status = repo.getCachedPathStatus(this.itemPath) 24 | if (repo.isStatusNew(status)) { 25 | statusIconDiv.className = 'status status-added icon icon-diff-added' 26 | this.element.appendChild(statusIconDiv) 27 | } else if (repo.isStatusModified(status)) { 28 | statusIconDiv.className = 'status status-modified icon icon-diff-modified' 29 | this.element.appendChild(statusIconDiv) 30 | } 31 | } 32 | 33 | this.firstLineDiv = this.element.appendChild(document.createElement('div')) 34 | this.firstLineDiv.classList.add('primary-line', 'file') 35 | if (typeof item.getIconName === 'function') { 36 | if (atom.config.get('tabs.showIcons')) this.firstLineDiv.classList.add('icon', 'icon-' + item.getIconName()) 37 | } else { 38 | getIconServices().updateMRUIcon(this) 39 | } 40 | this.firstLineDiv.setAttribute('data-name', item.getTitle()) 41 | this.firstLineDiv.innerText = item.getTitle() 42 | 43 | if (this.itemPath) { 44 | this.firstLineDiv.setAttribute('data-path', this.itemPath) 45 | const secondLineDiv = this.element.appendChild(document.createElement('div')) 46 | secondLineDiv.classList.add('secondary-line', 'path', 'no-icon') 47 | secondLineDiv.innerText = this.itemPath 48 | } 49 | } 50 | 51 | select () { 52 | this.element.classList.add('selected') 53 | } 54 | 55 | unselect () { 56 | this.element.classList.remove('selected') 57 | } 58 | 59 | static repositoryForPath (filePath) { 60 | if (filePath) { 61 | const projectPaths = atom.project.getPaths() 62 | for (let i = 0; i < projectPaths.length; i++) { 63 | if (filePath === projectPaths[i] || filePath.startsWith(projectPaths[i] + path.sep)) { 64 | return atom.project.getRepositories()[i] 65 | } 66 | } 67 | } 68 | return null 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /lib/mru-list-view.js: -------------------------------------------------------------------------------- 1 | 'use babel' 2 | 3 | import MRUItemView from './mru-item-view' 4 | import {CompositeDisposable} from 'atom' 5 | 6 | const displayListConfigKey = 'tabs.displayMruTabList' 7 | 8 | export default class MRUListView { 9 | initialize (pane) { 10 | this.ownerDiv = document.createElement('div') 11 | this.element = document.createElement('ol') 12 | this.ownerDiv.appendChild(this.element) 13 | this.ownerDiv.classList.add('select-list', 'tabs-mru-switcher') 14 | 15 | this.pane = pane 16 | this.pendingShow = false 17 | this.subscribe() 18 | this.panel = atom.workspace.addModalPanel({ 19 | item: this.ownerDiv, 20 | visible: false 21 | }) 22 | this.element.classList.add('list-group') 23 | 24 | this.displayMruList = atom.config.get(displayListConfigKey) 25 | this.hideClickHandler = this.hide.bind(this) 26 | this.preventPropagationClickHandler = this.preventPropagation.bind(this) 27 | } 28 | 29 | subscribe () { 30 | this.subscriptions = new CompositeDisposable() 31 | 32 | this.subscriptions.add(atom.config.observe( 33 | displayListConfigKey, 34 | (newValue) => (this.displayMruList = newValue))) 35 | 36 | /* Check for existence of events. Allows package tests to pass until this 37 | change hits stable. */ 38 | if (typeof this.pane.onChooseNextMRUItem === 'function') { 39 | /* Because the chosen item is passed in the callback, both the 40 | ChooseNext and ChooseLast events can call our our single choose 41 | method. */ 42 | this.subscriptions.add( 43 | this.pane.onChooseNextMRUItem((item) => this.choose(item))) 44 | this.subscriptions.add( 45 | this.pane.onChooseLastMRUItem((item) => this.choose(item))) 46 | 47 | this.subscriptions.add( 48 | this.pane.onDoneChoosingMRUItem(() => this.stopChoosing())) 49 | } 50 | 51 | this.subscriptions.add( 52 | this.pane.onDidDestroy(() => this.destroy())) 53 | 54 | this.subscriptions.add( 55 | atom.commands.add('atom-workspace', { 56 | 'core:cancel': (event) => { 57 | if (this.hide()) event.stopPropagation() 58 | } 59 | }) 60 | ) 61 | } 62 | 63 | destroy () { 64 | this.subscriptions.dispose() 65 | this.panel.destroy() 66 | } 67 | 68 | choose (selectedItem) { 69 | if (this.displayMruList) { 70 | this.pendingShow = true 71 | this.show(selectedItem) 72 | } 73 | } 74 | 75 | stopChoosing () { 76 | if (this.displayMruList) { 77 | this.pendingShow = false 78 | this.hide() 79 | } 80 | } 81 | 82 | show (selectedItem) { 83 | let selectedViewElement 84 | if (!this.panel.visible) { 85 | window.setTimeout(() => { 86 | if (!this.pendingShow) return 87 | selectedViewElement = this.buildListView(selectedItem) 88 | this.panel.show() 89 | this.addClickHandlers() 90 | this.scrollToItemView(selectedViewElement) 91 | }, 150) 92 | } else { 93 | selectedViewElement = this.updateSelectedItem(selectedItem) 94 | this.scrollToItemView(selectedViewElement) 95 | } 96 | } 97 | 98 | preventPropagation () { 99 | event.stopPropagation() 100 | } 101 | 102 | addClickHandlers () { 103 | document.body.addEventListener('click', this.hideClickHandler) 104 | this.ownerDiv.addEventListener('click', this.preventPropagationClickHandler) 105 | } 106 | 107 | removeClickHandler () { 108 | document.body.removeEventListener('click', this.hideClickHandler) 109 | this.ownerDiv.removeEventListener('click', this.preventPropagationClickHandler) 110 | } 111 | 112 | hide () { 113 | const willClose = this.panel.visible 114 | if (willClose) { 115 | this.removeClickHandler() 116 | this.panel.hide() 117 | } 118 | return willClose 119 | } 120 | 121 | updateSelectedItem (selectedItem) { 122 | let selectedView 123 | for (let viewElement of this.element.children) { 124 | if (viewElement.itemViewData.item === selectedItem) { 125 | viewElement.itemViewData.select() 126 | selectedView = viewElement 127 | } else viewElement.itemViewData.unselect() 128 | } 129 | return selectedView 130 | } 131 | 132 | scrollToItemView (view) { 133 | const desiredTop = view.offsetTop 134 | const desiredBottom = desiredTop + view.offsetHeight 135 | 136 | if (desiredTop < this.element.scrollTop) { 137 | this.element.scrollTop = desiredTop 138 | } else if (desiredBottom > this.element.scrollTop + this.element.clientHeight) { 139 | this.element.scrollTop = desiredBottom - this.element.clientHeight 140 | } 141 | } 142 | 143 | buildListView (selectedItem) { 144 | /* Making this more efficient, and not simply building the view for the 145 | entire stack every time it's shown, has significant complexity cost. 146 | The pane system completely owns the MRU stack. Adding events and 147 | handlers to incrementally update the UI here would mean two copies of 148 | the stack to maintain and keep in sync. Let's take on that complexity 149 | only if this exhibits real-world performance issues. */ 150 | while (this.element.firstChild) this.element.removeChild(this.element.firstChild) 151 | 152 | let selectedViewElement 153 | /* We're inserting each item at the top so we traverse the stack from 154 | the bottom, resulting in the most recently used item at the top of the 155 | UI. */ 156 | for (let i = this.pane.itemStack.length - 1; i >= 0; i--) { 157 | let item = this.pane.itemStack[i] 158 | let itemView = new MRUItemView() 159 | itemView.initialize(this, item) 160 | if (itemView.disposables) { 161 | this.subscriptions.add(itemView.disposables) 162 | } 163 | this.element.appendChild(itemView.element) 164 | if (item === selectedItem) { 165 | itemView.select() 166 | selectedViewElement = itemView 167 | } 168 | } 169 | return selectedViewElement.element 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /lib/tab-bar-view.coffee: -------------------------------------------------------------------------------- 1 | BrowserWindow = null # Defer require until actually used 2 | {ipcRenderer} = require 'electron' 3 | 4 | {CompositeDisposable} = require 'atom' 5 | TabView = require './tab-view' 6 | 7 | module.exports = 8 | class TabBarView 9 | constructor: (@pane, @location) -> 10 | @element = document.createElement('ul') 11 | @element.classList.add("list-inline") 12 | @element.classList.add("tab-bar") 13 | @element.classList.add("inset-panel") 14 | @element.setAttribute('is', 'atom-tabs') 15 | @element.setAttribute("tabindex", -1) 16 | @element.setAttribute("location", @location) 17 | 18 | @tabs = [] 19 | @tabsByElement = new WeakMap 20 | @subscriptions = new CompositeDisposable 21 | 22 | @paneElement = @pane.getElement() 23 | 24 | @subscriptions.add atom.commands.add @paneElement, 25 | 'tabs:keep-pending-tab': => @terminatePendingStates() 26 | 'tabs:close-tab': => @closeTab(@getActiveTab()) 27 | 'tabs:close-other-tabs': => @closeOtherTabs(@getActiveTab()) 28 | 'tabs:close-tabs-to-right': => @closeTabsToRight(@getActiveTab()) 29 | 'tabs:close-tabs-to-left': => @closeTabsToLeft(@getActiveTab()) 30 | 'tabs:close-saved-tabs': => @closeSavedTabs() 31 | 'tabs:close-all-tabs': (event) => 32 | event.stopPropagation() 33 | @closeAllTabs() 34 | 'tabs:open-in-new-window': => @openInNewWindow() 35 | 36 | addElementCommands = (commands) => 37 | commandsWithPropagationStopped = {} 38 | Object.keys(commands).forEach (name) -> 39 | commandsWithPropagationStopped[name] = (event) -> 40 | event.stopPropagation() 41 | commands[name]() 42 | 43 | @subscriptions.add(atom.commands.add(@element, commandsWithPropagationStopped)) 44 | 45 | addElementCommands 46 | 'tabs:close-tab': => @closeTab() 47 | 'tabs:close-other-tabs': => @closeOtherTabs() 48 | 'tabs:close-tabs-to-right': => @closeTabsToRight() 49 | 'tabs:close-tabs-to-left': => @closeTabsToLeft() 50 | 'tabs:close-saved-tabs': => @closeSavedTabs() 51 | 'tabs:close-all-tabs': => @closeAllTabs() 52 | 'tabs:split-up': => @splitTab('splitUp') 53 | 'tabs:split-down': => @splitTab('splitDown') 54 | 'tabs:split-left': => @splitTab('splitLeft') 55 | 'tabs:split-right': => @splitTab('splitRight') 56 | 57 | @element.addEventListener "mouseenter", @onMouseEnter.bind(this) 58 | @element.addEventListener "mouseleave", @onMouseLeave.bind(this) 59 | @element.addEventListener "mousewheel", @onMouseWheel.bind(this) 60 | @element.addEventListener "dragstart", @onDragStart.bind(this) 61 | @element.addEventListener "dragend", @onDragEnd.bind(this) 62 | @element.addEventListener "dragleave", @onDragLeave.bind(this) 63 | @element.addEventListener "dragover", @onDragOver.bind(this) 64 | @element.addEventListener "drop", @onDrop.bind(this) 65 | 66 | # Toggle the tab bar when a tab is dragged over the pane with alwaysShowTabBar = false 67 | @paneElement.addEventListener 'dragenter', @onPaneDragEnter.bind(this) 68 | @paneElement.addEventListener 'dragleave', @onPaneDragLeave.bind(this) 69 | 70 | @paneContainer = @pane.getContainer() 71 | @addTabForItem(item) for item in @pane.getItems() 72 | 73 | @subscriptions.add @pane.onDidDestroy => 74 | @destroy() 75 | 76 | @subscriptions.add @pane.onDidAddItem ({item, index}) => 77 | @addTabForItem(item, index) 78 | 79 | @subscriptions.add @pane.onDidMoveItem ({item, newIndex}) => 80 | @moveItemTabToIndex(item, newIndex) 81 | 82 | @subscriptions.add @pane.onDidRemoveItem ({item}) => 83 | @removeTabForItem(item) 84 | 85 | @subscriptions.add @pane.onDidChangeActiveItem (item) => 86 | @updateActiveTab() 87 | 88 | @subscriptions.add atom.config.observe 'tabs.tabScrolling', (value) => @updateTabScrolling(value) 89 | @subscriptions.add atom.config.observe 'tabs.tabScrollingThreshold', (value) => @updateTabScrollingThreshold(value) 90 | @subscriptions.add atom.config.observe 'tabs.alwaysShowTabBar', => @updateTabBarVisibility() 91 | 92 | @updateActiveTab() 93 | 94 | @element.addEventListener "mousedown", @onMouseDown.bind(this) 95 | @element.addEventListener "click", @onClick.bind(this) 96 | @element.addEventListener "auxclick", @onClick.bind(this) 97 | @element.addEventListener "dblclick", @onDoubleClick.bind(this) 98 | 99 | @onDropOnOtherWindow = @onDropOnOtherWindow.bind(this) 100 | ipcRenderer.on('tab:dropped', @onDropOnOtherWindow) 101 | 102 | destroy: -> 103 | ipcRenderer.removeListener('tab:dropped', @onDropOnOtherWindow) 104 | @subscriptions.dispose() 105 | @element.remove() 106 | 107 | terminatePendingStates: -> 108 | tab.terminatePendingState?() for tab in @getTabs() 109 | return 110 | 111 | addTabForItem: (item, index) -> 112 | tabView = new TabView({ 113 | item, 114 | @pane, 115 | @tabs, 116 | didClickCloseIcon: => 117 | @closeTab(tabView) 118 | return 119 | @location 120 | }) 121 | tabView.terminatePendingState() if @isItemMovingBetweenPanes 122 | @tabsByElement.set(tabView.element, tabView) 123 | @insertTabAtIndex(tabView, index) 124 | if atom.config.get('tabs.addNewTabsAtEnd') 125 | @pane.moveItem(item, @pane.getItems().length - 1) unless @isItemMovingBetweenPanes 126 | 127 | moveItemTabToIndex: (item, index) -> 128 | tabIndex = @tabs.findIndex((t) -> t.item is item) 129 | if tabIndex isnt -1 130 | tab = @tabs[tabIndex] 131 | tab.element.remove() 132 | @tabs.splice(tabIndex, 1) 133 | @insertTabAtIndex(tab, index) 134 | 135 | insertTabAtIndex: (tab, index) -> 136 | followingTab = @tabs[index] if index? 137 | if followingTab 138 | @element.insertBefore(tab.element, followingTab.element) 139 | @tabs.splice(index, 0, tab) 140 | else 141 | @element.appendChild(tab.element) 142 | @tabs.push(tab) 143 | 144 | tab.updateTitle() 145 | @updateTabBarVisibility() 146 | 147 | removeTabForItem: (item) -> 148 | tabIndex = @tabs.findIndex((t) -> t.item is item) 149 | if tabIndex isnt -1 150 | tab = @tabs[tabIndex] 151 | @tabs.splice(tabIndex, 1) 152 | @tabsByElement.delete(tab) 153 | tab.destroy() 154 | tab.updateTitle() for tab in @getTabs() 155 | @updateTabBarVisibility() 156 | 157 | updateTabBarVisibility: -> 158 | # Show tab bar if the setting is true or there is more than one tab 159 | if atom.config.get('tabs.alwaysShowTabBar') or @pane.getItems().length > 1 160 | @element.classList.remove('hidden') 161 | else 162 | @element.classList.add('hidden') 163 | 164 | getTabs: -> 165 | @tabs.slice() 166 | 167 | tabAtIndex: (index) -> 168 | @tabs[index] 169 | 170 | tabForItem: (item) -> 171 | @tabs.find((t) -> t.item is item) 172 | 173 | setActiveTab: (tabView) -> 174 | if tabView? and tabView isnt @activeTab 175 | @activeTab?.element.classList.remove('active') 176 | @activeTab = tabView 177 | @activeTab.element.classList.add('active') 178 | @activeTab.element.scrollIntoView(false) 179 | 180 | getActiveTab: -> 181 | @tabForItem(@pane.getActiveItem()) 182 | 183 | updateActiveTab: -> 184 | @setActiveTab(@tabForItem(@pane.getActiveItem())) 185 | 186 | closeTab: (tab) -> 187 | tab ?= @rightClickedTab 188 | @pane.destroyItem(tab.item) if tab? 189 | 190 | openInNewWindow: (tab) -> 191 | tab ?= @rightClickedTab 192 | item = tab?.item 193 | return unless item? 194 | if typeof item.getURI is 'function' 195 | itemURI = item.getURI() 196 | else if typeof item.getPath is 'function' 197 | itemURI = item.getPath() 198 | else if typeof item.getUri is 'function' 199 | itemURI = item.getUri() 200 | return unless itemURI? 201 | @closeTab(tab) 202 | tab.element.style.maxWidth = '' for tab in @getTabs() 203 | pathsToOpen = [atom.project.getPaths(), itemURI].reduce ((a, b) -> a.concat(b)), [] 204 | atom.open({pathsToOpen: pathsToOpen, newWindow: true, devMode: atom.devMode, safeMode: atom.safeMode}) 205 | 206 | splitTab: (fn) -> 207 | if item = @rightClickedTab?.item 208 | if copiedItem = item.copy?() 209 | @pane[fn](items: [copiedItem]) 210 | 211 | closeOtherTabs: (active) -> 212 | tabs = @getTabs() 213 | active ?= @rightClickedTab 214 | return unless active? 215 | @closeTab tab for tab in tabs when tab isnt active 216 | 217 | closeTabsToRight: (active) -> 218 | tabs = @getTabs() 219 | active ?= @rightClickedTab 220 | index = tabs.indexOf(active) 221 | return if index is -1 222 | @closeTab tab for tab, i in tabs when i > index 223 | 224 | closeTabsToLeft: (active) -> 225 | tabs = @getTabs() 226 | active ?= @rightClickedTab 227 | index = tabs.indexOf(active) 228 | return if index is -1 229 | @closeTab tab for tab, i in tabs when i < index 230 | 231 | closeSavedTabs: -> 232 | for tab in @getTabs() 233 | @closeTab(tab) unless tab.item.isModified?() 234 | 235 | closeAllTabs: -> 236 | @closeTab(tab) for tab in @getTabs() 237 | 238 | getWindowId: -> 239 | @windowId ?= atom.getCurrentWindow().id 240 | 241 | onDragStart: (event) -> 242 | @draggedTab = @tabForElement(event.target) 243 | return unless @draggedTab 244 | @lastDropTargetIndex = null 245 | 246 | event.dataTransfer.setData 'atom-tab-event', 'true' 247 | 248 | @draggedTab.element.classList.add('is-dragging') 249 | @draggedTab.destroyTooltip() 250 | 251 | tabIndex = @tabs.indexOf(@draggedTab) 252 | event.dataTransfer.setData 'sortable-index', tabIndex 253 | 254 | paneIndex = @paneContainer.getPanes().indexOf(@pane) 255 | event.dataTransfer.setData 'from-pane-index', paneIndex 256 | event.dataTransfer.setData 'from-pane-id', @pane.id 257 | event.dataTransfer.setData 'from-window-id', @getWindowId() 258 | 259 | item = @pane.getItems()[@tabs.indexOf(@draggedTab)] 260 | return unless item? 261 | 262 | if typeof item.getURI is 'function' 263 | itemURI = item.getURI() ? '' 264 | else if typeof item.getPath is 'function' 265 | itemURI = item.getPath() ? '' 266 | else if typeof item.getUri is 'function' 267 | itemURI = item.getUri() ? '' 268 | 269 | if typeof item.getAllowedLocations is 'function' 270 | for location in item.getAllowedLocations() 271 | event.dataTransfer.setData("allowed-location-#{location}", 'true') 272 | else 273 | event.dataTransfer.setData 'allow-all-locations', 'true' 274 | 275 | if itemURI? 276 | event.dataTransfer.setData 'text/plain', itemURI 277 | 278 | if process.platform is 'darwin' # see #69 279 | itemURI = "file://#{itemURI}" unless @uriHasProtocol(itemURI) 280 | event.dataTransfer.setData 'text/uri-list', itemURI 281 | 282 | if item.isModified?() and item.getText? 283 | event.dataTransfer.setData 'has-unsaved-changes', 'true' 284 | event.dataTransfer.setData 'modified-text', item.getText() 285 | 286 | uriHasProtocol: (uri) -> 287 | try 288 | require('url').parse(uri).protocol? 289 | catch error 290 | false 291 | 292 | onDragLeave: (event) -> 293 | # Do not do anything unless the drag goes outside the tab bar 294 | unless event.currentTarget.contains(event.relatedTarget) 295 | @removePlaceholder() 296 | @lastDropTargetIndex = null 297 | tab.element.style.maxWidth = '' for tab in @getTabs() 298 | 299 | onDragEnd: (event) -> 300 | return unless @tabForElement(event.target) 301 | 302 | @clearDropTarget() 303 | 304 | onDragOver: (event) -> 305 | return unless @isAtomTabEvent(event) 306 | return unless @itemIsAllowed(event, @location) 307 | 308 | event.preventDefault() 309 | event.stopPropagation() 310 | 311 | newDropTargetIndex = @getDropTargetIndex(event) 312 | return unless newDropTargetIndex? 313 | return if @lastDropTargetIndex is newDropTargetIndex 314 | @lastDropTargetIndex = newDropTargetIndex 315 | 316 | @removeDropTargetClasses() 317 | 318 | tabs = @getTabs() 319 | placeholder = @getPlaceholder() 320 | return unless placeholder? 321 | 322 | if newDropTargetIndex < tabs.length 323 | tab = tabs[newDropTargetIndex] 324 | tab.element.classList.add 'is-drop-target' 325 | tab.element.parentElement.insertBefore(placeholder, tab.element) 326 | else 327 | if tab = tabs[newDropTargetIndex - 1] 328 | tab.element.classList.add 'drop-target-is-after' 329 | if sibling = tab.element.nextSibling 330 | tab.element.parentElement.insertBefore(placeholder, sibling) 331 | else 332 | tab.element.parentElement.appendChild(placeholder) 333 | 334 | onDropOnOtherWindow: (event, fromPaneId, fromItemIndex) -> 335 | if @pane.id is fromPaneId 336 | if itemToRemove = @pane.getItems()[fromItemIndex] 337 | @pane.destroyItem(itemToRemove) 338 | 339 | @clearDropTarget() 340 | 341 | clearDropTarget: -> 342 | @draggedTab?.element.classList.remove('is-dragging') 343 | @draggedTab?.updateTooltip() 344 | @draggedTab = null 345 | @removeDropTargetClasses() 346 | @removePlaceholder() 347 | 348 | onDrop: (event) -> 349 | return unless @isAtomTabEvent(event) 350 | 351 | event.preventDefault() 352 | 353 | fromWindowId = parseInt(event.dataTransfer.getData('from-window-id')) 354 | fromPaneId = parseInt(event.dataTransfer.getData('from-pane-id')) 355 | fromIndex = parseInt(event.dataTransfer.getData('sortable-index')) 356 | fromPaneIndex = parseInt(event.dataTransfer.getData('from-pane-index')) 357 | 358 | hasUnsavedChanges = event.dataTransfer.getData('has-unsaved-changes') is 'true' 359 | modifiedText = event.dataTransfer.getData('modified-text') 360 | 361 | toIndex = @getDropTargetIndex(event) 362 | toPane = @pane 363 | 364 | @clearDropTarget() 365 | 366 | return unless @itemIsAllowed(event, @location) 367 | 368 | if fromWindowId is @getWindowId() 369 | fromPane = @paneContainer.getPanes()[fromPaneIndex] 370 | if fromPane?.id isnt fromPaneId 371 | # If dragging from a different pane container, we have to be more 372 | # exhaustive in our search. 373 | fromPane = Array.from document.querySelectorAll('atom-pane') 374 | .map (paneEl) -> paneEl.model 375 | .find (pane) -> pane.id is fromPaneId 376 | item = fromPane.getItems()[fromIndex] 377 | @moveItemBetweenPanes(fromPane, fromIndex, toPane, toIndex, item) if item? 378 | else 379 | droppedURI = event.dataTransfer.getData('text/plain') 380 | atom.workspace.open(droppedURI).then (item) => 381 | # Move the item from the pane it was opened on to the target pane 382 | # where it was dropped onto 383 | activePane = atom.workspace.getActivePane() 384 | activeItemIndex = activePane.getItems().indexOf(item) 385 | @moveItemBetweenPanes(activePane, activeItemIndex, toPane, toIndex, item) 386 | item.setText?(modifiedText) if hasUnsavedChanges 387 | 388 | if not isNaN(fromWindowId) 389 | # Let the window where the drag started know that the tab was dropped 390 | browserWindow = @browserWindowForId(fromWindowId) 391 | browserWindow?.webContents.send('tab:dropped', fromPaneId, fromIndex) 392 | 393 | atom.focus() 394 | 395 | # Show the tab bar when a tab is being dragged in this pane when alwaysShowTabBar = false 396 | onPaneDragEnter: (event) -> 397 | return unless @isAtomTabEvent(event) 398 | return unless @itemIsAllowed(event, @location) 399 | return if @pane.getItems().length > 1 or atom.config.get('tabs.alwaysShowTabBar') 400 | if @paneElement.contains(event.relatedTarget) 401 | @element.classList.remove('hidden') 402 | 403 | # Hide the tab bar when the dragged tab leaves this pane when alwaysShowTabBar = false 404 | onPaneDragLeave: (event) -> 405 | return unless @isAtomTabEvent(event) 406 | return unless @itemIsAllowed(event, @location) 407 | return if @pane.getItems().length > 1 or atom.config.get('tabs.alwaysShowTabBar') 408 | unless @paneElement.contains(event.relatedTarget) 409 | @element.classList.add('hidden') 410 | 411 | onMouseWheel: (event) -> 412 | return if event.shiftKey or not @tabScrolling 413 | 414 | @wheelDelta ?= 0 415 | @wheelDelta += event.wheelDeltaY 416 | 417 | if @wheelDelta <= -@tabScrollingThreshold 418 | @wheelDelta = 0 419 | @pane.activateNextItem() 420 | else if @wheelDelta >= @tabScrollingThreshold 421 | @wheelDelta = 0 422 | @pane.activatePreviousItem() 423 | 424 | onMouseDown: (event) -> 425 | @pane.activate() unless @pane.isDestroyed() 426 | 427 | tab = @tabForElement(event.target) 428 | return unless tab 429 | 430 | if event.button is 2 or (event.button is 0 and event.ctrlKey is true) 431 | @rightClickedTab = tab 432 | event.preventDefault() 433 | else if event.button is 1 434 | # This prevents Chromium from activating "scroll mode" when 435 | # middle-clicking while some tabs are off-screen. 436 | event.preventDefault() 437 | 438 | onClick: (event) -> 439 | tab = @tabForElement(event.target) 440 | return unless tab 441 | 442 | event.preventDefault() 443 | if event.button is 2 or (event.button is 0 and event.ctrlKey is true) 444 | # Bail out early when receiving this event, because we have already 445 | # handled it in the mousedown handler. 446 | return 447 | else if event.button is 0 and not event.target.classList.contains('close-icon') 448 | @pane.activateItem(tab.item) 449 | else if event.button is 1 450 | @pane.destroyItem(tab.item) 451 | 452 | onDoubleClick: (event) -> 453 | if tab = @tabForElement(event.target) 454 | tab.item.terminatePendingState?() 455 | else if event.target is @element 456 | atom.commands.dispatch(@element, 'application:new-file') 457 | event.preventDefault() 458 | 459 | updateTabScrollingThreshold: (value) -> 460 | @tabScrollingThreshold = value 461 | 462 | updateTabScrolling: (value) -> 463 | if value is 'platform' 464 | @tabScrolling = (process.platform is 'linux') 465 | else 466 | @tabScrolling = value 467 | 468 | browserWindowForId: (id) -> 469 | BrowserWindow ?= require('electron').remote.BrowserWindow 470 | 471 | BrowserWindow.fromId id 472 | 473 | moveItemBetweenPanes: (fromPane, fromIndex, toPane, toIndex, item) -> 474 | try 475 | if toPane is fromPane 476 | toIndex-- if fromIndex < toIndex 477 | toPane.moveItem(item, toIndex) 478 | else 479 | @isItemMovingBetweenPanes = true 480 | fromPane.moveItemToPane(item, toPane, toIndex--) 481 | toPane.activateItem(item) 482 | toPane.activate() 483 | finally 484 | @isItemMovingBetweenPanes = false 485 | 486 | removeDropTargetClasses: -> 487 | workspaceElement = atom.workspace.getElement() 488 | for dropTarget in workspaceElement.querySelectorAll('.tab-bar .is-drop-target') 489 | dropTarget.classList.remove('is-drop-target') 490 | 491 | for dropTarget in workspaceElement.querySelectorAll('.tab-bar .drop-target-is-after') 492 | dropTarget.classList.remove('drop-target-is-after') 493 | 494 | getDropTargetIndex: (event) -> 495 | target = event.target 496 | 497 | return if @isPlaceholder(target) 498 | 499 | tabs = @getTabs() 500 | tab = @tabForElement(target) 501 | tab ?= tabs[tabs.length - 1] 502 | 503 | return 0 unless tab? 504 | 505 | {left, width} = tab.element.getBoundingClientRect() 506 | elementCenter = left + width / 2 507 | elementIndex = tabs.indexOf(tab) 508 | 509 | if event.pageX < elementCenter 510 | elementIndex 511 | else 512 | elementIndex + 1 513 | 514 | getPlaceholder: -> 515 | return @placeholderEl if @placeholderEl? 516 | 517 | @placeholderEl = document.createElement("li") 518 | @placeholderEl.classList.add("placeholder") 519 | @placeholderEl 520 | 521 | removePlaceholder: -> 522 | @placeholderEl?.remove() 523 | @placeholderEl = null 524 | 525 | isPlaceholder: (element) -> 526 | element.classList.contains('placeholder') 527 | 528 | onMouseEnter: -> 529 | for tab in @getTabs() 530 | {width} = tab.element.getBoundingClientRect() 531 | tab.element.style.maxWidth = width.toFixed(2) + 'px' 532 | return 533 | 534 | onMouseLeave: -> 535 | tab.element.style.maxWidth = '' for tab in @getTabs() 536 | return 537 | 538 | tabForElement: (element) -> 539 | currentElement = element 540 | while currentElement? 541 | if tab = @tabsByElement.get(currentElement) 542 | return tab 543 | else 544 | currentElement = currentElement.parentElement 545 | 546 | isAtomTabEvent: (event) -> 547 | for item in event.dataTransfer.items 548 | if item.type is 'atom-tab-event' 549 | return true 550 | 551 | return false 552 | 553 | itemIsAllowed: (event, location) -> 554 | for item in event.dataTransfer.items 555 | if item.type is 'allow-all-locations' or item.type is "allowed-location-#{location}" 556 | return true 557 | 558 | return false 559 | -------------------------------------------------------------------------------- /lib/tab-view.coffee: -------------------------------------------------------------------------------- 1 | path = require 'path' 2 | {Disposable, CompositeDisposable} = require 'atom' 3 | getIconServices = require './get-icon-services' 4 | 5 | layout = require './layout' 6 | 7 | module.exports = 8 | class TabView 9 | constructor: ({@item, @pane, didClickCloseIcon, @tabs, location}) -> 10 | if typeof @item.getPath is 'function' 11 | @path = @item.getPath() 12 | 13 | @element = document.createElement('li') 14 | @element.setAttribute('is', 'tabs-tab') 15 | if ['TextEditor', 'TestView'].indexOf(@item.constructor.name) > -1 16 | @element.classList.add('texteditor') 17 | @element.classList.add('tab', 'sortable') 18 | 19 | @itemTitle = document.createElement('div') 20 | @itemTitle.classList.add('title') 21 | @element.appendChild(@itemTitle) 22 | 23 | if location is 'center' or not @item.isPermanentDockItem?() 24 | closeIcon = document.createElement('div') 25 | closeIcon.classList.add('close-icon') 26 | closeIcon.onclick = didClickCloseIcon 27 | @element.appendChild(closeIcon) 28 | 29 | @subscriptions = new CompositeDisposable() 30 | 31 | @handleEvents() 32 | @updateDataAttributes() 33 | @updateTitle() 34 | @updateIcon() 35 | @updateModifiedStatus() 36 | @setupTooltip() 37 | 38 | if @isItemPending() 39 | @itemTitle.classList.add('temp') 40 | @element.classList.add('pending-tab') 41 | 42 | @element.ondrag = (e) -> layout.drag e 43 | @element.ondragend = (e) -> layout.end e 44 | 45 | @element.pane = @pane 46 | @element.item = @item 47 | @element.itemTitle = @itemTitle 48 | @element.path = @path 49 | 50 | handleEvents: -> 51 | titleChangedHandler = => 52 | @updateTitle() 53 | 54 | @subscriptions.add @pane.onDidDestroy => @destroy() 55 | @subscriptions.add @pane.onItemDidTerminatePendingState (item) => 56 | @clearPending() if item is @item 57 | 58 | if typeof @item.onDidChangeTitle is 'function' 59 | onDidChangeTitleDisposable = @item.onDidChangeTitle(titleChangedHandler) 60 | if Disposable.isDisposable(onDidChangeTitleDisposable) 61 | @subscriptions.add(onDidChangeTitleDisposable) 62 | else 63 | console.warn "::onDidChangeTitle does not return a valid Disposable!", @item 64 | else if typeof @item.on is 'function' 65 | #TODO Remove once old events are no longer supported 66 | @item.on('title-changed', titleChangedHandler) 67 | @subscriptions.add dispose: => 68 | @item.off?('title-changed', titleChangedHandler) 69 | 70 | pathChangedHandler = (@path) => 71 | @updateDataAttributes() 72 | @updateTitle() 73 | @updateTooltip() 74 | @updateIcon() 75 | 76 | if typeof @item.onDidChangePath is 'function' 77 | onDidChangePathDisposable = @item.onDidChangePath(pathChangedHandler) 78 | if Disposable.isDisposable(onDidChangePathDisposable) 79 | @subscriptions.add(onDidChangePathDisposable) 80 | else 81 | console.warn "::onDidChangePath does not return a valid Disposable!", @item 82 | else if typeof @item.on is 'function' 83 | #TODO Remove once old events are no longer supported 84 | @item.on('path-changed', pathChangedHandler) 85 | @subscriptions.add dispose: => 86 | @item.off?('path-changed', pathChangedHandler) 87 | 88 | iconChangedHandler = => 89 | @updateIcon() 90 | 91 | @subscriptions.add getIconServices().onDidChange => @updateIcon() 92 | 93 | if typeof @item.onDidChangeIcon is 'function' 94 | onDidChangeIconDisposable = @item.onDidChangeIcon? => 95 | @updateIcon() 96 | if Disposable.isDisposable(onDidChangeIconDisposable) 97 | @subscriptions.add(onDidChangeIconDisposable) 98 | else 99 | console.warn "::onDidChangeIcon does not return a valid Disposable!", @item 100 | else if typeof @item.on is 'function' 101 | #TODO Remove once old events are no longer supported 102 | @item.on('icon-changed', iconChangedHandler) 103 | @subscriptions.add dispose: => 104 | @item.off?('icon-changed', iconChangedHandler) 105 | 106 | modifiedHandler = => 107 | @updateModifiedStatus() 108 | 109 | if typeof @item.onDidChangeModified is 'function' 110 | onDidChangeModifiedDisposable = @item.onDidChangeModified(modifiedHandler) 111 | if Disposable.isDisposable(onDidChangeModifiedDisposable) 112 | @subscriptions.add(onDidChangeModifiedDisposable) 113 | else 114 | console.warn "::onDidChangeModified does not return a valid Disposable!", @item 115 | else if typeof @item.on is 'function' 116 | #TODO Remove once old events are no longer supported 117 | @item.on('modified-status-changed', modifiedHandler) 118 | @subscriptions.add dispose: => 119 | @item.off?('modified-status-changed', modifiedHandler) 120 | 121 | if typeof @item.onDidSave is 'function' 122 | onDidSaveDisposable = @item.onDidSave (event) => 123 | @terminatePendingState() 124 | if event.path isnt @path 125 | @path = event.path 126 | @setupVcsStatus() if atom.config.get 'tabs.enableVcsColoring' 127 | 128 | if Disposable.isDisposable(onDidSaveDisposable) 129 | @subscriptions.add(onDidSaveDisposable) 130 | else 131 | console.warn "::onDidSave does not return a valid Disposable!", @item 132 | @subscriptions.add atom.config.observe 'tabs.showIcons', => 133 | @updateIconVisibility() 134 | 135 | @subscriptions.add atom.config.observe 'tabs.enableVcsColoring', (isEnabled) => 136 | if isEnabled and @path? then @setupVcsStatus() else @unsetVcsStatus() 137 | 138 | setupTooltip: -> 139 | # Defer creating the tooltip until the tab is moused over 140 | onMouseEnter = => 141 | @mouseEnterSubscription.dispose() 142 | @hasBeenMousedOver = true 143 | @updateTooltip() 144 | 145 | # Trigger again so the tooltip shows 146 | @element.dispatchEvent(new CustomEvent('mouseenter', bubbles: true)) 147 | 148 | @mouseEnterSubscription = dispose: => 149 | @element.removeEventListener('mouseenter', onMouseEnter) 150 | @mouseEnterSubscription = null 151 | 152 | @element.addEventListener('mouseenter', onMouseEnter) 153 | 154 | updateTooltip: -> 155 | return unless @hasBeenMousedOver 156 | 157 | @destroyTooltip() 158 | 159 | if @path 160 | @tooltip = atom.tooltips.add @element, 161 | title: @path 162 | html: false 163 | delay: 164 | show: 1000 165 | hide: 100 166 | placement: 'bottom' 167 | 168 | destroyTooltip: -> 169 | return unless @hasBeenMousedOver 170 | @tooltip?.dispose() 171 | 172 | destroy: -> 173 | @subscriptions?.dispose() 174 | @mouseEnterSubscription?.dispose() 175 | @repoSubscriptions?.dispose() 176 | @destroyTooltip() 177 | @element.remove() 178 | 179 | updateDataAttributes: -> 180 | if @path 181 | @itemTitle.dataset.name = path.basename(@path) 182 | @itemTitle.dataset.path = @path 183 | else 184 | delete @itemTitle.dataset.name 185 | delete @itemTitle.dataset.path 186 | 187 | if itemClass = @item.constructor?.name 188 | @element.dataset.type = itemClass 189 | else 190 | delete @element.dataset.type 191 | 192 | updateTitle: ({updateSiblings, useLongTitle}={}) -> 193 | return if @updatingTitle 194 | @updatingTitle = true 195 | 196 | if updateSiblings is false 197 | title = @item.getTitle() 198 | title = @item.getLongTitle?() ? title if useLongTitle 199 | @itemTitle.textContent = title 200 | else 201 | title = @item.getTitle() 202 | useLongTitle = false 203 | for tab in @tabs when tab isnt this 204 | if tab.item.getTitle() is title 205 | tab.updateTitle(updateSiblings: false, useLongTitle: true) 206 | useLongTitle = true 207 | title = @item.getLongTitle?() ? title if useLongTitle 208 | 209 | @itemTitle.textContent = title 210 | 211 | @updatingTitle = false 212 | 213 | updateIcon: -> 214 | getIconServices().updateTabIcon(this) 215 | 216 | isItemPending: -> 217 | if @pane.getPendingItem? 218 | @pane.getPendingItem() is @item 219 | else if @item.isPending? 220 | @item.isPending() 221 | 222 | terminatePendingState: -> 223 | if @pane.clearPendingItem? 224 | @pane.clearPendingItem() if @pane.getPendingItem() is @item 225 | else if @item.terminatePendingState? 226 | @item.terminatePendingState() 227 | 228 | clearPending: -> 229 | @itemTitle.classList.remove('temp') 230 | @element.classList.remove('pending-tab') 231 | 232 | updateIconVisibility: -> 233 | if atom.config.get 'tabs.showIcons' 234 | @itemTitle.classList.remove('hide-icon') 235 | else 236 | @itemTitle.classList.add('hide-icon') 237 | 238 | updateModifiedStatus: -> 239 | if @item.isModified?() 240 | @element.classList.add('modified') unless @isModified 241 | @isModified = true 242 | else 243 | @element.classList.remove('modified') if @isModified 244 | @isModified = false 245 | 246 | setupVcsStatus: -> 247 | return unless @path? 248 | @repoForPath(@path).then (repo) => 249 | @subscribeToRepo(repo) 250 | @updateVcsStatus(repo) 251 | 252 | # Subscribe to the project's repo for changes to the VCS status of the file. 253 | subscribeToRepo: (repo) -> 254 | return unless repo? 255 | 256 | # Remove previous repo subscriptions. 257 | @repoSubscriptions?.dispose() 258 | @repoSubscriptions = new CompositeDisposable() 259 | 260 | @repoSubscriptions.add repo.onDidChangeStatus (event) => 261 | @updateVcsStatus(repo, event.pathStatus) if event.path is @path 262 | @repoSubscriptions.add repo.onDidChangeStatuses => 263 | @updateVcsStatus(repo) 264 | 265 | repoForPath: -> 266 | for dir in atom.project.getDirectories() 267 | return atom.project.repositoryForDirectory(dir) if dir.contains @path 268 | Promise.resolve(null) 269 | 270 | # Update the VCS status property of this tab using the repo. 271 | updateVcsStatus: (repo, status) -> 272 | return unless repo? 273 | 274 | newStatus = null 275 | if repo.isPathIgnored(@path) 276 | newStatus = 'ignored' 277 | else 278 | status = repo.getCachedPathStatus(@path) unless status? 279 | if repo.isStatusModified(status) 280 | newStatus = 'modified' 281 | else if repo.isStatusNew(status) 282 | newStatus = 'added' 283 | 284 | if newStatus isnt @status 285 | @status = newStatus 286 | @updateVcsColoring() 287 | 288 | updateVcsColoring: -> 289 | @itemTitle.classList.remove('status-ignored', 'status-modified', 'status-added') 290 | if @status and atom.config.get 'tabs.enableVcsColoring' 291 | @itemTitle.classList.add("status-#{@status}") 292 | 293 | unsetVcsStatus: -> 294 | @repoSubscriptions?.dispose() 295 | delete @status 296 | @updateVcsColoring() 297 | -------------------------------------------------------------------------------- /menus/tabs.cson: -------------------------------------------------------------------------------- 1 | menu: [ 2 | { 3 | label: 'File' 4 | submenu: [ 5 | {label: 'Close All Tabs', command: 'tabs:close-all-tabs'} 6 | ] 7 | } 8 | ] 9 | 10 | 'context-menu': 11 | '.tab': [ 12 | {label: 'Close Tab', command: 'tabs:close-tab'} 13 | {label: 'Close Other Tabs', command: 'tabs:close-other-tabs'} 14 | {label: 'Close Tabs to the Right', command: 'tabs:close-tabs-to-right'} 15 | {label: 'Close Tabs to the Left', command: 'tabs:close-tabs-to-left'} 16 | {label: 'Close Saved Tabs', command: 'tabs:close-saved-tabs'} 17 | {label: 'Close All Tabs', command: 'tabs:close-all-tabs'} 18 | 19 | {type: 'separator'} 20 | 21 | {label: 'Split Up', command: 'tabs:split-up'} 22 | {label: 'Split Down', command: 'tabs:split-down'} 23 | {label: 'Split Left', command: 'tabs:split-left'} 24 | {label: 'Split Right', command: 'tabs:split-right'} 25 | 26 | ] 27 | '.tab.texteditor': [ 28 | {type: 'separator'} 29 | {label: 'Open In New Window', command: 'tabs:open-in-new-window'} 30 | {type: 'separator'} 31 | ] 32 | '.tab.pending-tab': [ 33 | {label: 'Keep Pending Tab', command: 'tabs:keep-pending-tab'} 34 | ] 35 | '.tab-bar': [ 36 | {label: 'Reopen Closed Tab', command: 'pane:reopen-closed-item'} 37 | ] 38 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tabs", 3 | "version": "0.110.2", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "acorn": { 8 | "version": "5.7.3", 9 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", 10 | "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==", 11 | "dev": true 12 | }, 13 | "acorn-jsx": { 14 | "version": "3.0.1", 15 | "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", 16 | "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", 17 | "dev": true, 18 | "requires": { 19 | "acorn": "^3.0.4" 20 | }, 21 | "dependencies": { 22 | "acorn": { 23 | "version": "3.3.0", 24 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", 25 | "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=", 26 | "dev": true 27 | } 28 | } 29 | }, 30 | "ajv-keywords": { 31 | "version": "1.5.1", 32 | "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-1.5.1.tgz", 33 | "integrity": "sha1-MU3QpLM2j609/NxU7eYXG4htrzw=", 34 | "dev": true 35 | }, 36 | "ansi-escapes": { 37 | "version": "1.4.0", 38 | "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", 39 | "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=", 40 | "dev": true 41 | }, 42 | "ansi-regex": { 43 | "version": "2.1.1", 44 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", 45 | "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", 46 | "dev": true 47 | }, 48 | "ansi-styles": { 49 | "version": "2.2.1", 50 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", 51 | "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", 52 | "dev": true 53 | }, 54 | "argparse": { 55 | "version": "1.0.10", 56 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", 57 | "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", 58 | "dev": true, 59 | "requires": { 60 | "sprintf-js": "~1.0.2" 61 | } 62 | }, 63 | "array.prototype.find": { 64 | "version": "2.0.4", 65 | "resolved": "https://registry.npmjs.org/array.prototype.find/-/array.prototype.find-2.0.4.tgz", 66 | "integrity": "sha1-VWpcU2LAhkgyPdrrnenRS8GGTJA=", 67 | "dev": true, 68 | "requires": { 69 | "define-properties": "^1.1.2", 70 | "es-abstract": "^1.7.0" 71 | } 72 | }, 73 | "async": { 74 | "version": "1.5.2", 75 | "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", 76 | "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" 77 | }, 78 | "babel-code-frame": { 79 | "version": "6.26.0", 80 | "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", 81 | "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", 82 | "dev": true, 83 | "requires": { 84 | "chalk": "^1.1.3", 85 | "esutils": "^2.0.2", 86 | "js-tokens": "^3.0.2" 87 | }, 88 | "dependencies": { 89 | "chalk": { 90 | "version": "1.1.3", 91 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", 92 | "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", 93 | "dev": true, 94 | "requires": { 95 | "ansi-styles": "^2.2.1", 96 | "escape-string-regexp": "^1.0.2", 97 | "has-ansi": "^2.0.0", 98 | "strip-ansi": "^3.0.0", 99 | "supports-color": "^2.0.0" 100 | } 101 | } 102 | } 103 | }, 104 | "balanced-match": { 105 | "version": "1.0.0", 106 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 107 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" 108 | }, 109 | "brace-expansion": { 110 | "version": "1.1.11", 111 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 112 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 113 | "requires": { 114 | "balanced-match": "^1.0.0", 115 | "concat-map": "0.0.1" 116 | } 117 | }, 118 | "buffer-from": { 119 | "version": "1.1.1", 120 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", 121 | "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", 122 | "dev": true 123 | }, 124 | "builtin-modules": { 125 | "version": "1.1.1", 126 | "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", 127 | "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", 128 | "dev": true 129 | }, 130 | "caller-path": { 131 | "version": "0.1.0", 132 | "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", 133 | "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", 134 | "dev": true, 135 | "requires": { 136 | "callsites": "^0.2.0" 137 | } 138 | }, 139 | "callsites": { 140 | "version": "0.2.0", 141 | "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", 142 | "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", 143 | "dev": true 144 | }, 145 | "chalk": { 146 | "version": "1.1.1", 147 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.1.tgz", 148 | "integrity": "sha1-UJr7ZwZudJn36zU1x3RFdyri0Bk=", 149 | "dev": true, 150 | "requires": { 151 | "ansi-styles": "^2.1.0", 152 | "escape-string-regexp": "^1.0.2", 153 | "has-ansi": "^2.0.0", 154 | "strip-ansi": "^3.0.0", 155 | "supports-color": "^2.0.0" 156 | } 157 | }, 158 | "circular-json": { 159 | "version": "0.3.3", 160 | "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", 161 | "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", 162 | "dev": true 163 | }, 164 | "cli-cursor": { 165 | "version": "1.0.2", 166 | "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", 167 | "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", 168 | "dev": true, 169 | "requires": { 170 | "restore-cursor": "^1.0.1" 171 | } 172 | }, 173 | "cli-width": { 174 | "version": "2.2.0", 175 | "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", 176 | "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", 177 | "dev": true 178 | }, 179 | "co": { 180 | "version": "4.6.0", 181 | "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", 182 | "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", 183 | "dev": true 184 | }, 185 | "code-point-at": { 186 | "version": "1.1.0", 187 | "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", 188 | "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", 189 | "dev": true 190 | }, 191 | "coffee-script": { 192 | "version": "1.11.1", 193 | "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.11.1.tgz", 194 | "integrity": "sha1-vxxHrWREOg2V0S3ysUfMCk2q1uk=", 195 | "dev": true 196 | }, 197 | "coffeelint": { 198 | "version": "1.16.2", 199 | "resolved": "https://registry.npmjs.org/coffeelint/-/coffeelint-1.16.2.tgz", 200 | "integrity": "sha512-6mzgOo4zb17WfdrSui/cSUEgQ0AQkW3gXDht+6lHkfkqGUtSYKwGdGcXsDfAyuScVzTlTtKdfwkAlJWfqul7zg==", 201 | "dev": true, 202 | "requires": { 203 | "coffee-script": "~1.11.0", 204 | "glob": "^7.0.6", 205 | "ignore": "^3.0.9", 206 | "optimist": "^0.6.1", 207 | "resolve": "^0.6.3", 208 | "strip-json-comments": "^1.0.2" 209 | } 210 | }, 211 | "concat-map": { 212 | "version": "0.0.1", 213 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 214 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" 215 | }, 216 | "concat-stream": { 217 | "version": "1.6.2", 218 | "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", 219 | "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", 220 | "dev": true, 221 | "requires": { 222 | "buffer-from": "^1.0.0", 223 | "inherits": "^2.0.3", 224 | "readable-stream": "^2.2.2", 225 | "typedarray": "^0.0.6" 226 | } 227 | }, 228 | "contains-path": { 229 | "version": "0.1.0", 230 | "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", 231 | "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=", 232 | "dev": true 233 | }, 234 | "core-util-is": { 235 | "version": "1.0.2", 236 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 237 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", 238 | "dev": true 239 | }, 240 | "d": { 241 | "version": "1.0.0", 242 | "resolved": "http://registry.npmjs.org/d/-/d-1.0.0.tgz", 243 | "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", 244 | "dev": true, 245 | "requires": { 246 | "es5-ext": "^0.10.9" 247 | } 248 | }, 249 | "debug": { 250 | "version": "2.6.9", 251 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 252 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 253 | "dev": true, 254 | "requires": { 255 | "ms": "2.0.0" 256 | } 257 | }, 258 | "debug-log": { 259 | "version": "1.0.1", 260 | "resolved": "http://registry.npmjs.org/debug-log/-/debug-log-1.0.1.tgz", 261 | "integrity": "sha1-IwdjLUwEOCuN+KMvcLiVBG1SdF8=", 262 | "dev": true 263 | }, 264 | "deep-is": { 265 | "version": "0.1.3", 266 | "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", 267 | "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", 268 | "dev": true 269 | }, 270 | "define-properties": { 271 | "version": "1.1.3", 272 | "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", 273 | "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", 274 | "dev": true, 275 | "requires": { 276 | "object-keys": "^1.0.12" 277 | } 278 | }, 279 | "deglob": { 280 | "version": "2.1.1", 281 | "resolved": "https://registry.npmjs.org/deglob/-/deglob-2.1.1.tgz", 282 | "integrity": "sha512-2kjwuGGonL7gWE1XU4Fv79+vVzpoQCl0V+boMwWtOQJV2AGDabCwez++nB1Nli/8BabAfZQ/UuHPlp6AymKdWw==", 283 | "dev": true, 284 | "requires": { 285 | "find-root": "^1.0.0", 286 | "glob": "^7.0.5", 287 | "ignore": "^3.0.9", 288 | "pkg-config": "^1.1.0", 289 | "run-parallel": "^1.1.2", 290 | "uniq": "^1.0.1" 291 | } 292 | }, 293 | "doctrine": { 294 | "version": "1.5.0", 295 | "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", 296 | "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", 297 | "dev": true, 298 | "requires": { 299 | "esutils": "^2.0.2", 300 | "isarray": "^1.0.0" 301 | } 302 | }, 303 | "error-ex": { 304 | "version": "1.3.2", 305 | "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", 306 | "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", 307 | "dev": true, 308 | "requires": { 309 | "is-arrayish": "^0.2.1" 310 | } 311 | }, 312 | "es-abstract": { 313 | "version": "1.12.0", 314 | "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.12.0.tgz", 315 | "integrity": "sha512-C8Fx/0jFmV5IPoMOFPA9P9G5NtqW+4cOPit3MIuvR2t7Ag2K15EJTpxnHAYTzL+aYQJIESYeXZmDBfOBE1HcpA==", 316 | "dev": true, 317 | "requires": { 318 | "es-to-primitive": "^1.1.1", 319 | "function-bind": "^1.1.1", 320 | "has": "^1.0.1", 321 | "is-callable": "^1.1.3", 322 | "is-regex": "^1.0.4" 323 | } 324 | }, 325 | "es-to-primitive": { 326 | "version": "1.2.0", 327 | "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", 328 | "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", 329 | "dev": true, 330 | "requires": { 331 | "is-callable": "^1.1.4", 332 | "is-date-object": "^1.0.1", 333 | "is-symbol": "^1.0.2" 334 | } 335 | }, 336 | "es5-ext": { 337 | "version": "0.10.46", 338 | "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.46.tgz", 339 | "integrity": "sha512-24XxRvJXNFwEMpJb3nOkiRJKRoupmjYmOPVlI65Qy2SrtxwOTB+g6ODjBKOtwEHbYrhWRty9xxOWLNdClT2djw==", 340 | "dev": true, 341 | "requires": { 342 | "es6-iterator": "~2.0.3", 343 | "es6-symbol": "~3.1.1", 344 | "next-tick": "1" 345 | } 346 | }, 347 | "es6-iterator": { 348 | "version": "2.0.3", 349 | "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", 350 | "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", 351 | "dev": true, 352 | "requires": { 353 | "d": "1", 354 | "es5-ext": "^0.10.35", 355 | "es6-symbol": "^3.1.1" 356 | } 357 | }, 358 | "es6-map": { 359 | "version": "0.1.5", 360 | "resolved": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.5.tgz", 361 | "integrity": "sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA=", 362 | "dev": true, 363 | "requires": { 364 | "d": "1", 365 | "es5-ext": "~0.10.14", 366 | "es6-iterator": "~2.0.1", 367 | "es6-set": "~0.1.5", 368 | "es6-symbol": "~3.1.1", 369 | "event-emitter": "~0.3.5" 370 | } 371 | }, 372 | "es6-set": { 373 | "version": "0.1.5", 374 | "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz", 375 | "integrity": "sha1-0rPsXU2ADO2BjbU40ol02wpzzLE=", 376 | "dev": true, 377 | "requires": { 378 | "d": "1", 379 | "es5-ext": "~0.10.14", 380 | "es6-iterator": "~2.0.1", 381 | "es6-symbol": "3.1.1", 382 | "event-emitter": "~0.3.5" 383 | } 384 | }, 385 | "es6-symbol": { 386 | "version": "3.1.1", 387 | "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", 388 | "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", 389 | "dev": true, 390 | "requires": { 391 | "d": "1", 392 | "es5-ext": "~0.10.14" 393 | } 394 | }, 395 | "es6-weak-map": { 396 | "version": "2.0.2", 397 | "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.2.tgz", 398 | "integrity": "sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8=", 399 | "dev": true, 400 | "requires": { 401 | "d": "1", 402 | "es5-ext": "^0.10.14", 403 | "es6-iterator": "^2.0.1", 404 | "es6-symbol": "^3.1.1" 405 | } 406 | }, 407 | "escape-string-regexp": { 408 | "version": "1.0.5", 409 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 410 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", 411 | "dev": true 412 | }, 413 | "escope": { 414 | "version": "3.6.0", 415 | "resolved": "https://registry.npmjs.org/escope/-/escope-3.6.0.tgz", 416 | "integrity": "sha1-4Bl16BJ4GhY6ba392AOY3GTIicM=", 417 | "dev": true, 418 | "requires": { 419 | "es6-map": "^0.1.3", 420 | "es6-weak-map": "^2.0.1", 421 | "esrecurse": "^4.1.0", 422 | "estraverse": "^4.1.1" 423 | } 424 | }, 425 | "eslint-config-standard-jsx": { 426 | "version": "4.0.2", 427 | "resolved": "https://registry.npmjs.org/eslint-config-standard-jsx/-/eslint-config-standard-jsx-4.0.2.tgz", 428 | "integrity": "sha512-F8fRh2WFnTek7dZH9ZaE0PCBwdVGkwVWZmizla/DDNOmg7Tx6B/IlK5+oYpiX29jpu73LszeJj5i1axEZv6VMw==", 429 | "dev": true 430 | }, 431 | "eslint-import-resolver-node": { 432 | "version": "0.2.3", 433 | "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.2.3.tgz", 434 | "integrity": "sha1-Wt2BBujJKNssuiMrzZ76hG49oWw=", 435 | "dev": true, 436 | "requires": { 437 | "debug": "^2.2.0", 438 | "object-assign": "^4.0.1", 439 | "resolve": "^1.1.6" 440 | }, 441 | "dependencies": { 442 | "resolve": { 443 | "version": "1.8.1", 444 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz", 445 | "integrity": "sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA==", 446 | "dev": true, 447 | "requires": { 448 | "path-parse": "^1.0.5" 449 | } 450 | } 451 | } 452 | }, 453 | "eslint-module-utils": { 454 | "version": "2.2.0", 455 | "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.2.0.tgz", 456 | "integrity": "sha1-snA2LNiLGkitMIl2zn+lTphBF0Y=", 457 | "dev": true, 458 | "requires": { 459 | "debug": "^2.6.8", 460 | "pkg-dir": "^1.0.0" 461 | } 462 | }, 463 | "eslint-plugin-import": { 464 | "version": "2.2.0", 465 | "resolved": "http://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.2.0.tgz", 466 | "integrity": "sha1-crowb60wXWfEgWNIpGmaQimsi04=", 467 | "dev": true, 468 | "requires": { 469 | "builtin-modules": "^1.1.1", 470 | "contains-path": "^0.1.0", 471 | "debug": "^2.2.0", 472 | "doctrine": "1.5.0", 473 | "eslint-import-resolver-node": "^0.2.0", 474 | "eslint-module-utils": "^2.0.0", 475 | "has": "^1.0.1", 476 | "lodash.cond": "^4.3.0", 477 | "minimatch": "^3.0.3", 478 | "pkg-up": "^1.0.0" 479 | } 480 | }, 481 | "eslint-plugin-node": { 482 | "version": "4.2.3", 483 | "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-4.2.3.tgz", 484 | "integrity": "sha512-vIUQPuwbVYdz/CYnlTLsJrRy7iXHQjdEe5wz0XhhdTym3IInM/zZLlPf9nZ2mThsH0QcsieCOWs2vOeCy/22LQ==", 485 | "dev": true, 486 | "requires": { 487 | "ignore": "^3.0.11", 488 | "minimatch": "^3.0.2", 489 | "object-assign": "^4.0.1", 490 | "resolve": "^1.1.7", 491 | "semver": "5.3.0" 492 | }, 493 | "dependencies": { 494 | "resolve": { 495 | "version": "1.8.1", 496 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz", 497 | "integrity": "sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA==", 498 | "dev": true, 499 | "requires": { 500 | "path-parse": "^1.0.5" 501 | } 502 | }, 503 | "semver": { 504 | "version": "5.3.0", 505 | "resolved": "http://registry.npmjs.org/semver/-/semver-5.3.0.tgz", 506 | "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=", 507 | "dev": true 508 | } 509 | } 510 | }, 511 | "eslint-plugin-react": { 512 | "version": "6.10.3", 513 | "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-6.10.3.tgz", 514 | "integrity": "sha1-xUNb6wZ3ThLH2y9qut3L+QDNP3g=", 515 | "dev": true, 516 | "requires": { 517 | "array.prototype.find": "^2.0.1", 518 | "doctrine": "^1.2.2", 519 | "has": "^1.0.1", 520 | "jsx-ast-utils": "^1.3.4", 521 | "object.assign": "^4.0.4" 522 | } 523 | }, 524 | "espree": { 525 | "version": "3.5.4", 526 | "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.4.tgz", 527 | "integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==", 528 | "dev": true, 529 | "requires": { 530 | "acorn": "^5.5.0", 531 | "acorn-jsx": "^3.0.0" 532 | } 533 | }, 534 | "esprima": { 535 | "version": "4.0.1", 536 | "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", 537 | "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", 538 | "dev": true 539 | }, 540 | "esquery": { 541 | "version": "1.0.1", 542 | "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", 543 | "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", 544 | "dev": true, 545 | "requires": { 546 | "estraverse": "^4.0.0" 547 | } 548 | }, 549 | "esrecurse": { 550 | "version": "4.2.1", 551 | "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", 552 | "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", 553 | "dev": true, 554 | "requires": { 555 | "estraverse": "^4.1.0" 556 | } 557 | }, 558 | "estraverse": { 559 | "version": "4.2.0", 560 | "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", 561 | "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", 562 | "dev": true 563 | }, 564 | "esutils": { 565 | "version": "2.0.2", 566 | "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", 567 | "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", 568 | "dev": true 569 | }, 570 | "event-emitter": { 571 | "version": "0.3.5", 572 | "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", 573 | "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", 574 | "dev": true, 575 | "requires": { 576 | "d": "1", 577 | "es5-ext": "~0.10.14" 578 | } 579 | }, 580 | "exit-hook": { 581 | "version": "1.1.1", 582 | "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz", 583 | "integrity": "sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g=", 584 | "dev": true 585 | }, 586 | "fast-levenshtein": { 587 | "version": "2.0.6", 588 | "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", 589 | "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", 590 | "dev": true 591 | }, 592 | "figures": { 593 | "version": "1.7.0", 594 | "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", 595 | "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", 596 | "dev": true, 597 | "requires": { 598 | "escape-string-regexp": "^1.0.5", 599 | "object-assign": "^4.1.0" 600 | } 601 | }, 602 | "find-root": { 603 | "version": "1.1.0", 604 | "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", 605 | "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", 606 | "dev": true 607 | }, 608 | "find-up": { 609 | "version": "1.1.2", 610 | "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", 611 | "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", 612 | "dev": true, 613 | "requires": { 614 | "path-exists": "^2.0.0", 615 | "pinkie-promise": "^2.0.0" 616 | }, 617 | "dependencies": { 618 | "path-exists": { 619 | "version": "2.1.0", 620 | "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", 621 | "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", 622 | "dev": true, 623 | "requires": { 624 | "pinkie-promise": "^2.0.0" 625 | } 626 | } 627 | } 628 | }, 629 | "flat-cache": { 630 | "version": "1.3.4", 631 | "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.4.tgz", 632 | "integrity": "sha512-VwyB3Lkgacfik2vhqR4uv2rvebqmDvFu4jlN/C1RzWoJEo8I7z4Q404oiqYCkq41mni8EzQnm95emU9seckwtg==", 633 | "dev": true, 634 | "requires": { 635 | "circular-json": "^0.3.1", 636 | "graceful-fs": "^4.1.2", 637 | "rimraf": "~2.6.2", 638 | "write": "^0.2.1" 639 | } 640 | }, 641 | "fs-plus": { 642 | "version": "3.0.2", 643 | "resolved": "https://registry.npmjs.org/fs-plus/-/fs-plus-3.0.2.tgz", 644 | "integrity": "sha1-a19Sp3EolMTd6f2PgfqMYN8EHz0=", 645 | "requires": { 646 | "async": "^1.5.2", 647 | "mkdirp": "^0.5.1", 648 | "rimraf": "^2.5.2", 649 | "underscore-plus": "1.x" 650 | } 651 | }, 652 | "fs.realpath": { 653 | "version": "1.0.0", 654 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 655 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" 656 | }, 657 | "function-bind": { 658 | "version": "1.1.1", 659 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 660 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", 661 | "dev": true 662 | }, 663 | "generate-function": { 664 | "version": "2.3.1", 665 | "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", 666 | "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", 667 | "dev": true, 668 | "requires": { 669 | "is-property": "^1.0.2" 670 | } 671 | }, 672 | "generate-object-property": { 673 | "version": "1.2.0", 674 | "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz", 675 | "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=", 676 | "dev": true, 677 | "requires": { 678 | "is-property": "^1.0.0" 679 | } 680 | }, 681 | "glob": { 682 | "version": "7.1.3", 683 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", 684 | "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", 685 | "requires": { 686 | "fs.realpath": "^1.0.0", 687 | "inflight": "^1.0.4", 688 | "inherits": "2", 689 | "minimatch": "^3.0.4", 690 | "once": "^1.3.0", 691 | "path-is-absolute": "^1.0.0" 692 | } 693 | }, 694 | "globals": { 695 | "version": "9.18.0", 696 | "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", 697 | "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", 698 | "dev": true 699 | }, 700 | "graceful-fs": { 701 | "version": "4.1.15", 702 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", 703 | "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", 704 | "dev": true 705 | }, 706 | "has": { 707 | "version": "1.0.3", 708 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 709 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 710 | "dev": true, 711 | "requires": { 712 | "function-bind": "^1.1.1" 713 | } 714 | }, 715 | "has-ansi": { 716 | "version": "2.0.0", 717 | "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", 718 | "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", 719 | "dev": true, 720 | "requires": { 721 | "ansi-regex": "^2.0.0" 722 | } 723 | }, 724 | "has-symbols": { 725 | "version": "1.0.0", 726 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", 727 | "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", 728 | "dev": true 729 | }, 730 | "ignore": { 731 | "version": "3.3.10", 732 | "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", 733 | "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", 734 | "dev": true 735 | }, 736 | "imurmurhash": { 737 | "version": "0.1.4", 738 | "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", 739 | "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", 740 | "dev": true 741 | }, 742 | "inflight": { 743 | "version": "1.0.6", 744 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 745 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 746 | "requires": { 747 | "once": "^1.3.0", 748 | "wrappy": "1" 749 | } 750 | }, 751 | "inherits": { 752 | "version": "2.0.3", 753 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 754 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 755 | }, 756 | "inquirer": { 757 | "version": "0.12.0", 758 | "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-0.12.0.tgz", 759 | "integrity": "sha1-HvK/1jUE3wvHV4X/+MLEHfEvB34=", 760 | "dev": true, 761 | "requires": { 762 | "ansi-escapes": "^1.1.0", 763 | "ansi-regex": "^2.0.0", 764 | "chalk": "^1.0.0", 765 | "cli-cursor": "^1.0.1", 766 | "cli-width": "^2.0.0", 767 | "figures": "^1.3.5", 768 | "lodash": "^4.3.0", 769 | "readline2": "^1.0.1", 770 | "run-async": "^0.1.0", 771 | "rx-lite": "^3.1.2", 772 | "string-width": "^1.0.1", 773 | "strip-ansi": "^3.0.0", 774 | "through": "^2.3.6" 775 | }, 776 | "dependencies": { 777 | "lodash": { 778 | "version": "4.17.11", 779 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", 780 | "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", 781 | "dev": true 782 | } 783 | } 784 | }, 785 | "interpret": { 786 | "version": "1.1.0", 787 | "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.1.0.tgz", 788 | "integrity": "sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ=", 789 | "dev": true 790 | }, 791 | "is-arrayish": { 792 | "version": "0.2.1", 793 | "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", 794 | "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", 795 | "dev": true 796 | }, 797 | "is-callable": { 798 | "version": "1.1.4", 799 | "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", 800 | "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", 801 | "dev": true 802 | }, 803 | "is-date-object": { 804 | "version": "1.0.1", 805 | "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", 806 | "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", 807 | "dev": true 808 | }, 809 | "is-fullwidth-code-point": { 810 | "version": "1.0.0", 811 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", 812 | "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", 813 | "dev": true, 814 | "requires": { 815 | "number-is-nan": "^1.0.0" 816 | } 817 | }, 818 | "is-my-ip-valid": { 819 | "version": "1.0.0", 820 | "resolved": "https://registry.npmjs.org/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz", 821 | "integrity": "sha512-gmh/eWXROncUzRnIa1Ubrt5b8ep/MGSnfAUI3aRp+sqTCs1tv1Isl8d8F6JmkN3dXKc3ehZMrtiPN9eL03NuaQ==", 822 | "dev": true 823 | }, 824 | "is-my-json-valid": { 825 | "version": "2.19.0", 826 | "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.19.0.tgz", 827 | "integrity": "sha512-mG0f/unGX1HZ5ep4uhRaPOS8EkAY8/j6mDRMJrutq4CqhoJWYp7qAlonIPy3TV7p3ju4TK9fo/PbnoksWmsp5Q==", 828 | "dev": true, 829 | "requires": { 830 | "generate-function": "^2.0.0", 831 | "generate-object-property": "^1.1.0", 832 | "is-my-ip-valid": "^1.0.0", 833 | "jsonpointer": "^4.0.0", 834 | "xtend": "^4.0.0" 835 | } 836 | }, 837 | "is-property": { 838 | "version": "1.0.2", 839 | "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", 840 | "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=", 841 | "dev": true 842 | }, 843 | "is-regex": { 844 | "version": "1.0.4", 845 | "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", 846 | "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", 847 | "dev": true, 848 | "requires": { 849 | "has": "^1.0.1" 850 | } 851 | }, 852 | "is-resolvable": { 853 | "version": "1.1.0", 854 | "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", 855 | "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", 856 | "dev": true 857 | }, 858 | "is-symbol": { 859 | "version": "1.0.2", 860 | "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", 861 | "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", 862 | "dev": true, 863 | "requires": { 864 | "has-symbols": "^1.0.0" 865 | } 866 | }, 867 | "isarray": { 868 | "version": "1.0.0", 869 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 870 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", 871 | "dev": true 872 | }, 873 | "js-tokens": { 874 | "version": "3.0.2", 875 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", 876 | "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", 877 | "dev": true 878 | }, 879 | "js-yaml": { 880 | "version": "3.12.0", 881 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.0.tgz", 882 | "integrity": "sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A==", 883 | "dev": true, 884 | "requires": { 885 | "argparse": "^1.0.7", 886 | "esprima": "^4.0.0" 887 | } 888 | }, 889 | "json-parse-better-errors": { 890 | "version": "1.0.2", 891 | "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", 892 | "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", 893 | "dev": true 894 | }, 895 | "json-stable-stringify": { 896 | "version": "1.0.1", 897 | "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", 898 | "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", 899 | "dev": true, 900 | "requires": { 901 | "jsonify": "~0.0.0" 902 | } 903 | }, 904 | "jsonify": { 905 | "version": "0.0.0", 906 | "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", 907 | "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", 908 | "dev": true 909 | }, 910 | "jsonpointer": { 911 | "version": "4.0.1", 912 | "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz", 913 | "integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=", 914 | "dev": true 915 | }, 916 | "jsx-ast-utils": { 917 | "version": "1.4.1", 918 | "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-1.4.1.tgz", 919 | "integrity": "sha1-OGchPo3Xm/Ho8jAMDPwe+xgsDfE=", 920 | "dev": true 921 | }, 922 | "levn": { 923 | "version": "0.3.0", 924 | "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", 925 | "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", 926 | "dev": true, 927 | "requires": { 928 | "prelude-ls": "~1.1.2", 929 | "type-check": "~0.3.2" 930 | } 931 | }, 932 | "locate-path": { 933 | "version": "2.0.0", 934 | "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", 935 | "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", 936 | "dev": true, 937 | "requires": { 938 | "p-locate": "^2.0.0", 939 | "path-exists": "^3.0.0" 940 | }, 941 | "dependencies": { 942 | "path-exists": { 943 | "version": "3.0.0", 944 | "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", 945 | "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", 946 | "dev": true 947 | } 948 | } 949 | }, 950 | "lodash.cond": { 951 | "version": "4.5.2", 952 | "resolved": "https://registry.npmjs.org/lodash.cond/-/lodash.cond-4.5.2.tgz", 953 | "integrity": "sha1-9HGh2khr5g9quVXRcRVSPdHSVdU=", 954 | "dev": true 955 | }, 956 | "minimatch": { 957 | "version": "3.0.4", 958 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 959 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 960 | "requires": { 961 | "brace-expansion": "^1.1.7" 962 | } 963 | }, 964 | "minimist": { 965 | "version": "0.0.8", 966 | "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", 967 | "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" 968 | }, 969 | "mkdirp": { 970 | "version": "0.5.1", 971 | "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", 972 | "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", 973 | "requires": { 974 | "minimist": "0.0.8" 975 | } 976 | }, 977 | "ms": { 978 | "version": "2.0.0", 979 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 980 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", 981 | "dev": true 982 | }, 983 | "mute-stream": { 984 | "version": "0.0.5", 985 | "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.5.tgz", 986 | "integrity": "sha1-j7+rsKmKJT0xhDMfno3rc3L6xsA=", 987 | "dev": true 988 | }, 989 | "natural-compare": { 990 | "version": "1.4.0", 991 | "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", 992 | "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", 993 | "dev": true 994 | }, 995 | "next-tick": { 996 | "version": "1.0.0", 997 | "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", 998 | "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", 999 | "dev": true 1000 | }, 1001 | "number-is-nan": { 1002 | "version": "1.0.1", 1003 | "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", 1004 | "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", 1005 | "dev": true 1006 | }, 1007 | "object-assign": { 1008 | "version": "4.1.1", 1009 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 1010 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", 1011 | "dev": true 1012 | }, 1013 | "object-keys": { 1014 | "version": "1.0.12", 1015 | "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.12.tgz", 1016 | "integrity": "sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag==", 1017 | "dev": true 1018 | }, 1019 | "object.assign": { 1020 | "version": "4.1.0", 1021 | "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", 1022 | "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", 1023 | "dev": true, 1024 | "requires": { 1025 | "define-properties": "^1.1.2", 1026 | "function-bind": "^1.1.1", 1027 | "has-symbols": "^1.0.0", 1028 | "object-keys": "^1.0.11" 1029 | } 1030 | }, 1031 | "once": { 1032 | "version": "1.4.0", 1033 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 1034 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 1035 | "requires": { 1036 | "wrappy": "1" 1037 | } 1038 | }, 1039 | "onetime": { 1040 | "version": "1.1.0", 1041 | "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", 1042 | "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", 1043 | "dev": true 1044 | }, 1045 | "optimist": { 1046 | "version": "0.6.1", 1047 | "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", 1048 | "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", 1049 | "dev": true, 1050 | "requires": { 1051 | "minimist": "~0.0.1", 1052 | "wordwrap": "~0.0.2" 1053 | } 1054 | }, 1055 | "optionator": { 1056 | "version": "0.8.2", 1057 | "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", 1058 | "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", 1059 | "dev": true, 1060 | "requires": { 1061 | "deep-is": "~0.1.3", 1062 | "fast-levenshtein": "~2.0.4", 1063 | "levn": "~0.3.0", 1064 | "prelude-ls": "~1.1.2", 1065 | "type-check": "~0.3.2", 1066 | "wordwrap": "~1.0.0" 1067 | }, 1068 | "dependencies": { 1069 | "wordwrap": { 1070 | "version": "1.0.0", 1071 | "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", 1072 | "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", 1073 | "dev": true 1074 | } 1075 | } 1076 | }, 1077 | "os-homedir": { 1078 | "version": "1.0.2", 1079 | "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", 1080 | "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", 1081 | "dev": true 1082 | }, 1083 | "os-tmpdir": { 1084 | "version": "1.0.2", 1085 | "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", 1086 | "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" 1087 | }, 1088 | "p-limit": { 1089 | "version": "1.3.0", 1090 | "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", 1091 | "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", 1092 | "dev": true, 1093 | "requires": { 1094 | "p-try": "^1.0.0" 1095 | } 1096 | }, 1097 | "p-locate": { 1098 | "version": "2.0.0", 1099 | "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", 1100 | "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", 1101 | "dev": true, 1102 | "requires": { 1103 | "p-limit": "^1.1.0" 1104 | } 1105 | }, 1106 | "p-try": { 1107 | "version": "1.0.0", 1108 | "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", 1109 | "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", 1110 | "dev": true 1111 | }, 1112 | "path-is-absolute": { 1113 | "version": "1.0.1", 1114 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 1115 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" 1116 | }, 1117 | "path-is-inside": { 1118 | "version": "1.0.2", 1119 | "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", 1120 | "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", 1121 | "dev": true 1122 | }, 1123 | "path-parse": { 1124 | "version": "1.0.6", 1125 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", 1126 | "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", 1127 | "dev": true 1128 | }, 1129 | "pinkie": { 1130 | "version": "2.0.4", 1131 | "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", 1132 | "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", 1133 | "dev": true 1134 | }, 1135 | "pinkie-promise": { 1136 | "version": "2.0.1", 1137 | "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", 1138 | "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", 1139 | "dev": true, 1140 | "requires": { 1141 | "pinkie": "^2.0.0" 1142 | } 1143 | }, 1144 | "pkg-conf": { 1145 | "version": "2.1.0", 1146 | "resolved": "https://registry.npmjs.org/pkg-conf/-/pkg-conf-2.1.0.tgz", 1147 | "integrity": "sha1-ISZRTKbyq/69FoWW3xi6V4Z/AFg=", 1148 | "dev": true, 1149 | "requires": { 1150 | "find-up": "^2.0.0", 1151 | "load-json-file": "^4.0.0" 1152 | }, 1153 | "dependencies": { 1154 | "find-up": { 1155 | "version": "2.1.0", 1156 | "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", 1157 | "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", 1158 | "dev": true, 1159 | "requires": { 1160 | "locate-path": "^2.0.0" 1161 | } 1162 | }, 1163 | "load-json-file": { 1164 | "version": "4.0.0", 1165 | "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", 1166 | "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", 1167 | "dev": true, 1168 | "requires": { 1169 | "graceful-fs": "^4.1.2", 1170 | "parse-json": "^4.0.0", 1171 | "pify": "^3.0.0", 1172 | "strip-bom": "^3.0.0" 1173 | } 1174 | }, 1175 | "parse-json": { 1176 | "version": "4.0.0", 1177 | "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", 1178 | "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", 1179 | "dev": true, 1180 | "requires": { 1181 | "error-ex": "^1.3.1", 1182 | "json-parse-better-errors": "^1.0.1" 1183 | } 1184 | }, 1185 | "pify": { 1186 | "version": "3.0.0", 1187 | "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", 1188 | "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", 1189 | "dev": true 1190 | }, 1191 | "strip-bom": { 1192 | "version": "3.0.0", 1193 | "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", 1194 | "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", 1195 | "dev": true 1196 | } 1197 | } 1198 | }, 1199 | "pkg-config": { 1200 | "version": "1.1.1", 1201 | "resolved": "https://registry.npmjs.org/pkg-config/-/pkg-config-1.1.1.tgz", 1202 | "integrity": "sha1-VX7yLXPaPIg3EHdmxS6tq94pj+Q=", 1203 | "dev": true, 1204 | "requires": { 1205 | "debug-log": "^1.0.0", 1206 | "find-root": "^1.0.0", 1207 | "xtend": "^4.0.1" 1208 | } 1209 | }, 1210 | "pkg-dir": { 1211 | "version": "1.0.0", 1212 | "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-1.0.0.tgz", 1213 | "integrity": "sha1-ektQio1bstYp1EcFb/TpyTFM89Q=", 1214 | "dev": true, 1215 | "requires": { 1216 | "find-up": "^1.0.0" 1217 | } 1218 | }, 1219 | "pkg-up": { 1220 | "version": "1.0.0", 1221 | "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-1.0.0.tgz", 1222 | "integrity": "sha1-Pgj7RhUlxEIWJKM7n35tCvWwWiY=", 1223 | "dev": true, 1224 | "requires": { 1225 | "find-up": "^1.0.0" 1226 | } 1227 | }, 1228 | "pluralize": { 1229 | "version": "1.2.1", 1230 | "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-1.2.1.tgz", 1231 | "integrity": "sha1-0aIUg/0iu0HlihL6NCGCMUCJfEU=", 1232 | "dev": true 1233 | }, 1234 | "prelude-ls": { 1235 | "version": "1.1.2", 1236 | "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", 1237 | "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", 1238 | "dev": true 1239 | }, 1240 | "process-nextick-args": { 1241 | "version": "2.0.0", 1242 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", 1243 | "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", 1244 | "dev": true 1245 | }, 1246 | "progress": { 1247 | "version": "1.1.8", 1248 | "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz", 1249 | "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=", 1250 | "dev": true 1251 | }, 1252 | "readable-stream": { 1253 | "version": "2.3.6", 1254 | "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", 1255 | "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", 1256 | "dev": true, 1257 | "requires": { 1258 | "core-util-is": "~1.0.0", 1259 | "inherits": "~2.0.3", 1260 | "isarray": "~1.0.0", 1261 | "process-nextick-args": "~2.0.0", 1262 | "safe-buffer": "~5.1.1", 1263 | "string_decoder": "~1.1.1", 1264 | "util-deprecate": "~1.0.1" 1265 | } 1266 | }, 1267 | "readline2": { 1268 | "version": "1.0.1", 1269 | "resolved": "https://registry.npmjs.org/readline2/-/readline2-1.0.1.tgz", 1270 | "integrity": "sha1-QQWWCP/BVHV7cV2ZidGZ/783LjU=", 1271 | "dev": true, 1272 | "requires": { 1273 | "code-point-at": "^1.0.0", 1274 | "is-fullwidth-code-point": "^1.0.0", 1275 | "mute-stream": "0.0.5" 1276 | } 1277 | }, 1278 | "rechoir": { 1279 | "version": "0.6.2", 1280 | "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", 1281 | "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", 1282 | "dev": true, 1283 | "requires": { 1284 | "resolve": "^1.1.6" 1285 | }, 1286 | "dependencies": { 1287 | "resolve": { 1288 | "version": "1.8.1", 1289 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz", 1290 | "integrity": "sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA==", 1291 | "dev": true, 1292 | "requires": { 1293 | "path-parse": "^1.0.5" 1294 | } 1295 | } 1296 | } 1297 | }, 1298 | "require-uncached": { 1299 | "version": "1.0.3", 1300 | "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", 1301 | "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", 1302 | "dev": true, 1303 | "requires": { 1304 | "caller-path": "^0.1.0", 1305 | "resolve-from": "^1.0.0" 1306 | } 1307 | }, 1308 | "resolve": { 1309 | "version": "0.6.3", 1310 | "resolved": "http://registry.npmjs.org/resolve/-/resolve-0.6.3.tgz", 1311 | "integrity": "sha1-3ZV5gufnNt699TtYpN2RdUV13UY=", 1312 | "dev": true 1313 | }, 1314 | "resolve-from": { 1315 | "version": "1.0.1", 1316 | "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", 1317 | "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", 1318 | "dev": true 1319 | }, 1320 | "restore-cursor": { 1321 | "version": "1.0.1", 1322 | "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", 1323 | "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", 1324 | "dev": true, 1325 | "requires": { 1326 | "exit-hook": "^1.0.0", 1327 | "onetime": "^1.0.0" 1328 | } 1329 | }, 1330 | "rimraf": { 1331 | "version": "2.6.2", 1332 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", 1333 | "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", 1334 | "requires": { 1335 | "glob": "^7.0.5" 1336 | } 1337 | }, 1338 | "run-async": { 1339 | "version": "0.1.0", 1340 | "resolved": "https://registry.npmjs.org/run-async/-/run-async-0.1.0.tgz", 1341 | "integrity": "sha1-yK1KXhEGYeQCp9IbUw4AnyX444k=", 1342 | "dev": true, 1343 | "requires": { 1344 | "once": "^1.3.0" 1345 | } 1346 | }, 1347 | "run-parallel": { 1348 | "version": "1.1.9", 1349 | "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.9.tgz", 1350 | "integrity": "sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q==", 1351 | "dev": true 1352 | }, 1353 | "rx-lite": { 1354 | "version": "3.1.2", 1355 | "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-3.1.2.tgz", 1356 | "integrity": "sha1-Gc5QLKVyZl87ZHsQk5+X/RYV8QI=", 1357 | "dev": true 1358 | }, 1359 | "safe-buffer": { 1360 | "version": "5.1.2", 1361 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 1362 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", 1363 | "dev": true 1364 | }, 1365 | "slice-ansi": { 1366 | "version": "0.0.4", 1367 | "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz", 1368 | "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=", 1369 | "dev": true 1370 | }, 1371 | "sprintf-js": { 1372 | "version": "1.0.3", 1373 | "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", 1374 | "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", 1375 | "dev": true 1376 | }, 1377 | "standard": { 1378 | "version": "10.0.3", 1379 | "resolved": "https://registry.npmjs.org/standard/-/standard-10.0.3.tgz", 1380 | "integrity": "sha512-JURZ+85ExKLQULckDFijdX5WHzN6RC7fgiZNSV4jFQVo+3tPoQGHyBrGekye/yf0aOfb4210EM5qPNlc2cRh4w==", 1381 | "dev": true, 1382 | "requires": { 1383 | "eslint": "~3.19.0", 1384 | "eslint-config-standard": "10.2.1", 1385 | "eslint-config-standard-jsx": "4.0.2", 1386 | "eslint-plugin-import": "~2.2.0", 1387 | "eslint-plugin-node": "~4.2.2", 1388 | "eslint-plugin-promise": "~3.5.0", 1389 | "eslint-plugin-react": "~6.10.0", 1390 | "eslint-plugin-standard": "~3.0.1", 1391 | "standard-engine": "~7.0.0" 1392 | }, 1393 | "dependencies": { 1394 | "chalk": { 1395 | "version": "1.1.3", 1396 | "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", 1397 | "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", 1398 | "dev": true, 1399 | "requires": { 1400 | "ansi-styles": "^2.2.1", 1401 | "escape-string-regexp": "^1.0.2", 1402 | "has-ansi": "^2.0.0", 1403 | "strip-ansi": "^3.0.0", 1404 | "supports-color": "^2.0.0" 1405 | } 1406 | }, 1407 | "doctrine": { 1408 | "version": "2.1.0", 1409 | "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", 1410 | "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", 1411 | "dev": true, 1412 | "requires": { 1413 | "esutils": "^2.0.2" 1414 | } 1415 | }, 1416 | "eslint": { 1417 | "version": "3.19.0", 1418 | "resolved": "https://registry.npmjs.org/eslint/-/eslint-3.19.0.tgz", 1419 | "integrity": "sha1-yPxiAcf0DdCJQbh8CFdnOGpnmsw=", 1420 | "dev": true, 1421 | "requires": { 1422 | "babel-code-frame": "^6.16.0", 1423 | "chalk": "^1.1.3", 1424 | "concat-stream": "^1.5.2", 1425 | "debug": "^2.1.1", 1426 | "doctrine": "^2.0.0", 1427 | "escope": "^3.6.0", 1428 | "espree": "^3.4.0", 1429 | "esquery": "^1.0.0", 1430 | "estraverse": "^4.2.0", 1431 | "esutils": "^2.0.2", 1432 | "file-entry-cache": "^2.0.0", 1433 | "glob": "^7.0.3", 1434 | "globals": "^9.14.0", 1435 | "ignore": "^3.2.0", 1436 | "imurmurhash": "^0.1.4", 1437 | "inquirer": "^0.12.0", 1438 | "is-my-json-valid": "^2.10.0", 1439 | "is-resolvable": "^1.0.0", 1440 | "js-yaml": "^3.5.1", 1441 | "json-stable-stringify": "^1.0.0", 1442 | "levn": "^0.3.0", 1443 | "lodash": "^4.0.0", 1444 | "mkdirp": "^0.5.0", 1445 | "natural-compare": "^1.4.0", 1446 | "optionator": "^0.8.2", 1447 | "path-is-inside": "^1.0.1", 1448 | "pluralize": "^1.2.1", 1449 | "progress": "^1.1.8", 1450 | "require-uncached": "^1.0.2", 1451 | "shelljs": "^0.7.5", 1452 | "strip-bom": "^3.0.0", 1453 | "strip-json-comments": "~2.0.1", 1454 | "table": "^3.7.8", 1455 | "text-table": "~0.2.0", 1456 | "user-home": "^2.0.0" 1457 | } 1458 | }, 1459 | "eslint-config-standard": { 1460 | "version": "10.2.1", 1461 | "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-10.2.1.tgz", 1462 | "integrity": "sha1-wGHk0GbzedwXzVYsZOgZtN1FRZE=", 1463 | "dev": true 1464 | }, 1465 | "eslint-plugin-promise": { 1466 | "version": "3.5.0", 1467 | "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-3.5.0.tgz", 1468 | "integrity": "sha1-ePu2/+BHIBYnVp6FpsU3OvKmj8o=", 1469 | "dev": true 1470 | }, 1471 | "eslint-plugin-standard": { 1472 | "version": "3.0.1", 1473 | "resolved": "https://registry.npmjs.org/eslint-plugin-standard/-/eslint-plugin-standard-3.0.1.tgz", 1474 | "integrity": "sha1-NNDJFbRe3G8BA5PH7vOCOwhWXPI=", 1475 | "dev": true 1476 | }, 1477 | "file-entry-cache": { 1478 | "version": "2.0.0", 1479 | "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", 1480 | "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", 1481 | "dev": true, 1482 | "requires": { 1483 | "flat-cache": "^1.2.1", 1484 | "object-assign": "^4.0.1" 1485 | } 1486 | }, 1487 | "lodash": { 1488 | "version": "4.17.11", 1489 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", 1490 | "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", 1491 | "dev": true 1492 | }, 1493 | "shelljs": { 1494 | "version": "0.7.8", 1495 | "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.7.8.tgz", 1496 | "integrity": "sha1-3svPh0sNHl+3LhSxZKloMEjprLM=", 1497 | "dev": true, 1498 | "requires": { 1499 | "glob": "^7.0.0", 1500 | "interpret": "^1.0.0", 1501 | "rechoir": "^0.6.2" 1502 | } 1503 | }, 1504 | "strip-bom": { 1505 | "version": "3.0.0", 1506 | "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", 1507 | "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", 1508 | "dev": true 1509 | }, 1510 | "strip-json-comments": { 1511 | "version": "2.0.1", 1512 | "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", 1513 | "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", 1514 | "dev": true 1515 | }, 1516 | "user-home": { 1517 | "version": "2.0.0", 1518 | "resolved": "https://registry.npmjs.org/user-home/-/user-home-2.0.0.tgz", 1519 | "integrity": "sha1-nHC/2Babwdy/SGBODwS4tJzenp8=", 1520 | "dev": true, 1521 | "requires": { 1522 | "os-homedir": "^1.0.0" 1523 | } 1524 | } 1525 | } 1526 | }, 1527 | "standard-engine": { 1528 | "version": "7.0.0", 1529 | "resolved": "https://registry.npmjs.org/standard-engine/-/standard-engine-7.0.0.tgz", 1530 | "integrity": "sha1-67d7nI/CyBZf+jU72Rug3/Qa9pA=", 1531 | "dev": true, 1532 | "requires": { 1533 | "deglob": "^2.1.0", 1534 | "get-stdin": "^5.0.1", 1535 | "minimist": "^1.1.0", 1536 | "pkg-conf": "^2.0.0" 1537 | }, 1538 | "dependencies": { 1539 | "get-stdin": { 1540 | "version": "5.0.1", 1541 | "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-5.0.1.tgz", 1542 | "integrity": "sha1-Ei4WFZHiH/TFJTAwVpPyDmOTo5g=", 1543 | "dev": true 1544 | }, 1545 | "minimist": { 1546 | "version": "1.2.0", 1547 | "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", 1548 | "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", 1549 | "dev": true 1550 | } 1551 | } 1552 | }, 1553 | "string-width": { 1554 | "version": "1.0.2", 1555 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", 1556 | "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", 1557 | "dev": true, 1558 | "requires": { 1559 | "code-point-at": "^1.0.0", 1560 | "is-fullwidth-code-point": "^1.0.0", 1561 | "strip-ansi": "^3.0.0" 1562 | } 1563 | }, 1564 | "string_decoder": { 1565 | "version": "1.1.1", 1566 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", 1567 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", 1568 | "dev": true, 1569 | "requires": { 1570 | "safe-buffer": "~5.1.0" 1571 | } 1572 | }, 1573 | "strip-ansi": { 1574 | "version": "3.0.1", 1575 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", 1576 | "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", 1577 | "dev": true, 1578 | "requires": { 1579 | "ansi-regex": "^2.0.0" 1580 | } 1581 | }, 1582 | "strip-json-comments": { 1583 | "version": "1.0.4", 1584 | "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-1.0.4.tgz", 1585 | "integrity": "sha1-HhX7ysl9Pumb8tc7TGVrCCu6+5E=", 1586 | "dev": true 1587 | }, 1588 | "supports-color": { 1589 | "version": "2.0.0", 1590 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", 1591 | "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", 1592 | "dev": true 1593 | }, 1594 | "table": { 1595 | "version": "3.8.3", 1596 | "resolved": "https://registry.npmjs.org/table/-/table-3.8.3.tgz", 1597 | "integrity": "sha1-K7xULw/amGGnVdOUf+/Ys/UThV8=", 1598 | "dev": true, 1599 | "requires": { 1600 | "ajv": "^4.7.0", 1601 | "ajv-keywords": "^1.0.0", 1602 | "chalk": "^1.1.1", 1603 | "lodash": "^4.0.0", 1604 | "slice-ansi": "0.0.4", 1605 | "string-width": "^2.0.0" 1606 | }, 1607 | "dependencies": { 1608 | "ajv": { 1609 | "version": "4.11.8", 1610 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", 1611 | "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", 1612 | "dev": true, 1613 | "requires": { 1614 | "co": "^4.6.0", 1615 | "json-stable-stringify": "^1.0.1" 1616 | } 1617 | }, 1618 | "ansi-regex": { 1619 | "version": "3.0.0", 1620 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", 1621 | "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", 1622 | "dev": true 1623 | }, 1624 | "is-fullwidth-code-point": { 1625 | "version": "2.0.0", 1626 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", 1627 | "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", 1628 | "dev": true 1629 | }, 1630 | "lodash": { 1631 | "version": "4.17.11", 1632 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", 1633 | "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", 1634 | "dev": true 1635 | }, 1636 | "string-width": { 1637 | "version": "2.1.1", 1638 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", 1639 | "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", 1640 | "dev": true, 1641 | "requires": { 1642 | "is-fullwidth-code-point": "^2.0.0", 1643 | "strip-ansi": "^4.0.0" 1644 | } 1645 | }, 1646 | "strip-ansi": { 1647 | "version": "4.0.0", 1648 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", 1649 | "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", 1650 | "dev": true, 1651 | "requires": { 1652 | "ansi-regex": "^3.0.0" 1653 | } 1654 | } 1655 | } 1656 | }, 1657 | "temp": { 1658 | "version": "0.8.3", 1659 | "resolved": "https://registry.npmjs.org/temp/-/temp-0.8.3.tgz", 1660 | "integrity": "sha1-4Ma8TSa5AxJEEOT+2BEDAU38H1k=", 1661 | "requires": { 1662 | "os-tmpdir": "^1.0.0", 1663 | "rimraf": "~2.2.6" 1664 | }, 1665 | "dependencies": { 1666 | "rimraf": { 1667 | "version": "2.2.8", 1668 | "resolved": "http://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", 1669 | "integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI=" 1670 | } 1671 | } 1672 | }, 1673 | "text-table": { 1674 | "version": "0.2.0", 1675 | "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", 1676 | "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", 1677 | "dev": true 1678 | }, 1679 | "through": { 1680 | "version": "2.3.8", 1681 | "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", 1682 | "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", 1683 | "dev": true 1684 | }, 1685 | "type-check": { 1686 | "version": "0.3.2", 1687 | "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", 1688 | "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", 1689 | "dev": true, 1690 | "requires": { 1691 | "prelude-ls": "~1.1.2" 1692 | } 1693 | }, 1694 | "typedarray": { 1695 | "version": "0.0.6", 1696 | "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", 1697 | "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", 1698 | "dev": true 1699 | }, 1700 | "underscore": { 1701 | "version": "1.8.3", 1702 | "resolved": "http://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz", 1703 | "integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=" 1704 | }, 1705 | "underscore-plus": { 1706 | "version": "1.6.8", 1707 | "resolved": "https://registry.npmjs.org/underscore-plus/-/underscore-plus-1.6.8.tgz", 1708 | "integrity": "sha512-88PrCeMKeAAC1L4xjSiiZ3Fg6kZOYrLpLGVPPeqKq/662DfQe/KTSKdSR/Q/tucKNnfW2MNAUGSCkDf8HmXC5Q==", 1709 | "requires": { 1710 | "underscore": "~1.8.3" 1711 | } 1712 | }, 1713 | "uniq": { 1714 | "version": "1.0.1", 1715 | "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz", 1716 | "integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=", 1717 | "dev": true 1718 | }, 1719 | "util-deprecate": { 1720 | "version": "1.0.2", 1721 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 1722 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", 1723 | "dev": true 1724 | }, 1725 | "wordwrap": { 1726 | "version": "0.0.3", 1727 | "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", 1728 | "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", 1729 | "dev": true 1730 | }, 1731 | "wrappy": { 1732 | "version": "1.0.2", 1733 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 1734 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" 1735 | }, 1736 | "write": { 1737 | "version": "0.2.1", 1738 | "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", 1739 | "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", 1740 | "dev": true, 1741 | "requires": { 1742 | "mkdirp": "^0.5.1" 1743 | } 1744 | }, 1745 | "xtend": { 1746 | "version": "4.0.1", 1747 | "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", 1748 | "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", 1749 | "dev": true 1750 | } 1751 | } 1752 | } 1753 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tabs", 3 | "version": "0.110.2", 4 | "main": "./lib/main", 5 | "description": "Display a selectable tab for each editor open.", 6 | "license": "MIT", 7 | "repository": "https://github.com/atom/tabs", 8 | "engines": { 9 | "atom": ">=1.17.0" 10 | }, 11 | "dependencies": { 12 | "fs-plus": "^3.0.0", 13 | "temp": "~0.8.1", 14 | "underscore-plus": "1.x" 15 | }, 16 | "consumedServices": { 17 | "atom.file-icons": { 18 | "versions": { 19 | "1.0.0": "consumeFileIcons" 20 | } 21 | }, 22 | "file-icons.element-icons": { 23 | "versions": { 24 | "1.0.0": "consumeElementIcons" 25 | } 26 | } 27 | }, 28 | "configSchema": { 29 | "showIcons": { 30 | "type": "boolean", 31 | "default": true, 32 | "description": "Show icons in tabs for panes which define an icon, such as the Settings and Project Find Results." 33 | }, 34 | "alwaysShowTabBar": { 35 | "type": "boolean", 36 | "default": true, 37 | "description": "Show the tab bar even when only one tab is open." 38 | }, 39 | "tabScrolling": { 40 | "type": [ 41 | "boolean", 42 | "string" 43 | ], 44 | "enum": [ 45 | true, 46 | false, 47 | "platform" 48 | ], 49 | "default": "platform", 50 | "description": "Jump to next or previous tab by scrolling on the tab bar." 51 | }, 52 | "tabScrollingThreshold": { 53 | "type": "integer", 54 | "default": 120, 55 | "description": "Threshold for switching to the next/previous tab when the `Tab Scrolling` config setting is enabled. Higher numbers mean that a longer scroll is needed to jump to the next/previous tab." 56 | }, 57 | "enableVcsColoring": { 58 | "type": "boolean", 59 | "title": "Enable VCS Coloring", 60 | "default": false, 61 | "description": "Color file names in tabs based on VCS status, similar to how file names are colored in the tree view." 62 | }, 63 | "addNewTabsAtEnd": { 64 | "type": "boolean", 65 | "default": false, 66 | "description": "Add new tabs at the end of the tab bar, rather than after active tab." 67 | }, 68 | "enableMruTabSwitching": { 69 | "type": "boolean", 70 | "title": "Enable MRU Tab Switching", 71 | "default": true, 72 | "description": "Enable tab switching in most-recently-used order. This setting has no effect if ctrl-tab or ctrl-shift-tab are already rebound via your keymap or another package." 73 | }, 74 | "displayMruTabList": { 75 | "type": "boolean", 76 | "title": "Display MRU Tab Switching List", 77 | "default": true, 78 | "description": "When MRU Tab Switching is enabled, display the most-recently-used tab list." 79 | } 80 | }, 81 | "devDependencies": { 82 | "coffeelint": "^1.9.7", 83 | "standard": "^10.0.3" 84 | }, 85 | "standard": { 86 | "env": { 87 | "atomtest": true, 88 | "browser": true, 89 | "jasmine": true, 90 | "node": true 91 | }, 92 | "globals": [ 93 | "atom" 94 | ], 95 | "ignore": [ 96 | "**/fixtures/*.js" 97 | ] 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /spec/async-spec-helpers.js: -------------------------------------------------------------------------------- 1 | function beforeEach (fn) { 2 | global.beforeEach(() => { 3 | const result = fn() 4 | if (result instanceof Promise) { 5 | waitsForPromise(() => result) 6 | } 7 | }) 8 | } 9 | 10 | function afterEach (fn) { 11 | global.afterEach(() => { 12 | const result = fn() 13 | if (result instanceof Promise) { 14 | waitsForPromise(() => result) 15 | } 16 | }) 17 | } 18 | 19 | ;['it', 'fit', 'ffit', 'fffit'].forEach(name => { 20 | exports[name] = (description, fn) => { 21 | if (fn === undefined) { 22 | global[name](description) 23 | return 24 | } 25 | 26 | global[name](description, () => { 27 | const result = fn() 28 | if (result instanceof Promise) { 29 | waitsForPromise(() => result) 30 | } 31 | }) 32 | } 33 | }) 34 | 35 | async function conditionPromise (condition, description = 'anonymous condition') { 36 | const startTime = Date.now() 37 | 38 | while (true) { 39 | await timeoutPromise(100) 40 | 41 | if (await condition()) { 42 | return 43 | } 44 | 45 | if (Date.now() - startTime > 5000) { 46 | throw new Error('Timed out waiting on ' + description) 47 | } 48 | } 49 | } 50 | 51 | function timeoutPromise (timeout) { 52 | return new Promise(resolve => { 53 | global.setTimeout(resolve, timeout) 54 | }) 55 | } 56 | 57 | function waitsForPromise (fn) { 58 | const promise = fn() 59 | global.waitsFor('spec promise to resolve', done => { 60 | promise.then(done, error => { 61 | jasmine.getEnv().currentSpec.fail(error) 62 | done() 63 | }) 64 | }) 65 | } 66 | 67 | function emitterEventPromise (emitter, event, timeout = 15000) { 68 | return new Promise((resolve, reject) => { 69 | const timeoutHandle = setTimeout(() => { 70 | reject(new Error(`Timed out waiting for '${event}' event`)) 71 | }, timeout) 72 | emitter.once(event, () => { 73 | clearTimeout(timeoutHandle) 74 | resolve() 75 | }) 76 | }) 77 | } 78 | 79 | function promisify (original) { 80 | return function (...args) { 81 | return new Promise((resolve, reject) => { 82 | args.push((err, ...results) => { 83 | if (err) { 84 | reject(err) 85 | } else { 86 | resolve(...results) 87 | } 88 | }) 89 | 90 | return original(...args) 91 | }) 92 | } 93 | } 94 | 95 | function promisifySome (obj, fnNames) { 96 | const result = {} 97 | for (const fnName of fnNames) { 98 | result[fnName] = promisify(obj[fnName]) 99 | } 100 | return result 101 | } 102 | 103 | exports.afterEach = afterEach 104 | exports.beforeEach = beforeEach 105 | exports.conditionPromise = conditionPromise 106 | exports.emitterEventPromise = emitterEventPromise 107 | exports.promisify = promisify 108 | exports.promisifySome = promisifySome 109 | exports.timeoutPromise = timeoutPromise 110 | -------------------------------------------------------------------------------- /spec/event-helpers.coffee: -------------------------------------------------------------------------------- 1 | buildMouseEvent = (type, target, {button, ctrlKey}={}) -> 2 | event = new MouseEvent(type, {bubbles: true, cancelable: true}) 3 | Object.defineProperty(event, 'button', get: -> button) if button? 4 | Object.defineProperty(event, 'ctrlKey', get: -> ctrlKey) if ctrlKey? 5 | Object.defineProperty(event, 'target', get: -> target) 6 | Object.defineProperty(event, 'srcObject', get: -> target) 7 | spyOn(event, "preventDefault") 8 | event 9 | 10 | module.exports.triggerMouseEvent = (type, target, {which, ctrlKey}={}) -> 11 | event = buildMouseEvent(arguments...) 12 | target.dispatchEvent(event) 13 | event 14 | 15 | module.exports.triggerClickEvent = (target, options) -> 16 | events = { 17 | mousedown: buildMouseEvent('mousedown', target, options), 18 | mouseup: buildMouseEvent('mouseup', target, options), 19 | click: buildMouseEvent('click', target, options) 20 | } 21 | 22 | target.dispatchEvent(events.mousedown) 23 | target.dispatchEvent(events.mouseup) 24 | target.dispatchEvent(events.click) 25 | 26 | events 27 | 28 | module.exports.buildDragEvents = (dragged, dropTarget) -> 29 | dataTransfer = 30 | data: {} 31 | setData: (key, value) -> @data[key] = "#{value}" # Drag events stringify data values 32 | getData: (key) -> @data[key] 33 | 34 | Object.defineProperty( 35 | dataTransfer, 36 | 'items', 37 | get: -> 38 | Object.keys(dataTransfer.data).map((key) -> {type: key}) 39 | ) 40 | 41 | dragStartEvent = buildMouseEvent("dragstart", dragged) 42 | Object.defineProperty(dragStartEvent, 'dataTransfer', get: -> dataTransfer) 43 | 44 | dropEvent = buildMouseEvent("drop", dropTarget) 45 | Object.defineProperty(dropEvent, 'dataTransfer', get: -> dataTransfer) 46 | 47 | [dragStartEvent, dropEvent] 48 | 49 | module.exports.buildWheelEvent = (delta) -> 50 | new WheelEvent("mousewheel", wheelDeltaY: delta) 51 | 52 | module.exports.buildWheelPlusShiftEvent = (delta) -> 53 | new WheelEvent("mousewheel", wheelDeltaY: delta, shiftKey: true) 54 | -------------------------------------------------------------------------------- /spec/event-helpers.js: -------------------------------------------------------------------------------- 1 | const buildMouseEvent = (type, target, {which, ctrlKey, relatedTarget} = {}) => { 2 | const event = new MouseEvent(type, {bubbles: true, cancelable: true}) 3 | if (which != null) { 4 | Object.defineProperty(event, 'which', { 5 | get () { 6 | return which 7 | } 8 | }) 9 | } 10 | 11 | if (ctrlKey != null) { 12 | Object.defineProperty(event, 'ctrlKey', { 13 | get () { 14 | return ctrlKey 15 | } 16 | }) 17 | } 18 | 19 | if (relatedTarget != null) { 20 | Object.defineProperty(event, 'relatedTarget', { 21 | get () { 22 | return relatedTarget 23 | } 24 | }) 25 | } 26 | 27 | Object.defineProperty(event, 'target', { 28 | get () { 29 | return target 30 | } 31 | }) 32 | 33 | Object.defineProperty(event, 'srcObject', { 34 | get () { 35 | return target 36 | } 37 | }) 38 | 39 | spyOn(event, 'preventDefault') 40 | return event 41 | } 42 | 43 | module.exports.triggerMouseEvent = (type, target, {which, ctrlKey} = {}) => { 44 | const event = buildMouseEvent(type, target, {which, ctrlKey}) 45 | target.dispatchEvent(event) 46 | return event 47 | } 48 | 49 | module.exports.triggerClickEvent = (target, options) => { 50 | const events = { 51 | mousedown: buildMouseEvent('mousedown', target, options), 52 | mouseup: buildMouseEvent('mouseup', target, options), 53 | click: buildMouseEvent('click', target, options) 54 | } 55 | 56 | target.dispatchEvent(events.mousedown) 57 | target.dispatchEvent(events.mouseup) 58 | target.dispatchEvent(events.click) 59 | 60 | return events 61 | } 62 | 63 | module.exports.buildDragEvents = (dragged, dropTarget) => { 64 | const dataTransfer = { 65 | data: {}, 66 | setData (key, value) { 67 | this.data[key] = `${value}` // Drag events stringify data values 68 | }, 69 | getData (key) { 70 | return this.data[key] 71 | }, 72 | clearData (key) { 73 | if (key) { 74 | delete this.data[key] 75 | } else { 76 | this.data = {} 77 | } 78 | } 79 | } 80 | 81 | Object.defineProperty( 82 | dataTransfer, 83 | 'items', { 84 | get () { 85 | return Object.keys(dataTransfer.data).map(key => ({type: key})) 86 | } 87 | } 88 | ) 89 | 90 | const dragStartEvent = buildMouseEvent('dragstart', dragged) 91 | Object.defineProperty(dragStartEvent, 'dataTransfer', { 92 | get () { 93 | return dataTransfer 94 | } 95 | }) 96 | 97 | const dropEvent = buildMouseEvent('drop', dropTarget) 98 | Object.defineProperty(dropEvent, 'dataTransfer', { 99 | get () { 100 | return dataTransfer 101 | } 102 | }) 103 | 104 | return [dragStartEvent, dropEvent] 105 | } 106 | 107 | module.exports.buildDragEnterLeaveEvents = (enterRelatedTarget, leaveRelatedTarget) => { 108 | const dataTransfer = { 109 | data: {}, 110 | setData (key, value) { 111 | this.data[key] = `${value}` // Drag events stringify data values 112 | }, 113 | getData (key) { 114 | return this.data[key] 115 | }, 116 | clearData (key) { 117 | if (key) { 118 | delete this.data[key] 119 | } else { 120 | this.data = {} 121 | } 122 | } 123 | } 124 | 125 | Object.defineProperty( 126 | dataTransfer, 127 | 'items', { 128 | get () { 129 | return Object.keys(dataTransfer.data).map(key => ({type: key})) 130 | } 131 | } 132 | ) 133 | 134 | const dragEnterEvent = buildMouseEvent('dragenter', null, {relatedTarget: enterRelatedTarget}) 135 | Object.defineProperty(dragEnterEvent, 'dataTransfer', { 136 | get () { 137 | return dataTransfer 138 | } 139 | }) 140 | dragEnterEvent.dataTransfer.setData('atom-tab-event', 'true') 141 | 142 | const dragLeaveEvent = buildMouseEvent('dragleave', null, {relatedTarget: leaveRelatedTarget}) 143 | Object.defineProperty(dragLeaveEvent, 'dataTransfer', { 144 | get () { 145 | return dataTransfer 146 | } 147 | }) 148 | dragLeaveEvent.dataTransfer.setData('atom-tab-event', 'true') 149 | 150 | return [dragEnterEvent, dragLeaveEvent] 151 | } 152 | 153 | module.exports.buildWheelEvent = delta => new WheelEvent('mousewheel', {wheelDeltaY: delta}) 154 | 155 | module.exports.buildWheelPlusShiftEvent = delta => new WheelEvent('mousewheel', {wheelDeltaY: delta, shiftKey: true}) 156 | -------------------------------------------------------------------------------- /spec/fixtures/sample.js: -------------------------------------------------------------------------------- 1 | var quicksort = function () { 2 | var sort = function(items) { 3 | if (items.length <= 1) return items; 4 | var pivot = items.shift(), current, left = [], right = []; 5 | while(items.length > 0) { 6 | current = items.shift(); 7 | current < pivot ? left.push(current) : right.push(current); 8 | } 9 | return sort(left).concat(pivot).concat(sort(right)); 10 | }; 11 | 12 | return sort(Array.apply(this, arguments)); 13 | }; -------------------------------------------------------------------------------- /spec/fixtures/sample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atom/tabs/2d13bfe426458fc64d84bcd4ef7498e783e90e86/spec/fixtures/sample.png -------------------------------------------------------------------------------- /spec/icon-services-spec.js: -------------------------------------------------------------------------------- 1 | /* eslint-env jasmine */ 2 | /* global waitsForPromise */ 3 | 4 | const path = require('path') 5 | const {Disposable} = require('atom') 6 | 7 | const DefaultFileIcons = require('../lib/default-file-icons') 8 | const getIconServices = require('../lib/get-icon-services') 9 | 10 | describe('icon services', () => { 11 | let iconServices, tab 12 | 13 | beforeEach(() => { 14 | iconServices = getIconServices() 15 | 16 | waitsForPromise(() => atom.workspace.open(path.join(__dirname, 'fixtures', 'sample.js'))) 17 | 18 | waitsForPromise(() => atom.packages.activatePackage('tabs')) 19 | 20 | runs(() => { 21 | tab = atom.workspace.getElement().querySelector('.tab') 22 | expect(tab.itemTitle.className).toBe('title') 23 | }) 24 | }) 25 | 26 | afterEach(() => { 27 | iconServices.resetElementIcons() 28 | iconServices.resetFileIcons() 29 | }) 30 | 31 | describe('atom.file-icons', () => { 32 | it('has a default handler', () => { 33 | expect(iconServices.fileIcons).toBe(DefaultFileIcons) 34 | }) 35 | 36 | it('shows no icons by default', () => { 37 | expect(DefaultFileIcons.iconClassForPath('foo.bar')).toEqual('') 38 | expect(DefaultFileIcons.iconClassForPath('README.md')).toEqual('') 39 | expect(DefaultFileIcons.iconClassForPath('foo.zip')).toEqual('') 40 | expect(DefaultFileIcons.iconClassForPath('foo.png')).toEqual('') 41 | expect(DefaultFileIcons.iconClassForPath('foo.pdf')).toEqual('') 42 | expect(DefaultFileIcons.iconClassForPath('foo.exe')).toEqual('') 43 | }) 44 | 45 | it('allows a service to replace the default', () => { 46 | const provider = {iconClassForPath: () => 'foo bar'} 47 | const disposable = atom.packages.serviceHub.provide('atom.file-icons', '1.0.0', provider) 48 | expect(iconServices.fileIcons).toBe(provider) 49 | expect(tab.itemTitle.className).toBe('title icon foo bar') 50 | disposable.dispose() 51 | expect(iconServices.fileIcons).toBe(DefaultFileIcons) 52 | expect(tab.itemTitle.className).toBe('title') 53 | }) 54 | 55 | it('accepts an array of strings as icon-classes', () => { 56 | const provider = {iconClassForPath: () => ['foo', 'bar']} 57 | const disposable = atom.packages.serviceHub.provide('atom.file-icons', '1.0.0', provider) 58 | expect(iconServices.fileIcons).toBe(provider) 59 | expect(tab.itemTitle.className).toBe('title icon foo bar') 60 | disposable.dispose() 61 | expect(iconServices.fileIcons).toBe(DefaultFileIcons) 62 | expect(tab.itemTitle.className).toBe('title') 63 | }) 64 | }) 65 | 66 | describe('file-icons.element-icons', () => { 67 | it('has no default handler', () => { 68 | expect(iconServices.elementIcons).toBe(null) 69 | }) 70 | 71 | it('uses the element-icon service if available', () => { 72 | const provider = (element) => { 73 | element.classList.add('foo', 'bar') 74 | return new Disposable(() => { 75 | element.classList.remove('foo', 'bar', 'icon') 76 | }) 77 | } 78 | const disposable = atom.packages.serviceHub.provide('file-icons.element-icons', '1.0.0', provider) 79 | expect(iconServices.elementIcons).toBe(provider) 80 | expect(tab.itemTitle.className).toBe('title icon foo bar') 81 | disposable.dispose() 82 | expect(iconServices.elementIcons).toBe(null) 83 | }) 84 | }) 85 | 86 | describe('when both services are provided', () => { 87 | it('gives priority to the element-icon service', () => { 88 | const basicProvider = {iconClassForPath: () => 'foo'} 89 | const elementProvider = (element) => { 90 | element.classList.add('bar') 91 | return new Disposable(() => { 92 | element.classList.remove('bar') 93 | }) 94 | } 95 | atom.packages.serviceHub.provide('atom.file-icons', '1.0.0', basicProvider) 96 | atom.packages.serviceHub.provide('file-icons.element-icons', '1.0.0', elementProvider) 97 | expect(iconServices.fileIcons).toBe(basicProvider) 98 | expect(iconServices.elementIcons).toBe(elementProvider) 99 | expect(tab.itemTitle.className).toBe('title icon bar') 100 | }) 101 | }) 102 | }) 103 | -------------------------------------------------------------------------------- /spec/mru-list-spec.js: -------------------------------------------------------------------------------- 1 | const {it, fit, ffit, beforeEach, afterEach} = require('./async-spec-helpers') // eslint-disable-line no-unused-vars 2 | 3 | const fs = require('fs-plus') 4 | const path = require('path') 5 | const temp = require('temp').track() 6 | 7 | describe('MRU List', () => { 8 | let workspaceElement = null 9 | const enableMruConfigKey = 'tabs.enableMruTabSwitching' 10 | const displayMruTabListConfigKey = 'tabs.displayMruTabList' 11 | 12 | beforeEach(async () => { 13 | workspaceElement = atom.workspace.getElement() 14 | 15 | await atom.workspace.open('sample.js') 16 | 17 | await atom.packages.activatePackage('tabs') 18 | }) 19 | 20 | describe('.activate()', () => { 21 | const initialPaneCount = atom.workspace.getPanes().length 22 | 23 | it('has exactly one modal panel per pane', async () => { 24 | expect(workspaceElement.querySelectorAll('.tabs-mru-switcher').length).toBe(initialPaneCount) 25 | 26 | let pane = atom.workspace.getActivePane() 27 | pane.splitRight() 28 | expect(workspaceElement.querySelectorAll('.tabs-mru-switcher').length).toBe(initialPaneCount + 1) 29 | 30 | pane = atom.workspace.getActivePane() 31 | pane.splitDown() 32 | expect(workspaceElement.querySelectorAll('.tabs-mru-switcher').length).toBe(initialPaneCount + 2) 33 | 34 | pane = atom.workspace.getActivePane() 35 | await Promise.resolve(pane.close()) 36 | 37 | expect(workspaceElement.querySelectorAll('.tabs-mru-switcher').length).toBe(initialPaneCount + 1) 38 | 39 | pane = atom.workspace.getActivePane() 40 | await Promise.resolve(pane.close()) 41 | 42 | expect(workspaceElement.querySelectorAll('.tabs-mru-switcher').length).toBe(initialPaneCount) 43 | }) 44 | 45 | it("Doesn't build list until activated for the first time", () => { 46 | expect(workspaceElement.querySelectorAll('.tabs-mru-switcher').length).toBe(initialPaneCount) 47 | expect(workspaceElement.querySelectorAll('.tabs-mru-switcher li').length).toBe(0) 48 | }) 49 | 50 | it("Doesn't activate when a single pane item is open", () => { 51 | const pane = atom.workspace.getActivePane() 52 | atom.commands.dispatch(pane, 'pane:show-next-recently-used-item') 53 | expect(workspaceElement.querySelectorAll('.tabs-mru-switcher li').length).toBe(0) 54 | }) 55 | }) 56 | 57 | describe('contents', () => { 58 | let pane = null 59 | const realSetTimeout = window.setTimeout 60 | 61 | beforeEach(async () => { 62 | // The MRU tab list is deliberately delayed before display. 63 | // Here we mock window.setTimeout rather than introducing a corresponding delay in tests 64 | // because faster tests are better. 65 | jasmine.getGlobal().setTimeout = (callback) => callback() 66 | await atom.workspace.open('sample.png') 67 | pane = atom.workspace.getActivePane() 68 | }) 69 | 70 | afterEach(() => { 71 | jasmine.getGlobal().setTimeout = realSetTimeout 72 | }) 73 | 74 | it('has one item per tab', () => { 75 | if (pane.onChooseNextMRUItem != null) { 76 | expect(pane.getItems().length).toBe(2) 77 | atom.commands.dispatch(workspaceElement, 'pane:show-next-recently-used-item') 78 | expect(workspaceElement.querySelectorAll('.tabs-mru-switcher li').length).toBe(2) 79 | } 80 | }) 81 | 82 | it('switches between two items', () => { 83 | const firstActiveItem = pane.getActiveItem() 84 | atom.commands.dispatch(workspaceElement, 'pane:show-next-recently-used-item') 85 | const secondActiveItem = pane.getActiveItem() 86 | expect(secondActiveItem).toNotBe(firstActiveItem) 87 | atom.commands.dispatch(workspaceElement, 'pane:move-active-item-to-top-of-stack') 88 | const thirdActiveItem = pane.getActiveItem() 89 | expect(thirdActiveItem).toBe(secondActiveItem) 90 | atom.commands.dispatch(workspaceElement, 'pane:show-next-recently-used-item') 91 | atom.commands.dispatch(workspaceElement, 'pane:move-active-item-to-top-of-stack') 92 | const fourthActiveItem = pane.getActiveItem() 93 | expect(fourthActiveItem).toBe(firstActiveItem) 94 | }) 95 | 96 | it('disables display when configured to', () => { 97 | atom.config.set(displayMruTabListConfigKey, false) 98 | expect(atom.config.get(displayMruTabListConfigKey)).toBe(false) 99 | if (pane.onChooseNextMRUItem != null) { 100 | expect(pane.getItems().length).toBe(2) 101 | atom.commands.dispatch(workspaceElement, 'pane:show-next-recently-used-item') 102 | expect(workspaceElement.querySelectorAll('.tabs-mru-switcher li').length).toBe(0) 103 | } 104 | }) 105 | }) 106 | 107 | describe('config', () => { 108 | let dotAtomPath = null 109 | 110 | beforeEach(() => { 111 | dotAtomPath = temp.path('tabs-spec-mru-config') 112 | atom.config.configDirPath = dotAtomPath 113 | atom.config.configFilePath = path.join(atom.config.configDirPath, 'atom.config.cson') 114 | atom.keymaps.configDirPath = dotAtomPath 115 | }) 116 | 117 | afterEach(() => fs.removeSync(dotAtomPath)) 118 | 119 | it('defaults on', () => { 120 | expect(atom.config.get(enableMruConfigKey)).toBe(true) 121 | expect(atom.config.get(displayMruTabListConfigKey)).toBe(true) 122 | 123 | let bindings = atom.keymaps.findKeyBindings({ 124 | target: document.body, 125 | keystrokes: 'ctrl-tab' 126 | }) 127 | expect(bindings.length).toBe(1) 128 | expect(bindings[0].command).toBe('pane:show-next-recently-used-item') 129 | 130 | bindings = atom.keymaps.findKeyBindings({ 131 | target: document.body, 132 | keystrokes: 'ctrl-tab ^ctrl' 133 | }) 134 | expect(bindings.length).toBe(1) 135 | expect(bindings[0].command).toBe('pane:move-active-item-to-top-of-stack') 136 | 137 | bindings = atom.keymaps.findKeyBindings({ 138 | target: document.body, 139 | keystrokes: 'ctrl-shift-tab' 140 | }) 141 | expect(bindings.length).toBe(1) 142 | expect(bindings[0].command).toBe('pane:show-previous-recently-used-item') 143 | 144 | bindings = atom.keymaps.findKeyBindings({ 145 | target: document.body, 146 | keystrokes: 'ctrl-shift-tab ^ctrl' 147 | }) 148 | expect(bindings.length).toBe(1) 149 | expect(bindings[0].command).toBe('pane:move-active-item-to-top-of-stack') 150 | }) 151 | 152 | it('alters keybindings when disabled', () => { 153 | atom.config.set(enableMruConfigKey, false) 154 | let bindings = atom.keymaps.findKeyBindings({ 155 | target: document.body, 156 | keystrokes: 'ctrl-tab' 157 | }) 158 | expect(bindings.length).toBe(2) 159 | expect(bindings[0].command).toBe('pane:show-next-item') 160 | 161 | bindings = atom.keymaps.findKeyBindings({ 162 | target: document.body, 163 | keystrokes: 'ctrl-tab ^ctrl' 164 | }) 165 | expect(bindings.length).toBe(2) 166 | expect(bindings[0].command).toBe('unset!') 167 | 168 | bindings = atom.keymaps.findKeyBindings({ 169 | target: document.body, 170 | keystrokes: 'ctrl-shift-tab' 171 | }) 172 | expect(bindings.length).toBe(2) 173 | expect(bindings[0].command).toBe('pane:show-previous-item') 174 | 175 | bindings = atom.keymaps.findKeyBindings({ 176 | target: document.body, 177 | keystrokes: 'ctrl-shift-tab ^ctrl' 178 | }) 179 | expect(bindings.length).toBe(2) 180 | expect(bindings[0].command).toBe('unset!') 181 | }) 182 | }) 183 | }) 184 | -------------------------------------------------------------------------------- /styles/layout.less: -------------------------------------------------------------------------------- 1 | .tabs-layout-overlay { 2 | background: grey; 3 | position: absolute; 4 | z-index: 11; // Make sure this covers absolutely-positioned docks 5 | opacity: 0; 6 | pointer-events: none; 7 | transition: all 0.2s; 8 | &.visible { 9 | opacity: 0.1; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /styles/tabs-mru-switcher.less: -------------------------------------------------------------------------------- 1 | 2 | .tabs-mru-switcher { 3 | .list-group.list-group { 4 | margin-top: 0; 5 | 6 | // Fix to make the scrollbar appear 7 | // Not necessary for all themes 8 | backface-visibility: hidden; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /styles/tabs.less: -------------------------------------------------------------------------------- 1 | @import "ui-variables"; 2 | @import "octicon-mixins"; 3 | 4 | @close-icon-size: 12px; 5 | 6 | .tab-bar { 7 | display: flex; 8 | -webkit-user-select: none; 9 | margin: 0; 10 | 11 | .tab { 12 | font-size: 11px; 13 | position: relative; 14 | padding-left: 10px; 15 | padding-right: 10px + @close-icon-size + 2px; 16 | -webkit-user-drag: element; 17 | flex: 1; 18 | max-width: 175px; 19 | min-width: 40px; 20 | transition: max-width .25s ease-in-out; 21 | 22 | &.active { 23 | flex: 2; 24 | width: -webkit-fit-content; 25 | 26 | .title { 27 | padding-right: 10px; 28 | } 29 | } 30 | 31 | .title { 32 | overflow: hidden; 33 | white-space: pre; 34 | text-overflow: ellipsis; 35 | &.temp { 36 | font-style: italic; 37 | } 38 | } 39 | 40 | .hide-icon { 41 | &.icon:before { 42 | display: none; 43 | } 44 | } 45 | 46 | .close-icon { 47 | .octicon(x, @close-icon-size); 48 | position: absolute; 49 | top: 1px; 50 | right: 10px; 51 | cursor: default; 52 | } 53 | 54 | &.modified:hover .close-icon { 55 | color: @background-color-info; 56 | } 57 | 58 | &.modified:not(:hover) .close-icon { 59 | &:before { display: none; } 60 | top: 11px; 61 | right: 11px; 62 | width: 8px; 63 | height: 8px; 64 | border: 2px solid @background-color-info; 65 | border-radius: 12px; 66 | } 67 | } 68 | 69 | /* Drag and Drop */ 70 | .placeholder { 71 | position: relative; 72 | top: 0; 73 | padding: 0; 74 | height: @tab-height; 75 | z-index: 999; 76 | list-style: none; 77 | background: @background-color-info; 78 | pointer-events: none; 79 | 80 | // bar 81 | &:before { 82 | content: ""; 83 | position: absolute; 84 | width: 2px; 85 | margin: -1px; padding: 0; 86 | height: inherit; 87 | background: inherit; 88 | } 89 | 90 | // dot 91 | &:after { 92 | content: ""; 93 | position: absolute; 94 | top: @tab-height; 95 | margin-top: -1px; 96 | margin-left: -2px; 97 | z-index: 9999; 98 | width: 4px; 99 | height: 4px; 100 | background: inherit; 101 | border-radius: 4px; 102 | border: 1px solid transparent; 103 | } 104 | } 105 | 106 | &.hidden { 107 | display: none; 108 | } 109 | } 110 | --------------------------------------------------------------------------------