├── .eslintignore ├── .eslintrc.json ├── .github ├── dependabot.yml └── workflows │ └── ci.yml ├── .gitignore ├── .remarkignore ├── .remarkrc ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── keymaps └── flex-tool-bar.cson ├── lib ├── defaults │ ├── toolbar.coffee │ ├── toolbar.cson │ ├── toolbar.js │ ├── toolbar.json │ └── toolbar.json5 ├── flex-tool-bar.js ├── types │ ├── button.js │ ├── file.js │ ├── function.js │ ├── spacer.js │ └── url.js └── url-replace.js ├── menus └── flex-toolbar.cson ├── package-lock.json ├── package.json ├── release.config.js ├── semantic-release.sh ├── spec ├── fixtures │ ├── config │ │ ├── config.coffee │ │ ├── config.cson │ │ ├── config.js │ │ ├── config.json │ │ └── config.json5 │ ├── pixel.jpg │ ├── pixel.png │ ├── project1 │ │ ├── sample.js │ │ └── toolbar.cson │ ├── project2 │ │ ├── sample.js │ │ └── toolbar.cson │ ├── project3 │ │ └── sample.js │ ├── sample.js │ └── toolbar.cson ├── flex-tool-bar-spec.js └── runner.js └── styles └── flex-tool-bar.less /.eslintignore: -------------------------------------------------------------------------------- 1 | # Ignore third-party packages 2 | node_modules 3 | 4 | # Ignore third-party icon sets 5 | iconsets 6 | 7 | # Ignore Atom folder when running Travis 8 | atom 9 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "es6": true, 4 | "node": true, 5 | "jasmine": true, 6 | "atomtest": true 7 | }, 8 | "parserOptions": { 9 | "sourceType": "module", 10 | "ecmaVersion": 2018, 11 | "ecmaFeatures": { 12 | "impliedStrict": true 13 | } 14 | }, 15 | "globals": { 16 | "atom": false, 17 | "snapshotResult": false 18 | }, 19 | "extends": "eslint:recommended", 20 | "rules": { 21 | "valid-jsdoc": 1, 22 | 23 | "block-scoped-var": 2, 24 | "curly": 2, 25 | "default-case": 2, 26 | "dot-location": [2, "property"], 27 | "eqeqeq": [2, "always", {"null": "ignore"}], 28 | "no-console": 1, 29 | "no-debugger": 1, 30 | "no-else-return": 1, 31 | "no-eval": 2, 32 | "no-loop-func": 1, 33 | "no-multi-spaces": 1, 34 | "no-unused-expressions": 1, 35 | "no-unused-vars": 1, 36 | "no-warning-comments": 1, 37 | "no-with": 2, 38 | "require-await": 2, 39 | "strict": 1, 40 | 41 | "no-restricted-globals": [ 42 | "error", 43 | { 44 | "name": "fit", 45 | "message": "Do not commit focused tests." 46 | }, 47 | { 48 | "name": "ffit", 49 | "message": "Do not commit focused tests." 50 | }, 51 | { 52 | "name": "fffit", 53 | "message": "Do not commit focused tests." 54 | }, 55 | { 56 | "name": "fdescribe", 57 | "message": "Do not commit focused tests." 58 | }, 59 | { 60 | "name": "ffdescribe", 61 | "message": "Do not commit focused tests." 62 | }, 63 | { 64 | "name": "fffdescribe", 65 | "message": "Do not commit focused tests." 66 | } 67 | ], 68 | "no-shadow": 1, 69 | "no-undef": 2, 70 | "no-undefined": 2, 71 | "no-use-before-define": 2, 72 | "no-sync": 1, 73 | 74 | "array-bracket-spacing": 2, 75 | "block-spacing": 2, 76 | "brace-style": [2, "1tbs"], 77 | "comma-spacing": 2, 78 | "comma-style": 2, 79 | "computed-property-spacing": 2, 80 | "eol-last": 1, 81 | "func-call-spacing": 2, 82 | "indent": ["error", "tab", {"SwitchCase": 1}], 83 | "key-spacing": 2, 84 | "keyword-spacing": 2, 85 | "line-comment-position": 1, 86 | "linebreak-style": 2, 87 | "lines-around-comment": 2, 88 | "lines-between-class-members": 2, 89 | "multiline-comment-style": 1, 90 | "new-parens": 2, 91 | "no-array-constructor": 2, 92 | "no-mixed-spaces-and-tabs": [2, "smart-tabs"], 93 | "no-trailing-spaces": 2, 94 | "no-whitespace-before-property": 2, 95 | "object-curly-newline": [2, {"consistent": true}], 96 | "quotes": [1, "single"], 97 | "semi": 2, 98 | "space-before-blocks": 2, 99 | "space-before-function-paren": [2, {"anonymous": "always", "named": "never", "asyncArrow": "always"}], 100 | "space-in-parens": 2, 101 | "space-infix-ops": 2, 102 | "space-unary-ops": 2, 103 | "spaced-comment": 1, 104 | "switch-colon-spacing": 2, 105 | 106 | "arrow-spacing": 2, 107 | "prefer-const": 1, 108 | "prefer-destructuring": 1, 109 | "prefer-rest-params": 1, 110 | "prefer-spread": 1, 111 | "prefer-template": 1, 112 | "rest-spread-spacing": 2, 113 | "template-curly-spacing": 2 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | - package-ecosystem: "npm" 8 | directory: "/" 9 | schedule: 10 | interval: "daily" 11 | versioning-strategy: "increase" 12 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: "CI" 2 | on: 3 | pull_request: 4 | push: 5 | branches: 6 | - master 7 | 8 | env: 9 | CI: true 10 | 11 | jobs: 12 | Test: 13 | if: "!contains(github.event.head_commit.message, '[skip ci]')" 14 | strategy: 15 | fail-fast: false 16 | matrix: 17 | os: [ubuntu-latest, macos-latest, windows-latest] 18 | channel: [stable, beta] 19 | runs-on: ${{ matrix.os }} 20 | steps: 21 | - name: Checkout Code 22 | uses: actions/checkout@v3 23 | - name: Install Atom 24 | uses: UziTech/action-setup-atom@v1 25 | with: 26 | channel: ${{ matrix.channel }} 27 | - name: Atom Version 28 | run: atom -v 29 | - name: APM Version 30 | run: apm -v 31 | - name: Install Dependencies 32 | run: apm ci 33 | - name: Install Package Dependencies 34 | run: apm install tool-bar 35 | - name: Test 👩🏾‍💻 36 | run: atom --test spec 37 | 38 | Lint: 39 | if: "!contains(github.event.head_commit.message, '[skip ci]')" 40 | runs-on: ubuntu-latest 41 | steps: 42 | - name: Checkout Code 43 | uses: actions/checkout@v3 44 | - name: Install Node 45 | uses: dcodeIO/setup-node-nvm@master 46 | with: 47 | node-version: 'lts/*' 48 | - name: Install Dependencies 49 | run: npm ci 50 | - name: Lint ✨ 51 | run: npm run lint 52 | 53 | Release: 54 | needs: [Test, Lint] 55 | if: | 56 | github.ref == 'refs/heads/master' && 57 | github.event.repository.fork == false 58 | runs-on: ubuntu-latest 59 | steps: 60 | - name: Checkout Code 61 | uses: actions/checkout@v3 62 | - name: Install Atom 63 | uses: UziTech/action-setup-atom@v1 64 | - name: Install Node 65 | uses: dcodeIO/setup-node-nvm@master 66 | with: 67 | node-version: 'lts/*' 68 | - name: Install Dependencies 69 | run: npm ci 70 | - name: Release 🎉 71 | env: 72 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 73 | ATOM_ACCESS_TOKEN: ${{ secrets.ATOM_ACCESS_TOKEN }} 74 | run: npx semantic-release 75 | 76 | Skip: 77 | if: contains(github.event.head_commit.message, '[skip ci]') 78 | runs-on: ubuntu-latest 79 | steps: 80 | - name: Skip CI 🚫 81 | run: echo skip ci 82 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | npm-debug.log 3 | node_modules 4 | ./toolbar.json 5 | -------------------------------------------------------------------------------- /.remarkignore: -------------------------------------------------------------------------------- 1 | # Ignore third-party packages 2 | node_modules 3 | 4 | # Ignore third-party icon sets 5 | iconsets 6 | 7 | # Ignore Atom folder when running Travis 8 | atom 9 | 10 | -------------------------------------------------------------------------------- /.remarkrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "remark-preset-lint-recommended" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [2.2.7](https://github.com/cakecatz/flex-toolbar/compare/v2.2.6...v2.2.7) (2021-03-25) 2 | 3 | 4 | ### Bug Fixes 5 | 6 | * fix grammar condition when substring of editor grammar ([3618f34](https://github.com/cakecatz/flex-toolbar/commit/3618f342ce6df2e6b531a7582c67608a26084522)) 7 | 8 | ## [2.2.6](https://github.com/cakecatz/flex-toolbar/compare/v2.2.5...v2.2.6) (2021-03-24) 9 | 10 | 11 | ### Bug Fixes 12 | 13 | * **deps:** bump atom-package-deps from 7.2.2 to 7.2.3 ([#232](https://github.com/cakecatz/flex-toolbar/issues/232)) ([e80bf08](https://github.com/cakecatz/flex-toolbar/commit/e80bf08dec6054b129c5b1c17a599d4fd93d2db2)) 14 | 15 | ## [2.2.5](https://github.com/cakecatz/flex-toolbar/compare/v2.2.4...v2.2.5) (2021-02-15) 16 | 17 | 18 | ### Bug Fixes 19 | 20 | * **deps:** bump atom-package-deps from 7.2.0 to 7.2.1 ([#219](https://github.com/cakecatz/flex-toolbar/issues/219)) ([79eb5dc](https://github.com/cakecatz/flex-toolbar/commit/79eb5dc88cebccf46890f64769b925595695f465)) 21 | 22 | ## [2.2.4](https://github.com/cakecatz/flex-toolbar/compare/v2.2.3...v2.2.4) (2021-01-18) 23 | 24 | 25 | ### Bug Fixes 26 | 27 | * **deps:** bump atom-package-deps from 7.0.3 to 7.1.0 ([#210](https://github.com/cakecatz/flex-toolbar/issues/210)) ([623a9c1](https://github.com/cakecatz/flex-toolbar/commit/623a9c1e21dd1c78f69a4ae9e319cf3a3f3f4bb2)) 28 | 29 | ## [2.2.3](https://github.com/cakecatz/flex-toolbar/compare/v2.2.2...v2.2.3) (2020-12-08) 30 | 31 | 32 | ### Bug Fixes 33 | 34 | * **deps:** bump atom-package-deps from 7.0.2 to 7.0.3 ([#203](https://github.com/cakecatz/flex-toolbar/issues/203)) ([7f25f6c](https://github.com/cakecatz/flex-toolbar/commit/7f25f6c8d905d82d4c9c8c461d4433bcdbbaca68)) 35 | 36 | ## [2.2.2](https://github.com/cakecatz/flex-toolbar/compare/v2.2.1...v2.2.2) (2020-12-02) 37 | 38 | 39 | ### Bug Fixes 40 | 41 | * **deps:** Bump change-case from 4.1.1 to 4.1.2 ([#201](https://github.com/cakecatz/flex-toolbar/issues/201)) ([5f957cf](https://github.com/cakecatz/flex-toolbar/commit/5f957cf9087f1e7cc26b09d787c3e9d28480fb20)) 42 | 43 | ## [2.2.1](https://github.com/cakecatz/flex-toolbar/compare/v2.2.0...v2.2.1) (2020-09-12) 44 | 45 | 46 | ### Bug Fixes 47 | 48 | * **deps:** update dependencies ([45f22da](https://github.com/cakecatz/flex-toolbar/commit/45f22da09b16196a382fadbf07c581e76b5ae790)) 49 | 50 | # [2.2.0](https://github.com/cakecatz/flex-toolbar/compare/v2.1.4...v2.2.0) (2020-03-12) 51 | 52 | 53 | ### Features 54 | 55 | * add `background`, `color`, and `class` options ([8c0740f](https://github.com/cakecatz/flex-toolbar/commit/8c0740fa12b2ca37cf5409915044d3f2612fbe8c)) 56 | 57 | ## [2.1.4](https://github.com/cakecatz/flex-toolbar/compare/v2.1.3...v2.1.4) (2019-08-05) 58 | 59 | 60 | ### Bug Fixes 61 | 62 | * **package condition:** Check if package is loaded not just active ([b248b1a](https://github.com/cakecatz/flex-toolbar/commit/b248b1a)), closes [#158](https://github.com/cakecatz/flex-toolbar/issues/158) 63 | 64 | ## [2.1.3](https://github.com/cakecatz/flex-toolbar/compare/v2.1.2...v2.1.3) (2019-04-23) 65 | 66 | 67 | ### Bug Fixes 68 | 69 | * **deps:** Add semantic-release ([6719a92](https://github.com/cakecatz/flex-toolbar/commit/6719a92)) 70 | * Fix reloading config on change 71 | 72 | ## [v2.1.2](https://github.com/cakecatz/flex-toolbar/compare/v2.1.1...v2.1.2) - 2018-08-01 73 | - Add an error when config is not an array [#153](https://github.com/cakecatz/flex-toolbar/issues/153) 74 | 75 | ## [v2.1.1](https://github.com/cakecatz/flex-toolbar/compare/v2.1.0...v2.1.1) - 2018-07-09 76 | - Use Atom's PathWatcher to watch the config files [#151](https://github.com/cakecatz/flex-toolbar/pull/151) (by [@UziTech](https://github.com/UziTech)) 77 | 78 | ## [v2.1.0](https://github.com/cakecatz/flex-toolbar/compare/v2.0.2...v2.1.0) - 2018-05-30 79 | - Add hover style [#149](https://github.com/cakecatz/flex-toolbar/pull/149) (by [@paradoXp](https://github.com/paradoXp)) 80 | 81 | ## [v2.0.2](https://github.com/cakecatz/flex-toolbar/compare/v2.0.1...v2.0.2) - 2018-04-11 82 | - Fix deactivate flex-tool-bar [#146](https://github.com/cakecatz/flex-toolbar/issues/146) 83 | 84 | ## [v2.0.1](https://github.com/cakecatz/flex-toolbar/compare/v2.0.0...v2.0.1) - 2018-04-11 85 | - Change default config file back to `.cson` since examples are written using CSON 86 | - Remove text/html hack since [tool-bar v1.1.7](https://github.com/suda/tool-bar/releases/tag/v1.1.7) allows text/html 87 | 88 | ## [v2.0.0](https://github.com/cakecatz/flex-toolbar/compare/v1.1.0...v2.0.0) - 2018-03-16 89 | - BREAKING: Get active item from workplace center [#139](https://github.com/cakecatz/flex-toolbar/pull/139) (by [@UziTech](https://github.com/UziTech)) 90 | 91 | ## [v1.1.0](https://github.com/cakecatz/flex-toolbar/compare/v1.0.0...v1.1.0) - 2018-03-08 92 | - Fix `snapshotResult is not defined` [#137](https://github.com/cakecatz/flex-toolbar/issues/137) 93 | - Text and HTML options [#125](https://github.com/cakecatz/flex-toolbar/pull/125) (by [@UziTech](https://github.com/UziTech)) 94 | - Use project repo for url if no active item repo is found 95 | 96 | ## [v1.0.0](https://github.com/cakecatz/flex-toolbar/compare/v0.16.0...v1.0.0) - 2018-03-07 97 | - Change default config file to `.js` 98 | - Move to JavaScript [#135](https://github.com/cakecatz/flex-toolbar/pull/135) (by [@UziTech](https://github.com/UziTech)) 99 | - Detect grammar change [#136](https://github.com/cakecatz/flex-toolbar/issues/136) 100 | - Add file type [#72](https://github.com/cakecatz/flex-toolbar/pull/72) (by [@ryanjbonnell](https://github.com/ryanjbonnell)) 101 | 102 | ## [v0.16.0](https://github.com/cakecatz/flex-toolbar/compare/v0.15.1...v0.16.0) - 2018-02-15 103 | - Add setting conditions 104 | - Fix when project toolbar config is deleted 105 | - Add function conditions [#127](https://github.com/cakecatz/flex-toolbar/pull/127) (by [@UziTech](https://github.com/UziTech)) 106 | - Observe changes to settings and better error handling 107 | 108 | ## [v0.15.1](https://github.com/cakecatz/flex-toolbar/compare/v0.15.0...v0.15.1) - 2018-01-22 109 | - Fix getGrammar() is undefined [#131](https://github.com/cakecatz/flex-toolbar/issues/131) 110 | 111 | ## [v0.15.0](https://github.com/cakecatz/flex-toolbar/compare/v0.14.0...v0.15.0) - 2018-01-19 112 | - Add project config path setting [#128](https://github.com/cakecatz/flex-toolbar/pull/128) (by [@malnvenshorn](https://github.com/malnvenshorn)) 113 | - Add persistent project tool bar setting [#129](https://github.com/cakecatz/flex-toolbar/pull/129) (by [@UziTech](https://github.com/UziTech)) 114 | 115 | ## [v0.14.0](https://github.com/cakecatz/flex-toolbar/compare/v0.13.2...v0.14.0) - 2018-01-17 116 | - Add package conditions [#126](https://github.com/cakecatz/flex-toolbar/pull/126) (by [@UziTech](https://github.com/UziTech)) 117 | 118 | ## [v0.13.2](https://github.com/cakecatz/flex-toolbar/compare/v0.13.1...v0.13.2) - 2018-01-16 119 | - Fix pattern matching images [#73](https://github.com/cakecatz/flex-toolbar/issues/73) 120 | 121 | ## [v0.13.1](https://github.com/cakecatz/flex-toolbar/compare/v0.13.0...v0.13.1) - 2018-01-16 122 | - Fix file pattern matching [#57](https://github.com/cakecatz/flex-toolbar/issues/57) 123 | 124 | ## [v0.13.0](https://github.com/cakecatz/flex-toolbar/compare/v0.12.0...v0.13.0) - 2018-01-15 125 | - Fix updating toolbar on config change [PR #114](https://github.com/cakecatz/flex-toolbar/pull/114) (by [@UziTech](https://github.com/UziTech)) 126 | - Add information about icon sets to the README [PR #199](https://github.com/cakecatz/flex-toolbar/pull/119) (by [@TJProgrammer](https://github.com/TJProgrammer)) 127 | - Add option to disable reload notification [PR #101](https://github.com/cakecatz/flex-toolbar/pull/101) (by [@danielbayley](https://github.com/danielbayley)) 128 | 129 | ## [v0.12.0](https://github.com/cakecatz/flex-toolbar/compare/v0.11.0...v0.12.0) - 2017-03-11 130 | - Fix loading when using `tool-bar@1.1.0`. [PR #98](https://github.com/cakecatz/flex-toolbar/pull/98) (by [@zertosh](https://github.com/zertosh)) 131 | - Use `atom.configDirPath` instead of `ATOM_HOME`. [PR #97](https://github.com/cakecatz/flex-toolbar/pull/97) (by [@danielbayley](https://github.com/danielbayley)) 132 | - Add ability to add user defined class' to a button. [PR #95](https://github.com/cakecatz/flex-toolbar/pull/95) (by [@blizzardengle](https://github.com/blizzardengle)) 133 | - Add Function callback. [PR #86](https://github.com/cakecatz/flex-toolbar/pull/85) (by [@UziTech](https://github.com/UziTech)) 134 | - Add `.js` and `.coffee` config file types. [PR #86](https://github.com/cakecatz/flex-toolbar/pull/85) (by [@UziTech](https://github.com/UziTech)) 135 | 136 | ## 0.4.4 137 | - Fixed resolveConfigPath not searching for toolbar.json (by Andrew Douglas) 138 | 139 | ## 0.4.3 140 | - Change default config file type 141 | 142 | ## 0.4.2 143 | - Fixed bugs 144 | 145 | ## 0.4.0 146 | - Support CSON, JSON5 config file 147 | - ADD show, enable property 148 | 149 | ## 0.3.0 150 | - Add style property 151 | - Add hide, disable property 152 | - Fixed callback target 153 | 154 | ## 0.2.1 155 | - Fixed callback target 156 | 157 | ## 0.2.0 158 | - Use ToolBar Services API (by Jeroen van Warmerdam) 159 | - Notifications on toolbar edit (by Jeroen van Warmerdam) 160 | - Rename flex-toolbar to flex-tool-bar 161 | 162 | ## 0.1.10 163 | - Fixed many open edit toolbar tab 164 | - Fixed be removed toolbar.json 165 | - Add dev mode (by Jeroen van Warmerdam) 166 | 167 | ## 0.1.9 168 | - Fixed diactivating behaviour 169 | - Fixed removing buttons from toolbar with new toolbar theme (by Jeroen van Warmerdam) 170 | 171 | ## 0.1.8 172 | - Fix some bugs 173 | 174 | ## 0.1.7 175 | - Fix some bugs (by Jeroen van Warmerdam) 176 | 177 | ## 0.1.6 178 | - Reload toolbar when edit toolbar.json 179 | 180 | ## 0.1.5 181 | - Fix default path for Windows users (by Jeroen van Warmerdam) 182 | - Refactoring (by Jeroen van Warmerdam) 183 | 184 | ## 0.1.4 185 | - Loading icon to be faster 186 | 187 | ## 0.1.3 188 | - Auto install toolbar when not installed 189 | - Added button for edit toolbar.json 190 | 191 | ## 0.0.1 - First Release 192 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Flex Tool Bar 2 | 3 | [![Build Status](https://travis-ci.org/cakecatz/flex-toolbar.svg?branch=master)](https://travis-ci.org/cakecatz/flex-toolbar) 4 | 5 | ## About 6 | 7 | This is a plugin for 8 | the [Atom Tool Bar](https://atom.io/packages/tool-bar) package. 9 | 10 | You can configure your toolbar buttons with a `CSON`, `JSON`, `JSON5`, `js`, `coffee` file 11 | to perform specific actions in Atom 12 | or to open web sites in your default browser. 13 | 14 | ![screenshot](https://raw.githubusercontent.com/cakecatz/flex-toolbar/docs/screenshot_cson.png) 15 | 16 | To edit your config file, 17 | type `Flex Tool Bar: Edit Config File` in the Atom command palette. 18 | 19 | ## Configuration 20 | 21 | **Flex Tool Bar** has five `type`s you can configure: 22 | `button`, `file`, `function`, `url` and `spacer`. 23 | 24 | - `button` creates default buttons for your toolbar. 25 | 26 | You can use it to set actions like `application:new-file`. 27 | 28 | - `file` creates buttons pointing to specific files that will be opened in Atom. 29 | 30 | You can use it to open files of nearly any kind that Atom supports, such as logs, configs or simply a project's `README.md`. 31 | 32 | - `function` creates buttons that can call a function with the previous target as a parameter 33 | 34 | This requires the config file to be a `.js` or `.coffee` file that exports the array of buttons 35 | 36 | 37 | - `url` creates buttons pointing to specific web pages. 38 | 39 | Use this to open any web site, such as your GitHub notifications, 40 | in your default browser. See this feature in action in this [screencast](http://quick.as/b5vafe4g). 41 | 42 | If you have the package [browser-plus](https://atom.io/packages/browser-plus) 43 | installed, you can use it to open your links. 44 | Just check the box in Flex Tool Bar's settings. 45 | 46 | Also Atom URI are allowed. For example 47 | `atom://config/packages/flex-tool-bar` will open Flex Tool Bar's settings. 48 | 49 | You can also create dynamic urls with the following placeholders: 50 | 51 | - `{repo-name}` The repo name for the current repo 52 | - `{repo-owner}` The GitHub user for the current repo 53 | - `{atom-version}` The current version of Atom 54 | 55 | e.g. `https://github.com/{repo-owner}/{repo-name}` 56 | 57 | - `spacer` adds separators between toolbar buttons. 58 | 59 | ### Features 60 | 61 | - multiple callbacks 62 | - function callback 63 | - button icons 64 | - button text 65 | - inline button styles 66 | - add class(s) to buttons 67 | - hide/disable a button in certain cases 68 | 69 | ### Button Icon 70 | 71 | The default iconset is [Octicons](https://octicons.github.com/) (Atom's flavor). 72 | 73 | Example: 74 | 75 | ```coffeescript 76 | { 77 | type: "button" 78 | tooltip: "New File" 79 | callback: "application:new-file" 80 | icon: "file-add" 81 | } 82 | ``` 83 | 84 | But you can specify the following iconsets: 85 | 86 | - [Ionicons](http://ionicons.com) (`ion`) 87 | - [FontAwesome](http://fortawesome.github.io/Font-Awesome) (`fa`) 88 | - [Foundation](http://zurb.com/playground/foundation-icon-fonts-3) (`fi`) 89 | - [IcoMoon](https://icomoon.io) (`icomoon`) 90 | - [Devicon](http://devicon.fr) (`devicon`) 91 | - [MaterialDesignIcons](https://materialdesignicons.com/) (`mdi`) 92 | 93 | Example: 94 | 95 | ```coffeescript 96 | { 97 | type: "button" 98 | tooltip: "Save File" 99 | callback: "core:save" 100 | icon: "floppy-o" 101 | iconset: "fa" 102 | } 103 | ``` 104 | 105 | ### Button text 106 | 107 | You can add text or html to the button. 108 | 109 | Example: 110 | 111 | ```coffeescript 112 | { 113 | type: "button" 114 | text: "M" 115 | callback: "minimap:toggle" 116 | } 117 | ``` 118 | 119 | Set `html` to `true` to render the `text` as html. You may combine text with an icon. 120 | 121 | Example: 122 | 123 | ```coffeescript 124 | { 125 | type: "button" 126 | icon: "list-unordered" 127 | text: "Toggle Minimap" 128 | html: true 129 | callback: "minimap:toggle" 130 | } 131 | ``` 132 | 133 | ### Button style 134 | 135 | You can use CSS styles per button. 136 | 137 | ```coffeescript 138 | style: { 139 | color: "red" 140 | background: "green" 141 | border: "1px solid blue" 142 | } 143 | ``` 144 | 145 | You can specify `color` and `background` directly as well. 146 | 147 | ```coffeescript 148 | color: "red" 149 | background: "green" 150 | ``` 151 | 152 | ### Button hover 153 | 154 | you can use CSS styles for button hover per button. 155 | ```coffeescript 156 | hover: { 157 | color: "green" 158 | background: "red" 159 | } 160 | ``` 161 | 162 | ### Button class 163 | 164 | Using an array you can add your own class names to buttons. 165 | This is great if you want to take advantage of native styles like Font Awesome 166 | or if you have your own styles you prefer to keep in a stylesheet. 167 | 168 | ```coffeescript 169 | class: ["fa-rotate-90", "custom-class"] 170 | ``` 171 | 172 | ### Multiple callback 173 | 174 | ```coffeescript 175 | callback: ["callback1", "callback2"] 176 | ``` 177 | 178 | ### Function callback 179 | 180 | ```coffeescript 181 | callback: target -> 182 | console.log target 183 | ``` 184 | 185 | ### Hide(Show), Disable(Enable) button 186 | 187 | You can hide or disable buttons when a certain grammar is 188 | used in the active file, a specified file is matched, a 189 | package is active, or based on a function. 190 | 191 | > If you don't know what language to use, see this [issue](https://github.com/cakecatz/flex-toolbar/issues/105). 192 | 193 | If you set `disable` (`show`, `hide` or `enable`) this way: 194 | 195 | ```coffeescript 196 | disable: { 197 | grammar: "coffee" 198 | } 199 | ``` 200 | 201 | It will disable the button if a CoffeeScript file is open. 202 | 203 | You can also look for a specific file using [globs](https://tr.im/glob): 204 | 205 | ```coffeescript 206 | show: { 207 | pattern: "*.js" 208 | } 209 | ``` 210 | 211 | You can also look for a specific package using: 212 | 213 | ```coffeescript 214 | show: { 215 | package: "context-git" 216 | } 217 | ``` 218 | 219 | or a specific setting using: 220 | 221 | ```coffeescript 222 | show: { 223 | setting: "tree-view.autoReveal" 224 | } 225 | ``` 226 | 227 | or pass a function that is given the current editor using: 228 | 229 | ```coffeescript 230 | show: { 231 | function: (editor) -> editor.isModified() 232 | } 233 | ``` 234 | 235 | > 🚨 Functions are polled every 300ms by default (this can be changed in the settings) to check for changes. 236 | > This could affect performance in Atom if long running operations are handled in the function. 237 | 238 | Of course, you can set it as an array. 239 | 240 | ```coffeescript 241 | disable: { 242 | grammar: [ 243 | "json" 244 | "less" 245 | ] 246 | } 247 | ``` 248 | 249 | You can use `!` in grammar and package :laughing: 250 | 251 | ```coffeescript 252 | hide: { 253 | grammar: "!Markdown" 254 | } 255 | ``` 256 | 257 | This will hide button when opened any file except Markdown. 258 | 259 | ```coffeescript 260 | show: { 261 | grammar: "Markdown" 262 | } 263 | ``` 264 | 265 | This is same above. 266 | 267 | ### Examples 268 | 269 | #### .cson Example 270 | 271 | ```coffeescript 272 | [ 273 | { 274 | type: "url" 275 | icon: "octoface" 276 | url: "https://github.com/" 277 | tooltip: "Github Page" 278 | } 279 | { 280 | type: "spacer" 281 | } 282 | { 283 | type: "button" 284 | icon: "document" 285 | callback: "application:new-file" 286 | tooltip: "New File" 287 | iconset: "ion" 288 | mode: "dev" 289 | } 290 | { 291 | type: "button" 292 | icon: "columns" 293 | iconset: "fa" 294 | callback: ["pane:split-right", "pane:split-right"] 295 | } 296 | { 297 | type: "button" 298 | icon: "circuit-board" 299 | callback: "git-diff:toggle-diff-list" 300 | style: 301 | color: "#FA4F28" 302 | hover: 303 | color: "#FA8F28" 304 | } 305 | { 306 | type: "button" 307 | icon: "markdown" 308 | callback: "markdown-preview:toggle" 309 | disable: "!markdown" 310 | } 311 | { 312 | type: "button" 313 | icon: "sitemap" 314 | iconset: "fa" 315 | class: "fa-rotate-180" 316 | color: "red" 317 | background: "green" 318 | tooltip: "This is just an example it does nothing" 319 | } 320 | { 321 | type: "file" 322 | iconset: "fa" 323 | icon: "book" 324 | file: "README.md" 325 | tooltip: "View Documentation" 326 | } 327 | { 328 | type: "button" 329 | icon: "list-unordered" 330 | text: "Toggle Minimap" 331 | html: true 332 | callback: "minimap:toggle" 333 | show: { 334 | package: "minimap" 335 | } 336 | } 337 | ] 338 | ``` 339 | 340 | #### .coffee Example 341 | 342 | ```coffeescript 343 | module.exports = [ 344 | { 345 | type: "function" 346 | icon: "bug" 347 | callback: (target) -> 348 | console.dir target 349 | tooltip: "Debug Target" 350 | } 351 | { 352 | type: "spacer" 353 | } 354 | { 355 | type: "url" 356 | icon: "octoface" 357 | url: "https://github.com/" 358 | tooltip: "Github Page" 359 | } 360 | { 361 | type: "spacer" 362 | } 363 | { 364 | type: "button" 365 | icon: "document" 366 | callback: "application:new-file" 367 | tooltip: "New File" 368 | iconset: "ion" 369 | mode: "dev" 370 | } 371 | { 372 | type: "button" 373 | icon: "columns" 374 | iconset: "fa" 375 | callback: ["pane:split-right", "pane:split-right"] 376 | } 377 | { 378 | type: "button" 379 | icon: "circuit-board" 380 | callback: "git-diff:toggle-diff-list" 381 | style: 382 | color: "#FA4F28" 383 | hover: 384 | color: "#FA8F28" 385 | } 386 | { 387 | type: "button" 388 | icon: "markdown" 389 | callback: "markdown-preview:toggle" 390 | disable: "!markdown" 391 | } 392 | { 393 | type: "button" 394 | icon: "sitemap" 395 | iconset: "fa" 396 | class: "fa-rotate-180" 397 | color: "red" 398 | background: "green" 399 | tooltip: "This is just an example it does nothing" 400 | } 401 | { 402 | type: "file" 403 | iconset: "fa" 404 | icon: "book" 405 | file: "README.md" 406 | tooltip: "View Documentation" 407 | } 408 | { 409 | type: "button" 410 | icon: "list-unordered" 411 | text: "Toggle Minimap" 412 | html: true 413 | callback: "minimap:toggle" 414 | show: { 415 | package: "minimap" 416 | } 417 | } 418 | ] 419 | ``` 420 | 421 | ### Per Project Configuration 422 | 423 | If you want buttons that are only for a specific project. Create a toolbar configuration file at the root of your project directory that is listed in the Atom Tree View. All buttons added to the project toolbar will append to the global toolbar buttons. 424 | 425 | See more examples on [Wiki](https://github.com/cakecatz/flex-toolbar/wiki) ✨ 426 | 427 | ## Authors 428 | 429 | | [![Ryo Narita][cakecatz avator]](https://github.com/cakecatz) | [![Jeroen van Warmerdam][jerone avator]](https://github.com/jerone) | [![Tony Brix][UziTech avator]](https://github.com/uzitech) | 430 | | :-----------------------------------------------------------: | :-----------------------------------------------------------------: | :-----------------------------------------------------------------: | 431 | | [Ryo Narita](https://github.com/cakecatz) | [Jeroen van Warmerdam](https://github.com/jerone) | [Tony Brix](https://github.com/uzitech) | 432 | 433 | ## License 434 | 435 | MIT © [Ryo Narita](https://github.com/cakecatz) 436 | 437 | [cakecatz avator]: https://avatars.githubusercontent.com/u/6136383?v=3&s=100 438 | [jerone avator]: https://avatars.githubusercontent.com/u/55841?v=3&s=100 439 | [UziTech avator]: https://avatars2.githubusercontent.com/u/97994?v=3&s=100 440 | -------------------------------------------------------------------------------- /keymaps/flex-tool-bar.cson: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cakecatz/flex-toolbar/a9d47114acb380dbd6b7380cb4968a2d3f42b4f7/keymaps/flex-tool-bar.cson -------------------------------------------------------------------------------- /lib/defaults/toolbar.coffee: -------------------------------------------------------------------------------- 1 | # This file is used by Flex Tool Bar to create buttons on your Tool Bar. 2 | # For more information how to use this package and create your own buttons, 3 | # read the documentation on https://atom.io/packages/flex-tool-bar 4 | 5 | module.exports = 6 | [ 7 | { 8 | type: "button" 9 | icon: "gear" 10 | callback: "flex-tool-bar:edit-config-file" 11 | tooltip: "Edit Tool Bar" 12 | } 13 | { 14 | type: "spacer" 15 | } 16 | ] 17 | -------------------------------------------------------------------------------- /lib/defaults/toolbar.cson: -------------------------------------------------------------------------------- 1 | # This file is used by Flex Tool Bar to create buttons on your Tool Bar. 2 | # For more information how to use this package and create your own buttons, 3 | # read the documentation on https://atom.io/packages/flex-tool-bar 4 | 5 | [ 6 | { 7 | type: "button" 8 | icon: "gear" 9 | callback: "flex-tool-bar:edit-config-file" 10 | tooltip: "Edit Tool Bar" 11 | } 12 | { 13 | type: "spacer" 14 | } 15 | ] 16 | -------------------------------------------------------------------------------- /lib/defaults/toolbar.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is used by Flex Tool Bar to create buttons on your Tool Bar. 3 | * For more information how to use this package and create your own buttons, 4 | * read the documentation on https://atom.io/packages/flex-tool-bar 5 | */ 6 | 7 | module.exports = [ 8 | { 9 | type: 'button', 10 | icon: 'gear', 11 | callback: 'flex-tool-bar:edit-config-file', 12 | tooltip: 'Edit Tool Bar' 13 | }, 14 | { 15 | type: 'spacer' 16 | } 17 | ]; 18 | -------------------------------------------------------------------------------- /lib/defaults/toolbar.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "button", 4 | "icon": "gear", 5 | "callback": "flex-tool-bar:edit-config-file", 6 | "tooltip": "Edit Tool Bar" 7 | }, 8 | { 9 | "type": "spacer" 10 | } 11 | ] 12 | -------------------------------------------------------------------------------- /lib/defaults/toolbar.json5: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is used by Flex Tool Bar to create buttons on your Tool Bar. 3 | * For more information how to use this package and create your own buttons, 4 | * read the documentation on https://atom.io/packages/flex-tool-bar 5 | */ 6 | 7 | [ 8 | { 9 | type: 'button', 10 | icon: 'gear', 11 | callback: 'flex-tool-bar:edit-config-file', 12 | tooltip: 'Edit Tool Bar' 13 | }, 14 | { 15 | type: 'spacer' 16 | }, 17 | ] 18 | -------------------------------------------------------------------------------- /lib/flex-tool-bar.js: -------------------------------------------------------------------------------- 1 | /** @babel */ 2 | 3 | import path from 'path'; 4 | import util from 'util'; 5 | import fs from 'fs-plus'; 6 | import globToRegexp from 'glob-to-regexp'; 7 | import { CompositeDisposable, watchPath } from 'atom'; 8 | import * as changeCase from 'change-case'; 9 | 10 | const VALID_EXTENSIONS = [ 11 | '.cson', 12 | '.coffee', 13 | '.json5', 14 | '.json', 15 | '.js' 16 | ]; 17 | 18 | export default { 19 | toolBar: null, 20 | configFilePath: null, 21 | activeItem: null, 22 | buttonTypes: [], 23 | configWatcher: null, 24 | projectConfigwatcher: null, 25 | functionConditions: [], 26 | functionPoll: null, 27 | conditionTypes: {}, 28 | 29 | config: { 30 | persistentProjectToolBar: { 31 | description: 'Project tool bar will stay when focus is moved away from a project file', 32 | type: 'boolean', 33 | default: false, 34 | }, 35 | pollFunctionConditionsToReloadWhenChanged: { 36 | type: 'integer', 37 | description: 'set to 0 to stop polling', 38 | default: 300, 39 | minimum: 0, 40 | }, 41 | reloadToolBarNotification: { 42 | type: 'boolean', 43 | default: true, 44 | }, 45 | reloadToolBarWhenEditConfigFile: { 46 | type: 'boolean', 47 | default: true, 48 | }, 49 | toolBarConfigurationFilePath: { 50 | type: 'string', 51 | default: atom.getConfigDirPath(), 52 | }, 53 | toolBarProjectConfigurationFilePath: { 54 | type: 'string', 55 | default: '.', 56 | }, 57 | useBrowserPlusWhenItIsActive: { 58 | type: 'boolean', 59 | default: false, 60 | }, 61 | }, 62 | 63 | activate() { 64 | this.subscriptions = new CompositeDisposable(); 65 | this.changeTextEditorSubscriptions = new CompositeDisposable(); 66 | 67 | require('atom-package-deps').install('flex-tool-bar'); 68 | 69 | this.activeItem = this.getActiveItem(); 70 | this.registerTypes(); 71 | this.registerCommands(); 72 | this.registerEvents(); 73 | this.observeConfig(); 74 | 75 | this.resolveConfigPath(); 76 | this.registerWatch(); 77 | 78 | this.resolveProjectConfigPath(); 79 | this.registerProjectWatch(); 80 | 81 | this.reloadToolbar(); 82 | }, 83 | 84 | pollFunctions() { 85 | if (!this.conditionTypes.function) { 86 | return; 87 | } 88 | const pollTimeout = atom.config.get('flex-tool-bar.pollFunctionConditionsToReloadWhenChanged'); 89 | if (this.functionConditions.length === 0 && pollTimeout === 0) { 90 | return; 91 | } 92 | 93 | this.functionPoll = setTimeout(() => { 94 | 95 | if (!this.activeItem) { 96 | return; 97 | } 98 | 99 | let reload = false; 100 | 101 | for (const condition of this.functionConditions) { 102 | try { 103 | if (condition.value !== !!condition.func(this.activeItem.item)) { 104 | reload = true; 105 | break; 106 | } 107 | } catch (err) { 108 | const buttons = [{ 109 | text: 'Edit Config', 110 | onDidClick: () => atom.workspace.open(this.configFilePath) 111 | }]; 112 | if (this.projectConfigFilePath) { 113 | buttons.push([{ 114 | text: 'Edit Project Config', 115 | onDidClick: () => atom.workspace.open(this.projectConfigFilePath) 116 | }]); 117 | } 118 | atom.notifications.addError('Invalid toolbar config', { 119 | detail: err.stack ? err.stack : err.toString(), 120 | dismissable: true, 121 | buttons, 122 | }); 123 | return; 124 | } 125 | } 126 | 127 | if (reload) { 128 | this.reloadToolbar(); 129 | } else { 130 | this.pollFunctions(); 131 | } 132 | }, pollTimeout); 133 | }, 134 | 135 | observeConfig() { 136 | this.subscriptions.add(atom.config.onDidChange('flex-tool-bar.persistentProjectToolBar', ({newValue}) => { 137 | this.unregisterProjectWatch(); 138 | if (this.resolveProjectConfigPath(null, newValue)) { 139 | this.registerProjectWatch(); 140 | } 141 | this.reloadToolbar(); 142 | })); 143 | 144 | this.subscriptions.add(atom.config.onDidChange('flex-tool-bar.pollFunctionConditionsToReloadWhenChanged', ({newValue}) => { 145 | clearTimeout(this.functionPoll); 146 | if (newValue !== 0) { 147 | this.pollFunctions(); 148 | } 149 | })); 150 | 151 | this.subscriptions.add(atom.config.onDidChange('flex-tool-bar.reloadToolBarWhenEditConfigFile', ({newValue}) => { 152 | this.unregisterWatch(); 153 | this.unregisterProjectWatch(); 154 | if (newValue) { 155 | this.registerWatch(true); 156 | this.registerProjectWatch(true); 157 | } 158 | })); 159 | 160 | this.subscriptions.add(atom.config.onDidChange('flex-tool-bar.toolBarConfigurationFilePath', ({newValue}) => { 161 | this.unregisterWatch(); 162 | if (this.resolveConfigPath(newValue, false)) { 163 | this.registerWatch(); 164 | } 165 | this.reloadToolbar(); 166 | })); 167 | 168 | this.subscriptions.add(atom.config.onDidChange('flex-tool-bar.toolBarProjectConfigurationFilePath', ({newValue}) => { 169 | this.unregisterProjectWatch(); 170 | if (this.resolveProjectConfigPath(newValue)) { 171 | this.registerProjectWatch(); 172 | } 173 | this.reloadToolbar(); 174 | })); 175 | }, 176 | 177 | resolveConfigPath(configFilePath, createIfNotFound) { 178 | if (configFilePath == null) { 179 | configFilePath = atom.config.get('flex-tool-bar.toolBarConfigurationFilePath'); 180 | } 181 | if (createIfNotFound == null) { 182 | createIfNotFound = true; 183 | } 184 | let configPath = configFilePath; 185 | if (!fs.isFileSync(configPath)) { 186 | configPath = fs.resolve(configPath, 'toolbar', VALID_EXTENSIONS); 187 | } 188 | 189 | if (configPath) { 190 | this.configFilePath = configPath; 191 | return true; 192 | } else if (createIfNotFound) { 193 | configPath = configFilePath; 194 | const exists = fs.existsSync(configPath); 195 | if ((exists && fs.isDirectorySync(configPath)) || (!exists && !VALID_EXTENSIONS.includes(path.extname(configPath)))) { 196 | configPath = path.resolve(configPath, 'toolbar.cson'); 197 | } 198 | if (this.createConfig(configPath)) { 199 | this.configFilePath = configPath; 200 | return true; 201 | } 202 | } 203 | 204 | return false; 205 | }, 206 | 207 | createConfig(configPath) { 208 | try { 209 | const ext = path.extname(configPath); 210 | if (!VALID_EXTENSIONS.includes(ext)) { 211 | throw new Error(`'${ext}' is not a valid extension. Please us one of ['${VALID_EXTENSIONS.join('\',\'')}']`); 212 | } 213 | const content = fs.readFileSync(path.resolve(__dirname, `./defaults/toolbar${ext}`)); 214 | fs.writeFileSync(configPath, content); 215 | atom.notifications.addInfo('We created a Tool Bar config file for you...', { 216 | detail: configPath, 217 | dismissable: true, 218 | buttons: [{ 219 | text: 'Edit Config', 220 | onDidClick() { 221 | atom.workspace.open(configPath); 222 | } 223 | }] 224 | }); 225 | return true; 226 | } catch (err) { 227 | var notification = atom.notifications.addError('Something went wrong creating the Tool Bar config file!', { 228 | detail: `${configPath}\n\n${err.stack ? err.stack : err.toString()}`, 229 | dismissable: true, 230 | buttons: [{ 231 | text: 'Reload Toolbar', 232 | onDidClick: () => { 233 | notification.dismiss(); 234 | this.resolveConfigPath(); 235 | this.registerWatch(); 236 | this.reloadToolbar(); 237 | } 238 | }] 239 | }); 240 | // eslint-disable-next-line no-console 241 | console.error(err); 242 | return false; 243 | } 244 | }, 245 | 246 | resolveProjectConfigPath(configFilePath, persistent) { 247 | if (configFilePath == null) { 248 | configFilePath = atom.config.get('flex-tool-bar.toolBarProjectConfigurationFilePath'); 249 | } 250 | if (persistent == null) { 251 | persistent = atom.config.get('flex-tool-bar.persistentProjectToolBar'); 252 | } 253 | if (!persistent || !fs.isFileSync(this.projectConfigFilePath)) { 254 | this.projectConfigFilePath = null; 255 | } 256 | 257 | if (this.activeItem && this.activeItem.project) { 258 | const projectPath = path.join(this.activeItem.project, configFilePath); 259 | if (fs.isFileSync(projectPath)) { 260 | this.projectConfigFilePath = projectPath; 261 | } else { 262 | const found = fs.resolve(projectPath, 'toolbar', VALID_EXTENSIONS); 263 | if (found) { 264 | this.projectConfigFilePath = found; 265 | } 266 | } 267 | } 268 | 269 | if (this.projectConfigFilePath === this.configFilePath) { 270 | this.projectConfigFilePath = null; 271 | } 272 | 273 | return !!this.projectConfigFilePath; 274 | }, 275 | 276 | registerCommands() { 277 | this.subscriptions.add(atom.commands.add('atom-workspace', { 278 | 'flex-tool-bar:edit-config-file': () => { 279 | if (this.configFilePath) { 280 | atom.workspace.open(this.configFilePath); 281 | } 282 | } 283 | })); 284 | 285 | this.subscriptions.add(atom.commands.add('atom-workspace', { 286 | 'flex-tool-bar:edit-project-config-file': () => { 287 | if (this.projectConfigFilePath) { 288 | atom.workspace.open(this.projectConfigFilePath); 289 | } 290 | } 291 | })); 292 | }, 293 | 294 | registerEvents() { 295 | this.subscriptions.add(atom.packages.onDidActivateInitialPackages(() => { 296 | if (this.conditionTypes.package) { 297 | this.reloadToolbar(); 298 | } 299 | 300 | this.subscriptions.add(atom.packages.onDidActivatePackage(() => { 301 | if (this.conditionTypes.package) { 302 | this.reloadToolbar(); 303 | } 304 | })); 305 | 306 | this.subscriptions.add(atom.packages.onDidDeactivatePackage(() => { 307 | if (this.conditionTypes.package) { 308 | this.reloadToolbar(); 309 | } 310 | }) 311 | ); 312 | })); 313 | 314 | this.subscriptions.add(atom.config.onDidChange(() => { 315 | if (this.conditionTypes.setting) { 316 | this.reloadToolbar(); 317 | } 318 | })); 319 | 320 | this.subscriptions.add(atom.workspace.onDidChangeActiveTextEditor(this.onDidChangeItem.bind(this))); 321 | 322 | this.subscriptions.add(atom.workspace.onDidChangeActivePaneItem(this.onDidChangeItem.bind(this))); 323 | }, 324 | 325 | onDidChangeItem() { 326 | const active = this.getActiveItem(); 327 | 328 | if (!this.activeItem || this.activeItem.item === active.item) { 329 | return; 330 | } 331 | 332 | this.activeItem.item = active.item; 333 | this.activeItem.file = active.file; 334 | this.activeItem.grammar = active.grammar; 335 | 336 | this.changeTextEditorSubscriptions.dispose(); 337 | this.changeTextEditorSubscriptions.clear(); 338 | if (this.activeItem.item) { 339 | if (this.activeItem.item.onDidChangeGrammar) { 340 | this.changeTextEditorSubscriptions.add(this.activeItem.item.onDidChangeGrammar(() => { 341 | if (this.activeItem) { 342 | this.activeItem.grammar = this.getActiveItem().grammar; 343 | this.reloadToolbar(); 344 | } 345 | })); 346 | } 347 | 348 | if (this.activeItem.item.onDidChangePath) { 349 | this.changeTextEditorSubscriptions.add(this.activeItem.item.onDidChangePath(() => { 350 | if (this.activeItem) { 351 | this.activeItem.file = this.getActiveItem().file; 352 | this.reloadToolbar(); 353 | } 354 | })); 355 | } 356 | } 357 | 358 | const oldProject = this.activeItem.project; 359 | this.activeItem.project = active.project; 360 | if (oldProject !== this.activeItem.project) { 361 | this.unregisterProjectWatch(); 362 | this.resolveProjectConfigPath(); 363 | this.registerProjectWatch(); 364 | } 365 | this.activeItem.grammar = active.grammar; 366 | this.reloadToolbar(); 367 | }, 368 | 369 | unregisterWatch() { 370 | if (this.configWatcher) { 371 | this.configWatcher.dispose(); 372 | } 373 | this.configWatcher = null; 374 | }, 375 | 376 | async registerWatch(shouldWatch) { 377 | if (shouldWatch == null) { 378 | shouldWatch = atom.config.get('flex-tool-bar.reloadToolBarWhenEditConfigFile'); 379 | } 380 | if (!shouldWatch || !this.configFilePath) { 381 | return; 382 | } 383 | 384 | if (this.configWatcher) { 385 | this.configWatcher.dispose(); 386 | } 387 | this.configWatcher = await watchPath(this.configFilePath, {}, () => { 388 | this.reloadToolbar(atom.config.get('flex-tool-bar.reloadToolBarNotification')); 389 | }); 390 | }, 391 | 392 | unregisterProjectWatch() { 393 | if (this.projectConfigWatcher) { 394 | this.projectConfigWatcher.dispose(); 395 | } 396 | this.projectConfigWatcher = null; 397 | }, 398 | 399 | async registerProjectWatch(shouldWatch) { 400 | if (shouldWatch == null) { 401 | shouldWatch = atom.config.get('flex-tool-bar.reloadToolBarWhenEditConfigFile'); 402 | } 403 | if (!shouldWatch || !this.projectConfigFilePath) { 404 | return; 405 | } 406 | 407 | if (this.projectConfigWatcher) { 408 | this.projectConfigWatcher.dispose(); 409 | } 410 | this.projectConfigWatcher = await watchPath(this.projectConfigFilePath, {}, () => { 411 | this.reloadToolbar(atom.config.get('flex-tool-bar.reloadToolBarNotification')); 412 | }); 413 | }, 414 | 415 | registerTypes() { 416 | const typeFiles = fs.listSync(path.join(__dirname, './types')); 417 | typeFiles.forEach(typeFile => { 418 | const typeName = path.basename(typeFile, '.js'); 419 | this.buttonTypes[typeName] = require(typeFile); 420 | }); 421 | }, 422 | 423 | consumeToolBar(toolBar) { 424 | this.toolBar = toolBar('flex-toolBar'); 425 | this.reloadToolbar(); 426 | }, 427 | 428 | getToolbarView() { 429 | // This is an undocumented API that moved in tool-bar@1.1.0 430 | return this.toolBar.toolBarView || this.toolBar.toolBar; 431 | }, 432 | 433 | reloadToolbar(withNotification) { 434 | this.conditionTypes = {}; 435 | clearTimeout(this.functionPoll); 436 | if (!this.toolBar) { 437 | return; 438 | } 439 | try { 440 | this.fixToolBarHeight(); 441 | const toolBarButtons = this.loadConfig(); 442 | this.removeButtons(); 443 | this.addButtons(toolBarButtons); 444 | if (withNotification) { 445 | atom.notifications.addSuccess('The tool-bar was successfully updated.'); 446 | } 447 | this.unfixToolBarHeight(); 448 | } catch (error) { 449 | this.unfixToolBarHeight(); 450 | atom.notifications.addError(`Could not load your toolbar from \`${fs.tildify(this.configFilePath)}\``, {dismissable: true}); 451 | throw error; 452 | } 453 | }, 454 | 455 | fixToolBarHeight() { 456 | const toolBarView = this.getToolbarView(); 457 | if (!toolBarView || !toolBarView.element) { 458 | return; 459 | } 460 | toolBarView.element.style.height = `${this.getToolbarView().element.offsetHeight}px`; 461 | }, 462 | 463 | unfixToolBarHeight() { 464 | const toolBarView = this.getToolbarView(); 465 | if (!toolBarView || !toolBarView.element) { 466 | return; 467 | } 468 | toolBarView.element.style.height = ''; 469 | }, 470 | 471 | addButtons(toolBarButtons) { 472 | const buttons = []; 473 | 474 | if (!toolBarButtons) { 475 | return buttons; 476 | } 477 | 478 | if (!Array.isArray(toolBarButtons)) { 479 | console.error('Invalid Toolbar Config', toolBarButtons); 480 | throw new Error('Invalid Toolbar Config'); 481 | } 482 | 483 | const devMode = atom.inDevMode(); 484 | this.functionConditions = []; 485 | const btnErrors = []; 486 | 487 | for (const btn of toolBarButtons) { 488 | 489 | var button, disable, hide; 490 | try { 491 | hide = (btn.hide && this.checkConditions(btn.hide)) || (btn.show && !this.checkConditions(btn.show)); 492 | disable = (btn.disable && this.checkConditions(btn.disable)) || (btn.enable && !this.checkConditions(btn.enable)); 493 | } catch (err) { 494 | btnErrors.push(`${err.message || err.toString()}\n${util.inspect(btn, {depth: 4})}`); 495 | continue; 496 | } 497 | 498 | if (hide) { 499 | continue; 500 | } 501 | if (btn.mode && btn.mode === 'dev' && !devMode) { 502 | continue; 503 | } 504 | 505 | if (this.buttonTypes[btn.type]) { 506 | button = this.buttonTypes[btn.type](this.toolBar, btn, this.getActiveItem); 507 | } 508 | 509 | if (button && button.element) { 510 | if (btn.mode) { 511 | button.element.classList.add(`tool-bar-mode-${btn.mode}`); 512 | } 513 | 514 | if (btn.style) { 515 | for (const propName in btn.style) { 516 | const style = btn.style[propName]; 517 | button.element.style[changeCase.camelCase(propName)] = style; 518 | } 519 | } 520 | 521 | if (btn.hover && !disable) { 522 | button.element.addEventListener('mouseenter', this.onMouseEnter(btn), {passive: true}); 523 | button.element.addEventListener('mouseleave', this.onMouseLeave(btn), {passive: true}); 524 | } 525 | 526 | if (btn.className) { 527 | const ary = btn.className.split(','); 528 | for (const val of ary) { 529 | button.element.classList.add(val.trim()); 530 | } 531 | } 532 | 533 | if (disable) { 534 | button.setEnabled(false); 535 | } 536 | } 537 | 538 | buttons.push(button); 539 | } 540 | 541 | if (btnErrors.length > 0) { 542 | const notificationButtons = [{ 543 | text: 'Edit Config', 544 | onDidClick: () => atom.workspace.open(this.configFilePath) 545 | }]; 546 | if (this.projectConfigFilePath) { 547 | notificationButtons.push([{ 548 | text: 'Edit Project Config', 549 | onDidClick: () => atom.workspace.open(this.projectConfigFilePath) 550 | }]); 551 | } 552 | atom.notifications.addError('Invalid toolbar config', { 553 | detail: btnErrors.join('\n\n'), 554 | dismissable: true, 555 | buttons: notificationButtons, 556 | }); 557 | } 558 | 559 | this.pollFunctions(); 560 | 561 | return buttons; 562 | }, 563 | 564 | onMouseEnter(btn) { 565 | return function () { 566 | // Map to hold the values as they were before the hover modifications. 567 | btn['preHoverVal'] = new Object(); 568 | 569 | for (const propName in btn.hover) { 570 | const camelPropName = changeCase.camelCase(propName); 571 | btn.preHoverVal[camelPropName] = this.style[camelPropName]; 572 | this.style[camelPropName] = btn.hover[propName]; 573 | } 574 | }; 575 | }, 576 | 577 | onMouseLeave(btn) { 578 | return function () { 579 | for (const propName in btn.preHoverVal) { 580 | const style = btn.preHoverVal[propName]; 581 | this.style[propName] = style; 582 | } 583 | }; 584 | }, 585 | 586 | removeCache(filePath) { 587 | delete require.cache[filePath]; 588 | 589 | try { 590 | let relativeFilePath = path.relative(path.join(atom.getLoadSettings().resourcePath, 'static'), filePath); 591 | if (process.platform === 'win32') { 592 | relativeFilePath = relativeFilePath.replace(/\\/g, '/'); 593 | } 594 | delete snapshotResult.customRequire.cache[relativeFilePath]; 595 | } catch (err) { 596 | // most likely snapshotResult does not exist 597 | } 598 | }, 599 | 600 | loadConfig() { 601 | let CSON, ext; 602 | let config = [{ 603 | type: 'function', 604 | icon: 'tools', 605 | callback: () => { 606 | this.resolveConfigPath(); 607 | this.registerWatch(); 608 | this.reloadToolbar(); 609 | }, 610 | tooltip: 'Create Global Tool Bar Config', 611 | }]; 612 | 613 | if (this.configFilePath) { 614 | let globalConfig; 615 | ext = path.extname(this.configFilePath); 616 | this.removeCache(this.configFilePath); 617 | 618 | switch (ext) { 619 | case '.js': 620 | case '.json': 621 | case '.coffee': 622 | globalConfig = require(this.configFilePath); 623 | break; 624 | 625 | case '.json5': 626 | require('json5/lib/register'); 627 | globalConfig = require(this.configFilePath); 628 | break; 629 | 630 | case '.cson': 631 | CSON = require('cson'); 632 | globalConfig = CSON.requireCSONFile(this.configFilePath); 633 | break; 634 | 635 | default: 636 | // do nothing 637 | } 638 | 639 | if (globalConfig) { 640 | if (!Array.isArray(globalConfig)) { 641 | globalConfig = [globalConfig]; 642 | } 643 | config = globalConfig; 644 | } 645 | } 646 | 647 | if (this.projectConfigFilePath) { 648 | let projConfig = []; 649 | ext = path.extname(this.projectConfigFilePath); 650 | this.removeCache(this.projectConfigFilePath); 651 | 652 | switch (ext) { 653 | case '.js': 654 | case '.json': 655 | case '.coffee': 656 | projConfig = require(this.projectConfigFilePath); 657 | break; 658 | 659 | case '.json5': 660 | require('json5/lib/register'); 661 | projConfig = require(this.projectConfigFilePath); 662 | break; 663 | 664 | case '.cson': 665 | CSON = require('cson'); 666 | projConfig = CSON.requireCSONFile(this.projectConfigFilePath); 667 | break; 668 | 669 | default: 670 | // do nothing 671 | } 672 | 673 | config = config.concat(projConfig); 674 | } 675 | 676 | return config; 677 | }, 678 | 679 | loopThrough(items, func) { 680 | if (!Array.isArray(items)) { 681 | items = [items]; 682 | } 683 | let ret = false; 684 | for (const item of items) { 685 | ret = func(item) || ret; 686 | } 687 | 688 | return !!ret; 689 | }, 690 | 691 | checkConditions(conditions) { 692 | return this.loopThrough(conditions, condition => { 693 | let ret = condition === true; 694 | 695 | if (typeof condition === 'string') { 696 | if (/^[^/]+\.(.*?)$/.test(condition)) { 697 | ret = this.patternCondition(condition) || ret; 698 | } else { 699 | ret = this.grammarCondition(condition) || ret; 700 | } 701 | } else if (typeof condition === 'function') { 702 | ret = this.functionCondition(condition) || ret; 703 | } else { 704 | 705 | if (condition.function) { 706 | ret = this.loopThrough(condition.function, this.functionCondition.bind(this)) || ret; 707 | } 708 | 709 | if (condition.grammar) { 710 | ret = this.loopThrough(condition.grammar, this.grammarCondition.bind(this)) || ret; 711 | } 712 | 713 | if (condition.pattern) { 714 | ret = this.loopThrough(condition.pattern, this.patternCondition.bind(this)) || ret; 715 | } 716 | 717 | if (condition.package) { 718 | ret = this.loopThrough(condition.package, this.packageCondition.bind(this)) || ret; 719 | } 720 | 721 | if (condition.setting) { 722 | ret = this.loopThrough(condition.setting, this.settingCondition.bind(this)) || ret; 723 | } 724 | } 725 | 726 | return ret; 727 | }); 728 | }, 729 | 730 | functionCondition(condition) { 731 | this.conditionTypes.function = true; 732 | const value = !!condition(this.activeItem ? this.activeItem.item : null); 733 | 734 | this.functionConditions.push({ 735 | func: condition, 736 | value 737 | }); 738 | 739 | return value; 740 | }, 741 | 742 | getActiveItem() { 743 | const active = { 744 | item: null, 745 | grammar: null, 746 | file: null, 747 | project: null, 748 | }; 749 | 750 | const editor = atom.workspace.getActiveTextEditor(); 751 | if (editor) { 752 | const grammar = editor.getGrammar(); 753 | active.item = editor; 754 | active.grammar = (grammar && grammar.name.toLowerCase()) || null; 755 | active.file = (editor && editor.buffer && editor.buffer.file && editor.buffer.file.getPath()) || null; 756 | } else { 757 | const item = atom.workspace.getCenter().getActivePaneItem(); 758 | if (item && item.file) { 759 | active.item = item; 760 | active.file = item.file.getPath(); 761 | } 762 | } 763 | 764 | if (active.file) { 765 | const [project] = atom.project.relativizePath(active.file); 766 | if (project) { 767 | active.project = project; 768 | } 769 | } 770 | 771 | return active; 772 | }, 773 | 774 | grammarCondition(condition) { 775 | this.conditionTypes.grammar = true; 776 | return this.reversableStringCondition(condition, (c) => { 777 | return this.activeItem && this.activeItem.grammar && this.activeItem.grammar === c.toLowerCase(); 778 | }); 779 | }, 780 | 781 | patternCondition(condition) { 782 | this.conditionTypes.pattern = true; 783 | return this.reversableStringCondition(condition, (c) => { 784 | return this.activeItem && this.activeItem.file && globToRegexp(c, {extended: true}).test(this.activeItem.file); 785 | }); 786 | }, 787 | 788 | packageCondition(condition) { 789 | this.conditionTypes.package = true; 790 | return this.reversableStringCondition(condition, (c) => (atom.packages.isPackageLoaded(c) && !atom.packages.isPackageDisabled(c))); 791 | }, 792 | 793 | settingCondition(condition) { 794 | this.conditionTypes.setting = true; 795 | return this.reversableStringCondition(condition, (c) => atom.config.get(c)); 796 | }, 797 | 798 | reversableStringCondition(condition, matches) { 799 | let result = false; 800 | let reverse = false; 801 | if (/^!/.test(condition)) { 802 | condition = condition.replace('!', ''); 803 | reverse = true; 804 | } 805 | 806 | result = matches(condition); 807 | 808 | if (reverse) { 809 | result = !result; 810 | } 811 | return result; 812 | }, 813 | 814 | removeButtons() { 815 | if (this.toolBar && this.toolBar.removeItems) { 816 | this.toolBar.removeItems(); 817 | } 818 | }, 819 | 820 | deactivate() { 821 | this.unregisterWatch(); 822 | this.unregisterProjectWatch(); 823 | this.subscriptions.dispose(); 824 | this.subscriptions = null; 825 | this.changeTextEditorSubscriptions.dispose(); 826 | this.changeTextEditorSubscriptions = null; 827 | this.removeButtons(); 828 | this.toolBar = null; 829 | clearTimeout(this.functionPoll); 830 | this.functionPoll = null; 831 | this.activeItem = null; 832 | }, 833 | 834 | serialize() {} 835 | }; 836 | -------------------------------------------------------------------------------- /lib/types/button.js: -------------------------------------------------------------------------------- 1 | /** @babel */ 2 | 3 | export default function (toolBar, button) { 4 | const options = { 5 | icon: button.icon, 6 | iconset: button.iconset, 7 | text: button.text, 8 | html: button.html, 9 | tooltip: button.tooltip, 10 | priority: button.priority || 45, 11 | background: button.background, 12 | color: button.color, 13 | class: button.class, 14 | callback: button.callback, 15 | }; 16 | 17 | return toolBar.addButton(options); 18 | } 19 | -------------------------------------------------------------------------------- /lib/types/file.js: -------------------------------------------------------------------------------- 1 | /** @babel */ 2 | 3 | export default function (toolBar, button) { 4 | const options = { 5 | icon: button.icon, 6 | iconset: button.iconset, 7 | text: button.text, 8 | html: button.html, 9 | tooltip: button.tooltip, 10 | priority: button.priority || 45, 11 | data: button.file, 12 | background: button.background, 13 | color: button.color, 14 | class: button.class, 15 | callback: (file) => { 16 | atom.workspace.open(file); 17 | } 18 | }; 19 | 20 | return toolBar.addButton(options); 21 | } 22 | -------------------------------------------------------------------------------- /lib/types/function.js: -------------------------------------------------------------------------------- 1 | /** @babel */ 2 | 3 | export default function (toolBar, button, getActiveItem) { 4 | const options = { 5 | icon: button.icon, 6 | iconset: button.iconset, 7 | text: button.text, 8 | html: button.html, 9 | tooltip: button.tooltip, 10 | priority: button.priority || 45, 11 | data: button.callback, 12 | background: button.background, 13 | color: button.color, 14 | class: button.class, 15 | callback(data) { 16 | return data.call(this, getActiveItem().item); 17 | }, 18 | }; 19 | 20 | return toolBar.addButton(options); 21 | } 22 | -------------------------------------------------------------------------------- /lib/types/spacer.js: -------------------------------------------------------------------------------- 1 | /** @babel */ 2 | 3 | export default function (toolBar, button) { 4 | const options = { 5 | priority: button.priority || 45, 6 | }; 7 | return toolBar.addSpacer(options); 8 | } 9 | -------------------------------------------------------------------------------- /lib/types/url.js: -------------------------------------------------------------------------------- 1 | /** @babel */ 2 | 3 | import { shell } from 'electron'; 4 | import UrlReplace from '../url-replace'; 5 | 6 | export default function (toolBar, button) { 7 | var options = { 8 | icon: button.icon, 9 | iconset: button.iconset, 10 | text: button.text, 11 | html: button.html, 12 | tooltip: button.tooltip, 13 | priority: button.priority || 45, 14 | data: button.url, 15 | background: button.background, 16 | color: button.color, 17 | class: button.class, 18 | callback(url) { 19 | const urlReplace = new UrlReplace(); 20 | // eslint-disable-next-line no-param-reassign 21 | url = urlReplace.replace(url); 22 | if (url.startsWith('atom://')) { 23 | return atom.workspace.open(url); 24 | } else if (atom.config.get('flex-tool-bar.useBrowserPlusWhenItIsActive')) { 25 | if (atom.packages.isPackageActive('browser-plus')) { 26 | return atom.workspace.open(url, {split: 'right'}); 27 | } 28 | const warning = 'Package browser-plus is not active. Using default browser instead!'; 29 | options = {detail: 'Use apm install browser-plus to install the needed package.'}; 30 | atom.notifications.addWarning(warning, options); 31 | return shell.openExternal(url); 32 | 33 | } 34 | return shell.openExternal(url); 35 | }, 36 | }; 37 | 38 | return toolBar.addButton(options); 39 | } 40 | -------------------------------------------------------------------------------- /lib/url-replace.js: -------------------------------------------------------------------------------- 1 | /** @babel */ 2 | 3 | export default class UrlReplace { 4 | constructor() { 5 | const repo = this.getRepositoryForActiveItem() || this.getRepositoryForProject(); 6 | this.repoInfo = this.parseUrl(repo ? repo.getOriginURL() : null); 7 | this.info = { 8 | 'repo-name': this.repoInfo.name, 9 | 'repo-owner': this.repoInfo.owner, 10 | 'atom-version': atom.getVersion(), 11 | }; 12 | } 13 | 14 | replace(url) { 15 | const re = /({[^}]*})/; 16 | 17 | let m = re.exec(url); 18 | while (m) { 19 | const [matchedText] = m; 20 | // eslint-disable-next-line no-param-reassign 21 | url = url.replace(m[0], this.getInfo(matchedText)); 22 | 23 | m = re.exec(url); 24 | } 25 | 26 | return url; 27 | } 28 | 29 | getInfo(key) { 30 | // eslint-disable-next-line no-param-reassign 31 | key = key.replace(/{([^}]*)}/, '$1'); 32 | if (key in this.info) { 33 | return this.info[key]; 34 | } 35 | 36 | return key; 37 | } 38 | 39 | getRepositoryForActiveItem() { 40 | const item = atom.workspace.getActivePaneItem(); 41 | const path = item && item.getPath && item.getPath(); 42 | if (!path) { 43 | return; 44 | } 45 | const [rootDir] = atom.project.relativizePath(path); 46 | const rootDirIndex = atom.project.getPaths().indexOf(rootDir); 47 | if (rootDirIndex >= 0) { 48 | return atom.project.getRepositories()[rootDirIndex]; 49 | } 50 | } 51 | 52 | getRepositoryForProject() { 53 | for (const repo of atom.project.getRepositories()) { 54 | if (repo) { 55 | return repo; 56 | } 57 | } 58 | } 59 | 60 | parseUrl(url) { 61 | const repoInfo = { 62 | owner: '', 63 | name: '', 64 | }; 65 | 66 | if (!url) { 67 | return repoInfo; 68 | } 69 | 70 | let re; 71 | if (url.indexOf('http' >= 0)) { 72 | re = /github\.com\/([^/]*)\/([^/]*)\.git/; 73 | } 74 | if (url.indexOf('git@') >= 0) { 75 | re = /:([^/]*)\/([^/]*)\.git/; 76 | } 77 | const m = re.exec(url); 78 | 79 | if (m) { 80 | return {owner: m[1], name: m[2]}; 81 | } 82 | 83 | return repoInfo; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /menus/flex-toolbar.cson: -------------------------------------------------------------------------------- 1 | # See https://atom.io/docs/latest/creating-a-package#menus for more details 2 | 'context-menu': 3 | '.tool-bar': [ 4 | label: 'Edit Tool Bar' 5 | command: 'flex-tool-bar:edit-config-file' 6 | ] 7 | 'menu': [] 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "flex-tool-bar", 3 | "main": "./lib/flex-tool-bar", 4 | "version": "2.2.7", 5 | "description": "Easily Customizable ToolBar for Atom", 6 | "repository": "https://github.com/cakecatz/flex-toolbar", 7 | "license": "MIT", 8 | "keywords": [ 9 | "toolbar", 10 | "tool-bar" 11 | ], 12 | "engines": { 13 | "atom": ">=1.27.0 <2.0.0" 14 | }, 15 | "dependencies": { 16 | "atom-package-deps": "^8.0.0", 17 | "change-case": "^4.1.2", 18 | "cson": "^7.20.0", 19 | "fs-plus": "^3.1.1", 20 | "glob-to-regexp": "0.4.1", 21 | "json5": "^2.2.1" 22 | }, 23 | "package-deps": [ 24 | { 25 | "name": "tool-bar" 26 | } 27 | ], 28 | "consumedServices": { 29 | "tool-bar": { 30 | "versions": { 31 | "^0 || ^1": "consumeToolBar" 32 | } 33 | } 34 | }, 35 | "atomTestRunner": "./spec/runner", 36 | "scripts": { 37 | "test": "atom --test ./spec && npm run lint", 38 | "lint": "remark . & eslint ." 39 | }, 40 | "devDependencies": { 41 | "@semantic-release/apm-config": "^9.0.1", 42 | "atom-jasmine3-test-runner": "^5.2.13", 43 | "eslint": "^8.17.0", 44 | "remark-cli": "^10.0.1", 45 | "remark-preset-lint-recommended": "^6.1.2", 46 | "semantic-release": "^19.0.2" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /release.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "extends": "@semantic-release/apm-config", 3 | }; 4 | -------------------------------------------------------------------------------- /semantic-release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "Downloading latest Atom release on the stable channel..." 4 | curl -s -L "https://atom.io/download/deb?channel=stable" \ 5 | -H 'Accept: application/octet-stream' \ 6 | -o "atom-amd64.deb" 7 | dpkg-deb -x atom-amd64.deb "${HOME}/atom" 8 | export APM_SCRIPT_PATH="${HOME}/atom/usr/bin" 9 | export PATH="${APM_SCRIPT_PATH}:${PATH}" 10 | 11 | echo "Using APM version:" 12 | apm --version 13 | echo "Using Node version:" 14 | node --version 15 | echo "Using NPM version:" 16 | npm --version 17 | 18 | echo "Installing dependencies..." 19 | npm install 20 | 21 | echo "Running semantic-release..." 22 | npm run semantic-release 23 | -------------------------------------------------------------------------------- /spec/fixtures/config/config.coffee: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { 3 | type: 'coffee' 4 | } 5 | ] 6 | -------------------------------------------------------------------------------- /spec/fixtures/config/config.cson: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | type: 'cson' 4 | } 5 | ] 6 | -------------------------------------------------------------------------------- /spec/fixtures/config/config.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { 3 | type: 'js' 4 | } 5 | ]; 6 | -------------------------------------------------------------------------------- /spec/fixtures/config/config.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "json" 4 | } 5 | ] 6 | -------------------------------------------------------------------------------- /spec/fixtures/config/config.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "json5" 4 | } 5 | ] 6 | -------------------------------------------------------------------------------- /spec/fixtures/pixel.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cakecatz/flex-toolbar/a9d47114acb380dbd6b7380cb4968a2d3f42b4f7/spec/fixtures/pixel.jpg -------------------------------------------------------------------------------- /spec/fixtures/pixel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cakecatz/flex-toolbar/a9d47114acb380dbd6b7380cb4968a2d3f42b4f7/spec/fixtures/pixel.png -------------------------------------------------------------------------------- /spec/fixtures/project1/sample.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | undefined 3 | -------------------------------------------------------------------------------- /spec/fixtures/project1/toolbar.cson: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | type: "button" 4 | icon: "octoface" 5 | callback: "application:about" 6 | tooltip: "project1" 7 | } 8 | ] 9 | -------------------------------------------------------------------------------- /spec/fixtures/project2/sample.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | undefined 3 | -------------------------------------------------------------------------------- /spec/fixtures/project2/toolbar.cson: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | type: "button" 4 | icon: "octoface" 5 | callback: "application:about" 6 | tooltip: "project2" 7 | } 8 | ] 9 | -------------------------------------------------------------------------------- /spec/fixtures/project3/sample.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | undefined 3 | -------------------------------------------------------------------------------- /spec/fixtures/sample.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | undefined 3 | -------------------------------------------------------------------------------- /spec/fixtures/toolbar.cson: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | type: "url" 4 | icon: "octoface" 5 | url: "http://github.com" 6 | tooltip: "Github Page" 7 | } 8 | { 9 | type: "spacer" 10 | } 11 | { 12 | type: "button" 13 | icon: "document" 14 | callback: "application:new-file" 15 | tooltip: "New File" 16 | iconset: "ion" 17 | mode: "dev" 18 | } 19 | { 20 | type: "button" 21 | icon: "columns" 22 | iconset: "fa" 23 | callback: ["pane:split-right", "pane:split-right"] 24 | } 25 | { 26 | type: "button" 27 | icon: "circuit-board" 28 | callback: "git-diff:toggle-diff-list" 29 | style: 30 | color: "#FA4F28" 31 | } 32 | { 33 | type: "button" 34 | icon: "markdown" 35 | callback: "markdown-preview:toggle" 36 | disable: "!markdown" 37 | } 38 | ] -------------------------------------------------------------------------------- /spec/flex-tool-bar-spec.js: -------------------------------------------------------------------------------- 1 | const flexToolBar = require('../lib/flex-tool-bar'); 2 | const path = require('path'); 3 | 4 | describe('FlexToolBar', function () { 5 | beforeEach(async function () { 6 | atom.packages.triggerDeferredActivationHooks(); 7 | atom.packages.triggerActivationHook('core:loaded-shell-environment'); 8 | await atom.packages.activatePackage('tool-bar'); 9 | await atom.packages.activatePackage('flex-tool-bar'); 10 | }); 11 | 12 | describe('activate', function () { 13 | it('should store grammar', async function () { 14 | await atom.packages.activatePackage('language-javascript'); 15 | const editor = await atom.workspace.open('./fixtures/sample.js'); 16 | 17 | expect(flexToolBar.activeItem.grammar).toBe(editor.getGrammar().name.toLowerCase()); 18 | }); 19 | }); 20 | 21 | describe('config type', function () { 22 | it('should load .json', function () { 23 | flexToolBar.configFilePath = path.resolve(__dirname, './fixtures/config/config.json'); 24 | expect(flexToolBar.loadConfig()[0].type).toBe('json'); 25 | }); 26 | 27 | it('should load .js', function () { 28 | flexToolBar.configFilePath = path.resolve(__dirname, './fixtures/config/config.js'); 29 | expect(flexToolBar.loadConfig()[0].type).toBe('js'); 30 | }); 31 | 32 | it('should load .json5', function () { 33 | flexToolBar.configFilePath = path.resolve(__dirname, './fixtures/config/config.json5'); 34 | expect(flexToolBar.loadConfig()[0].type).toBe('json5'); 35 | }); 36 | 37 | it('should load .coffee', function () { 38 | flexToolBar.configFilePath = path.resolve(__dirname, './fixtures/config/config.coffee'); 39 | expect(flexToolBar.loadConfig()[0].type).toBe('coffee'); 40 | }); 41 | 42 | it('should load .cson', function () { 43 | flexToolBar.configFilePath = path.resolve(__dirname, './fixtures/config/config.cson'); 44 | expect(flexToolBar.loadConfig()[0].type).toBe('cson'); 45 | }); 46 | }); 47 | 48 | describe('default config', function () { 49 | it('should load .json', function () { 50 | flexToolBar.configFilePath = path.resolve(__dirname, '../lib/defaults/toolbar.json'); 51 | expect(flexToolBar.loadConfig()[0]).toEqual(jasmine.objectContaining({ 52 | type: 'button', 53 | icon: 'gear', 54 | callback: 'flex-tool-bar:edit-config-file', 55 | tooltip: 'Edit Tool Bar' 56 | })); 57 | }); 58 | 59 | it('should load .js', function () { 60 | flexToolBar.configFilePath = path.resolve(__dirname, '../lib/defaults/toolbar.js'); 61 | expect(flexToolBar.loadConfig()[0]).toEqual(jasmine.objectContaining({ 62 | type: 'button', 63 | icon: 'gear', 64 | callback: 'flex-tool-bar:edit-config-file', 65 | tooltip: 'Edit Tool Bar' 66 | })); 67 | }); 68 | 69 | it('should load .json5', function () { 70 | flexToolBar.configFilePath = path.resolve(__dirname, '../lib/defaults/toolbar.json5'); 71 | expect(flexToolBar.loadConfig()[0]).toEqual(jasmine.objectContaining({ 72 | type: 'button', 73 | icon: 'gear', 74 | callback: 'flex-tool-bar:edit-config-file', 75 | tooltip: 'Edit Tool Bar' 76 | })); 77 | }); 78 | 79 | it('should load .coffee', function () { 80 | flexToolBar.configFilePath = path.resolve(__dirname, '../lib/defaults/toolbar.coffee'); 81 | expect(flexToolBar.loadConfig()[0]).toEqual(jasmine.objectContaining({ 82 | type: 'button', 83 | icon: 'gear', 84 | callback: 'flex-tool-bar:edit-config-file', 85 | tooltip: 'Edit Tool Bar' 86 | })); 87 | }); 88 | 89 | it('should load .cson', function () { 90 | flexToolBar.configFilePath = path.resolve(__dirname, '../lib/defaults/toolbar.cson'); 91 | expect(flexToolBar.loadConfig()[0]).toEqual(jasmine.objectContaining({ 92 | type: 'button', 93 | icon: 'gear', 94 | callback: 'flex-tool-bar:edit-config-file', 95 | tooltip: 'Edit Tool Bar' 96 | })); 97 | }); 98 | }); 99 | 100 | describe('button types', function () { 101 | beforeEach(function () { 102 | spyOn(flexToolBar.toolBar, 'addButton'); 103 | }); 104 | it('should load a url', function () { 105 | flexToolBar.addButtons([{ 106 | type: 'url', 107 | icon: 'octoface', 108 | url: 'http://github.com', 109 | tooltip: 'Github Page' 110 | }]); 111 | 112 | expect(flexToolBar.toolBar.addButton).toHaveBeenCalledWith(jasmine.objectContaining({ 113 | icon: 'octoface', 114 | data: 'http://github.com', 115 | tooltip: 'Github Page' 116 | })); 117 | }); 118 | it('should load a file', function () { 119 | flexToolBar.addButtons([{ 120 | type: 'file', 121 | icon: 'octoface', 122 | file: 'README.md', 123 | tooltip: 'Github Page' 124 | }]); 125 | 126 | expect(flexToolBar.toolBar.addButton).toHaveBeenCalledWith(jasmine.objectContaining({ 127 | icon: 'octoface', 128 | data: 'README.md', 129 | tooltip: 'Github Page' 130 | })); 131 | }); 132 | it('should load a spacer', function () { 133 | spyOn(flexToolBar.toolBar, 'addSpacer'); 134 | flexToolBar.addButtons([{ 135 | type: 'spacer' 136 | }]); 137 | 138 | expect(flexToolBar.toolBar.addSpacer).toHaveBeenCalledWith({ 139 | priority: 45 140 | }); 141 | }); 142 | it('should load a button', function () { 143 | flexToolBar.addButtons([{ 144 | type: 'button', 145 | icon: 'columns', 146 | iconset: 'fa', 147 | tooltip: 'Split Right', 148 | callback: 'pane:split-right', 149 | }]); 150 | 151 | expect(flexToolBar.toolBar.addButton).toHaveBeenCalledWith(jasmine.objectContaining({ 152 | icon: 'columns', 153 | iconset: 'fa', 154 | tooltip: 'Split Right', 155 | callback: 'pane:split-right' 156 | })); 157 | }); 158 | it('should load a function', function () { 159 | const callback = (target) => target; 160 | flexToolBar.addButtons([{ 161 | type: 'function', 162 | icon: 'bug', 163 | callback: callback, 164 | tooltip: 'Debug Target' 165 | }]); 166 | 167 | expect(flexToolBar.toolBar.addButton).toHaveBeenCalledWith(jasmine.objectContaining({ 168 | icon: 'bug', 169 | data: callback, 170 | tooltip: 'Debug Target' 171 | })); 172 | }); 173 | }); 174 | 175 | describe('text and html options', function () { 176 | beforeEach(function () { 177 | spyOn(flexToolBar.toolBar, 'addButton'); 178 | this.obj = { 179 | text: 'text', 180 | html: true 181 | }; 182 | }); 183 | it('should allow text and html options on button', function () { 184 | flexToolBar.addButtons([{ 185 | type: 'button', 186 | text: this.obj.text, 187 | html: this.obj.html 188 | }]); 189 | 190 | expect(flexToolBar.toolBar.addButton).toHaveBeenCalledWith(jasmine.objectContaining(this.obj)); 191 | }); 192 | it('should allow text and html options on file', function () { 193 | flexToolBar.addButtons([{ 194 | type: 'file', 195 | text: this.obj.text, 196 | html: this.obj.html 197 | }]); 198 | 199 | expect(flexToolBar.toolBar.addButton).toHaveBeenCalledWith(jasmine.objectContaining(this.obj)); 200 | }); 201 | it('should allow text and html options on function', function () { 202 | flexToolBar.addButtons([{ 203 | type: 'function', 204 | text: this.obj.text, 205 | html: this.obj.html 206 | }]); 207 | 208 | expect(flexToolBar.toolBar.addButton).toHaveBeenCalledWith(jasmine.objectContaining(this.obj)); 209 | }); 210 | it('should allow text and html options on url', function () { 211 | flexToolBar.addButtons([{ 212 | type: 'url', 213 | text: this.obj.text, 214 | html: this.obj.html 215 | }]); 216 | 217 | expect(flexToolBar.toolBar.addButton).toHaveBeenCalledWith(jasmine.objectContaining(this.obj)); 218 | }); 219 | }); 220 | 221 | describe('styling options', function () { 222 | beforeEach(function () { 223 | spyOn(flexToolBar.toolBar, 'addButton'); 224 | this.obj = { 225 | background: 'red', 226 | color: 'purple', 227 | class: 'my-awesome-class' 228 | }; 229 | }); 230 | it('should allow background, color, and class options on button', function () { 231 | flexToolBar.addButtons([{ 232 | type: 'button', 233 | background: this.obj.background, 234 | color: this.obj.color, 235 | class: this.obj.class, 236 | }]); 237 | 238 | expect(flexToolBar.toolBar.addButton).toHaveBeenCalledWith(jasmine.objectContaining(this.obj)); 239 | }); 240 | it('should allow background, color, and class options on file', function () { 241 | flexToolBar.addButtons([{ 242 | type: 'file', 243 | background: this.obj.background, 244 | color: this.obj.color, 245 | class: this.obj.class, 246 | }]); 247 | 248 | expect(flexToolBar.toolBar.addButton).toHaveBeenCalledWith(jasmine.objectContaining(this.obj)); 249 | }); 250 | it('should allow background, color, and class options on function', function () { 251 | flexToolBar.addButtons([{ 252 | type: 'function', 253 | background: this.obj.background, 254 | color: this.obj.color, 255 | class: this.obj.class, 256 | }]); 257 | 258 | expect(flexToolBar.toolBar.addButton).toHaveBeenCalledWith(jasmine.objectContaining(this.obj)); 259 | }); 260 | it('should allow background, color, and class options on url', function () { 261 | flexToolBar.addButtons([{ 262 | type: 'url', 263 | background: this.obj.background, 264 | color: this.obj.color, 265 | class: this.obj.class, 266 | }]); 267 | 268 | expect(flexToolBar.toolBar.addButton).toHaveBeenCalledWith(jasmine.objectContaining(this.obj)); 269 | }); 270 | }); 271 | 272 | describe('observed events', function () { 273 | it('should observe grammar change', async function () { 274 | await atom.packages.activatePackage('language-javascript'); 275 | await atom.packages.activatePackage('language-text'); 276 | await atom.workspace.open('./fixtures/sample.js'); 277 | 278 | const editor = atom.workspace.getActiveTextEditor(); 279 | editor.setGrammar(atom.grammars.grammarForScopeName('source.js')); 280 | 281 | expect(flexToolBar.activeItem.grammar).toBe('javascript'); 282 | 283 | editor.setGrammar(atom.grammars.grammarForScopeName('text.plain')); 284 | 285 | expect(flexToolBar.activeItem.grammar).toBe('plain text'); 286 | }); 287 | }); 288 | 289 | describe('grammar condition', function () { 290 | it('should check @activeItem.grammar', function () { 291 | flexToolBar.activeItem.grammar = 'js'; 292 | 293 | const match = flexToolBar.checkConditions('js'); 294 | const notMatch = flexToolBar.checkConditions('!js'); 295 | expect(match).toBe(true); 296 | expect(notMatch).toBe(false); 297 | }); 298 | 299 | it('should check .grammar', function () { 300 | flexToolBar.activeItem.grammar = 'js'; 301 | 302 | const match = flexToolBar.checkConditions({grammar: 'js'}); 303 | const notMatch = flexToolBar.checkConditions({grammar: '!js'}); 304 | expect(match).toBe(true); 305 | expect(notMatch).toBe(false); 306 | }); 307 | 308 | it('should not match substring', function () { 309 | flexToolBar.activeItem.grammar = 'cpp'; 310 | 311 | const match = flexToolBar.checkConditions({grammar: 'c'}); 312 | const notMatch = flexToolBar.checkConditions({grammar: '!c'}); 313 | expect(match).toBe(false); 314 | expect(notMatch).toBe(true); 315 | }); 316 | 317 | it('should not match grammar substring', function () { 318 | flexToolBar.activeItem.grammar = 'c'; 319 | 320 | const match = flexToolBar.checkConditions({grammar: 'cpp'}); 321 | const notMatch = flexToolBar.checkConditions({grammar: '!cpp'}); 322 | expect(match).toBe(false); 323 | expect(notMatch).toBe(true); 324 | }); 325 | }); 326 | 327 | describe('pattern condition', function () { 328 | it('should check .pattern', async function () { 329 | await atom.workspace.open('./fixtures/sample.js'); 330 | const matchJs = flexToolBar.checkConditions({pattern: '*.js'}); 331 | const matchCoffee = flexToolBar.checkConditions({pattern: '*.coffee'}); 332 | expect(matchJs).toBe(true); 333 | expect(matchCoffee).toBe(false); 334 | }); 335 | }); 336 | 337 | describe('setting condition', function () { 338 | it('should check .setting', async function () { 339 | await atom.config.set('test.trueSetting', true); 340 | await atom.config.set('test.falseSetting', false); 341 | 342 | const matchNonExistant = flexToolBar.checkConditions({setting: 'test.nonExistantSetting'}); 343 | const matchTrue = flexToolBar.checkConditions({setting: 'test.trueSetting'}); 344 | const matchFalse = flexToolBar.checkConditions({setting: 'test.falseSetting'}); 345 | expect(matchNonExistant).toBe(false); 346 | expect(matchTrue).toBe(true); 347 | expect(matchFalse).toBe(false); 348 | }); 349 | }); 350 | 351 | describe('image file', function () { 352 | beforeEach(async function () { 353 | await atom.packages.activatePackage('image-view'); 354 | }); 355 | it('should set grammar to null', async function () { 356 | await atom.packages.activatePackage('language-javascript'); 357 | await atom.workspace.open('./fixtures/sample.js'); 358 | expect(flexToolBar.activeItem.grammar).toBe('javascript'); 359 | 360 | await atom.workspace.open('./fixtures/pixel.png'); 361 | expect(flexToolBar.activeItem.grammar).toBeNull(); 362 | }); 363 | it('should check .pattern', async function () { 364 | let matchPng, matchJpg; 365 | 366 | await atom.workspace.open('./fixtures/pixel.png'); 367 | matchPng = flexToolBar.checkConditions({pattern: '*.png'}); 368 | matchJpg = flexToolBar.checkConditions({pattern: '*.jpg'}); 369 | expect(matchPng).toBe(true); 370 | expect(matchJpg).toBe(false); 371 | 372 | await atom.workspace.open('./fixtures/pixel.jpg'); 373 | matchPng = flexToolBar.checkConditions({pattern: '*.png'}); 374 | matchJpg = flexToolBar.checkConditions({pattern: '*.jpg'}); 375 | expect(matchPng).toBe(false); 376 | expect(matchJpg).toBe(true); 377 | }); 378 | }); 379 | 380 | describe('package condition', function () { 381 | it('should check .package', async function () { 382 | let notMatch, match; 383 | 384 | notMatch = flexToolBar.checkConditions({package: '!language-text'}); 385 | match = flexToolBar.checkConditions({package: 'language-text'}); 386 | expect(notMatch).toBe(true); 387 | expect(match).toBe(false); 388 | 389 | await atom.packages.activatePackage('language-text'); 390 | 391 | notMatch = flexToolBar.checkConditions({package: '!language-text'}); 392 | match = flexToolBar.checkConditions({package: 'language-text'}); 393 | expect(notMatch).toBe(false); 394 | expect(match).toBe(true); 395 | }); 396 | 397 | it('should check .package loaded', async function () { 398 | await atom.packages.loadPackage('language-text'); 399 | 400 | const notMatch = flexToolBar.checkConditions({package: '!language-text'}); 401 | const match = flexToolBar.checkConditions({package: 'language-text'}); 402 | expect(notMatch).toBe(false); 403 | expect(match).toBe(true); 404 | }); 405 | 406 | it('should check .package disabled', async function () { 407 | await atom.packages.loadPackage('language-text'); 408 | await atom.packages.disablePackage('language-text'); 409 | 410 | const notMatch = flexToolBar.checkConditions({package: 'language-text'}); 411 | const match = flexToolBar.checkConditions({package: '!language-text'}); 412 | expect(notMatch).toBe(false); 413 | expect(match).toBe(true); 414 | }); 415 | }); 416 | 417 | describe('function condition', function () { 418 | beforeEach(function () { 419 | this.pollTimeout = atom.config.get('flex-tool-bar.pollFunctionConditionsToReloadWhenChanged'); 420 | jasmine.clock().install(); 421 | }); 422 | 423 | afterEach(function () { 424 | jasmine.clock().uninstall(); 425 | }); 426 | 427 | it('should check condition and return boolean', function () { 428 | let match; 429 | 430 | match = flexToolBar.checkConditions(() => true); 431 | expect(match).toBe(true); 432 | 433 | match = flexToolBar.checkConditions(() => 1); 434 | expect(match).toBe(true); 435 | 436 | match = flexToolBar.checkConditions(() => false); 437 | expect(match).toBe(false); 438 | 439 | match = flexToolBar.checkConditions(() => 0); 440 | expect(match).toBe(false); 441 | }); 442 | 443 | it('should poll function conditions', async function () { 444 | await atom.workspace.open('./fixtures/sample.js'); 445 | 446 | spyOn(flexToolBar, 'pollFunctions').and.callThrough(); 447 | spyOn(flexToolBar, 'reloadToolbar').and.callThrough(); 448 | spyOn(flexToolBar, 'loadConfig').and.returnValues([{ 449 | text: 'test', 450 | callback: 'application:about', 451 | show: { 452 | function: (editor) => editor.isModified() 453 | } 454 | }]); 455 | 456 | flexToolBar.reloadToolbar(); 457 | 458 | expect(flexToolBar.functionConditions.length).toBe(1); 459 | 460 | jasmine.clock().tick(this.pollTimeout * 3); 461 | 462 | expect(flexToolBar.pollFunctions).toHaveBeenCalledTimes(4); 463 | expect(flexToolBar.reloadToolbar).toHaveBeenCalledTimes(1); 464 | }); 465 | 466 | it('should not poll if no function conditions', async function () { 467 | await atom.workspace.open('./fixtures/sample.js'); 468 | 469 | spyOn(flexToolBar, 'pollFunctions').and.callThrough(); 470 | spyOn(flexToolBar, 'reloadToolbar').and.callThrough(); 471 | spyOn(flexToolBar, 'loadConfig').and.returnValues([{ 472 | text: 'test', 473 | callback: 'application:about', 474 | show: { 475 | pattern: '*.js' 476 | } 477 | }]); 478 | 479 | flexToolBar.reloadToolbar(); 480 | 481 | expect(flexToolBar.functionConditions.length).toBe(0); 482 | 483 | jasmine.clock().tick(this.pollTimeout * 2); 484 | 485 | expect(flexToolBar.pollFunctions).toHaveBeenCalledTimes(1); 486 | expect(flexToolBar.reloadToolbar).toHaveBeenCalledTimes(1); 487 | }); 488 | 489 | it('should reload if a function condition changes', async function () { 490 | const textEditor = await atom.workspace.open('./fixtures/sample.js'); 491 | 492 | spyOn(flexToolBar, 'pollFunctions').and.callThrough(); 493 | spyOn(flexToolBar, 'reloadToolbar').and.callThrough(); 494 | spyOn(flexToolBar, 'loadConfig').and.returnValues([{ 495 | text: 'test', 496 | callback: 'application:about', 497 | show: { 498 | function: (editor) => editor.isModified() 499 | } 500 | }]); 501 | 502 | flexToolBar.reloadToolbar(); 503 | 504 | expect(flexToolBar.pollFunctions).toHaveBeenCalledTimes(1); 505 | expect(flexToolBar.reloadToolbar).toHaveBeenCalledTimes(1); 506 | 507 | jasmine.clock().tick(this.pollTimeout); 508 | 509 | spyOn(textEditor, 'isModified').and.returnValues(true); 510 | 511 | jasmine.clock().tick(this.pollTimeout * 2); 512 | 513 | expect(flexToolBar.pollFunctions).toHaveBeenCalledTimes(3); 514 | expect(flexToolBar.reloadToolbar).toHaveBeenCalledTimes(2); 515 | }); 516 | }); 517 | 518 | describe('correct project config path', function () { 519 | beforeEach(function () { 520 | flexToolBar.configFilePath = path.resolve(__dirname, './fixtures/config/config.json'); 521 | }); 522 | 523 | it('should load toolbar.cson from specified path', async function () { 524 | atom.config.set('flex-tool-bar.toolBarProjectConfigurationFilePath', '.'); 525 | await atom.workspace.open(path.join(__dirname, 'fixtures/sample.js')); 526 | expect(flexToolBar.projectConfigFilePath).toBe(path.resolve(__dirname, './fixtures/toolbar.cson')); 527 | }); 528 | 529 | it('should load specified config file', async function () { 530 | atom.config.set('flex-tool-bar.toolBarProjectConfigurationFilePath', './config/config.cson'); 531 | await atom.workspace.open(path.join(__dirname, 'fixtures/sample.js')); 532 | expect(flexToolBar.projectConfigFilePath).toBe(path.resolve(__dirname, './fixtures/config/config.cson')); 533 | }); 534 | 535 | it('should not load if path equals global config file', async function () { 536 | atom.config.set('flex-tool-bar.toolBarProjectConfigurationFilePath', './config/config.json'); 537 | await atom.workspace.open(path.join(__dirname, 'fixtures/sample.js')); 538 | expect(flexToolBar.projectConfigFilePath).toBe(null); 539 | }); 540 | }); 541 | 542 | describe('persistent project tool bar', function () { 543 | beforeEach(async function () { 544 | this.project1Config = path.join(__dirname, 'fixtures/project1/toolbar.cson'); 545 | this.project2Config = path.join(__dirname, 'fixtures/project2/toolbar.cson'); 546 | this.project1Sample = path.join(__dirname, 'fixtures/project1/sample.js'); 547 | this.project2Sample = path.join(__dirname, 'fixtures/project2/sample.js'); 548 | this.project3Sample = path.join(__dirname, 'fixtures/project3/sample.js'); 549 | this.settingsView = 'atom://config/packages/flex-toolbar'; 550 | 551 | await atom.packages.activatePackage('settings-view'); 552 | flexToolBar.projectConfigFilePath = null; 553 | atom.project.setPaths([ 554 | path.join(__dirname, 'fixtures/project1/'), 555 | path.join(__dirname, 'fixtures/project2/'), 556 | path.join(__dirname, 'fixtures/project3/'), 557 | ]); 558 | }); 559 | it('should not persistent when an editor is open that does not have a project config', async function () { 560 | atom.config.set('flex-tool-bar.persistentProjectToolBar', false); 561 | 562 | await atom.workspace.open(this.project1Sample); 563 | expect(flexToolBar.projectConfigFilePath).toBe(this.project1Config); 564 | 565 | await atom.workspace.open(this.settingsView); 566 | expect(flexToolBar.projectConfigFilePath).toBeNull(); 567 | 568 | await atom.workspace.open(this.project3Sample); 569 | expect(flexToolBar.projectConfigFilePath).toBeNull(); 570 | 571 | await atom.workspace.open(this.project2Sample); 572 | expect(flexToolBar.projectConfigFilePath).toBe(this.project2Config); 573 | 574 | await atom.workspace.open(this.settingsView); 575 | expect(flexToolBar.projectConfigFilePath).toBeNull(); 576 | 577 | await atom.workspace.open(this.project3Sample); 578 | expect(flexToolBar.projectConfigFilePath).toBeNull(); 579 | 580 | await atom.workspace.open(this.project1Sample); 581 | expect(flexToolBar.projectConfigFilePath).toBe(this.project1Config); 582 | }); 583 | 584 | it('should persistent when an editor is open that does not have a project config', async function () { 585 | atom.config.set('flex-tool-bar.persistentProjectToolBar', true); 586 | 587 | await atom.workspace.open(this.project1Sample); 588 | expect(flexToolBar.projectConfigFilePath).toBe(this.project1Config); 589 | 590 | await atom.workspace.open(this.settingsView); 591 | expect(flexToolBar.projectConfigFilePath).toBe(this.project1Config); 592 | 593 | await atom.workspace.open(this.project3Sample); 594 | expect(flexToolBar.projectConfigFilePath).toBe(this.project1Config); 595 | 596 | await atom.workspace.open(this.project2Sample); 597 | expect(flexToolBar.projectConfigFilePath).toBe(this.project2Config); 598 | 599 | await atom.workspace.open(this.settingsView); 600 | expect(flexToolBar.projectConfigFilePath).toBe(this.project2Config); 601 | 602 | await atom.workspace.open(this.project3Sample); 603 | expect(flexToolBar.projectConfigFilePath).toBe(this.project2Config); 604 | 605 | await atom.workspace.open(this.project1Sample); 606 | expect(flexToolBar.projectConfigFilePath).toBe(this.project1Config); 607 | }); 608 | }); 609 | 610 | describe('removeCache', function () { 611 | it('should reload the module from the file', function (done) { 612 | const fs = require('fs'); 613 | const file = path.join(__dirname, 'fixtures', 'removeCache.js'); 614 | 615 | fs.writeFile(file, 'module.exports = 1;', (err) => { 616 | if (err) { 617 | throw err; 618 | } 619 | expect(require(file)).toBe(1); 620 | fs.writeFile(file, 'module.exports = 2;', (err2) => { 621 | if (err2) { 622 | throw err2; 623 | } 624 | expect(require(file)).toBe(1); 625 | flexToolBar.removeCache(file); 626 | expect(require(file)).toBe(2); 627 | fs.unlink(file, (err3) => { 628 | if (err3) { 629 | throw err3; 630 | } 631 | done(); 632 | }); 633 | }); 634 | }); 635 | }); 636 | }); 637 | 638 | describe('button style', function () { 639 | it('should style the button', function () { 640 | const [button] = flexToolBar.addButtons([{ 641 | type: 'button', 642 | icon: 'octoface', 643 | style: { 644 | color: 'rgb(12, 34, 56)' 645 | } 646 | }]); 647 | 648 | expect(button.element.style.color).toBe('rgb(12, 34, 56)'); 649 | }); 650 | 651 | it('should change style on hover', function () { 652 | const [button] = flexToolBar.addButtons([{ 653 | type: 'button', 654 | icon: 'octoface', 655 | style: { 656 | color: 'rgb(12, 34, 56)' 657 | }, 658 | hover: { 659 | color: 'rgb(65, 43, 21)' 660 | } 661 | }]); 662 | 663 | button.element.dispatchEvent(new global.MouseEvent('mouseenter')); 664 | expect(button.element.style.color).toBe('rgb(65, 43, 21)'); 665 | 666 | button.element.dispatchEvent(new global.MouseEvent('mouseleave')); 667 | expect(button.element.style.color).toBe('rgb(12, 34, 56)'); 668 | }); 669 | 670 | it('should remove style on mouseleave', function () { 671 | const [button] = flexToolBar.addButtons([{ 672 | type: 'button', 673 | icon: 'octoface', 674 | hover: { 675 | color: 'rgb(65, 43, 21)' 676 | } 677 | }]); 678 | 679 | button.element.dispatchEvent(new global.MouseEvent('mouseenter')); 680 | expect(button.element.style.color).toBe('rgb(65, 43, 21)'); 681 | 682 | button.element.dispatchEvent(new global.MouseEvent('mouseleave')); 683 | expect(button.element.style.color).toBe(''); 684 | }); 685 | }); 686 | 687 | if (!global.headless) { 688 | // show linting errors in atom test window 689 | describe('linting', function () { 690 | it('should pass linting', function (done) { 691 | const { exec } = require('child_process'); 692 | exec('npm run lint', { 693 | cwd: __dirname 694 | }, function (err, stdout) { 695 | if (err) { 696 | expect(stdout).toBeFalsy(); 697 | } 698 | 699 | done(); 700 | }); 701 | }, 60000); 702 | }); 703 | } 704 | }); 705 | -------------------------------------------------------------------------------- /spec/runner.js: -------------------------------------------------------------------------------- 1 | /** @babel */ 2 | 3 | import {createRunner} from 'atom-jasmine3-test-runner'; 4 | 5 | export default createRunner({ 6 | testPackages: ['tool-bar'], 7 | }); 8 | -------------------------------------------------------------------------------- /styles/flex-tool-bar.less: -------------------------------------------------------------------------------- 1 | @import "ui-variables"; 2 | 3 | .tool-bar button.tool-bar-btn.tool-bar-mode-dev { 4 | color: @text-color-error; 5 | &:focus, 6 | &:hover { 7 | color: darken(@text-color-error, 5%); 8 | } 9 | } 10 | --------------------------------------------------------------------------------