├── .gitignore
├── Changelog.md
├── Jakefile
├── LICENSE
├── README.md
├── package.json
└── src
├── .eslintignore
├── .eslintrc.json
├── bootstrap.js
├── chrome.manifest
├── content
└── icons
│ ├── extension-32.png
│ ├── extension-64.png
│ └── togglebutton
│ ├── LICENSE
│ ├── icon-16.png
│ ├── icon-32.png
│ ├── icon-inverted-16.png
│ └── icon-inverted-32.png
├── data
├── action_creators.js
├── assets
│ └── css
│ │ └── groupspanel.css
├── components
│ ├── app.js
│ ├── group.js
│ ├── groupaddbutton.js
│ ├── groupcontrols.js
│ ├── grouplist.js
│ ├── tab.js
│ └── tablist.js
├── groupspanel.html
├── groupspanel.js
├── reducer.js
└── vendor
│ ├── css
│ └── font-awesome.css
│ ├── fonts
│ ├── fontawesome.eot
│ ├── fontawesome.svg
│ ├── fontawesome.ttf
│ └── fontawesome.woff
│ └── js
│ ├── classnames.min.js
│ ├── immutable.min.js
│ ├── react-dom.min.js
│ ├── react-redux.min.js
│ ├── react.min.js
│ └── redux.min.js
├── index.js
├── install.rdf
├── lib
├── storage
│ └── session.js
├── tabmanager.js
└── utils.js
├── locale
├── de-DE.properties
├── en-US.properties
├── es-AR.properties
├── es-ES.properties
├── fr-FR.properties
└── nl-NL.properties
└── package.json
/.gitignore:
--------------------------------------------------------------------------------
1 | dist/
2 | node_modules/
3 |
--------------------------------------------------------------------------------
/Changelog.md:
--------------------------------------------------------------------------------
1 | # 0.6.0
2 |
3 | ## Features
4 |
5 | * Added a timeout when closing a group to restore a group when closed by accident (PR [34](https://github.com/denschub/firefox-tabgroups/pull/34))
6 |
7 | ## Bug fixes
8 |
9 | * Show the correct icon when using the dark compact theme
10 | * Fix invalid group selection after closing tabs (PR [51](https://github.com/denschub/firefox-tabgroups/pull/51))
11 |
12 | # 0.5.1
13 |
14 | Set `multiprocessCompatible` to `true`!
15 |
16 | # 0.5.0
17 |
18 | ## Bug fixes
19 |
20 | * Use the inverted toolbar icon if needed on high resolutions.
21 |
22 | ## Refactorings
23 |
24 | * Use the default favicon instead of no favicon at all. (PR [35](https://github.com/denschub/firefox-tabgroups/pull/35))
25 | * Removed the Panorama migration to avoid breakage in Firefox 52
26 |
27 | # 0.4.0
28 |
29 | ## Features
30 |
31 | * You can now drag and drop tabs in the panel to move them between groups. Dragging a tab onto the "Create new group" button will create a new group with that tab. (PR [32](https://github.com/denschub/firefox-tabgroups/pull/32))
32 |
33 | # 0.3.0
34 |
35 | ## Bug fixes
36 |
37 | * Clicking the input field while renaming will no longer select the group. (PR [28](https://github.com/denschub/firefox-tabgroups/pull/28))
38 | * Use `label` instead of `visibleLabel` since the latter was removed in bug 1247920.
39 |
40 | # 0.2.1
41 |
42 | * Don't try to set the groups active tab if an app tab is active. (Issue [#27](https://github.com/denschub/firefox-tabgroups/issues/27))
43 |
44 | # 0.2.0
45 |
46 | ## Features
47 |
48 | * Added a migration override so tab groups won't be migrated away.
49 | * Added compatiblity with Quicksavers Tab Groups add-on.
50 | * Added keyboard shortcuts to switch between groups. (PR [#23](https://github.com/denschub/firefox-tabgroups/pull/23))
51 | * Added option for alphabetic tab group sorting. (PR [#24](https://github.com/denschub/firefox-tabgroups/pull/24))
52 |
53 | ## Refactorings
54 |
55 | * `minVersion` is now set to 44 so people have a chance to install the addon before the migration kicks in.
56 | * `Tab Groups` was renamed to `Simplified Tab Groups` to avoid confusion and conflicts with other add-ons.
57 | * Simpilified development by adding `jake` and some basic scripts.
58 |
59 | # 0.1.1
60 |
61 | This was the first public release.
62 |
--------------------------------------------------------------------------------
/Jakefile:
--------------------------------------------------------------------------------
1 | /* global desc, task, jake, complete */
2 | /* vim: set fdl=0: */
3 | "use strict";
4 |
5 | const SRC_DIR = "./src";
6 | const DIST_DIR = "./dist";
7 |
8 | desc("Builds the source");
9 | task("build", ["cleanup"], () => {
10 | jake.cpR(SRC_DIR, DIST_DIR);
11 | });
12 |
13 | desc("Removes all build-related files");
14 | task("cleanup", () => {
15 | jake.rmRf(DIST_DIR);
16 | });
17 |
18 | desc("runs wslint on the built source");
19 | task("lint", ["build"], {async: true}, () => {
20 | jake.exec([`cd ${DIST_DIR}; eslint .`], {
21 | interactive: true
22 | }, complete);
23 | });
24 |
25 | desc("Builds the source and starts a test installation. You can specify jpm parameters with 'JPM_PARAMS=\"...\" jake run'");
26 | task("run", ["build"], {async: true}, () => {
27 | jake.exec([`cd ${DIST_DIR}; jpm run ${process.env.JPM_PARAMS}`], {
28 | interactive: true
29 | }, complete);
30 | });
31 |
32 | desc("Builds the source and generates a XPI");
33 | task("xpi", ["build"], {async: true}, () => {
34 | jake.exec([
35 | `cd ${DIST_DIR}; jpm xpi`,
36 | `cd ${DIST_DIR}; find . -not -name "*.xpi" -delete`
37 | ], {printStderr: true}, () => {
38 | console.log(`.xpi was created in ${DIST_DIR}`);
39 | complete();
40 | });
41 | });
42 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | NOTE: The menu button icons were imported from Firefox. Please see
2 | data/assets/images/LICENSE for further information!
3 |
4 | --------------------------------------------------------------------------------
5 |
6 | Copyright (c) 2015 Dennis Schubert
7 |
8 | Permission is hereby granted, free of charge, to any person obtaining a copy of
9 | this software and associated documentation files (the "Software"), to deal in
10 | the Software without restriction, including without limitation the rights to
11 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
12 | of the Software, and to permit persons to whom the Software is furnished to do
13 | so, subject to the following conditions:
14 |
15 | The above copyright notice and this permission notice shall be included in all
16 | copies or substantial portions of the Software.
17 |
18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 | SOFTWARE.
25 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Simplified Tab Groups for Firefox
2 | =================================
3 |
4 | **NOTE**: This project is currently unmaintained. If someone wants to take over, [check this discussion](https://github.com/denschub/firefox-tabgroups/issues/60#issuecomment-388541616) and drop me a note.
5 |
6 | ---
7 |
8 | This project aims to provide a simple add-on to replace some functionalities
9 | from TabView/Tab Groups/Panorama which were removed from Firefox due to a lot
10 | of open bugs and a very low overall usage.
11 |
12 | Installation
13 | ------------
14 |
15 | The add-on is available at [addons.mozilla.org][amo] and should be installed
16 | there to ensure the add-on stays updated.
17 |
18 | Warning
19 | -------
20 |
21 | Please note that this extension is currently in a very unstable and untested
22 | state and may kill your tabs or small kittens. While it may get improved and
23 | secured in the future, I strongly advise you to make a backup of your important
24 | tabs...
25 |
26 | Building
27 | --------
28 |
29 | Assuming you have Node.js v5 installed on your machine, building this project
30 | is rather easy.
31 |
32 | 1. Install the dependencies: `npm install`.
33 | 2. Run `./node_modules/.bin/jake build` to build all source files into the
34 | `dist/` directory or run `./node_modules/.bin/jake run` to build the add-on
35 | and start a Firefox instance for testing.
36 |
37 | `jake run` uses `jpm` and you can pass additional parameters to it by setting
38 | an environment variable, for example: `JPM_PARAMS="-b nightly" jake run`
39 |
40 | Contributing
41 | ------------
42 |
43 | Feel free to [fix some open issues][issues] and submit a pull request. Please
44 | make sure to file the pull request against the `develop` branch, which should
45 | be the default. Please make sure your code passes the coding styleguides by
46 | running `jake lint` before submitting the PR.
47 |
48 | If you want to help translating this add-on, feel free to alter or add new
49 | files in `src/locale`. The extensions name and descriptions have to be changed
50 | in `src/install.rdf`.
51 |
52 | License
53 | -------
54 |
55 | MIT.
56 |
57 | [amo]: https://addons.mozilla.org/en-US/firefox/addon/tab-groups/
58 | [issues]: https://github.com/denschub/firefox-tabgroups/issues
59 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "tabgroups",
3 | "author": "",
4 | "license": "MIT",
5 | "private": true,
6 | "devDependencies": {
7 | "jake": "^8.0.12",
8 | "jpm": "^1.0.3"
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/.eslintignore:
--------------------------------------------------------------------------------
1 | data/vendor/
2 | bootstrap.js
3 |
--------------------------------------------------------------------------------
/src/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "parserOptions": {
3 | "ecmaVersion": 6,
4 | "sourceType": "module"
5 | },
6 | "env": {
7 | "browser": true,
8 | "es6": true,
9 | "mocha": true,
10 | "node": true
11 | },
12 | "globals": {
13 | "Cc": true,
14 | "Ci": true,
15 | "Components": true,
16 | "Cr": true,
17 | "Cu": true,
18 | "EventEmitter": true,
19 | "Services": true,
20 | "Task": true,
21 | "XPCNativeWrapper": true,
22 | "XPCOMUtils": true,
23 | "console": true,
24 | "console": true,
25 | "devtools": true,
26 | "dump": true,
27 | "exports": true,
28 | "exports": true,
29 | "loader": true,
30 | "module": true,
31 | "require": true,
32 | "require": true,
33 |
34 | "addon": true,
35 | "ActionCreators": true,
36 | "App": true,
37 | "classNames": true,
38 | "Group": true,
39 | "GroupAddButton": true,
40 | "GroupControls": true,
41 | "GroupList": true,
42 | "Immutable": true,
43 | "React": true,
44 | "ReactDOM": true,
45 | "ReactRedux": true,
46 | "Reducer": true,
47 | "Redux": true,
48 | "Tab": true,
49 | "TabList": true
50 | },
51 | "rules": {
52 | "block-scoped-var": 0,
53 | "brace-style": [2, "1tbs", {"allowSingleLine": false}],
54 | "camelcase": 2,
55 | "comma-dangle": 1,
56 | "comma-spacing": [2, {"before": false, "after": true}],
57 | "comma-style": [2, "last"],
58 | "complexity": 1,
59 | "consistent-return": 2,
60 | "consistent-this": 0,
61 | "curly": 2,
62 | "default-case": 0,
63 | "dot-location": [1, "property"],
64 | "dot-notation": 2,
65 | "eol-last": 2,
66 | "eqeqeq": 0,
67 | "func-names": 0,
68 | "func-style": 0,
69 | "generator-star": 0,
70 | "generator-star-spacing": [1, "after"],
71 | "global-strict": 0,
72 | "guard-for-in": 0,
73 | "handle-callback-err": 0,
74 | "indent": [2, 2, {"SwitchCase": 1}],
75 | "key-spacing": [1, {"beforeColon": false, "afterColon": true}],
76 | "keyword-spacing": 1,
77 | "linebreak-style": 0,
78 | "max-depth": 0,
79 | "max-len": [1, 80],
80 | "max-nested-callbacks": [2, 3],
81 | "max-params": 0,
82 | "max-statements": 0,
83 | "new-cap": [2, {"capIsNew": false}],
84 | "new-parens": 2,
85 | "newline-after-var": 0,
86 | "no-alert": 0,
87 | "no-array-constructor": 2,
88 | "no-bitwise": 0,
89 | "no-caller": 2,
90 | "no-catch-shadow": 1,
91 | "no-comma-dangle": 0,
92 | "no-cond-assign": 2,
93 | "no-console": 0,
94 | "no-constant-condition": 0,
95 | "no-continue": 0,
96 | "no-control-regex": 2,
97 | "no-debugger": 2,
98 | "no-delete-var": 2,
99 | "no-div-regex": 0,
100 | "no-dupe-args": 2,
101 | "no-dupe-keys": 2,
102 | "no-duplicate-case": 2,
103 | "no-else-return": 2,
104 | "no-empty": 2,
105 | "no-empty-character-class": 2,
106 | "no-eq-null": 0,
107 | "no-eval": 2,
108 | "no-ex-assign": 2,
109 | "no-extend-native": 2,
110 | "no-extra-bind": 2,
111 | "no-extra-boolean-cast": 2,
112 | "no-extra-parens": 0,
113 | "no-extra-semi": 2,
114 | "no-extra-strict": 0,
115 | "no-fallthrough": 2,
116 | "no-floating-decimal": 0,
117 | "no-func-assign": 0,
118 | "no-implied-eval": 0,
119 | "no-inline-comments": 1,
120 | "no-inner-declarations": 0,
121 | "no-invalid-regexp": 0,
122 | "no-irregular-whitespace": 0,
123 | "no-iterator": 0,
124 | "no-label-var": 0,
125 | "no-labels": 0,
126 | "no-lone-blocks": 0,
127 | "no-lonely-if": 2,
128 | "no-loop-func": 0,
129 | "no-mixed-requires": 0,
130 | "no-mixed-spaces-and-tabs": 2,
131 | "no-multi-spaces": 1,
132 | "no-multi-str": 1,
133 | "no-multiple-empty-lines": [1, {"max": 1}],
134 | "no-native-reassign": 2,
135 | "no-negated-in-lhs": 0,
136 | "no-nested-ternary": 2,
137 | "no-new": 0,
138 | "no-new-func": 0,
139 | "no-new-object": 0,
140 | "no-new-require": 0,
141 | "no-new-wrappers": 0,
142 | "no-obj-calls": 0,
143 | "no-octal": 1,
144 | "no-octal-escape": 0,
145 | "no-param-reassign": 0,
146 | "no-path-concat": 0,
147 | "no-plusplus": 0,
148 | "no-process-env": 0,
149 | "no-process-exit": 0,
150 | "no-proto": 2,
151 | "no-redeclare": 2,
152 | "no-regex-spaces": 2,
153 | "no-reserved-keys": 0,
154 | "no-restricted-modules": 0,
155 | "no-return-assign": 2,
156 | "no-script-url": 0,
157 | "no-self-compare": 2,
158 | "no-sequences": 2,
159 | "no-shadow": 1,
160 | "no-shadow-restricted-names": 2,
161 | "no-space-before-semi": 0,
162 | "no-spaced-func": 1,
163 | "no-sparse-arrays": 2,
164 | "no-sync": 0,
165 | "no-ternary": 0,
166 | "no-throw-literal": 2,
167 | "no-trailing-spaces": 2,
168 | "no-undef": 2,
169 | "no-undef-init": 0,
170 | "no-undefined": 0,
171 | "no-underscore-dangle": 0,
172 | "no-unneeded-ternary": 2,
173 | "no-unreachable": 2,
174 | "no-unused-expressions": 0,
175 | "no-unused-vars": 1,
176 | "no-use-before-define": 0,
177 | "no-var": 0,
178 | "no-void": 0,
179 | "no-warning-comments": 0,
180 | "no-with": 2,
181 | "no-wrap-func": 0,
182 | "object-shorthand": 0,
183 | "one-var": 0,
184 | "operator-assignment": 0,
185 | "operator-linebreak": 0,
186 | "padded-blocks": [1, "never"],
187 | "quote-props": 0,
188 | "quotes": [1, "double", "avoid-escape"],
189 | "radix": 2,
190 | "semi": [1, "always"],
191 | "semi-spacing": [1, {"before": false, "after": true}],
192 | "sort-vars": 0,
193 | "space-before-blocks": [1, "always"],
194 | "space-before-function-paren": [1, "never"],
195 | "space-before-function-parentheses": 0,
196 | "space-in-brackets": 0,
197 | "space-in-parens": [1, "never"],
198 | "space-infix-ops": [1, {"int32Hint": true}],
199 | "space-unary-ops": [1, { "words": true, "nonwords": false }],
200 | "space-unary-word-ops": 0,
201 | "spaced-comment": [1, "always"],
202 | "strict": [2, "global"],
203 | "use-isnan": 2,
204 | "valid-jsdoc": 0,
205 | "valid-typeof": 2,
206 | "vars-on-top": 0,
207 | "wrap-iife": 0,
208 | "wrap-regex": 0,
209 | "yoda": 2
210 | }
211 | }
212 |
--------------------------------------------------------------------------------
/src/bootstrap.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This Source Code is based on the original bootstrap.js generated by the
3 | * add-on sdk which is subject to the terms of the Mozilla Public License, v.
4 | * 2.0. You can obtain a copy of this license at http://mozilla.org/MPL/2.0/.
5 | */
6 |
7 | "use strict";
8 |
9 | const { utils: Cu } = Components;
10 | const rootURI = __SCRIPT_URI_SPEC__.replace("bootstrap.js", "");
11 | const COMMONJS_URI = "resource://gre/modules/commonjs";
12 | const { require } = Cu.import(COMMONJS_URI + "/toolkit/require.js", {});
13 | const { Bootstrap } = require(COMMONJS_URI + "/sdk/addon/bootstrap.js");
14 |
15 | var { startup, shutdown, install, uninstall } = new Bootstrap(rootURI);
16 |
--------------------------------------------------------------------------------
/src/chrome.manifest:
--------------------------------------------------------------------------------
1 | content simplified-tabgroups content/
2 |
--------------------------------------------------------------------------------
/src/content/icons/extension-32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/denschub/firefox-tabgroups/b92a0224ac0cb6f0eddc170cca9c08b98894ea63/src/content/icons/extension-32.png
--------------------------------------------------------------------------------
/src/content/icons/extension-64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/denschub/firefox-tabgroups/b92a0224ac0cb6f0eddc170cca9c08b98894ea63/src/content/icons/extension-64.png
--------------------------------------------------------------------------------
/src/content/icons/togglebutton/LICENSE:
--------------------------------------------------------------------------------
1 | NOTE: Icons were imported from Firefox as the original TabView was
2 | removed and those icons are no longer needed.
3 |
4 | --------------------------------------------------------------------------------
5 |
6 | Mozilla Public License Version 2.0
7 | ==================================
8 |
9 | 1. Definitions
10 | --------------
11 |
12 | 1.1. "Contributor"
13 | means each individual or legal entity that creates, contributes to
14 | the creation of, or owns Covered Software.
15 |
16 | 1.2. "Contributor Version"
17 | means the combination of the Contributions of others (if any) used
18 | by a Contributor and that particular Contributor's Contribution.
19 |
20 | 1.3. "Contribution"
21 | means Covered Software of a particular Contributor.
22 |
23 | 1.4. "Covered Software"
24 | means Source Code Form to which the initial Contributor has attached
25 | the notice in Exhibit A, the Executable Form of such Source Code
26 | Form, and Modifications of such Source Code Form, in each case
27 | including portions thereof.
28 |
29 | 1.5. "Incompatible With Secondary Licenses"
30 | means
31 |
32 | (a) that the initial Contributor has attached the notice described
33 | in Exhibit B to the Covered Software; or
34 |
35 | (b) that the Covered Software was made available under the terms of
36 | version 1.1 or earlier of the License, but not also under the
37 | terms of a Secondary License.
38 |
39 | 1.6. "Executable Form"
40 | means any form of the work other than Source Code Form.
41 |
42 | 1.7. "Larger Work"
43 | means a work that combines Covered Software with other material, in
44 | a separate file or files, that is not Covered Software.
45 |
46 | 1.8. "License"
47 | means this document.
48 |
49 | 1.9. "Licensable"
50 | means having the right to grant, to the maximum extent possible,
51 | whether at the time of the initial grant or subsequently, any and
52 | all of the rights conveyed by this License.
53 |
54 | 1.10. "Modifications"
55 | means any of the following:
56 |
57 | (a) any file in Source Code Form that results from an addition to,
58 | deletion from, or modification of the contents of Covered
59 | Software; or
60 |
61 | (b) any new file in Source Code Form that contains any Covered
62 | Software.
63 |
64 | 1.11. "Patent Claims" of a Contributor
65 | means any patent claim(s), including without limitation, method,
66 | process, and apparatus claims, in any patent Licensable by such
67 | Contributor that would be infringed, but for the grant of the
68 | License, by the making, using, selling, offering for sale, having
69 | made, import, or transfer of either its Contributions or its
70 | Contributor Version.
71 |
72 | 1.12. "Secondary License"
73 | means either the GNU General Public License, Version 2.0, the GNU
74 | Lesser General Public License, Version 2.1, the GNU Affero General
75 | Public License, Version 3.0, or any later versions of those
76 | licenses.
77 |
78 | 1.13. "Source Code Form"
79 | means the form of the work preferred for making modifications.
80 |
81 | 1.14. "You" (or "Your")
82 | means an individual or a legal entity exercising rights under this
83 | License. For legal entities, "You" includes any entity that
84 | controls, is controlled by, or is under common control with You. For
85 | purposes of this definition, "control" means (a) the power, direct
86 | or indirect, to cause the direction or management of such entity,
87 | whether by contract or otherwise, or (b) ownership of more than
88 | fifty percent (50%) of the outstanding shares or beneficial
89 | ownership of such entity.
90 |
91 | 2. License Grants and Conditions
92 | --------------------------------
93 |
94 | 2.1. Grants
95 |
96 | Each Contributor hereby grants You a world-wide, royalty-free,
97 | non-exclusive license:
98 |
99 | (a) under intellectual property rights (other than patent or trademark)
100 | Licensable by such Contributor to use, reproduce, make available,
101 | modify, display, perform, distribute, and otherwise exploit its
102 | Contributions, either on an unmodified basis, with Modifications, or
103 | as part of a Larger Work; and
104 |
105 | (b) under Patent Claims of such Contributor to make, use, sell, offer
106 | for sale, have made, import, and otherwise transfer either its
107 | Contributions or its Contributor Version.
108 |
109 | 2.2. Effective Date
110 |
111 | The licenses granted in Section 2.1 with respect to any Contribution
112 | become effective for each Contribution on the date the Contributor first
113 | distributes such Contribution.
114 |
115 | 2.3. Limitations on Grant Scope
116 |
117 | The licenses granted in this Section 2 are the only rights granted under
118 | this License. No additional rights or licenses will be implied from the
119 | distribution or licensing of Covered Software under this License.
120 | Notwithstanding Section 2.1(b) above, no patent license is granted by a
121 | Contributor:
122 |
123 | (a) for any code that a Contributor has removed from Covered Software;
124 | or
125 |
126 | (b) for infringements caused by: (i) Your and any other third party's
127 | modifications of Covered Software, or (ii) the combination of its
128 | Contributions with other software (except as part of its Contributor
129 | Version); or
130 |
131 | (c) under Patent Claims infringed by Covered Software in the absence of
132 | its Contributions.
133 |
134 | This License does not grant any rights in the trademarks, service marks,
135 | or logos of any Contributor (except as may be necessary to comply with
136 | the notice requirements in Section 3.4).
137 |
138 | 2.4. Subsequent Licenses
139 |
140 | No Contributor makes additional grants as a result of Your choice to
141 | distribute the Covered Software under a subsequent version of this
142 | License (see Section 10.2) or under the terms of a Secondary License (if
143 | permitted under the terms of Section 3.3).
144 |
145 | 2.5. Representation
146 |
147 | Each Contributor represents that the Contributor believes its
148 | Contributions are its original creation(s) or it has sufficient rights
149 | to grant the rights to its Contributions conveyed by this License.
150 |
151 | 2.6. Fair Use
152 |
153 | This License is not intended to limit any rights You have under
154 | applicable copyright doctrines of fair use, fair dealing, or other
155 | equivalents.
156 |
157 | 2.7. Conditions
158 |
159 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
160 | in Section 2.1.
161 |
162 | 3. Responsibilities
163 | -------------------
164 |
165 | 3.1. Distribution of Source Form
166 |
167 | All distribution of Covered Software in Source Code Form, including any
168 | Modifications that You create or to which You contribute, must be under
169 | the terms of this License. You must inform recipients that the Source
170 | Code Form of the Covered Software is governed by the terms of this
171 | License, and how they can obtain a copy of this License. You may not
172 | attempt to alter or restrict the recipients' rights in the Source Code
173 | Form.
174 |
175 | 3.2. Distribution of Executable Form
176 |
177 | If You distribute Covered Software in Executable Form then:
178 |
179 | (a) such Covered Software must also be made available in Source Code
180 | Form, as described in Section 3.1, and You must inform recipients of
181 | the Executable Form how they can obtain a copy of such Source Code
182 | Form by reasonable means in a timely manner, at a charge no more
183 | than the cost of distribution to the recipient; and
184 |
185 | (b) You may distribute such Executable Form under the terms of this
186 | License, or sublicense it under different terms, provided that the
187 | license for the Executable Form does not attempt to limit or alter
188 | the recipients' rights in the Source Code Form under this License.
189 |
190 | 3.3. Distribution of a Larger Work
191 |
192 | You may create and distribute a Larger Work under terms of Your choice,
193 | provided that You also comply with the requirements of this License for
194 | the Covered Software. If the Larger Work is a combination of Covered
195 | Software with a work governed by one or more Secondary Licenses, and the
196 | Covered Software is not Incompatible With Secondary Licenses, this
197 | License permits You to additionally distribute such Covered Software
198 | under the terms of such Secondary License(s), so that the recipient of
199 | the Larger Work may, at their option, further distribute the Covered
200 | Software under the terms of either this License or such Secondary
201 | License(s).
202 |
203 | 3.4. Notices
204 |
205 | You may not remove or alter the substance of any license notices
206 | (including copyright notices, patent notices, disclaimers of warranty,
207 | or limitations of liability) contained within the Source Code Form of
208 | the Covered Software, except that You may alter any license notices to
209 | the extent required to remedy known factual inaccuracies.
210 |
211 | 3.5. Application of Additional Terms
212 |
213 | You may choose to offer, and to charge a fee for, warranty, support,
214 | indemnity or liability obligations to one or more recipients of Covered
215 | Software. However, You may do so only on Your own behalf, and not on
216 | behalf of any Contributor. You must make it absolutely clear that any
217 | such warranty, support, indemnity, or liability obligation is offered by
218 | You alone, and You hereby agree to indemnify every Contributor for any
219 | liability incurred by such Contributor as a result of warranty, support,
220 | indemnity or liability terms You offer. You may include additional
221 | disclaimers of warranty and limitations of liability specific to any
222 | jurisdiction.
223 |
224 | 4. Inability to Comply Due to Statute or Regulation
225 | ---------------------------------------------------
226 |
227 | If it is impossible for You to comply with any of the terms of this
228 | License with respect to some or all of the Covered Software due to
229 | statute, judicial order, or regulation then You must: (a) comply with
230 | the terms of this License to the maximum extent possible; and (b)
231 | describe the limitations and the code they affect. Such description must
232 | be placed in a text file included with all distributions of the Covered
233 | Software under this License. Except to the extent prohibited by statute
234 | or regulation, such description must be sufficiently detailed for a
235 | recipient of ordinary skill to be able to understand it.
236 |
237 | 5. Termination
238 | --------------
239 |
240 | 5.1. The rights granted under this License will terminate automatically
241 | if You fail to comply with any of its terms. However, if You become
242 | compliant, then the rights granted under this License from a particular
243 | Contributor are reinstated (a) provisionally, unless and until such
244 | Contributor explicitly and finally terminates Your grants, and (b) on an
245 | ongoing basis, if such Contributor fails to notify You of the
246 | non-compliance by some reasonable means prior to 60 days after You have
247 | come back into compliance. Moreover, Your grants from a particular
248 | Contributor are reinstated on an ongoing basis if such Contributor
249 | notifies You of the non-compliance by some reasonable means, this is the
250 | first time You have received notice of non-compliance with this License
251 | from such Contributor, and You become compliant prior to 30 days after
252 | Your receipt of the notice.
253 |
254 | 5.2. If You initiate litigation against any entity by asserting a patent
255 | infringement claim (excluding declaratory judgment actions,
256 | counter-claims, and cross-claims) alleging that a Contributor Version
257 | directly or indirectly infringes any patent, then the rights granted to
258 | You by any and all Contributors for the Covered Software under Section
259 | 2.1 of this License shall terminate.
260 |
261 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all
262 | end user license agreements (excluding distributors and resellers) which
263 | have been validly granted by You or Your distributors under this License
264 | prior to termination shall survive termination.
265 |
266 | ************************************************************************
267 | * *
268 | * 6. Disclaimer of Warranty *
269 | * ------------------------- *
270 | * *
271 | * Covered Software is provided under this License on an "as is" *
272 | * basis, without warranty of any kind, either expressed, implied, or *
273 | * statutory, including, without limitation, warranties that the *
274 | * Covered Software is free of defects, merchantable, fit for a *
275 | * particular purpose or non-infringing. The entire risk as to the *
276 | * quality and performance of the Covered Software is with You. *
277 | * Should any Covered Software prove defective in any respect, You *
278 | * (not any Contributor) assume the cost of any necessary servicing, *
279 | * repair, or correction. This disclaimer of warranty constitutes an *
280 | * essential part of this License. No use of any Covered Software is *
281 | * authorized under this License except under this disclaimer. *
282 | * *
283 | ************************************************************************
284 |
285 | ************************************************************************
286 | * *
287 | * 7. Limitation of Liability *
288 | * -------------------------- *
289 | * *
290 | * Under no circumstances and under no legal theory, whether tort *
291 | * (including negligence), contract, or otherwise, shall any *
292 | * Contributor, or anyone who distributes Covered Software as *
293 | * permitted above, be liable to You for any direct, indirect, *
294 | * special, incidental, or consequential damages of any character *
295 | * including, without limitation, damages for lost profits, loss of *
296 | * goodwill, work stoppage, computer failure or malfunction, or any *
297 | * and all other commercial damages or losses, even if such party *
298 | * shall have been informed of the possibility of such damages. This *
299 | * limitation of liability shall not apply to liability for death or *
300 | * personal injury resulting from such party's negligence to the *
301 | * extent applicable law prohibits such limitation. Some *
302 | * jurisdictions do not allow the exclusion or limitation of *
303 | * incidental or consequential damages, so this exclusion and *
304 | * limitation may not apply to You. *
305 | * *
306 | ************************************************************************
307 |
308 | 8. Litigation
309 | -------------
310 |
311 | Any litigation relating to this License may be brought only in the
312 | courts of a jurisdiction where the defendant maintains its principal
313 | place of business and such litigation shall be governed by laws of that
314 | jurisdiction, without reference to its conflict-of-law provisions.
315 | Nothing in this Section shall prevent a party's ability to bring
316 | cross-claims or counter-claims.
317 |
318 | 9. Miscellaneous
319 | ----------------
320 |
321 | This License represents the complete agreement concerning the subject
322 | matter hereof. If any provision of this License is held to be
323 | unenforceable, such provision shall be reformed only to the extent
324 | necessary to make it enforceable. Any law or regulation which provides
325 | that the language of a contract shall be construed against the drafter
326 | shall not be used to construe this License against a Contributor.
327 |
328 | 10. Versions of the License
329 | ---------------------------
330 |
331 | 10.1. New Versions
332 |
333 | Mozilla Foundation is the license steward. Except as provided in Section
334 | 10.3, no one other than the license steward has the right to modify or
335 | publish new versions of this License. Each version will be given a
336 | distinguishing version number.
337 |
338 | 10.2. Effect of New Versions
339 |
340 | You may distribute the Covered Software under the terms of the version
341 | of the License under which You originally received the Covered Software,
342 | or under the terms of any subsequent version published by the license
343 | steward.
344 |
345 | 10.3. Modified Versions
346 |
347 | If you create software not governed by this License, and you want to
348 | create a new license for such software, you may create and use a
349 | modified version of this License if you rename the license and remove
350 | any references to the name of the license steward (except to note that
351 | such modified license differs from this License).
352 |
353 | 10.4. Distributing Source Code Form that is Incompatible With Secondary
354 | Licenses
355 |
356 | If You choose to distribute Source Code Form that is Incompatible With
357 | Secondary Licenses under the terms of this version of the License, the
358 | notice described in Exhibit B of this License must be attached.
359 |
360 | Exhibit A - Source Code Form License Notice
361 | -------------------------------------------
362 |
363 | This Source Code Form is subject to the terms of the Mozilla Public
364 | License, v. 2.0. If a copy of the MPL was not distributed with this
365 | file, You can obtain one at http://mozilla.org/MPL/2.0/.
366 |
367 | If it is not possible or desirable to put the notice in a particular
368 | file, then You may include the notice in a location (such as a LICENSE
369 | file in a relevant directory) where a recipient would be likely to look
370 | for such a notice.
371 |
372 | You may add additional accurate notices of copyright ownership.
373 |
374 | Exhibit B - "Incompatible With Secondary Licenses" Notice
375 | ---------------------------------------------------------
376 |
377 | This Source Code Form is "Incompatible With Secondary Licenses", as
378 | defined by the Mozilla Public License, v. 2.0.
379 |
380 |
--------------------------------------------------------------------------------
/src/content/icons/togglebutton/icon-16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/denschub/firefox-tabgroups/b92a0224ac0cb6f0eddc170cca9c08b98894ea63/src/content/icons/togglebutton/icon-16.png
--------------------------------------------------------------------------------
/src/content/icons/togglebutton/icon-32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/denschub/firefox-tabgroups/b92a0224ac0cb6f0eddc170cca9c08b98894ea63/src/content/icons/togglebutton/icon-32.png
--------------------------------------------------------------------------------
/src/content/icons/togglebutton/icon-inverted-16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/denschub/firefox-tabgroups/b92a0224ac0cb6f0eddc170cca9c08b98894ea63/src/content/icons/togglebutton/icon-inverted-16.png
--------------------------------------------------------------------------------
/src/content/icons/togglebutton/icon-inverted-32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/denschub/firefox-tabgroups/b92a0224ac0cb6f0eddc170cca9c08b98894ea63/src/content/icons/togglebutton/icon-inverted-32.png
--------------------------------------------------------------------------------
/src/data/action_creators.js:
--------------------------------------------------------------------------------
1 | const ActionCreators = {
2 | setTabgroups: function(tabgroups) {
3 | return {
4 | type: "TABGROUPS_RECEIVE",
5 | tabgroups: tabgroups
6 | };
7 | },
8 |
9 | setGroupCloseTimeout: function(timeout) {
10 | return {
11 | type: "GROUP_CLOSE_TIMEOUT_RECIEVE",
12 | closeTimeout: timeout
13 | };
14 | }
15 | };
16 |
--------------------------------------------------------------------------------
/src/data/assets/css/groupspanel.css:
--------------------------------------------------------------------------------
1 | ul, li {
2 | display: block;
3 | margin: 0;
4 | padding: 0;
5 | }
6 |
7 | li {
8 | list-style-type: none;
9 | cursor: move;
10 | }
11 |
12 | .group-title {
13 | display: block;
14 | overflow: hidden;
15 | padding-right: 16px;
16 | text-overflow: ellipsis;
17 | white-space: nowrap;
18 | }
19 |
20 | .group.active .group-title, .tab.active {
21 | font-weight: bold;
22 | }
23 |
24 | .group:hover .group-title {
25 | padding-right: 15px;
26 | }
27 |
28 | .group-title input {
29 | background: transparent;
30 | border: 1px solid rgba(0, 0, 0, 0.12);
31 | margin: 0;
32 | padding: 1px;
33 | width: calc(100% - 25px);
34 | }
35 |
36 | .group, .tab {
37 | border-radius: 3px;
38 | border: 1px solid transparent;
39 | cursor: pointer;
40 | padding: 5px;
41 | position: relative;
42 | }
43 |
44 | .tab {
45 | padding: 0;
46 | }
47 |
48 | .group:hover, .tab:hover {
49 | background-color: rgba(0, 0, 0, 0.06);
50 | border-color: rgba(0, 0, 0, 0.12);
51 | }
52 |
53 | .group.dragSourceGroup {
54 | border: inherit;
55 | background-color : inherit;
56 | }
57 |
58 | .group.draggingOver, .draggingOver {
59 | border: 1px dashed #ccc;
60 | background-color: rgba(0, 0, 0, 0.04);
61 | }
62 |
63 | .tab-list {
64 | display: none;
65 | margin-top: 5px;
66 | }
67 |
68 | .expanded .tab-list {
69 | display: block;
70 | }
71 |
72 | .tab-title {
73 | display: inline-block;
74 | overflow: hidden;
75 | text-overflow: ellipsis;
76 | white-space: nowrap;
77 | max-width: calc(100% - 32px);
78 | padding: 5px 5px 1px;
79 | margin-bottom: 1px;
80 | }
81 |
82 | .tab-icon {
83 | height: 16px;
84 | width: 16px;
85 | margin-right: 1px;
86 | margin-bottom: 1px;
87 | margin-left: 4px;
88 | }
89 |
90 | .tab-icon:-moz-broken {
91 | background: url("chrome://mozapps/skin/places/defaultFavicon.png");
92 | background-size: contain;
93 | display: inline-block;
94 | }
95 |
96 | @media (min-resolution: 1.1dppx) {
97 | .tab-icon:-moz-broken {
98 | background-image: url("chrome://mozapps/skin/places/defaultFavicon@2x.png");
99 | }
100 | }
101 |
102 | .expanded .group-title {
103 | border-bottom: 1px solid rgba(0, 0, 0, 0.12);
104 | padding-bottom: 5px;
105 | }
106 |
107 | .group-controls {
108 | display: inline-block;
109 | font-size: 120%;
110 | opacity: 0.7;
111 | position: absolute;
112 | right: 5px;
113 | top: 4px;
114 | }
115 |
116 | .group.editing .group-controls {
117 | top: 6px;
118 | }
119 |
120 | .group.closing .group-controls .group-close,
121 | .group.closing:hover .group-controls .group-close,
122 | .group.closing .group-controls .fa-chevron-down,
123 | .group.closing .group-controls .group-edit {
124 | display: none;
125 | }
126 |
127 | .group.closing .group-title {
128 | color: #ccc;
129 | text-decoration: line-through;
130 | }
131 |
132 | .group:hover .group-title {
133 | padding-right: 55px;
134 | }
135 |
136 | .group .group-controls .group-close,
137 | .group .group-controls .group-edit {
138 | display: none;
139 | }
140 |
141 | .group:hover .group-controls .group-close,
142 | .group:hover .group-controls .group-edit {
143 | display: inline-block;
144 | }
145 |
--------------------------------------------------------------------------------
/src/data/components/app.js:
--------------------------------------------------------------------------------
1 | const App = React.createClass({
2 | propTypes: {
3 | onGroupAddClick: React.PropTypes.func,
4 | onGroupAddDrop: React.PropTypes.func,
5 | onGroupClick: React.PropTypes.func,
6 | onGroupDrop: React.PropTypes.func,
7 | onGroupCloseClick: React.PropTypes.func,
8 | onGroupTitleChange: React.PropTypes.func,
9 | onTabClick: React.PropTypes.func,
10 | onTabDrag: React.PropTypes.func,
11 | onTabDragStart: React.PropTypes.func,
12 | uiHeightChanged: React.PropTypes.func
13 | },
14 |
15 | render: function() {
16 | return React.createElement(GroupList, this.props);
17 | }
18 | });
19 |
--------------------------------------------------------------------------------
/src/data/components/group.js:
--------------------------------------------------------------------------------
1 | const Group = React.createClass({
2 | propTypes: {
3 | closeTimeout: React.PropTypes.number,
4 | group: React.PropTypes.object.isRequired,
5 | onGroupClick: React.PropTypes.func,
6 | onGroupDrop: React.PropTypes.func,
7 | onGroupCloseClick: React.PropTypes.func,
8 | onGroupTitleChange: React.PropTypes.func,
9 | onTabClick: React.PropTypes.func,
10 | onTabDrag: React.PropTypes.func,
11 | onTabDragStart: React.PropTypes.func,
12 | uiHeightChanged: React.PropTypes.func
13 | },
14 |
15 | getInitialState: function() {
16 | return {
17 | closing: false,
18 | editing: false,
19 | expanded: false,
20 | draggingOverCounter: 0,
21 | dragSourceGroup: false,
22 | newTitle: this.getTitle()
23 | };
24 | },
25 |
26 | componentDidUpdate: function() {
27 | this.props.uiHeightChanged && this.props.uiHeightChanged();
28 | },
29 |
30 | getTitle: function() {
31 | return this.props.group.title || (
32 | addon.options.l10n.unnamed_group + " " + this.props.group.id
33 | );
34 | },
35 |
36 | render: function() {
37 | let titleElement;
38 | if (this.state.editing) {
39 | titleElement = React.DOM.input(
40 | {
41 | type: "text",
42 | defaultValue: this.getTitle(),
43 | onChange: (event) => {
44 | this.setState({newTitle: event.target.value});
45 | },
46 | onClick: (event) => {
47 | event.stopPropagation();
48 | },
49 | onKeyUp: this.handleGroupTitleInputKey
50 | }
51 | );
52 | } else {
53 | titleElement = React.DOM.span({}, this.getTitle());
54 | }
55 |
56 | let groupClasses = classNames({
57 | active: this.props.group.active,
58 | editing: this.state.editing,
59 | closing: this.state.closing,
60 | draggingOver: this.state.draggingOverCounter !== 0,
61 | dragSourceGroup: this.state.dragSourceGroup,
62 | expanded: this.state.expanded,
63 | group: true
64 | });
65 |
66 | return (
67 | React.DOM.li(
68 | {
69 | className: groupClasses,
70 | onClick: this.handleGroupClick,
71 | onDragOver: this.handleGroupDragOver,
72 | onDragEnter: this.handleGroupDragEnter,
73 | onDragLeave: this.handleGroupDragLeave,
74 | onDrop: this.handleGroupDrop
75 | },
76 | React.DOM.span(
77 | {
78 | className: "group-title"
79 | },
80 | titleElement,
81 | React.createElement(
82 | GroupControls,
83 | {
84 | closing: this.state.closing,
85 | editing: this.state.editing,
86 | expanded: this.state.expanded,
87 | onClose: this.handleGroupCloseClick,
88 | onEdit: this.handleGroupEditClick,
89 | onEditAbort: this.handleGroupEditAbortClick,
90 | onEditSave: this.handleGroupEditSaveClick,
91 | onExpand: this.handleGroupExpandClick,
92 | onUndoCloseClick: this.handleGroupCloseAbortClick
93 | }
94 | )
95 | ),
96 | this.state.expanded && React.createElement(
97 | TabList,
98 | {
99 | tabs: this.props.group.tabs,
100 | onTabClick: this.props.onTabClick,
101 | onTabDrag: this.props.onTabDrag,
102 | onTabDragStart: this.props.onTabDragStart,
103 | onTabDragEnd: this.props.onTabDragEnd
104 | }
105 | )
106 | )
107 | );
108 | },
109 |
110 | handleGroupCloseClick: function(event) {
111 | event.stopPropagation();
112 | this.setState({editing: false});
113 | this.setState({closing: true});
114 |
115 | let group = this;
116 |
117 | if (this.props.closeTimeout == 0) {
118 | group.props.onGroupCloseClick(group.props.group.id);
119 | return;
120 | }
121 |
122 | setTimeout(function() {
123 | group.props.onGroupCloseClick(group.props.group.id);
124 | }, this.props.closeTimeout * 1000);
125 | },
126 |
127 | handleGroupClick: function(event) {
128 | event.stopPropagation();
129 | this.props.onGroupClick(this.props.group.id);
130 | },
131 |
132 | handleGroupEditClick: function(event) {
133 | event.stopPropagation();
134 | this.setState({editing: !this.state.editing});
135 | },
136 |
137 | handleGroupEditAbortClick: function(event) {
138 | event.stopPropagation();
139 | this.setState({editing: false});
140 | },
141 |
142 | handleGroupEditSaveClick: function(event) {
143 | event.stopPropagation();
144 | this.setState({editing: false});
145 | this.props.onGroupTitleChange(this.props.group.id, this.state.newTitle);
146 | },
147 |
148 | handleGroupExpandClick: function(event) {
149 | event.stopPropagation();
150 | this.setState({expanded: !this.state.expanded});
151 | },
152 |
153 | handleGroupTitleInputKey: function(event) {
154 | if (event.keyCode == 13) {
155 | this.setState({editing: false});
156 | this.props.onGroupTitleChange(this.props.group.id, this.state.newTitle);
157 | }
158 | },
159 |
160 | handleGroupDrop: function(event) {
161 | event.stopPropagation();
162 |
163 | this.setState({draggingOverCounter: 0});
164 |
165 | let sourceGroup = event.dataTransfer.getData("tab/group");
166 | let tabIndex = event.dataTransfer.getData("tab/index");
167 |
168 | this.props.onGroupDrop(
169 | sourceGroup,
170 | tabIndex,
171 | this.props.group.id
172 | );
173 | },
174 |
175 | handleGroupDragOver: function(event) {
176 | event.stopPropagation();
177 | event.preventDefault();
178 | return false;
179 | },
180 |
181 | handleGroupDragEnter: function(event) {
182 | event.stopPropagation();
183 | event.preventDefault();
184 |
185 | let sourceGroupId = event.dataTransfer.getData("tab/group");
186 | let isSourceGroup = sourceGroupId == this.props.group.id;
187 | this.setState({dragSourceGroup: isSourceGroup});
188 |
189 | let draggingCounterValue = (this.state.draggingOverCounter == 1) ? 2 : 1;
190 | this.setState({draggingOverCounter: draggingCounterValue});
191 | },
192 |
193 | handleGroupDragLeave: function(event) {
194 | event.stopPropagation();
195 | event.preventDefault();
196 |
197 | if (this.state.draggingOverCounter == 2) {
198 | this.setState({draggingOverCounter: 1});
199 | } else if (this.state.draggingOverCounter == 1) {
200 | this.setState({draggingOverCounter: 0});
201 | }
202 |
203 | return false;
204 | },
205 |
206 | handleGroupCloseAbortClick: function(event) {
207 | event.stopPropagation();
208 |
209 | this.setState({closing: false});
210 | }
211 | });
212 |
--------------------------------------------------------------------------------
/src/data/components/groupaddbutton.js:
--------------------------------------------------------------------------------
1 | const GroupAddButton = React.createClass({
2 | propTypes: {
3 | onClick: React.PropTypes.func,
4 | onDrop: React.PropTypes.func
5 | },
6 |
7 | getInitialState: function() {
8 | return {
9 | draggingOverCounter: 0
10 | };
11 | },
12 |
13 | render: function() {
14 | let buttonClasses = classNames({
15 | draggingOver: this.state.draggingOverCounter !== 0,
16 | group: true
17 | });
18 |
19 | return (
20 | React.DOM.li(
21 | {
22 | className: buttonClasses,
23 | onClick: this.handleClick,
24 | onDrop: this.handleDrop,
25 | onDragOver: this.handleGroupDragOver,
26 | onDragEnter: this.handleDragEnter,
27 | onDragLeave: this.handleDragLeave
28 | },
29 | React.DOM.span(
30 | {className: "group-title"},
31 | addon.options.l10n.add_group
32 | )
33 | )
34 | );
35 | },
36 |
37 | handleClick: function(event) {
38 | event.stopPropagation();
39 | this.props.onClick();
40 | },
41 |
42 | handleGroupDragOver: function(event) {
43 | event.stopPropagation();
44 | event.preventDefault();
45 | },
46 |
47 | handleDragEnter: function(event) {
48 | event.stopPropagation();
49 | event.preventDefault();
50 |
51 | let draggingCounterValue = (this.state.draggingOverCounter == 1) ? 2 : 1;
52 | this.setState({draggingOverCounter: draggingCounterValue});
53 | },
54 |
55 | handleDragLeave: function(event) {
56 | event.stopPropagation();
57 | event.preventDefault();
58 |
59 | if (this.state.draggingOverCounter == 2) {
60 | this.setState({draggingOverCounter: 1});
61 | } else if (this.state.draggingOverCounter == 1) {
62 | this.setState({draggingOverCounter: 0});
63 | }
64 | },
65 |
66 | handleDrop: function(event) {
67 | event.stopPropagation();
68 |
69 | this.setState({draggingOverCounter: 0});
70 |
71 | let sourceGroup = event.dataTransfer.getData("tab/group");
72 | let tabIndex = event.dataTransfer.getData("tab/index");
73 |
74 | this.props.onDrop(
75 | sourceGroup,
76 | tabIndex
77 | );
78 | }
79 | });
80 |
--------------------------------------------------------------------------------
/src/data/components/groupcontrols.js:
--------------------------------------------------------------------------------
1 | const GroupControls = React.createClass({
2 | propTypes: {
3 | expanded: React.PropTypes.bool.isRequired,
4 | onClose: React.PropTypes.func,
5 | onEdit: React.PropTypes.func,
6 | onEditAbort: React.PropTypes.func,
7 | onEditSave: React.PropTypes.func,
8 | onExpand: React.PropTypes.func,
9 | onUndoCloseClick: React.PropTypes.func
10 | },
11 |
12 | getEditControls: function() {
13 | let controls;
14 | if (this.props.editing) {
15 | controls = [
16 | React.DOM.i({
17 | className: "group-edit fa fa-fw fa-check",
18 | onClick: this.props.onEditSave
19 | }),
20 | React.DOM.i({
21 | className: "group-edit fa fa-fw fa-ban",
22 | onClick: this.props.onEditAbort
23 | })
24 | ];
25 | } else {
26 | controls = React.DOM.i({
27 | className: "group-edit fa fa-fw fa-pencil",
28 | onClick: this.props.onEdit
29 | });
30 | }
31 |
32 | return controls;
33 | },
34 |
35 | getClosingControls: function() {
36 | return [
37 | React.DOM.i({
38 | className: "group-close-undo fa fa-fw fa-undo",
39 | onClick: this.props.onUndoCloseClick
40 | })
41 | ];
42 | },
43 |
44 | render: function() {
45 | let groupControls;
46 | if (this.props.closing) {
47 | groupControls = this.getClosingControls();
48 | } else {
49 | groupControls = this.getEditControls();
50 | }
51 |
52 | let expanderClasses = classNames({
53 | "group-expand": true,
54 | "fa": true,
55 | "fa-fw": true,
56 | "fa-chevron-down": !this.props.expanded,
57 | "fa-chevron-up": this.props.expanded
58 | });
59 |
60 | return React.DOM.span(
61 | {
62 | className: "group-controls"
63 | },
64 | groupControls,
65 | React.DOM.i({
66 | className: "group-close fa fa-fw fa-times",
67 | onClick: this.props.onClose
68 | }),
69 | React.DOM.i({
70 | className: expanderClasses,
71 | onClick: this.props.onExpand
72 | })
73 | );
74 | }
75 | });
76 |
--------------------------------------------------------------------------------
/src/data/components/grouplist.js:
--------------------------------------------------------------------------------
1 | const GroupList = (() => {
2 | const GroupListStandalone = React.createClass({
3 | propTypes: {
4 | groups: React.PropTypes.object.isRequired,
5 | closeTimeout: React.PropTypes.number,
6 | onGroupAddClick: React.PropTypes.func,
7 | onGroupAddDrop: React.PropTypes.func,
8 | onGroupClick: React.PropTypes.func,
9 | onGroupDrop: React.PropTypes.func,
10 | onGroupCloseClick: React.PropTypes.func,
11 | onGroupTitleChange: React.PropTypes.func,
12 | onTabClick: React.PropTypes.func,
13 | onTabDrag: React.PropTypes.func,
14 | onTabDragStart: React.PropTypes.func,
15 | uiHeightChanged: React.PropTypes.func
16 | },
17 |
18 | componentDidUpdate: function() {
19 | this.props.uiHeightChanged && this.props.uiHeightChanged();
20 | },
21 |
22 | render: function() {
23 | return React.DOM.ul(
24 | {className: "group-list"},
25 | this.props.groups.map((group) => {
26 | return React.createElement(Group, {
27 | key: group.id,
28 | group: group,
29 | closeTimeout: this.props.closeTimeout,
30 | onGroupClick: this.props.onGroupClick,
31 | onGroupDrop: this.props.onGroupDrop,
32 | onGroupCloseClick: this.props.onGroupCloseClick,
33 | onGroupTitleChange: this.props.onGroupTitleChange,
34 | onTabClick: this.props.onTabClick,
35 | onTabDrag: this.props.onTabDrag,
36 | onTabDragStart: this.props.onTabDragStart,
37 | uiHeightChanged: this.props.uiHeightChanged
38 | });
39 | }),
40 | React.createElement(
41 | GroupAddButton,
42 | {
43 | onClick: this.props.onGroupAddClick,
44 | onDrop: this.props.onGroupAddDrop
45 | }
46 | )
47 | );
48 | }
49 | });
50 |
51 | return ReactRedux.connect((state) => {
52 | return {
53 | groups: state.get("tabgroups"),
54 | closeTimeout: state.get("closeTimeout")
55 | };
56 | }, ActionCreators)(GroupListStandalone);
57 | })();
58 |
--------------------------------------------------------------------------------
/src/data/components/tab.js:
--------------------------------------------------------------------------------
1 | const Tab = React.createClass({
2 | propTypes: {
3 | onTabClick: React.PropTypes.func,
4 | onTabDrag: React.PropTypes.func,
5 | onTabDragStart: React.PropTypes.func,
6 | tab: React.PropTypes.object.isRequired
7 | },
8 |
9 | render: function() {
10 | let favicon = React.DOM.img({
11 | alt: "",
12 | className: "tab-icon",
13 | src: this.props.tab.icon
14 | });
15 |
16 | let tabClasses = classNames({
17 | active: this.props.tab.active,
18 | tab: true
19 | });
20 |
21 | return (
22 | React.DOM.li(
23 | {
24 | className: tabClasses,
25 | onClick: this.handleTabClick,
26 | onDrag: this.handleTabDrag,
27 | onDragStart: this.handleTabDragStart,
28 | draggable: true
29 | },
30 | favicon,
31 | React.DOM.span({className: "tab-title"}, this.props.tab.title)
32 | )
33 | );
34 | },
35 |
36 | handleTabClick: function(event) {
37 | event.stopPropagation();
38 |
39 | let tab = this.props.tab;
40 | this.props.onTabClick(
41 | tab.group,
42 | tab.index
43 | );
44 | },
45 |
46 | handleTabDrag: function(event) {
47 | event.stopPropagation();
48 |
49 | let tab = this.props.tab;
50 | event.dataTransfer.setData("tab/index", tab.index);
51 | event.dataTransfer.setData("tab/group", tab.group);
52 |
53 | this.props.onTabDrag(
54 | tab.group,
55 | tab.index
56 | );
57 | },
58 |
59 | handleTabDragStart: function(event) {
60 | event.stopPropagation();
61 |
62 | let tab = this.props.tab;
63 | event.dataTransfer.setData("tab/index", tab.index);
64 | event.dataTransfer.setData("tab/group", tab.group);
65 |
66 | this.props.onTabDragStart(
67 | tab.group,
68 | tab.index
69 | );
70 | }
71 | });
72 |
--------------------------------------------------------------------------------
/src/data/components/tablist.js:
--------------------------------------------------------------------------------
1 | const TabList = React.createClass({
2 | propTypes: {
3 | onTabClick: React.PropTypes.func,
4 | onTabDrag: React.PropTypes.func,
5 | onTabDragStart: React.PropTypes.func,
6 | tabs: React.PropTypes.array.isRequired
7 | },
8 |
9 | render: function() {
10 | return (
11 | React.DOM.ul(
12 | {className: "tab-list"},
13 | this.props.tabs.map((tab) => {
14 | return React.createElement(Tab, {
15 | key: tab.index,
16 | tab: tab,
17 | onTabClick: this.props.onTabClick,
18 | onTabDrag: this.props.onTabDrag,
19 | onTabDragStart: this.props.onTabDragStart,
20 | uiHeightChanged: this.props.uiHeightChanged
21 | });
22 | })
23 | )
24 | );
25 | }
26 | });
27 |
--------------------------------------------------------------------------------
/src/data/groupspanel.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |