├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── pull_request_template.md ├── release_description_template.md └── workflows │ └── ci.yaml ├── .gitignore ├── .vscode └── launch.json ├── .whitesource ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── PRIVACY ├── README.md ├── TabsAside.html ├── _locales ├── en │ └── messages.json ├── es │ └── messages.json ├── it │ └── messages.json ├── pl │ └── messages.json ├── pt_BR │ └── messages.json ├── ru │ └── messages.json └── zh_CN │ └── messages.json ├── css ├── style.css ├── style.dark.css ├── style.generic.css └── style.listview.css ├── fonts ├── Microsoft_Fabric_assets_license_agreement_May_2019.pdf ├── Segoe_EULA.txt ├── segoemdl2.ttf ├── segoemdl2.woff ├── segoeui.ttf └── segoeui.woff ├── icons ├── bmc.svg ├── check.svg ├── feedback.svg ├── github.svg ├── icon-128.png ├── icon-16.png ├── icon-32.png ├── icon-48.png └── list.svg ├── images ├── edge_icon.png ├── tab_icon.png ├── tab_icon_dark.png ├── tab_thumbnail.png └── tab_thumbnail_dark.png ├── js ├── aside-script.js ├── background.js └── lib │ └── lzutf8.min.js └── manifest.json /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | #github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | #patreon: # Replace with a single Patreon username 5 | open_collective: TabsAside 6 | #ko_fi: # Replace with a single Ko-fi username 7 | #tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | #community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | #liberapay: # Replace with a single Liberapay username 10 | #issuehunt: # Replace with a single IssueHunt username 11 | #otechie: # Replace with a single Otechie username 12 | custom: [ "https://buymeacoffee.com/xfox111" ] 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve the extension 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | ### Description 11 | A clear and concise description of what the bug is. 12 | 13 | ### Reproduction steps 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '...' 17 | 3. Scroll down to '...' 18 | 4. See error 19 | 20 | ### Expected behavior 21 | A clear and concise description of what you expected to happen. 22 | 23 | ### Screenshots 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | ### Environment 27 | Please provide the following information: 28 | - Operating System: [e.g. Windows 10 Pro 1909 (10.0.18363)] 29 | - Browser: [e.g. Microsoft Edge 83.0.478.56] 30 | - Extension version: [e.g. 1.5] 31 | 32 | ### Additional context 33 | Add any other context about the problem here. -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when '...' 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | Implements following issues: 2 | 3 | ## Changelog 4 | - Item 1 5 | - Item 2 6 | - Item 3 7 | 8 | ## PR Checklist 9 | - [ ] Change extension version in the manifest -------------------------------------------------------------------------------- /.github/release_description_template.md: -------------------------------------------------------------------------------- 1 | ## What's new 2 | 3 | ## How to install 4 | 1. Download attached archive and unpack it 5 | 2. Enable Developers mode on your browser extensions page 6 | 3. Click "Load unpacked" button and navigate to the extension root folder (contains `manifest.json`) 7 | 4. Done! 8 | 9 | *On Firefox you should open manifest file instead of extension's folder 10 | 11 | **Note:** If you delete extension folder it will disappear from your browser 12 | 13 | _Sideloaded extensions don't replace officially installed ones_ -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | Firefox: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - uses: actions/checkout@v2 13 | 14 | - name: Build Extension for Firefox 15 | id: web-ext-build 16 | uses: kewisch/action-web-ext@v1 17 | with: 18 | cmd: build 19 | 20 | - name: 'Sign & publish' 21 | id: web-ext-sign 22 | uses: kewisch/action-web-ext@v1 23 | with: 24 | cmd: sign 25 | channel: listed 26 | source: ${{ steps.web-ext-build.outputs.target }} 27 | apiKey: ${{ secrets.FIREFOX_API_KEY }} 28 | apiSecret: ${{ secrets.FIREFOX_CLIENT_SECRET }} 29 | 30 | - name: Drop artifacts 31 | uses: actions/upload-artifact@v2 32 | with: 33 | name: 'Firefox Artefacts' 34 | path: ${{ steps.web-ext-build.outputs.target }} 35 | 36 | Chrome: 37 | runs-on: ubuntu-latest 38 | 39 | steps: 40 | - uses: actions/checkout@v2 41 | 42 | - name: Pack extension 43 | uses: TheDoctor0/zip-release@0.4.1 44 | with: 45 | filename: ./TabsAside.zip 46 | exclusions: '.git/* .vscode/* .github/* *.md' 47 | 48 | - name: Publish to Chrome Webstore 49 | uses: trmcnvn/chrome-addon@v2 50 | with: 51 | extension: mgmjbodjgijnebfgohlnjkegdpbdjgin 52 | zip: ./TabsAside.zip 53 | client-id: ${{ secrets.CHROME_CLIENT_ID }} 54 | client-secret: ${{ secrets.CHROME_CLIENT_SECRET }} 55 | refresh-token: ${{ secrets.CHROME_REFRESH_TOKEN }} 56 | 57 | - name: Upload artifact 58 | uses: xresloader/upload-to-github-release@v1 59 | env: 60 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 61 | with: 62 | file: ./TabsAside.zip 63 | tags: true 64 | draft: false 65 | 66 | - name: Drop artifacts 67 | uses: actions/upload-artifact@v2 68 | with: 69 | name: 'Chrome Artifacts' 70 | path: ./TabsAside.zip -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | \.vscode -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": 4 | [ 5 | { 6 | "name": "Debug", 7 | "type": "firefox", 8 | "request": "launch", 9 | "reAttach": true, 10 | "addonPath": "${workspaceFolder}" 11 | } 12 | ] 13 | } -------------------------------------------------------------------------------- /.whitesource: -------------------------------------------------------------------------------- 1 | { 2 | "scanSettings": { 3 | "baseBranches": [] 4 | }, 5 | "checkRunSettings": { 6 | "vulnerableCheckRunConclusionLevel": "failure", 7 | "displayMode": "diff" 8 | }, 9 | "issueSettings": { 10 | "minSeverityLevel": "LOW" 11 | } 12 | } -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | - Using welcoming and inclusive language 18 | - Being respectful of differing viewpoints and experiences 19 | - Gracefully accepting constructive criticism 20 | - Focusing on what is best for the community 21 | - Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | - The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | - Trolling, insulting/derogatory comments, and personal or political attacks 28 | - Public or private harassment 29 | - Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | - Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at opensource@xfox111.net. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | 77 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribution Guidelines 2 | Welcome, and thank you for your interest in contributing to my project! 3 | 4 | There are many ways in which you can contribute, beyond writing code. The goal of this document is to provide a high-level overview of how you can get involved. 5 | 6 | ## Table of Contents 7 | - [Contribution Guidelines](#contribution-guidelines) 8 | - [Table of Contents](#table-of-contents) 9 | - [Asking Questions](#asking-questions) 10 | - [Providing Feedback](#providing-feedback) 11 | - [Reporting Issues](#reporting-issues) 12 | - [Look For an Existing Issue](#look-for-an-existing-issue) 13 | - [Writing Good Bug Reports and Feature Requests](#writing-good-bug-reports-and-feature-requests) 14 | - [Final Checklist](#final-checklist) 15 | - [Follow Your Issue](#follow-your-issue) 16 | - [Contributing to the codebase](#contributing-to-the-codebase) 17 | - [Deploy test version on your browser](#deploy-test-version-on-your-browser) 18 | - [Development workflow](#development-workflow) 19 | - [Release](#release) 20 | - [Coding guidelines](#coding-guidelines) 21 | - [Indentation](#indentation) 22 | - [Names](#names) 23 | - [Comments](#comments) 24 | - [Strings](#strings) 25 | - [Style](#style) 26 | - [Finding an issue to work on](#finding-an-issue-to-work-on) 27 | - [Contributing to translations](#contributing-to-translations) 28 | - [Submitting pull requests](#submitting-pull-requests) 29 | - [Spell check errors](#spell-check-errors) 30 | - [Thank You!](#thank-you) 31 | - [Attribution](#attribution) 32 | 33 | ## Asking Questions 34 | Have a question? Rather than opening an issue, please ask me directly on opensource@xfox111.net. 35 | 36 | ## Providing Feedback 37 | Your comments and feedback are welcome. 38 | You can leave your feedbak on feedback@xfox111.net or do it on [Microsoft Edge Add-ons Webstore](https://microsoftedge.microsoft.com/addons/detail/tabs-aside/kmnblllmalkiapkfknnlpobmjjdnlhnd), [Chrome Extensions Webstore](https://chrome.google.com/webstore/detail/tabs-aside/mgmjbodjgijnebfgohlnjkegdpbdjgin) or [Mozilla Add-ons Webstore](https://addons.mozilla.org/firefox/addon/ms-edge-tabs-aside/) 39 | 40 | ## Reporting Issues 41 | Have you identified a reproducible problem in the extension? Have a feature request? I'd like to hear it! Here's how you can make reporting your issue as effective as possible. 42 | 43 | ### Look For an Existing Issue 44 | Before you create a new issue, please do a search in [open issues](https://github.com/xfox111/TabsAsideExtension/issues) to see if the issue or feature request has already been filed. 45 | 46 | Be sure to scan through the [feature requests](https://github.com/XFox111/TabsAsideExtension/issues?q=is%3Aissue+is%3Aopen+label%3Aenhancement). 47 | 48 | If you find your issue already exists, make relevant comments and add your [reaction](https://github.com/blog/2119-add-reactions-to-pull-requests-issues-and-comments). Use a reaction in place of a "+1" comment: 49 | 50 | - 👍 - upvote 51 | - 👎 - downvote 52 | 53 | If you cannot find an existing issue that describes your bug or feature, create a new issue using the guidelines below. 54 | 55 | ### Writing Good Bug Reports and Feature Requests 56 | File a single issue per problem and feature request. Do not enumerate multiple bugs or feature requests in the same issue. 57 | 58 | Do not add your issue as a comment to an existing issue unless they are the same ones. Many issues look similar, but have different causes. 59 | 60 | The more information you can provide, the more likely someone will be successful at reproducing the issue and finding a solution. 61 | 62 | Please include the following with each issue: 63 | - Current version of the extension 64 | - Your current browser and OS name 65 | - Reproducible steps (1... 2... 3...) that cause the issue 66 | - What you expected to see, versus what you actually saw 67 | - Images, animations, or a link to a video showing the issue occurring 68 | 69 | ### Final Checklist 70 | Please remember to do the following: 71 | 72 | - [ ] Search the issue repository to ensure your report is a new issue 73 | - [ ] Separate issues reports 74 | - [ ] Include as much information as you can to your report 75 | 76 | Don't feel bad if the developers can't reproduce the issue right away. They will simply ask for more information! 77 | 78 | ### Follow Your Issue 79 | Once your report is submitted, be sure to stay in touch with the devs in case they need more help from you. 80 | 81 | ## Contributing to the codebase 82 | If you are interested in writing code to fix issues or implement new awesome features you can follow these guidelines to get a better result 83 | 84 | ### Deploy test version on your browser 85 | 1. Clone repository to local storage using [Git](https://guides.github.com/introduction/git-handbook/) 86 | 87 | ```bash 88 | git clone https://github.com/xfox111/TabsAsideExtension.git 89 | ``` 90 | 2. Enable Developers mode on your browser extensions page 91 | 3. Click "Load unpacked" button and navigate to the extension root folder (contains `manifest.json`) 92 | 4. Done! 93 | 94 | Next time you make any changes to the codebase, reload the extension by toggling it off and on or by pressing "Reload" button on the extensions list page 95 | 96 | > **Note:** You can also check [this article](https://xfox111.net/46hsgv) to get more information about debugging web extensions 97 | 98 | ### Development workflow 99 | This section represents how contributors should interact with codebase implementing features and fixing bugs 100 | 101 | 1. Getting assigned to the issue 102 | 2. Creating a repository fork 103 | 3. Making changes to the codebase 104 | 4. Creating a pull request to `master` 105 | 5. Reviewing & completing PR 106 | 6. Done 107 | 108 | #### Release 109 | The next stage is the release. Release performs on every push to master (which makes functional changes to the source code). Release performs manually by @XFox111 into: Chrome webstore, Edge webstore and GitHub releases 110 | 111 | ### Coding guidelines 112 | #### Indentation 113 | We use tabs, not spaces. 114 | 115 | #### Names 116 | The project naming rules inherit [.NET Naming Guidelines](https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/naming-guidelines). Nevertheless there'is some distinction with the guidelines as well as additions to the one: 117 | 118 | - Use `camelCase` for variables instead of `CamelCase` stated in [Capitalization Conventions](https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/capitalization-conventions#capitalization-rules-for-identifiers) 119 | - Use `snake_case` for file names 120 | 121 | #### Comments 122 | Leave as more comments as you can. Remember: the more detailed documentation your code has the less programmers will curse you in the future 123 | 124 | #### Strings 125 | Use "double quotes" wherever it's possible 126 | 127 | #### Style 128 | - Prefer to use lambda functions 129 | - Put curly braces on new lines 130 | - Wrong: 131 | 132 | ```javascript 133 | if (condition) { 134 | ... 135 | } 136 | ``` 137 | 138 | - Correct: 139 | 140 | ```javascript 141 | if (condition) 142 | { 143 | ... 144 | } 145 | ``` 146 | - Put spaces between operators and before braces in methods declarations, conditionals and loops 147 | - Wrong: 148 | - `y=k*x+b` 149 | - `function FunctionName()` 150 | - Correct: 151 | - `y = k * x + b` 152 | - `function FunctionName ()` 153 | - Use ternary conditionals wherever it's possible 154 | - Wrong: 155 | ```javascript 156 | var s; 157 | if (condition) 158 | s = "Life"; 159 | else 160 | s = "Death"; 161 | ``` 162 | - Correct: 163 | 164 | ```javascript 165 | var s = condition ? "Life" : "Death"; 166 | ``` 167 | - Do not surround loop and conditional bodies with curly braces if they can be avoided 168 | - Wrong: 169 | ```javascript 170 | if (condition) 171 | { 172 | console.log("Hello, World!"); 173 | } 174 | else 175 | { 176 | return; 177 | } 178 | ``` 179 | - Correct 180 | 181 | ```javascript 182 | if (condition) 183 | console.log("Hello, World!"); 184 | else 185 | return; 186 | ``` 187 | 188 | ### Finding an issue to work on 189 | Check out the [full issues list](https://github.com/XFox111/TabsAsideExtension/issues?utf8=%E2%9C%93&q=is%3Aopen+is%3Aissue) for a list of all potential areas for contributions. **Note** that just because an issue exists in the repository does not mean we will accept a contribution. There are several reasons we may not accept a pull request like: 190 | 191 | - Performance - One of Tabs Aside core values is to deliver a lightweight extension, that means it should perform well in both real and test environments. 192 | - User experience - Since we want to deliver a lightweight extension, the UX should feel lightweight as well and not be cluttered. Most changes to the UI should go through the issue owner and project owner (@XFox111). 193 | - Architectural - Project owner needs to agree with any architectural impact a change may make. Such things must be discussed with and agreed upon by the project owner. 194 | 195 | To improve the chances to get a pull request merged you should select an issue that is labelled with the `help-wanted` or `bug` labels. If the issue you want to work on is not labelled with `help-wanted` or `bug`, you can start a conversation with the project owner asking whether an external contribution will be considered. 196 | 197 | To avoid multiple pull requests resolving the same issue, let others know you are working on it by saying so in a comment. 198 | 199 | ### Contributing to translations 200 | If you want to help us to translate this extension into other languages, please read [this article](https://developer.chrome.com/extensions/i18n) 201 | 202 | **Note** that whatever you want to contribute to the codebase, you should do it only after you got assigned on an issue 203 | 204 | ### Submitting pull requests 205 | To enable us to quickly review and accept your pull requests, always create one pull request per issue and [link the issue in the pull request](https://github.com/blog/957-introducing-issue-mentions). Never merge multiple requests in one unless they have the same root cause. Be sure to follow our [Coding Guidelines](#coding-guidelines) and keep code changes as small as possible. Avoid pure formatting changes to code that has not been modified otherwise. Pull requests should contain tests whenever possible. Fill pull request content according to its template. Deviations from template are not recommended 206 | 207 | #### Spell check errors 208 | Pull requests that fix typos are welcomed but please make sure it doesn't touch multiple feature areas, otherwise it will be difficult to review. Pull requests only fixing spell check errors in source code are not recommended. 209 | 210 | ## Thank You! 211 | Your contributions to open source, large or small, make great projects like this possible. Thank you for taking the time to contribute. 212 | 213 | ## Attribution 214 | These Contribution Guidelines are adapted from the [Contributing to VS Code](https://github.com/microsoft/vscode/blob/master/CONTRIBUTING.md) 215 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Michael "XFox" Gordeev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /PRIVACY: -------------------------------------------------------------------------------- 1 | Tabs Aside Extension Privacy Policy 2 | 1. Developers of the extension don't affiliate with Google LLC. or Microsoft Corporation in any way 3 | 2. This extension doesn't transfer any personal data (data which may be used to track your location or reveal your identity) to any remote or local server 4 | 3. This extension doesn't share any personal data with third parties 5 | 4. This extension stores following personal data: 6 | - Browser tabs which have been saved for later by user via this extension (if user click "Set current tabs aside" button). This includes titles, favicons and web links 7 | 5. User can delete all saved personal data by removing this extension from his browser 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tabs aside 2 | [![GitHub release (latest by date)](https://img.shields.io/github/v/release/xfox111/TabsAsideExtension)](https://github.com/xfox111/TabsAsideExtension/releases/latest) 3 | ![CI](https://github.com/XFox111/TabsAsideExtension/workflows/CI/badge.svg) 4 | [![Mozilla Add-on](https://img.shields.io/amo/rating/ms-edge-tabs-aside?label=Firefox%20rating)](https://addons.mozilla.org/firefox/addon/ms-edge-tabs-aside/) 5 | 6 | [![Chrome Web Store](https://img.shields.io/chrome-web-store/users/mgmjbodjgijnebfgohlnjkegdpbdjgin?label=Chrome%20Webstore%20downloads)](https://chrome.google.com/webstore/detail/tabs-aside/mgmjbodjgijnebfgohlnjkegdpbdjgin) 7 | [![Mozilla Add-on](https://img.shields.io/amo/users/ms-edge-tabs-aside?label=Firefox%20Webstore%20downloads)](https://addons.mozilla.org/firefox/addon/ms-edge-tabs-aside/) 8 | 9 | [![GitHub issues](https://img.shields.io/github/issues/xfox111/TabsAsideExtension)](https://github.com/xfox111/TabsAsideExtension/issues) 10 | [![GitHub last commit](https://img.shields.io/github/last-commit/xfox111/TabsAsideExtension)](https://github.com/xfox111/TabsAsideExtension/commits/master) 11 | [![GitHub repo size](https://img.shields.io/github/repo-size/xfox111/TabsAsideExtension?label=repo%20size)](https://github.com/xfox111/TabsAsideExtension) 12 | [![MIT License](https://img.shields.io/github/license/xfox111/TabsAsideExtension)](https://opensource.org/licenses/MIT) 13 | 14 | [![Twitter Follow](https://img.shields.io/twitter/follow/xfox111?style=social)](https://twitter.com/xfox111) 15 | [![GitHub followers](https://img.shields.io/github/followers/xfox111?label=Follow%20@xfox111&style=social)](https://github.com/xfox111) 16 | [![Buy Me a Coffee](https://img.shields.io/badge/Buy%20Me%20a%20Coffee-%40xfox111-orange)](https://buymeacoffee.com/xfox111) 17 | 18 | ![Tabs aside](https://xfox111.net/y7xk3z) 19 | 20 | If you’re like me, you often find yourself with a bunch of open tabs. You’d like to get those tabs out of the way sometimes, but they’re maybe not worth saving as actual bookmarks. 21 | 22 | In the Edge browser, Microsoft has introduced a new feature called "Tabs aside" (or Tab groups) which lets you set tabs aside in a sort of temporary workspace so that you can call them back up later. 23 | 24 | ![Tabs aside demo](https://xfox111.net/knrp7b) 25 | 26 | Unfortunately, in new Chromium-based Microsoft Edge, the devs decided not to implement this must-have-feature. So I've decided to create a browser extension which replicates this awesome feature 27 | 28 | ## Features 29 | - Familiar UI inherited from legacy Microsoft Edge with some improvements 30 | - Auto Dark mode 31 | - Now you can restore one tab from collection without removing 32 | - Now you can choose if you want to load restored tabs only when you're navigating onto them 33 | - Set tabs you've selected aside 34 | - Sync your saved tabs between different PCs 35 | - **Now available for Firefox!** 36 | 37 | ## Download 38 | - [Google Chrome Webstore](https://chrome.google.com/webstore/detail/tabs-aside/mgmjbodjgijnebfgohlnjkegdpbdjgin) 39 | - [Microsoft Edge Add-ons Webstore](https://microsoftedge.microsoft.com/addons/detail/kmnblllmalkiapkfknnlpobmjjdnlhnd) 40 | - [Firefox Add-ons](https://addons.mozilla.org/firefox/addon/ms-edge-tabs-aside/) 41 | - [GitHub Releases](https://github.com/xfox111/TabsAsideExtension/releases/latest) 42 | 43 | ## Project roadmap 44 | You can go to the project's [roadmap kanban board](https://github.com/XFox111/TabsAsideExtension/projects/1) and see what have we planned and watch our progress in realtime 45 | 46 | ## Contributing 47 | There are many ways in which you can participate in the project, for example: 48 | - [Submit bugs and feature requests](https://github.com/xfox111/TabsAsideExtension/issues), and help us verify as they are checked in 49 | - Review [source code changes](https://github.com/xfox111/TabsAsideExtension/pulls) 50 | - Review documentation and make pull requests for anything from typos to new content 51 | 52 | If you are interested in fixing issues and contributing directly to the code base, please see the [Contribution Guidelines](https://github.com/XFox111/TabsAsideExtension/blob/master/CONTRIBUTING.md), which covers the following: 53 | - [How to deploy the extension on your browser](https://github.com/XFox111/TabsAsideExtension/blob/master/CONTRIBUTING.md#deploy-test-version-on-your-browser) 54 | - [The development workflow](https://github.com/XFox111/TabsAsideExtension/blob/master/CONTRIBUTING.md#development-workflow), including debugging and running tests 55 | - [Coding guidelines](https://github.com/XFox111/TabsAsideExtension/blob/master/CONTRIBUTING.md#coding-guidelines) 56 | - [Submitting pull requests](https://github.com/XFox111/TabsAsideExtension/blob/master/CONTRIBUTING.md#submitting-pull-requests) 57 | - [Finding an issue to work on](https://github.com/XFox111/TabsAsideExtension/blob/master/CONTRIBUTING.md#finding-an-issue-to-work-on) 58 | - [Contributing to translations](https://github.com/XFox111/TabsAsideExtension/blob/master/CONTRIBUTING.md#contributing-to-translations) 59 | 60 | ## Code of Conduct 61 | This project has adopted the Contributor Covenant. For more information see the [Code of Conduct](https://github.com/XFox111/TabsAsideExtension/blob/master/CODE_OF_CONDUCT.md) 62 | 63 | ## Copyrights 64 | > ©2021 Michael "XFox" Gordeev 65 | 66 | Font copyrights: Microsoft Corportation ©2021 (Additional ELUA applied) 67 | 68 | Licensed under [MIT License](https://opensource.org/licenses/MIT) -------------------------------------------------------------------------------- /TabsAside.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Tabs aside 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 |
19 | 91 |
92 | 93 | 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /_locales/en/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": 3 | { 4 | "message": "Tabs aside", 5 | "description": "Extension name. Displayed in the manifest and pane header" 6 | }, 7 | "description": 8 | { 9 | "message": "Classic Microsoft Edge \"Tabs Aside\" feature", 10 | "description": "Extension description" 11 | }, 12 | "author": 13 | { 14 | "message": "Michael \"XFox\" Gordeev", 15 | "description": "Author name" 16 | }, 17 | "options": 18 | { 19 | "message": "Options", 20 | "description": "Alternative text for options button in the pane" 21 | }, 22 | "closePanel": 23 | { 24 | "message": "Close", 25 | "description": "Alternative text for close panel button" 26 | }, 27 | "loadOnRestore": 28 | { 29 | "message": "Load tabs on restore", 30 | "description": "Label for option" 31 | }, 32 | "showDeleteDialog": 33 | { 34 | "message": "Show confirmation dialog before deleting an item", 35 | "description": "Label for option" 36 | }, 37 | "swapIconAction": 38 | { 39 | "message": "Set tabs aside on extension icon click (%TOGGLE_SHORTCUT% or right-click to open the pane)", 40 | "description": "Label for option" 41 | }, 42 | "github": 43 | { 44 | "message": "Visit GitHub page", 45 | "description": "Link title" 46 | }, 47 | "changelog": 48 | { 49 | "message": "Changelog", 50 | "description": "Link title" 51 | }, 52 | "feedback": 53 | { 54 | "message": "Leave feedback", 55 | "description": "Link title" 56 | }, 57 | "buyMeACoffee": 58 | { 59 | "message": "Buy me a coffee!", 60 | "description": "Link title" 61 | }, 62 | "credits": 63 | { 64 | "message": "Developed by Michael 'XFox' Gordeev", 65 | "description": "Options menu credits" 66 | }, 67 | "setAside": 68 | { 69 | "message": "Set current tabs aside", 70 | "description": "Save collection action name. Used in the pane, extension context menu and manifest shortcuts" 71 | }, 72 | "setMultipleTabsAsideTooltip": 73 | { 74 | "message": "Tip : You can set aside specific tabs by selecting them (by holding CTRL or SHIFT and clicking on the tabs) before clicking on the TabsAside extension", 75 | "description": "Tooltip displayed on hover in the pane to explain how to set specific tabs aside" 76 | }, 77 | "nothingSaved": 78 | { 79 | "message": "You have no aside tabs", 80 | "description": "Placeholder for empty pane" 81 | }, 82 | "removeTab": 83 | { 84 | "message": "Remove tab from collection", 85 | "description": "Button hint on a tab card" 86 | }, 87 | "restoreTabs": 88 | { 89 | "message": "Restore tabs", 90 | "description": "Collection restore action link name" 91 | }, 92 | "more": 93 | { 94 | "message": "More...", 95 | "description": "Collections' more button title" 96 | }, 97 | "restoreNoRemove": 98 | { 99 | "message": "Restore without removing", 100 | "description": "Context action item name" 101 | }, 102 | "removeCollection": 103 | { 104 | "message": "Remove collection", 105 | "description": "Collection remove action name" 106 | }, 107 | "removeCollectionConfirm": 108 | { 109 | "message": "Are you sure you want to delete this collection?", 110 | "description": "Prompt dialog content on collection deletion" 111 | }, 112 | "removeTabConfirm": 113 | { 114 | "message": "Are you sure you want to delete this tab?", 115 | "description": "Prompt dialog content on one tab deletion" 116 | }, 117 | "togglePaneContext": 118 | { 119 | "message": "Toggle tabs aside pane", 120 | "description": "Context action name. Used in extension context menu and manifest shortcuts" 121 | }, 122 | "noTabsToSave": 123 | { 124 | "message": "No tabs available to save", 125 | "description": "Alert dialog message when there's no tabs to save" 126 | }, 127 | "errorSavingTabs": 128 | { 129 | "message": "Tabs could not be set aside. You may be trying to set too many tabs aside at once, or have too many tabs already set aside.", 130 | "description": "Alert dialog message when there is an issue saving tabs" 131 | }, 132 | "olderDataMigrationFailed": 133 | { 134 | "message": "Some tabs set aside from a previous version could not be migrated. They have been backed up as browser bookmarks.", 135 | "description": "Alert dialog message when there is an issue migrating previous versions data" 136 | }, 137 | "tabs": 138 | { 139 | "message": "items", 140 | "description": "Collection tabs counter label (e.g. 8 items)" 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /_locales/es/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": 3 | { 4 | "message": "Pestañas que has reservado", 5 | "description": "Extension name. Displayed in the manifest and pane header" 6 | }, 7 | "description": 8 | { 9 | "message": "Función \"Reservar pestañas\" de Microsoft Edge clásico", 10 | "description": "Extension description" 11 | }, 12 | "author": 13 | { 14 | "message": "Michael \"XFox\" Gordeev", 15 | "description": "Author name" 16 | }, 17 | "options": 18 | { 19 | "message": "Opciones", 20 | "description": "Alternative text for options button in the pane" 21 | }, 22 | "closePanel": 23 | { 24 | "message": "Cerrar", 25 | "description": "Alternative text for close panel button" 26 | }, 27 | "loadOnRestore": 28 | { 29 | "message": "Cargar pestañas al restaurar", 30 | "description": "Label for option" 31 | }, 32 | "showDeleteDialog": 33 | { 34 | "message": "Mostrar diálogo de confirmacion antes de eliminar un item", 35 | "description": "Label for option" 36 | }, 37 | "swapIconAction": 38 | { 39 | "message": "Reservar pestañas al apretar el icono de la extensión (%TOGGLE_SHORTCUT% o click-derecho para abrir el panel)", 40 | "description": "Label for option" 41 | }, 42 | "github": 43 | { 44 | "message": "Visitar la pagina de GitHub", 45 | "description": "Link title" 46 | }, 47 | "changelog": 48 | { 49 | "message": "Registro de cambios", 50 | "description": "Link title" 51 | }, 52 | "feedback": 53 | { 54 | "message": "Deja un comentario", 55 | "description": "Link title" 56 | }, 57 | "buyMeACoffee": 58 | { 59 | "message": "¡Comprame un cafe!", 60 | "description": "Link title" 61 | }, 62 | "credits": 63 | { 64 | "message": "Desarrollado por Michael 'XFox' Gordeev", 65 | "description": "Options menu credits" 66 | }, 67 | "setAside": 68 | { 69 | "message": "Reservar las pestañas actuales", 70 | "description": "Save collection action name. Used in the pane, extension context menu and manifest shortcuts" 71 | }, 72 | "setMultipleTabsAsideTooltip": 73 | { 74 | "message": "Consejo: puede dejar de lado pestañas específicas seleccionándolas (manteniendo presionada la tecla CTRL o SHIFT y haciendo clic en las pestañas) antes de hacer clic en la extensión TabsAside", 75 | "description": "Tooltip displayed on hover in the pane to explain how to set specific tabs aside" 76 | }, 77 | "nothingSaved": 78 | { 79 | "message": "No tienes pestañas reservadas", 80 | "description": "Placeholder for empty pane" 81 | }, 82 | "removeTab": 83 | { 84 | "message": "Eliminar pestañas de la colección", 85 | "description": "Button hint on a tab card" 86 | }, 87 | "restoreTabs": 88 | { 89 | "message": "Restaurar pestañas", 90 | "description": "Collection restore action link name" 91 | }, 92 | "more": 93 | { 94 | "message": "Más...", 95 | "description": "Collections' more button title" 96 | }, 97 | "restoreNoRemove": 98 | { 99 | "message": "Restaurar sin eliminar", 100 | "description": "Context action item name" 101 | }, 102 | "removeCollection": 103 | { 104 | "message": "Eliminar la colección", 105 | "description": "Collection remove action name" 106 | }, 107 | "removeCollectionConfirm": 108 | { 109 | "message": "¿Está seguro que quiere eliminar esta colección?", 110 | "description": "Prompt dialog content on collection deletion" 111 | }, 112 | "removeTabConfirm": 113 | { 114 | "message": "¿Está seguro que quiere eliminar esta pestaña?", 115 | "description": "Prompt dialog content on one tab deletion" 116 | }, 117 | "togglePaneContext": 118 | { 119 | "message": "Abrir panel de pestañas", 120 | "description": "Context action name. Used in extension context menu and manifest shortcuts" 121 | }, 122 | "noTabsToSave": 123 | { 124 | "message": "No hay pestañas disponibles para reservar", 125 | "description": "Alert dialog message when there's no tabs to save" 126 | }, 127 | "errorSavingTabs": 128 | { 129 | "message": "Las pestañas no se pueden dejar de lado. Es posible que esté intentando dejar demasiadas pestañas a un lado a la vez o que ya haya reservado demasiadas pestañas.", 130 | "description": "Alert dialog message when there is an issue saving tabs" 131 | }, 132 | "olderDataMigrationFailed": 133 | { 134 | "message": "Algunas pestañas apartadas de una versión anterior no se pudieron migrar. Se han respaldado como marcadores del navegador.", 135 | "description": "Alert dialog message when there is an issue migrating previous versions data" 136 | }, 137 | "tabs": 138 | { 139 | "message": "items", 140 | "description": "Collection tabs counter label (e.g. 8 items)" 141 | } 142 | } -------------------------------------------------------------------------------- /_locales/it/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": 3 | { 4 | "message": "Schede a parte", 5 | "description": "name of the extension. Displayed in the manifest and pane header" 6 | }, 7 | "description": 8 | { 9 | "message": "La funzione del classico Microsoft Edge \"Tabs Aside\"", 10 | "description": "Extension description" 11 | }, 12 | "author": 13 | { 14 | "message": "Michael \"XFox\" Gordeev", 15 | "description": "Author name" 16 | }, 17 | "options": 18 | { 19 | "message": "Opzioni", 20 | "description": "Alternative text for options button in the pane" 21 | }, 22 | "closePanel": 23 | { 24 | "message": "Chiudi", 25 | "description": "Alternative text for close panel button" 26 | }, 27 | "loadOnRestore": 28 | { 29 | "message": "Riapri le schede al ripristino", 30 | "description": "Label for option" 31 | }, 32 | "showDeleteDialog": 33 | { 34 | "message": "Mostra una finestra di conferma prima di eliminare qualcosa", 35 | "description": "Label for option" 36 | }, 37 | "swapIconAction": 38 | { 39 | "message": "Metti da parte le schede in Tabs Aside al click sull'estensione (%TOGGLE_SHORTCUT% o click con il tasto destro sull'icona dell'estensione per aprire il pannello)", 40 | "description": "Label for option" 41 | }, 42 | "github": 43 | { 44 | "message": "Visita la pagina di GitHub", 45 | "description": "Link title" 46 | }, 47 | "changelog": 48 | { 49 | "message": "Registro dei cambiamenti", 50 | "description": "Link title" 51 | }, 52 | "feedback": 53 | { 54 | "message": "Lascia un feedback", 55 | "description": "Link title" 56 | }, 57 | "buyMeACoffee": 58 | { 59 | "message": "Comprami un caffè!", 60 | "description": "Link title" 61 | }, 62 | "credits": 63 | { 64 | "message": "Sviluppato da Michael 'XFox' Gordeev", 65 | "description": "Options menu credits" 66 | }, 67 | "setAside": 68 | { 69 | "message": "Sposta le schede correnti in Tabs Aside", 70 | "description": "Save collection action name. Used in the pane, extension context menu and manifest shortcuts" 71 | }, 72 | "setMultipleTabsAsideTooltip": 73 | { 74 | "message": "Suggerimento: puoi mettere da parte schede specifiche selezionandole (tenendo premuto CTRL o MAIUSC e facendo clic sulle schede) e cliccando sull'estensione Tabs Aside.", 75 | "description": "Tooltip displayed on hover in the pane to explain how to set specific tabs aside" 76 | }, 77 | "nothingSaved": 78 | { 79 | "message": "Non hai schede da parte qua", 80 | "description": "Placeholder for empty pane" 81 | }, 82 | "removeTab": 83 | { 84 | "message": "Rimuovi la scheda da questo gruppo di schede", 85 | "description": "Button hint on a tab card" 86 | }, 87 | "restoreTabs": 88 | { 89 | "message": "Ripristina schede", 90 | "description": "Collection restore action link name" 91 | }, 92 | "more": 93 | { 94 | "message": "Altro...", 95 | "description": "Collections' more button title" 96 | }, 97 | "restoreNoRemove": 98 | { 99 | "message": "Ripristina senza rimuovere le schede da da Tabs Aside", 100 | "description": "Context action item name" 101 | }, 102 | "removeCollection": 103 | { 104 | "message": "Rimuovi gruppo di schede", 105 | "description": "Collection remove action name" 106 | }, 107 | "removeCollectionConfirm": 108 | { 109 | "message": "Sei sicuro di voler eliminare questo gruppo di schede?", 110 | "description": "Prompt dialog content on collection deletion" 111 | }, 112 | "removeTabConfirm": 113 | { 114 | "message": "Sei sucuro di voler rimuovere questa scheda?", 115 | "description": "Prompt dialog content on one tab deletion" 116 | }, 117 | "togglePaneContext": 118 | { 119 | "message": "Mostra/nascondi il pannello di Tabs Aside", 120 | "description": "Context action name. Used in extension context menu and manifest shortcuts" 121 | }, 122 | "noTabsToSave": 123 | { 124 | "message": "Nessuna scheda da salvare", 125 | "description": "Alert dialog message when there's no tabs to save" 126 | }, 127 | "errorSavingTabs": 128 | { 129 | "message": "Queste schede non possono essere messe da parte. Forse perché stai cercando di mettere da parte troppe schede o perché ce ne sono già troppe messe da parte.", 130 | "description": "Alert dialog message when there is an issue saving tabs" 131 | }, 132 | "olderDataMigrationFailed": 133 | { 134 | "message": "Alcune schede messe da parte in una versione precedente non potevano essere mantenute. Sono state salvate come preferiti del browser.", 135 | "description": "Alert dialog message when there is an issue migrating previous versions data" 136 | }, 137 | "tabs": 138 | { 139 | "message": "schede", 140 | "description": "Collection tabs counter label (e.g. 8 items)" 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /_locales/pl/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": 3 | { 4 | "message": "Kolekcje zakładek", 5 | "description": "Extension name. Displayed in the manifest and pane header" 6 | }, 7 | "description": 8 | { 9 | "message": "Klasyczna funkcjonalność Microsoft Edge \"Kolekcje zakładek\"", 10 | "description": "Extension description" 11 | }, 12 | "author": 13 | { 14 | "message": "Michael \"XFox\" Gordeev", 15 | "description": "Author name" 16 | }, 17 | "options": 18 | { 19 | "message": "Opcje", 20 | "description": "Alternative text for options button in the pane" 21 | }, 22 | "closePanel": 23 | { 24 | "message": "Zamknij", 25 | "description": "Alternative text for close panel button" 26 | }, 27 | "loadOnRestore": 28 | { 29 | "message": "Przeładuj zawartość kart podczas przywracania", 30 | "description": "Label for option" 31 | }, 32 | "showDeleteDialog": 33 | { 34 | "message": "Pokaż potwierdzenie podczas kasowania elementu", 35 | "description": "Label for option" 36 | }, 37 | "swapIconAction": 38 | { 39 | "message": "Kliknięcie na ikonę zapisuje aktualne karty (%TOGGLE_SHORTCUT% lub prawy przycisk myszki, aby otworzyć panel)", 40 | "description": "Label for option" 41 | }, 42 | "github": 43 | { 44 | "message": "Strona GitHub", 45 | "description": "Link title" 46 | }, 47 | "changelog": 48 | { 49 | "message": "Historia zmian", 50 | "description": "Link title" 51 | }, 52 | "feedback": 53 | { 54 | "message": "Wyślij opinię", 55 | "description": "Link title" 56 | }, 57 | "buyMeACoffee": 58 | { 59 | "message": "Kup mi kawę!", 60 | "description": "Link title" 61 | }, 62 | "credits": 63 | { 64 | "message": "Opracowane przez Michael 'XFox' Gordeev", 65 | "description": "Options menu credits" 66 | }, 67 | "setAside": 68 | { 69 | "message": "Zapisz aktualne karty", 70 | "description": "Save collection action name. Used in the pane, extension context menu and manifest shortcuts" 71 | }, 72 | "setMultipleTabsAsideTooltip": 73 | { 74 | "message": "Wskazówka : możesz zaznaczyć interesujące karty używając CTRL lub SHIFT na karcie, aby tylko te później zapisać", 75 | "description": "Tooltip displayed on hover in the pane to explain how to set specific tabs aside" 76 | }, 77 | "nothingSaved": 78 | { 79 | "message": "Nie posiadasz żadnej kolekcji zakładek", 80 | "description": "Placeholder for empty pane" 81 | }, 82 | "removeTab": 83 | { 84 | "message": "Usunięcie karty z kolekcji", 85 | "description": "Button hint on a tab card" 86 | }, 87 | "restoreTabs": 88 | { 89 | "message": "Przywrócenie kart", 90 | "description": "Collection restore action link name" 91 | }, 92 | "more": 93 | { 94 | "message": "Więcej...", 95 | "description": "Collections' more button title" 96 | }, 97 | "restoreNoRemove": 98 | { 99 | "message": "Przywrócenie kart bez usunięcia kolekcji", 100 | "description": "Context action item name" 101 | }, 102 | "removeCollection": 103 | { 104 | "message": "Usunięcie kolekcji", 105 | "description": "Collection remove action name" 106 | }, 107 | "removeCollectionConfirm": 108 | { 109 | "message": "Jesteś pewny, że chcesz skasować kolekcję?", 110 | "description": "Prompt dialog content on collection deletion" 111 | }, 112 | "removeTabConfirm": 113 | { 114 | "message": "Jesteś pewny, że chcesz skasować zakładkę?", 115 | "description": "Prompt dialog content on one tab deletion" 116 | }, 117 | "togglePaneContext": 118 | { 119 | "message": "Przełącz panel kolekcji", 120 | "description": "Context action name. Used in extension context menu and manifest shortcuts" 121 | }, 122 | "noTabsToSave": 123 | { 124 | "message": "Brak zakładek do zapisu", 125 | "description": "Alert dialog message when there's no tabs to save" 126 | }, 127 | "errorSavingTabs": 128 | { 129 | "message": "Zakładki nie mogą zostać zapisane. Przypuszczalnie zbyt wiele zakładek lub aktualnie posiadanych kolekcji.", 130 | "description": "Alert dialog message when there is an issue saving tabs" 131 | }, 132 | "olderDataMigrationFailed": 133 | { 134 | "message": "Niektóre kolekcje z poprzedniej wersji nie mogły zostać przekonwertowane, w związku z czym zostały zapisane jako zwykłe zakładki w przeglądarce", 135 | "description": "Alert dialog message when there is an issue migrating previous versions data" 136 | }, 137 | "tabs": 138 | { 139 | "message": "elementów", 140 | "description": "Collection tabs counter label (e.g. 8 items)" 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /_locales/pt_BR/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": 3 | { 4 | "message": "Reservar abas", 5 | "description": "Extension name. Displayed in the manifest and pane header" 6 | }, 7 | "description": 8 | { 9 | "message": "Função \"Reservar abas\" do Microsoft Edge Clássico", 10 | "description": "Extension description" 11 | }, 12 | "author": 13 | { 14 | "message": "Michael \"XFox\" Gordeev", 15 | "description": "Author name" 16 | }, 17 | "options": 18 | { 19 | "message": "Opções", 20 | "description": "Alternative text for options button in the pane" 21 | }, 22 | "closePanel": 23 | { 24 | "message": "Fechar", 25 | "description": "Alternative text for close panel button" 26 | }, 27 | "loadOnRestore": 28 | { 29 | "message": "Carregar abas ao restaurar", 30 | "description": "Label for option" 31 | }, 32 | "showDeleteDialog": 33 | { 34 | "message": "Mostrar diálogo de confirmação antes de deletar um item", 35 | "description": "Label for option" 36 | }, 37 | "swapIconAction": 38 | { 39 | "message": "Reservar abas ao apertar o ícone da extensão (%TOGGLE_SHORTCUT% ou clique-direito para abrir o painel)", 40 | "description": "Label for option" 41 | }, 42 | "github": 43 | { 44 | "message": "Visite a página do GitHub", 45 | "description": "Link title" 46 | }, 47 | "changelog": 48 | { 49 | "message": "Registro de mudanças", 50 | "description": "Link title" 51 | }, 52 | "feedback": 53 | { 54 | "message": "Deixe um comentário", 55 | "description": "Link title" 56 | }, 57 | "buyMeACoffee": 58 | { 59 | "message": "Me compre um café!", 60 | "description": "Link title" 61 | }, 62 | "credits": 63 | { 64 | "message": "Desenvolvido por Michael 'XFox' Gordeev", 65 | "description": "Options menu credits" 66 | }, 67 | "setAside": 68 | { 69 | "message": "Reservas as abas atuais", 70 | "description": "Save collection action name. Used in the pane, extension context menu and manifest shortcuts" 71 | }, 72 | "setMultipleTabsAsideTooltip": 73 | { 74 | "message": "Dica: Você pode reservar abas específicas ao selecioná-las (ao apertar CTRL ou SHIFT e clicando nas abas) antes de clicar na extensão Reservar Abas", 75 | "description": "Tooltip displayed on hover in the pane to explain how to set specific tabs aside" 76 | }, 77 | "nothingSaved": 78 | { 79 | "message": "Você não tem abas reservadas", 80 | "description": "Placeholder for empty pane" 81 | }, 82 | "removeTab": 83 | { 84 | "message": "Remover aba da coleção", 85 | "description": "Button hint on a tab card" 86 | }, 87 | "restoreTabs": 88 | { 89 | "message": "Restaurar abas", 90 | "description": "Collection restore action link name" 91 | }, 92 | "more": 93 | { 94 | "message": "Mais...", 95 | "description": "Collections' more button title" 96 | }, 97 | "restoreNoRemove": 98 | { 99 | "message": "Restaurar sem remover", 100 | "description": "Context action item name" 101 | }, 102 | "removeCollection": 103 | { 104 | "message": "Remover coleção", 105 | "description": "Collection remove action name" 106 | }, 107 | "removeCollectionConfirm": 108 | { 109 | "message": "Você tem certeza que quer deletar esta coleção?", 110 | "description": "Prompt dialog content on collection deletion" 111 | }, 112 | "removeTabConfirm": 113 | { 114 | "message": "Você tem certeza que quer deletar esta aba?", 115 | "description": "Prompt dialog content on one tab deletion" 116 | }, 117 | "togglePaneContext": 118 | { 119 | "message": "Alternar painel de abas", 120 | "description": "Context action name. Used in extension context menu and manifest shortcuts" 121 | }, 122 | "noTabsToSave": 123 | { 124 | "message": "Não há abas disponíveis para salvar", 125 | "description": "Alert dialog message when there's no tabs to save" 126 | }, 127 | "errorSavingTabs": 128 | { 129 | "message": "As abas não puderam ser reservadas. Você pode ter tentado reservar muitas abas de uma vez só, ou pode ter abas demais já reservadas.", 130 | "description": "Alert dialog message when there is an issue saving tabs" 131 | }, 132 | "olderDataMigrationFailed": 133 | { 134 | "message": "Algumas abas reservadas de uma versão antiga não puderam ser migradas. Elas foram salvas como marcadores do navegador.", 135 | "description": "Alert dialog message when there is an issue migrating previous versions data" 136 | }, 137 | "tabs": 138 | { 139 | "message": "itens", 140 | "description": "Collection tabs counter label (e.g. 8 items)" 141 | } 142 | } -------------------------------------------------------------------------------- /_locales/ru/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": 3 | { 4 | "message": "Отложенные вкладки", 5 | "description": "Extension name. Displayed in the manifest and pane header" 6 | }, 7 | "description": 8 | { 9 | "message": "Функиця отложенных вкладок классического Microsoft Edge", 10 | "description": "Extension description" 11 | }, 12 | "author": 13 | { 14 | "message": "Михаил \"XFox\" Гордеев", 15 | "description": "Author name" 16 | }, 17 | "options": 18 | { 19 | "message": "Настройки", 20 | "description": "Alternative text for options button in the pane" 21 | }, 22 | "closePanel": 23 | { 24 | "message": "Закрыть", 25 | "description": "Alternative text for close panel button" 26 | }, 27 | "loadOnRestore": 28 | { 29 | "message": "Загружать вкладки после открытия", 30 | "description": "Label for option" 31 | }, 32 | "showDeleteDialog": 33 | { 34 | "message": "Показывать окно подтверждения перед удалением элемента", 35 | "description": "Label for option" 36 | }, 37 | "swapIconAction": 38 | { 39 | "message": "Откладывать вкладки при нажатии на иконку расширения (%TOGGLE_SHORTCUT% или правая кнопка мыши для открытия панели)", 40 | "description": "Label for option" 41 | }, 42 | "github": 43 | { 44 | "message": "Страница GitHub", 45 | "description": "Link title" 46 | }, 47 | "changelog": 48 | { 49 | "message": "Список изменений", 50 | "description": "Link title" 51 | }, 52 | "feedback": 53 | { 54 | "message": "Оставить отзыв", 55 | "description": "Link title" 56 | }, 57 | "buyMeACoffee": 58 | { 59 | "message": "Купить мне кофе!", 60 | "description": "Link title" 61 | }, 62 | "credits": 63 | { 64 | "message": "Разработано: Михаил 'XFox' Гордеев", 65 | "description": "Options menu credits" 66 | }, 67 | "setAside": 68 | { 69 | "message": "Отложить открытые вкладки", 70 | "description": "Save collection action name. Used in the pane, extension context menu and manifest shortcuts" 71 | }, 72 | "setMultipleTabsAsideTooltip": 73 | { 74 | "message": "Подсказка: теперь вы можете откладывать только некоторые вкладки. Просто выделите нужные вкладки с помощью Shift или Ctrl перед использованием расширения", 75 | "description": "Tooltip displayed on hover in the pane to explain how to set specific tabs aside" 76 | }, 77 | "nothingSaved": 78 | { 79 | "message": "У вас нет отложенных вкладок", 80 | "description": "Placeholder for empty pane" 81 | }, 82 | "removeTab": 83 | { 84 | "message": "Удалить вкладку из коллекции", 85 | "description": "Button hint on a tab card" 86 | }, 87 | "restoreTabs": 88 | { 89 | "message": "Восстановить вкладки", 90 | "description": "Collection restore action link name" 91 | }, 92 | "more": 93 | { 94 | "message": "Больше...", 95 | "description": "Collections' more button title" 96 | }, 97 | "restoreNoRemove": 98 | { 99 | "message": "Восстановить без удаления", 100 | "description": "Context action item name" 101 | }, 102 | "removeCollection": 103 | { 104 | "message": "Удалить коллекцию", 105 | "description": "Collection remove action name" 106 | }, 107 | "removeCollectionConfirm": 108 | { 109 | "message": "Вы уверены, что хотите удалить эту коллекцию?", 110 | "description": "Prompt dialog content on collection deletion" 111 | }, 112 | "removeTabConfirm": 113 | { 114 | "message": "Вы уверены, что хотите удалить эту вкладку из коллекции?", 115 | "description": "Prompt dialog content on one tab deletion" 116 | }, 117 | "togglePaneContext": 118 | { 119 | "message": "Открыть/закрыть панель", 120 | "description": "Context action name. Used in extension context menu and manifest shortcuts" 121 | }, 122 | "noTabsToSave": 123 | { 124 | "message": "Нет доступных для сохранения вкладок", 125 | "description": "Alert dialog message when there's no tabs to save" 126 | }, 127 | "errorSavingTabs": 128 | { 129 | "message": "Не удается отложить вкладки. Возможно, вы пытаетесь отложить слишком много вкладок за раз или уже отложили слишком много вкладок", 130 | "description": "Alert dialog message when there is an issue saving tabs" 131 | }, 132 | "olderDataMigrationFailed": 133 | { 134 | "message": "Не удалось перенести отложенные вкладки, сохраненные в старой версии расширения. Они были сохранены в закладках", 135 | "description": "Alert dialog message when there is an issue migrating previous versions data" 136 | }, 137 | "tabs": 138 | { 139 | "message": "вкладок", 140 | "description": "Collection tabs counter label (e.g. 8 items)" 141 | } 142 | } -------------------------------------------------------------------------------- /_locales/zh_CN/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": 3 | { 4 | "message": "搁置的标签页", 5 | "description": "Extension name. Displayed in the manifest and pane header" 6 | }, 7 | "description": 8 | { 9 | "message": "为Chromium浏览器提供旧版 Microsoft Edge 中的\"搁置标签页\" 功能", 10 | "description": "Extension description" 11 | }, 12 | "author": 13 | { 14 | "message": "Michael \"XFox\" Gordeev", 15 | "description": "Author name" 16 | }, 17 | "options": 18 | { 19 | "message": "设置", 20 | "description": "Alternative text for options button in the pane" 21 | }, 22 | "closePanel": 23 | { 24 | "message": "关闭", 25 | "description": "Alternative text for close panel button" 26 | }, 27 | "loadOnRestore": 28 | { 29 | "message": "在重新打开时加载页面", 30 | "description": "Label for option" 31 | }, 32 | "showDeleteDialog": 33 | { 34 | "message": "在删除项目之前显示确认对话框", 35 | "description": "Label for option" 36 | }, 37 | "swapIconAction": 38 | { 39 | "message": "点击拓展图标来搁置所有标签页 (按%TOGGLE_SHORTCUT%或右键来打开侧栏)", 40 | "description": "Label for option" 41 | }, 42 | "github": 43 | { 44 | "message": "查看GitHub页面", 45 | "description": "Link title" 46 | }, 47 | "changelog": 48 | { 49 | "message": "变更日志", 50 | "description": "Link title" 51 | }, 52 | "feedback": 53 | { 54 | "message": "给我们反馈", 55 | "description": "Link title" 56 | }, 57 | "buyMeACoffee": 58 | { 59 | "message": "给我买杯咖啡!", 60 | "description": "Link title" 61 | }, 62 | "credits": 63 | { 64 | "message": "由Michael 'XFox' Gordeev开发", 65 | "description": "Options menu credits" 66 | }, 67 | "setAside": 68 | { 69 | "message": "搁置当前的所有标签页", 70 | "description": "Save collection action name. Used in the pane, extension context menu and manifest shortcuts" 71 | }, 72 | "setMultipleTabsAsideTooltip": 73 | { 74 | "message": "提示:在单击TabsAside扩展名之前,可以通过选择特定选项卡(按住CTRL或SHIFT并单击选项卡)来搁置它们", 75 | "description": "Tooltip displayed on hover in the pane to explain how to set specific tabs aside" 76 | }, 77 | "nothingSaved": 78 | { 79 | "message": "你目前没有搁置的标签页", 80 | "description": "Placeholder for empty pane" 81 | }, 82 | "removeTab": 83 | { 84 | "message": "从标签页集中移除标签页", 85 | "description": "Button hint on a tab card" 86 | }, 87 | "restoreTabs": 88 | { 89 | "message": "恢复标签页", 90 | "description": "Collection restore action link name" 91 | }, 92 | "more": 93 | { 94 | "message": "更多...", 95 | "description": "Collections' more button title" 96 | }, 97 | "restoreNoRemove": 98 | { 99 | "message": "恢复但不移除标签页", 100 | "description": "Context action item name" 101 | }, 102 | "removeCollection": 103 | { 104 | "message": "移除标签页集", 105 | "description": "Collection remove action name" 106 | }, 107 | "removeCollectionConfirm": 108 | { 109 | "message": "你确定要移除这个标签页集吗?", 110 | "description": "Prompt dialog content on collection deletion" 111 | }, 112 | "removeTabConfirm": 113 | { 114 | "message": "你确定要移除这个标签页吗?", 115 | "description": "Prompt dialog content on one tab deletion" 116 | }, 117 | "togglePaneContext": 118 | { 119 | "message": "打开或关闭侧栏", 120 | "description": "Context action name. Used in extension context menu and manifest shortcuts" 121 | }, 122 | "noTabsToSave": 123 | { 124 | "message": "没有可以搁置的标签页", 125 | "description": "Alert dialog message when there's no tabs to save" 126 | }, 127 | "errorSavingTabs": 128 | { 129 | "message": "标签页无法被搁置. 可能是因为您尝试一次性搁置过多的标签页, 或是先前搁置的标签页过多.", 130 | "description": "Alert dialog message when there is an issue saving tabs" 131 | }, 132 | "olderDataMigrationFailed": 133 | { 134 | "message": "一部分搁置的标签页无法从上一个版本中迁移. 这些标签页已经作为浏览器书签被备份.", 135 | "description": "Alert dialog message when there is an issue migrating previous versions data" 136 | }, 137 | "tabs": 138 | { 139 | "message": "项", 140 | "description": "Collection tabs counter label (e.g. 8 items)" 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /css/style.css: -------------------------------------------------------------------------------- 1 | .tabsAside.background 2 | { 3 | background-color: rgba(255, 255, 255, .5); 4 | position: fixed; 5 | top: 0; 6 | bottom: 0; 7 | right: 0; 8 | left: 0; 9 | transition: .2s; 10 | color: black; 11 | } 12 | 13 | .tabsAside.closeArea 14 | { 15 | position: fixed; 16 | top: 0; 17 | bottom: 0; 18 | right: 0; 19 | left: 0; 20 | 21 | background-color: transparent; 22 | } 23 | 24 | .tabsAside.pane 25 | { 26 | user-select: none; 27 | position: fixed; 28 | 29 | right: 0px; 30 | top: 0px; 31 | bottom: 0px; 32 | overflow: hidden; 33 | display: grid; 34 | grid-template-rows: auto 1fr; 35 | 36 | width: 100%; 37 | min-width: 500px; 38 | 39 | background-color: #f7f7f7; 40 | border: 1px solid rgba(100, 100, 100, .5); 41 | border-width: 0px 0px 0px 1px; 42 | box-shadow: 6px 0px 12px black; 43 | 44 | transform: translateX(110%); /* pane is hidded */ 45 | transition: .2s; 46 | } 47 | 48 | aside[embedded] 49 | { 50 | width: 500px !important; 51 | } 52 | 53 | .tabsAside.pane[opened] 54 | { 55 | transform: translateX(0px); 56 | } 57 | 58 | /* Pane header*/ 59 | .tabsAside.pane > header 60 | { 61 | z-index: 1; 62 | padding: 14px 20px 16px 20px; 63 | box-shadow: 0px 0px 5px rgba(0,0,0,.5); 64 | background-color: white; 65 | display: grid; 66 | grid-template-columns: 1fr auto auto; 67 | grid-column-gap: 10px; 68 | grid-row-gap: 20px; 69 | } 70 | 71 | .tabsAside.pane > header > h1 72 | { 73 | margin: 0px 5px; 74 | font-weight: 400; 75 | font-size: 24px; 76 | } 77 | 78 | .tabsAside.pane > header > button .updateBadge 79 | { 80 | position: absolute; 81 | bottom: 2px; 82 | right: 2px; 83 | } 84 | 85 | .tabsAside.pane > header > nav 86 | { 87 | top: 45px; 88 | right: 55px; 89 | } 90 | 91 | .tabsAside.pane > header nav > p 92 | { 93 | margin: 10px; 94 | } 95 | 96 | .tabsAside.pane > header nav > p > a 97 | { 98 | text-decoration: none; 99 | } 100 | 101 | .tabsAside.pane > header nav > p > a:hover 102 | { 103 | text-decoration: underline; 104 | } 105 | 106 | .saveTabs 107 | { 108 | display: inline-grid; 109 | grid-template-columns: 16px auto 16px; 110 | grid-column-gap: 15px; 111 | 112 | margin-right: auto; 113 | } 114 | 115 | .saveTabs:hover 116 | { 117 | text-decoration: none !important; 118 | } 119 | 120 | .saveTabs:hover span:nth-child(2) 121 | { 122 | text-decoration: underline; 123 | } 124 | 125 | .iconArrowRight,.iconQuestionCircle 126 | { 127 | width: 16px; 128 | height: 16px; 129 | display: inline-block; 130 | font-family: "SegoeMDL2Assets"; 131 | margin: 2px; 132 | } 133 | .iconQuestionCircle 134 | { 135 | font-size: small; 136 | padding-top: 2px; 137 | margin-bottom: 0; 138 | } 139 | 140 | .tabsAside.pane section 141 | { 142 | overflow: auto; 143 | } 144 | 145 | .tabsAside.pane > section > h2 146 | { 147 | margin: 20px; 148 | font-weight: normal; 149 | } 150 | 151 | /* Collection header */ 152 | .tabsAside.pane > section > div 153 | { 154 | transition: .2s; 155 | } 156 | 157 | .collectionSet 158 | { 159 | background-color: white; 160 | margin: 10px; 161 | border-radius: 5px; 162 | border: 1px solid #eee; 163 | } 164 | 165 | .collectionSet:hover 166 | { 167 | box-shadow: 0px 0px 5px rgba(0, 0, 0, .25); 168 | } 169 | 170 | .collectionSet .header > * 171 | { 172 | visibility: hidden; 173 | } 174 | 175 | .collectionSet:hover .header > * 176 | { 177 | visibility: visible; 178 | } 179 | 180 | .collectionSet > .header 181 | { 182 | margin: 10px 10px 0px 20px; 183 | display: grid; 184 | grid-template-columns: 1fr auto auto auto; 185 | grid-column-gap: 10px; 186 | align-items: center; 187 | } 188 | 189 | .collectionSet > .header > small 190 | { 191 | color: gray; 192 | visibility: visible !important; 193 | } 194 | 195 | .collectionSet > .header > input 196 | { 197 | margin: 0px; 198 | visibility: visible !important; 199 | font-weight: 600; 200 | border: none; 201 | background: transparent; 202 | } 203 | 204 | .collectionSet > .header > input:hover 205 | { 206 | border: 1px solid black; 207 | } 208 | 209 | .collectionSet > .header > div > nav 210 | { 211 | width: 250px; 212 | right: 0px; 213 | top: 35px; 214 | } 215 | 216 | /* Tabs collection */ 217 | .collectionSet > .set 218 | { 219 | padding: 5px 10px; 220 | white-space: nowrap; 221 | overflow: auto; 222 | } 223 | 224 | .collectionSet > .set::-webkit-scrollbar-thumb 225 | { 226 | visibility: hidden; 227 | } 228 | 229 | .collectionSet > .set:hover::-webkit-scrollbar-thumb 230 | { 231 | visibility: visible; 232 | } 233 | 234 | .collectionSet > .set > div 235 | { 236 | width: 175px; 237 | height: 148px; 238 | margin: 5px; 239 | 240 | background-color: #c2c2c2; 241 | background-image: url("chrome-extension://__MSG_@@extension_id__/images/tab_thumbnail.png"), 242 | url("moz-extension://__MSG_@@extension_id__/images/tab_thumbnail.png"); 243 | background-size: cover; 244 | background-position-x: center; 245 | 246 | display: inline-grid; 247 | grid-template-rows: 1fr auto; 248 | 249 | transition: .25s; 250 | cursor: pointer; 251 | 252 | border: 1px solid #eee; 253 | border-radius: 5px; 254 | } 255 | .collectionSet > .set > div:hover 256 | { 257 | box-shadow: 0px 0px 5px rgba(100, 100, 100, .5); 258 | } 259 | 260 | .collectionSet > .set > div > div 261 | { 262 | background-color: rgba(233, 233, 233, .75); 263 | grid-row: 2; 264 | display: grid; 265 | grid-template-columns: auto 1fr auto; 266 | } 267 | 268 | .collectionSet > .set > div > div > button 269 | { 270 | margin: auto; 271 | margin-right: 5px; 272 | display: none; 273 | } 274 | 275 | .collectionSet > .set > div:hover > div > button 276 | { 277 | display: initial; 278 | } 279 | 280 | .collectionSet > .set > div > div > div 281 | { 282 | width: 20px; 283 | height: 20px; 284 | margin: 8px; 285 | 286 | background-image: url("chrome-extension://__MSG_@@extension_id__/images/tab_icon.png"), 287 | url("moz-extension://__MSG_@@extension_id__/images/tab_icon.png"); 288 | background-size: 20px; 289 | } 290 | 291 | .collectionSet > .set > div > div > span 292 | { 293 | overflow: hidden; 294 | margin: auto 0px; 295 | margin-right: 10px; 296 | font-size: 12px; 297 | } 298 | .collectionSet > .set > div:hover > div > span 299 | { 300 | margin-right: 5px; 301 | } 302 | 303 | @media only screen and (max-width: 500px) 304 | { 305 | .tabsAside.pane 306 | { 307 | width: 100% !important; 308 | min-width: initial; 309 | } 310 | } 311 | -------------------------------------------------------------------------------- /css/style.dark.css: -------------------------------------------------------------------------------- 1 | .tabsAside[darkmode].background 2 | { 3 | background-color: rgba(0, 0, 0, .5); 4 | } 5 | 6 | .tabsAside[darkmode] .pane 7 | { 8 | background-color: #333333; 9 | color: white; 10 | } 11 | 12 | .tabsAside[darkmode] .pane header 13 | { 14 | background-color: #3b3b3b; 15 | } 16 | 17 | .tabsAside[darkmode] nav hr 18 | { 19 | filter: invert(1); 20 | } 21 | 22 | .tabsAside[darkmode] .saveTabs > div 23 | { 24 | filter: invert(); 25 | } 26 | 27 | /* Button style */ 28 | .tabsAside[darkmode] button 29 | { 30 | color: white; 31 | } 32 | .tabsAside[darkmode] .pane button:hover, 33 | .tabsAside[darkmode] .pane .control.checkbox:hover 34 | { 35 | background-color: gray; 36 | } 37 | .tabsAside[darkmode] .pane button img, 38 | .tabsAside[darkmode] .pane label > input + span 39 | { 40 | filter: invert(); 41 | } 42 | .tabsAside[darkmode] .pane button:active 43 | { 44 | background-color: dimgray; 45 | } 46 | 47 | .tabsAside[darkmode] .collectionSet > .header > input 48 | { 49 | color: white; 50 | } 51 | 52 | .tabsAside[darkmode] .collectionSet > .header > input:hover 53 | { 54 | border: 1px solid dimgray; 55 | } 56 | 57 | .tabsAside[darkmode] .collectionSet > .header > input:focus 58 | { 59 | background: white; 60 | color: black; 61 | } 62 | 63 | .tabsAside[darkmode] a 64 | { 65 | color: #48adff; 66 | } 67 | 68 | /* Timestamp label */ 69 | .tabsAside[darkmode] > .pane > section small 70 | { 71 | color: lightgray !important; 72 | } 73 | 74 | /* Scrollbar style */ 75 | .tabsAside[darkmode] .pane ::-webkit-scrollbar-thumb 76 | { 77 | background: gray; 78 | border-radius: 3px; 79 | } 80 | .tabsAside[darkmode] .pane ::-webkit-scrollbar-thumb:hover 81 | { 82 | background: dimgray; 83 | } 84 | 85 | .tabsAside[darkmode] .pane .collectionSet 86 | { 87 | background-color: #3b3b3b; 88 | border-color: #444; 89 | } 90 | 91 | /* Tab style */ 92 | .tabsAside[darkmode] .pane .collectionSet > .set > div 93 | { 94 | background-color: #0c0c0c; 95 | background-image: url("chrome-extension://__MSG_@@extension_id__/images/tab_thumbnail_dark.png"), 96 | url("moz-extension://__MSG_@@extension_id__/images/tab_thumbnail_dark.png"); 97 | border-color: #444; 98 | } 99 | 100 | .tabsAside[darkmode] .pane .collectionSet > .set > div > div 101 | { 102 | background-color: rgba(50, 50, 50, .75); 103 | } 104 | 105 | .tabsAside[darkmode] .pane .collectionSet > .set > div > div > div 106 | { 107 | background-image: url("chrome-extension://__MSG_@@extension_id__/images/tab_icon_dark.png"), 108 | url("moz-extension://__MSG_@@extension_id__/images/tab_icon_dark.png"); 109 | } 110 | 111 | /* Context menu style */ 112 | .tabsAside[darkmode] .pane nav 113 | { 114 | background-color: #4a4a4a; 115 | } 116 | 117 | .tabsAside[darkmode] .listviewSwitch > div 118 | { 119 | border-radius: 1px; 120 | background-color: gray; 121 | } 122 | 123 | .tabsAside[darkmode] .tabsAside .listviewSwitch.tile > div 124 | { 125 | background-color: #c2c2c2; 126 | } 127 | 128 | .tabsAside[darkmode] .tabsAside[listview] .listviewSwitch.tile > div 129 | { 130 | background-color: gray; 131 | } 132 | 133 | .tabsAside[darkmode] .tabsAside[listview] .listviewSwitch.list > div 134 | { 135 | background-color: #c2c2c2; 136 | } -------------------------------------------------------------------------------- /css/style.generic.css: -------------------------------------------------------------------------------- 1 | .updateBadge 2 | { 3 | display: none; 4 | } 5 | .tabsAside[updated] .updateBadge 6 | { 7 | width: 10px; 8 | height: 10px; 9 | background-color: #0078d7; 10 | border-radius: 5px; 11 | display: block; 12 | } 13 | 14 | /* Custom scrollbar */ 15 | .tabsAside ::-webkit-scrollbar 16 | { 17 | height: 6px; 18 | width: 6px; 19 | } 20 | 21 | .tabsAside ::-webkit-scrollbar-thumb 22 | { 23 | background: darkgray; 24 | border-radius: 3px; 25 | } 26 | .tabsAside ::-webkit-scrollbar-thumb:hover 27 | { 28 | background: gray; 29 | } 30 | 31 | .tabsAside 32 | { 33 | font-family: "SegoeUI", "SegoeMDL2Assets" !important; 34 | font-size: 14px; 35 | user-select: none; 36 | } 37 | 38 | /* Links style */ 39 | .tabsAside a 40 | { 41 | color: #0078d7; 42 | } 43 | .tabsAside a:hover 44 | { 45 | text-decoration: underline; 46 | cursor: pointer; 47 | } 48 | 49 | .tabsAside a:visited 50 | { 51 | color: #0078d7; 52 | } 53 | 54 | /* Buttons style */ 55 | .tabsAside button 56 | { 57 | width: 28px; 58 | height: 28px; 59 | font-size: 16px; 60 | background-color: transparent; 61 | border: none; 62 | cursor: pointer; 63 | outline: none !important; 64 | position: relative; 65 | } 66 | .tabsAside button:hover 67 | { 68 | background-color: #f2f2f2; 69 | } 70 | .tabsAside button:active 71 | { 72 | background-color: gray; 73 | } 74 | 75 | /* Context menus style */ 76 | .tabsAside .contextContainer 77 | { 78 | position: relative; 79 | } 80 | 81 | .tabsAside nav 82 | { 83 | user-select: none; 84 | 85 | position: absolute; 86 | width: 390px; 87 | font-size: 12px; 88 | 89 | box-shadow: 0px 4px 10px rgba(0,0,0,.25); 90 | background-color: white; 91 | border-radius: 5px; 92 | 93 | z-index: 10; 94 | 95 | visibility: hidden; 96 | padding: 4px 0px; 97 | } 98 | 99 | .tabsAside nav hr 100 | { 101 | border: none; 102 | height: 1px; 103 | background-color: rgba(0, 0, 0, .1); 104 | margin: 4px 0px 105 | } 106 | 107 | .tabsAside nav button 108 | { 109 | align-content: center; 110 | text-align: start; 111 | 112 | padding: 0px 12px; 113 | width: 100%; 114 | 115 | height: 32px; 116 | font-family: "SegoeUI"; 117 | font-size: 12px; 118 | 119 | display: grid; 120 | grid-template-columns: auto 1fr auto; 121 | grid-column-gap: 14px; 122 | } 123 | 124 | .tabsAside nav button:hover 125 | { 126 | background-color: #eeee; 127 | } 128 | 129 | .tabsAside nav button img:first-child 130 | { 131 | width: 16px; 132 | height: 16px; 133 | } 134 | 135 | .tabsAside nav button img:nth-child(3) 136 | { 137 | width: 12px; 138 | height: 12px; 139 | } 140 | 141 | .tabsAside nav button *:nth-child(3) 142 | { 143 | align-self: center; 144 | } 145 | 146 | .tabsAside button + nav:active, 147 | .tabsAside button:focus + nav 148 | { 149 | visibility: visible; 150 | } 151 | 152 | /* Icon buttons style */ 153 | .btn 154 | { 155 | background-repeat: no-repeat; 156 | background-size: 12px; 157 | background-position: center; 158 | font-family: "SegoeUI", "SegoeMDL2Assets"; 159 | } 160 | 161 | .control.checkbox 162 | { 163 | display: block; 164 | position: relative; 165 | padding: 8px 12px 8px 42px; 166 | box-sizing: border-box; 167 | cursor: pointer; 168 | min-height: 32px; 169 | } 170 | .control.checkbox:hover 171 | { 172 | background-color: #eeeeee; 173 | } 174 | .control.checkbox input 175 | { 176 | position: absolute; 177 | opacity: 0; 178 | width: 0; 179 | height: 0; 180 | } 181 | .control.checkbox input + span 182 | { 183 | position: absolute; 184 | left: 12px; 185 | height: 16px; 186 | width: 16px; 187 | 188 | transition-property: background, border, border-color; 189 | transition-duration: 200ms; 190 | transition-timing-function: cubic-bezier(0.4, 0, 0.23, 1); 191 | } 192 | .control.checkbox input + span::after 193 | { 194 | position: absolute; 195 | color: rgba(0, 0, 0, .4); 196 | 197 | font-family: "SegoeMDL2Assets"; 198 | content: "\E73E"; 199 | 200 | width: 16px; 201 | height: 16px; 202 | font-size: 16px; 203 | text-align: center; 204 | 205 | display: none; 206 | } 207 | .control.checkbox input:checked + span::after 208 | { 209 | display: block; 210 | color: black; 211 | } 212 | .control.checkbox:hover input + span::after 213 | { 214 | display: block; 215 | } 216 | 217 | @font-face 218 | { 219 | font-family: "SegoeUI"; 220 | src: local("Segoe UI"), 221 | url("../fonts/segoeui.ttf") format("truetype"), 222 | url("../fonts/segoeui.woff") format("woff") 223 | } 224 | 225 | @font-face 226 | { 227 | font-family: "SegoeMDL2Assets"; 228 | src: local("Segoe MDL2 Assets"), 229 | url("../fonts/segoemdl2.ttf") format("truetype"), 230 | url("../fonts/segoemdl2.woff") format("woff") 231 | } -------------------------------------------------------------------------------- /css/style.listview.css: -------------------------------------------------------------------------------- 1 | .tabsAside[listview] .collectionSet > .header 2 | { 3 | margin-bottom: 5px; 4 | } 5 | 6 | .tabsAside[listview] .collectionSet > .set 7 | { 8 | max-height: 250px; 9 | } 10 | 11 | .tabsAside[listview] .collectionSet > .set > div 12 | { 13 | width: initial; 14 | height: initial; 15 | background-image: none !important; 16 | display: block; 17 | } 18 | 19 | .listviewSwitch 20 | { 21 | width: 20px; 22 | height: 20px; 23 | display: grid; 24 | grid-row-gap: 2px; 25 | grid-column-gap: 2px; 26 | cursor: pointer; 27 | margin: 0px auto; 28 | } 29 | 30 | .listviewSwitch.tile 31 | { 32 | grid-template-columns: 1fr 1fr; 33 | } 34 | 35 | .listviewSwitch > div 36 | { 37 | border-radius: 1px; 38 | background-color: #c2c2c2; 39 | } 40 | 41 | .listviewSwitch:hover > div 42 | { 43 | background-color: #a0a0aa; 44 | } 45 | 46 | .tabsAside .listviewSwitch.tile > div 47 | { 48 | background-color: gray; 49 | } 50 | 51 | .tabsAside[listview] .listviewSwitch.tile > div 52 | { 53 | background-color: #c2c2c2; 54 | } 55 | 56 | .tabsAside[listview] .listviewSwitch.list > div 57 | { 58 | background-color: gray; 59 | } -------------------------------------------------------------------------------- /fonts/Microsoft_Fabric_assets_license_agreement_May_2019.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XFox111/TabsAsideExtension/d6996031b684dfe05e253b0fb23d44897d930db2/fonts/Microsoft_Fabric_assets_license_agreement_May_2019.pdf -------------------------------------------------------------------------------- /fonts/Segoe_EULA.txt: -------------------------------------------------------------------------------- 1 | You may use the Segoe MDL2 Assets and Segoe UI fonts or glyphs included in this file (“Software”) solely to design, develop and test your programs for Microsoft Office. Microsoft Office includes but is not limited to any software product or service branded by trademark, trade dress, copyright or some other recognized means, as a product or service of Microsoft Office. This license does not grant you the right to distribute or sublicense all or part of the Software to any third party. By using the Software you agree to these terms. If you do not agree to these terms, do not use the Segoe MDL2 and Segoe UI Fonts or Glyphs. -------------------------------------------------------------------------------- /fonts/segoemdl2.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XFox111/TabsAsideExtension/d6996031b684dfe05e253b0fb23d44897d930db2/fonts/segoemdl2.ttf -------------------------------------------------------------------------------- /fonts/segoemdl2.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XFox111/TabsAsideExtension/d6996031b684dfe05e253b0fb23d44897d930db2/fonts/segoemdl2.woff -------------------------------------------------------------------------------- /fonts/segoeui.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XFox111/TabsAsideExtension/d6996031b684dfe05e253b0fb23d44897d930db2/fonts/segoeui.ttf -------------------------------------------------------------------------------- /fonts/segoeui.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XFox111/TabsAsideExtension/d6996031b684dfe05e253b0fb23d44897d930db2/fonts/segoeui.woff -------------------------------------------------------------------------------- /icons/bmc.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Group 5 | Created with Sketch. 6 | 7 | 8 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /icons/check.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /icons/feedback.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /icons/github.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /icons/icon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XFox111/TabsAsideExtension/d6996031b684dfe05e253b0fb23d44897d930db2/icons/icon-128.png -------------------------------------------------------------------------------- /icons/icon-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XFox111/TabsAsideExtension/d6996031b684dfe05e253b0fb23d44897d930db2/icons/icon-16.png -------------------------------------------------------------------------------- /icons/icon-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XFox111/TabsAsideExtension/d6996031b684dfe05e253b0fb23d44897d930db2/icons/icon-32.png -------------------------------------------------------------------------------- /icons/icon-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XFox111/TabsAsideExtension/d6996031b684dfe05e253b0fb23d44897d930db2/icons/icon-48.png -------------------------------------------------------------------------------- /icons/list.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /images/edge_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XFox111/TabsAsideExtension/d6996031b684dfe05e253b0fb23d44897d930db2/images/edge_icon.png -------------------------------------------------------------------------------- /images/tab_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XFox111/TabsAsideExtension/d6996031b684dfe05e253b0fb23d44897d930db2/images/tab_icon.png -------------------------------------------------------------------------------- /images/tab_icon_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XFox111/TabsAsideExtension/d6996031b684dfe05e253b0fb23d44897d930db2/images/tab_icon_dark.png -------------------------------------------------------------------------------- /images/tab_thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XFox111/TabsAsideExtension/d6996031b684dfe05e253b0fb23d44897d930db2/images/tab_thumbnail.png -------------------------------------------------------------------------------- /images/tab_thumbnail_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XFox111/TabsAsideExtension/d6996031b684dfe05e253b0fb23d44897d930db2/images/tab_thumbnail_dark.png -------------------------------------------------------------------------------- /js/aside-script.js: -------------------------------------------------------------------------------- 1 | if (window.location === window.parent.location && !window.location.protocol.includes("-extension:")) // For open/close call 2 | { 3 | var iframe = document.querySelector("iframe.tabsAsideIframe"); 4 | if (!iframe) 5 | { 6 | iframe = document.createElement('iframe'); 7 | 8 | iframe.setAttribute("class", "tabsAsideIframe"); 9 | 10 | iframe.style.position = "fixed"; 11 | iframe.style.zIndex = "2147483647"; 12 | 13 | iframe.style.height = "100%"; 14 | iframe.style.width = "100%"; 15 | 16 | iframe.style.top = "0px"; 17 | iframe.style.right = "0px"; 18 | iframe.style.left = "0px"; 19 | iframe.style.bottom = "0px"; 20 | 21 | iframe.style.border = "none"; 22 | iframe.style.background = "transparent"; 23 | iframe.style.opacity = 0; 24 | 25 | var bodyStyle = document.body.getAttribute("style"); 26 | document.body.setAttribute("style", "overflow: hidden !important"); 27 | 28 | iframe.onload = () => setTimeout(() => iframe.style.opacity = 1, 100); 29 | 30 | iframe.src = chrome.extension.getURL("TabsAside.html"); 31 | document.body.appendChild(iframe); 32 | } 33 | else 34 | { 35 | iframe.contentWindow.postMessage({ target: "TabsAside", command: "TogglePane" }, "*"); 36 | setTimeout(() => 37 | { 38 | iframe.remove(); 39 | document.body.setAttribute("style", bodyStyle); 40 | }, 250); 41 | } 42 | } 43 | else // For init call 44 | Initialize(); 45 | 46 | function Initialize() 47 | { 48 | var pane = document.querySelector(".tabsAside.pane"); 49 | if (!pane) 50 | return; 51 | 52 | if (window.location !== window.parent.location) 53 | { 54 | pane.setAttribute("embedded", ""); 55 | window.addEventListener('message', event => 56 | { 57 | if (event.data.target == "TabsAside") 58 | { 59 | pane.parentElement.style.opacity = 0; 60 | pane.removeAttribute("opened"); 61 | } 62 | }); 63 | } 64 | 65 | UpdateLocale(); 66 | 67 | if (window.matchMedia("(prefers-color-scheme: dark)").matches) 68 | { 69 | pane.parentElement.setAttribute("darkmode", ""); 70 | document.querySelector("#icon").href = "icons/dark/empty/16.png"; 71 | } 72 | 73 | document.querySelector(".tabsAside .saveTabs").onclick = SetTabsAside; 74 | document.querySelector(".tabsAside header .btn.remove").addEventListener("click", () => 75 | chrome.runtime.sendMessage({ command: "togglePane" }) 76 | ); 77 | document.querySelector(".tabsAside.closeArea").addEventListener("click", () => 78 | chrome.runtime.sendMessage({ command: "togglePane" }) 79 | ); 80 | 81 | document.querySelector("nav > p > small").textContent = chrome.runtime.getManifest()["version"]; 82 | 83 | // Tabs dismiss option 84 | var loadOnRestoreCheckbox = document.querySelector("#loadOnRestore"); 85 | chrome.storage.sync.get( 86 | { "loadOnRestore": true }, 87 | values => loadOnRestoreCheckbox.checked = values?.loadOnRestore ?? true 88 | ); 89 | chrome.storage.onChanged.addListener((changes, namespace) => 90 | { 91 | if (namespace == 'sync') 92 | for (key in changes) 93 | if (key === 'loadOnRestore') 94 | loadOnRestoreCheckbox.checked = changes[key].newValue 95 | }); 96 | loadOnRestoreCheckbox.addEventListener("click", () => 97 | chrome.storage.sync.set( 98 | { 99 | "loadOnRestore": loadOnRestoreCheckbox.checked 100 | }) 101 | ); 102 | 103 | // Extension browser icon action 104 | var swapIconAction = document.querySelector("#swapIconAction"); 105 | chrome.storage.sync.get( 106 | { "setAsideOnClick": false }, 107 | values => swapIconAction.checked = values?.setAsideOnClick ?? false 108 | ); 109 | chrome.storage.onChanged.addListener((changes, namespace) => 110 | { 111 | if (namespace == 'sync') 112 | for (key in changes) 113 | if (key === 'setAsideOnClick') 114 | swapIconAction.checked = changes[key].newValue 115 | }); 116 | swapIconAction.addEventListener("click", () => 117 | chrome.storage.sync.set( 118 | { 119 | "setAsideOnClick": swapIconAction.checked 120 | }) 121 | ); 122 | 123 | // Deletion confirmation dialog 124 | var showDeleteDialog = document.querySelector("#showDeleteDialog"); 125 | chrome.storage.sync.get( 126 | { "showDeleteDialog": true }, 127 | values => showDeleteDialog.checked = values.showDeleteDialog 128 | ); 129 | chrome.storage.onChanged.addListener((changes, namespace) => 130 | { 131 | if (namespace == 'sync') 132 | for (key in changes) 133 | if (key === 'showDeleteDialog') 134 | showDeleteDialog.checked = changes[key].newValue 135 | }); 136 | showDeleteDialog.addEventListener("click", () => 137 | chrome.storage.sync.set( 138 | { 139 | "showDeleteDialog": showDeleteDialog.checked 140 | }) 141 | ); 142 | 143 | // Collections view switch 144 | chrome.storage.sync.get( 145 | { "listview": false }, 146 | values => 147 | { 148 | if (values?.listview) 149 | pane.setAttribute("listview", ""); 150 | } 151 | ); 152 | document.querySelectorAll(".listviewSwitch").forEach(i => 153 | { 154 | i.onclick = (args) => 155 | { 156 | if (args.currentTarget.classList[1] == "list") 157 | { 158 | pane.setAttribute("listview", ""); 159 | chrome.storage.sync.set({ "listview": true }); 160 | } 161 | else 162 | { 163 | pane.removeAttribute("listview"); 164 | chrome.storage.sync.set({ "listview": false }); 165 | } 166 | } 167 | }); 168 | chrome.storage.onChanged.addListener((changes, namespace) => 169 | { 170 | if (namespace == 'sync') 171 | for (key in changes) 172 | if (key === 'listview') 173 | if (changes[key].newValue) 174 | pane.setAttribute("listview", ""); 175 | else 176 | pane.removeAttribute("listview"); 177 | }); 178 | 179 | document.querySelectorAll(".tabsAside.pane > header nav button").forEach(i => 180 | i.onclick = () => 181 | { 182 | if (i.hasAttribute("feedback-button")) 183 | { 184 | if (chrome.runtime.getManifest()["update_url"] && chrome.runtime.getManifest()["update_url"].includes("edge.microsoft.com")) 185 | window.open("https://microsoftedge.microsoft.com/addons/detail/tabs-aside/kmnblllmalkiapkfknnlpobmjjdnlhnd", "_blank") 186 | else 187 | window.open("https://chrome.google.com/webstore/detail/tabs-aside/mgmjbodjgijnebfgohlnjkegdpbdjgin", "_blank") 188 | } 189 | else 190 | window.open(i.value, "_blank"); 191 | }); 192 | 193 | // Showing changelog badge if updated 194 | chrome.storage.local.get({ "showUpdateBadge": false }, values => 195 | { 196 | if (values.showUpdateBadge) 197 | { 198 | pane.setAttribute("updated", ""); 199 | let settingsButton = document.querySelector("header .btn.more"); 200 | settingsButton.addEventListener("focusout", () => 201 | { 202 | if (!pane.hasAttribute("updated")) 203 | return; 204 | pane.removeAttribute("updated"); 205 | chrome.storage.local.set({ "showUpdateBadge": false }); 206 | }); 207 | } 208 | }); 209 | 210 | chrome.runtime.sendMessage({ command: "loadData" }, ({ collections, thumbnails }) => 211 | ReloadCollections(collections, thumbnails)); 212 | 213 | chrome.runtime.onMessage.addListener((message) => 214 | { 215 | switch (message.command) 216 | { 217 | case "reloadCollections": 218 | ReloadCollections(message.collections, message.thumbnails); 219 | break; 220 | } 221 | }); 222 | 223 | setTimeout(() => pane.setAttribute("opened", ""), 100); 224 | } 225 | 226 | function UpdateLocale() 227 | { 228 | document.querySelectorAll("*[loc]").forEach(i => i.textContent = chrome.i18n.getMessage(i.getAttribute("loc"))); 229 | document.querySelectorAll("*[loc_alt]").forEach(i => i.title = chrome.i18n.getMessage(i.getAttribute("loc_alt"))); 230 | 231 | var swapActionsLabel = document.querySelector("span[loc=swapIconAction]"); 232 | chrome.runtime.sendMessage({ command: "getShortcuts" }, (shortcuts) => 233 | swapActionsLabel.textContent = swapActionsLabel.textContent.replace("%TOGGLE_SHORTCUT%", shortcuts.filter(i => i.name == "toggle-pane")[0].shortcut) 234 | ); 235 | } 236 | 237 | 238 | function ReloadCollections(collections, thumbnails) 239 | { 240 | document.querySelector(".tabsAside section h2").removeAttribute("hidden"); 241 | document.querySelectorAll(".tabsAside section > div").forEach(i => i.remove()); 242 | 243 | for (var collection of Object.values(collections).reverse()) 244 | AddCollection(collection, thumbnails); 245 | } 246 | 247 | function AddCollection(collection, thumbnails) 248 | { 249 | var list = document.querySelector(".tabsAside section"); 250 | list.querySelector("h2").setAttribute("hidden", ""); 251 | 252 | var rawTabs = ""; 253 | 254 | for (var i = 0; i < collection.links.length; i++) 255 | rawTabs += 256 | "
" + 257 | "
" + 258 | "
" + 259 | "" + collection.titles[i] + "" + 260 | "" + 261 | "
" + 262 | ""; 263 | 264 | list.innerHTML += 265 | "
" + 266 | "
" + 267 | "" + 268 | "Restore tabs" + 269 | "
" + 270 | "" + 271 | "" + 274 | "
" + 275 | "" + 276 | "" + collection.links.length + " " + chrome.i18n.getMessage("tabs") +"" + 277 | "
" + 278 | 279 | "
" + rawTabs + "
" + 280 | "
"; 281 | 282 | UpdateLocale(); 283 | 284 | list.querySelectorAll("input").forEach(i => 285 | i.addEventListener("focusout",(event) => RenameCollection(i.parentElement.parentElement, event.target.value))); 286 | 287 | list.querySelectorAll(".restoreCollection").forEach(i => 288 | i.onclick = () => RestoreTabs(i.parentElement.parentElement)); 289 | 290 | list.querySelectorAll(".restoreCollection.noDelete").forEach(i => 291 | i.onclick = () => RestoreTabs(i.parentElement.parentElement.parentElement.parentElement, false)); 292 | 293 | list.querySelectorAll(".set > div").forEach(i => 294 | i.onclick = (args) => 295 | { 296 | if (args.target.localName != "button") 297 | chrome.runtime.sendMessage( 298 | { 299 | command: "openTab", 300 | url: i.getAttribute("value") 301 | } 302 | ); 303 | }); 304 | 305 | document.querySelectorAll(".header .btn.remove").forEach(i => 306 | i.onclick = () => RemoveTabs(i.parentElement.parentElement)); 307 | 308 | document.querySelectorAll(".set .btn.remove").forEach(i => 309 | i.onclick = () => 310 | RemoveOneTab(i.parentElement.parentElement)); 311 | } 312 | 313 | function SetTabsAside() 314 | { 315 | chrome.runtime.sendMessage({ command: "saveTabs" }); 316 | } 317 | 318 | function RenameCollection(collectionData, name) 319 | { 320 | chrome.runtime.sendMessage( 321 | { 322 | command: "renameCollection", 323 | newName: name, 324 | collectionKey: collectionData.id 325 | }); 326 | } 327 | 328 | function RestoreTabs(collectionData, removeCollection = true) 329 | { 330 | chrome.runtime.sendMessage( 331 | { 332 | command: "restoreTabs", 333 | removeCollection: removeCollection, 334 | collectionKey: collectionData.id 335 | }, 336 | () => 337 | { 338 | if (removeCollection) 339 | RemoveCollectionElement(collectionData); 340 | } 341 | ); 342 | } 343 | 344 | function RemoveTabs(collectionData) 345 | { 346 | chrome.storage.sync.get({ "showDeleteDialog": true }, values => 347 | { 348 | if (values.showDeleteDialog && !confirm(chrome.i18n.getMessage("removeCollectionConfirm"))) 349 | return; 350 | 351 | chrome.runtime.sendMessage( 352 | { 353 | command: "deleteTabs", 354 | collectionKey: collectionData.id 355 | }, 356 | () => RemoveCollectionElement(collectionData) 357 | ); 358 | }); 359 | } 360 | 361 | function RemoveOneTab(tabData) 362 | { 363 | chrome.storage.sync.get({ "showDeleteDialog": true }, values => 364 | { 365 | if (values.showDeleteDialog && !confirm(chrome.i18n.getMessage("removeTabConfirm"))) 366 | return; 367 | 368 | chrome.runtime.sendMessage( 369 | { 370 | command: "removeTab", 371 | collectionKey: tabData.parentElement.parentElement.id, 372 | tabIndex: Array.prototype.slice.call(tabData.parentElement.children).indexOf(tabData) 373 | }, 374 | () => 375 | { 376 | tabData.parentElement.previousElementSibling.querySelector("small").textContent = (tabData.parentElement.children.length - 1) + " " + chrome.i18n.getMessage("tabs"); 377 | if (tabData.parentElement.children.length < 2) 378 | { 379 | RemoveElement(tabData.parentElement.parentElement); 380 | if (document.querySelector("tabsAside.pane > section").children.length < 2) 381 | setTimeout(() => document.querySelector(".tabsAside.pane > section > h2").removeAttribute("hidden"), 250); 382 | } 383 | else 384 | RemoveElement(tabData); 385 | }); 386 | }); 387 | } 388 | 389 | function RemoveElement(el) 390 | { 391 | el.style.opacity = 0; 392 | setTimeout(() => el.remove(), 200); 393 | } 394 | 395 | function RemoveCollectionElement(el) 396 | { 397 | if (el.parentElement.children.length < 3) 398 | setTimeout(() => document.querySelector(".tabsAside.pane > section > h2").removeAttribute("hidden"), 250); 399 | RemoveElement(el); 400 | } -------------------------------------------------------------------------------- /js/background.js: -------------------------------------------------------------------------------- 1 | //This variable is populated when the browser action icon is clicked, or a command is called (with a shortcut for example). 2 | //We can't populate it later, as selected tabs get deselected on a click inside a tab. 3 | var tabsToSave = []; 4 | 5 | var syncEnabled = true; //This variable controls whether to use the chrome sync storage, along with its size limitations, or the much larger chrome local storage. The option is currently not exposed to the users. 6 | var collectionStorage = syncEnabled ? chrome.storage.sync : chrome.storage.local; 7 | 8 | //Get the tabs to save, either all the window or the selected tabs only, and pass them through a callback. 9 | function GetTabsToSave (callback) 10 | { 11 | chrome.tabs.query({ currentWindow: true }, (windowTabs) => 12 | { 13 | var highlightedTabs = windowTabs.filter(item => item.highlighted); 14 | //If there are more than one selected tab in the window, we set only those aside. 15 | // Otherwise, all the window's tabs get saved. 16 | return callback((highlightedTabs.length > 1 ? highlightedTabs : windowTabs)); 17 | }); 18 | 19 | } 20 | 21 | function TogglePane (tab) 22 | { 23 | if (tab.url.startsWith("http") 24 | && !tab.url.includes("chrome.google.com") 25 | && !tab.url.includes("addons.mozilla.org") 26 | && !tab.url.includes("microsoftedge.microsoft.com")) 27 | { 28 | chrome.tabs.executeScript(tab.id, 29 | { 30 | file: "js/aside-script.js", 31 | allFrames: true, 32 | runAt: "document_idle" 33 | }); 34 | } 35 | else if (tab.url == chrome.runtime.getURL("TabsAside.html")) 36 | chrome.tabs.remove(tab.id); 37 | else 38 | { 39 | chrome.tabs.create( 40 | { 41 | url: chrome.extension.getURL("TabsAside.html"), 42 | active: true 43 | }, 44 | (activeTab) => 45 | chrome.tabs.onActivated.addListener(function TabsAsideCloser(activeInfo) 46 | { 47 | chrome.tabs.query({ url: chrome.extension.getURL("TabsAside.html") }, (result) => 48 | { 49 | if (result.length) 50 | setTimeout(() => 51 | { 52 | result.forEach(i => 53 | { 54 | if (activeInfo.tabId != i.id) 55 | chrome.tabs.remove(i.id); 56 | }); 57 | }, 200); 58 | else chrome.tabs.onActivated.removeListener(TabsAsideCloser); 59 | }); 60 | })); 61 | } 62 | } 63 | 64 | function ProcessCommand (command) 65 | { 66 | GetTabsToSave((returnedTabs) => 67 | { 68 | tabsToSave = returnedTabs; 69 | switch(command) 70 | { 71 | case "set-aside": 72 | SaveCollection(); 73 | break; 74 | case "toggle-pane": 75 | chrome.tabs.query( 76 | { 77 | active: true, 78 | currentWindow: true 79 | }, 80 | (tabs) => TogglePane(tabs[0]) 81 | ) 82 | break; 83 | } 84 | }); 85 | } 86 | 87 | chrome.browserAction.onClicked.addListener((tab) => 88 | { 89 | GetTabsToSave((returnedTabs) => 90 | { 91 | tabsToSave = returnedTabs; 92 | 93 | chrome.storage.sync.get({ "setAsideOnClick": false }, values => 94 | { 95 | if (values?.setAsideOnClick) 96 | SaveCollection(); 97 | else 98 | TogglePane(tab); 99 | }); 100 | }); 101 | }); 102 | 103 | collections = { }; 104 | thumbnails = { }; 105 | 106 | /** 107 | * Updates the thumbnail storage, then the collection synced storage. Calls the onSuccess callback when successful, and onFailure when failed. 108 | * @param {Object} collectionsToUpdate An object containing one or more collections to be updated. By default, updates the whole "collections" item. 109 | * @param {function} onSuccess A function without arguments, that will be called after the collections and thumbnails values are obtained, if collections are successfully updated 110 | * @param {function} onFailure A function that will be called with the error, after the collections and thumbnails values are obtained, if collections are not successfully updated 111 | */ 112 | function UpdateStorages (collectionsToUpdate = collections, onSuccess = () => null, onFailure = (error) => { throw error.message; }) 113 | { 114 | //The collections storage is updated after the thumbnail storage, so that the thumbnails are ready when the collections are updated. 115 | chrome.storage.local.set({ "thumbnails": thumbnails }, 116 | () => collectionStorage.set(CompressCollectionsStorage(collectionsToUpdate), 117 | () => 118 | { 119 | if (!chrome.runtime.lastError) 120 | onSuccess(); 121 | else 122 | onFailure(chrome.runtime.lastError); 123 | }) 124 | ); 125 | //When the collection storage is updated, a listener set up below reacts and updates the collections global variable, so we do not need to update that variable here 126 | } 127 | 128 | /** 129 | * Use a compression mechanism to compress collections in an object, one by one. 130 | * @param {Object} collectionsToCompress object of collections to compress 131 | * @returns {Object} Object of compressed stringified collections. 132 | */ 133 | function CompressCollectionsStorage (collectionsToCompress) 134 | { 135 | var compressedStorage = { }; 136 | for (var [key, value] of Object.entries(collectionsToCompress)) //For each collection in the uncompressed collectionsToCompress 137 | { 138 | var cloneWithoutTimestamp = Object.assign({ }, value, { timestamp: null }); 139 | compressedStorage[key] = LZUTF8.compress(JSON.stringify(cloneWithoutTimestamp), { outputEncoding: "StorageBinaryString" }); 140 | } 141 | return compressedStorage; 142 | } 143 | 144 | /** 145 | * Load and decompresses the thumbnails and collections global variables from the storage, updates the theme and eventually sends the collections and thumbnails to a callback 146 | * @param {function} callback A function that will be called after the collections and thumbnails values are obtained. 147 | * These collections and thumbnails are sent as a data={"collections":collections,"thumbnails":thumbnails} argument to the callback. 148 | */ 149 | function LoadStorages (callback = () => null) 150 | { 151 | chrome.storage.local.get("thumbnails", values => 152 | { 153 | thumbnails = values?.thumbnails ?? { }; 154 | collectionStorage.get(null, values => 155 | { 156 | collections = DecompressCollectionsStorage(values); 157 | UpdateBadgeCounter(); 158 | callback({ "collections": collections, "thumbnails": thumbnails }); 159 | }); 160 | }); 161 | } 162 | 163 | /** 164 | * Use a decompression mechanism to decompress stringified collections in an object, one by one. 165 | * Ignores non collections items (items with a key not starting with "set_" 166 | * @param {Object} compressedCollections object of stringified collections to decompress 167 | * @returns {Object} Object of decompressed and parsed collections. 168 | */ 169 | function DecompressCollectionsStorage (compressedCollections) 170 | { 171 | var decompressedStorage = { }; 172 | for (var [key, value] of Object.entries(compressedCollections)) 173 | { 174 | if (!key.startsWith("set_")) 175 | continue; 176 | 177 | decompressedStorage[key] = JSON.parse(LZUTF8.decompress(value, { inputEncoding: "StorageBinaryString" })); 178 | decompressedStorage[key].timestamp = parseInt(key.substr(4)); 179 | } 180 | return decompressedStorage; 181 | } 182 | 183 | /** 184 | * Merges a provided collections array with older pre v2 collections, 185 | * saving the result into the new post v2 storage, or into bookmarks on failure. 186 | * Allows to preserve backward compatibility with the localStorage method of storing collections in pre v2 versions. 187 | * @param {Object} collections The current collections object 188 | */ 189 | function MergePreV2Collections () 190 | { 191 | if (localStorage.getItem("sets")) 192 | { 193 | console.log("Found pre-v2 data"); 194 | var old_collections = JSON.parse(localStorage.getItem("sets")); 195 | //Migrate thumbnails and icons to follow the new format . 196 | old_collections.forEach(collection => 197 | { 198 | for (var i = 0; i < collection.links.length; i++) 199 | thumbnails[collection.links[i]] = 200 | { 201 | "pageCapture": collection.thumbnails[i], 202 | "iconUrl": collection.icons[i] 203 | }; 204 | 205 | delete collection.thumbnails; 206 | delete collection.icons; 207 | 208 | UpdateStorages({ ["set_" + collection.timestamp]: collection }, 209 | () => null, 210 | () => 211 | { 212 | SaveCollectionAsBookmarks(collection); 213 | alert(chrome.i18n.getMessage("olderDataMigrationFailed")); 214 | }); 215 | }); 216 | localStorage.removeItem("sets"); 217 | } 218 | } 219 | 220 | function SaveCollectionAsBookmarks (collection) 221 | { 222 | //The id 1 is the browser's bookmark bar 223 | chrome.bookmarks.create({ 224 | "parentId": "1", 225 | "title": "TabsAside " + (collection.name ?? new Date(collection.timestamp).toISOString()) 226 | }, 227 | (collectionFolder) => 228 | { 229 | for (var i = 0; i < collection.links.length; i++) 230 | chrome.bookmarks.create( 231 | { 232 | "parentId": collectionFolder.id, 233 | "title": collection.titles[i], 234 | "url": collection.links[i] 235 | }); 236 | }); 237 | } 238 | 239 | LoadStorages(MergePreV2Collections); 240 | 241 | chrome.storage.onChanged.addListener((changes, namespace) => 242 | { 243 | if (namespace == "sync") 244 | for (key in changes) 245 | if (key.startsWith("set_")) 246 | { 247 | if (changes[key].newValue) 248 | { 249 | collections[key] = DecompressCollectionsStorage({ [key]: changes[key].newValue })[key]; 250 | 251 | } 252 | else 253 | delete collections[key]; 254 | 255 | UpdateBadgeCounter(); 256 | chrome.runtime.sendMessage( 257 | { 258 | command: "reloadCollections", 259 | collections: collections, 260 | thumbnails: thumbnails 261 | }); 262 | } 263 | }); 264 | 265 | var shortcuts; 266 | chrome.commands.getAll((commands) => shortcuts = commands); 267 | 268 | chrome.commands.onCommand.addListener(ProcessCommand); 269 | chrome.contextMenus.onClicked.addListener((info) => ProcessCommand(info.menuItemId)); 270 | 271 | chrome.runtime.onInstalled.addListener((updateInfo) => 272 | { 273 | if (updateInfo.reason == "update" && updateInfo.previousVersion != chrome.runtime.getManifest()["version"]) 274 | chrome.storage.local.set({ "showUpdateBadge": true }); 275 | 276 | // Adding context menu options, must be done on extension install and update, and probably chrome update as well. 277 | chrome.contextMenus.create( 278 | { 279 | id: "toggle-pane", 280 | contexts: ["browser_action"], 281 | title: chrome.i18n.getMessage("togglePaneContext") 282 | } 283 | ); 284 | chrome.contextMenus.create( 285 | { 286 | id: "set-aside", 287 | contexts: ["browser_action"], 288 | title: chrome.i18n.getMessage("setAside") 289 | } 290 | ); 291 | }); 292 | 293 | //We receive a message from the pane aside-script, which means the tabsToSave are already assigned on message reception. 294 | chrome.runtime.onMessage.addListener((message, sender, sendResponse) => 295 | { 296 | switch (message.command) 297 | { 298 | case "openTab": 299 | chrome.tabs.create({ url: message.url }); 300 | break; 301 | case "loadData": 302 | LoadStorages(sendResponse); //Sends the collections as a response 303 | return true; //Required to indicate the answer will be sent asynchronously https://developer.chrome.com/extensions/messaging 304 | case "saveTabs": 305 | SaveCollection(); 306 | break; 307 | case "restoreTabs": 308 | RestoreCollection(message.collectionKey, message.removeCollection); 309 | sendResponse(); 310 | break; 311 | case "deleteTabs": 312 | DeleteCollection(message.collectionKey); 313 | sendResponse(); 314 | break; 315 | case "removeTab": 316 | RemoveTab(message.collectionKey, message.tabIndex); 317 | sendResponse(); 318 | break; 319 | case "renameCollection": 320 | collections[message.collectionKey].name = message.newName; 321 | UpdateStorages({ [message.collectionKey]: collections[message.collectionKey] }); 322 | break; 323 | case "togglePane": 324 | chrome.tabs.query( 325 | { 326 | active: true, 327 | currentWindow: true 328 | }, 329 | (tabs) => TogglePane(tabs[0]) 330 | ); 331 | break; 332 | case "getShortcuts": 333 | sendResponse(shortcuts); 334 | break; 335 | } 336 | }); 337 | 338 | // This function updates the extension's toolbar icon 339 | function UpdateBadgeCounter () 340 | { 341 | var collectionsLength = Object.keys(collections).length; 342 | // Updating badge counter 343 | chrome.browserAction.setBadgeText({ text: collectionsLength < 1 ? "" : collectionsLength.toString() }); 344 | 345 | } 346 | 347 | // Set current tabs aside 348 | function SaveCollection () 349 | { 350 | var tabs = tabsToSave.filter(i => i.url != chrome.runtime.getURL("TabsAside.html") && !i.pinned && !i.url.includes("//newtab") && !i.url.includes("about:blank") && !i.url.includes("about:home")); 351 | 352 | if (tabs.length < 1) 353 | { 354 | alert(chrome.i18n.getMessage("noTabsToSave")); 355 | return; 356 | } 357 | 358 | var collection = 359 | { 360 | timestamp: Date.now(), 361 | tabsCount: tabs.length, 362 | titles: tabs.map(tab => (tab.title ?? "").substr(0, 100)), 363 | links: tabs.map(tab => tab.url ?? "") 364 | }; 365 | 366 | tabs.forEach(tab => //For each tab to save, Add relevant thumbnails in the thumbnails object 367 | { 368 | thumbnails[tab.url] = 369 | { 370 | "pageCapture": sessionCaptures[tab.url] ?? thumbnails[tab.url]?.pageCapture, 371 | "iconUrl": tab.favIconUrl 372 | }; 373 | }); 374 | 375 | UpdateStorages({["set_" + collection.timestamp]: collection}, () => 376 | chrome.tabs.create({}, (tab) => 377 | { 378 | var newTabId = tab.id; 379 | chrome.tabs.remove(tabsToSave.filter(i => !i.pinned && i.id != newTabId).map(tab => tab.id)); 380 | }), 381 | () => alert(chrome.i18n.getMessage("errorSavingTabs")) 382 | ); 383 | } 384 | 385 | function DeleteCollection (collectionKey) 386 | { 387 | var deletedUrls = collections[collectionKey].links; 388 | delete collections[collectionKey]; 389 | ForEachUnusedUrl(deletedUrls, (url) => delete thumbnails[url]); //We delete the thumbnails that are not used in any other collection. 390 | UpdateStorages({});//Updates the thumbnails storage only, by providing an empty "collectionsToUpdate" object. 391 | collectionStorage.remove(collectionKey);//Remove the collection from the collectionstorage 392 | } 393 | 394 | function RestoreCollection (collectionKey, removeCollection) 395 | { 396 | collections[collectionKey].links.forEach(i => 397 | { 398 | chrome.tabs.create( 399 | { 400 | url: i, 401 | active: false 402 | }, 403 | (createdTab) => 404 | { 405 | chrome.storage.sync.get({"loadOnRestore": true}, values => 406 | { 407 | if (!(values?.loadOnRestore)) 408 | chrome.tabs.onUpdated.addListener(function DiscardTab (updatedTabId, changeInfo, updatedTab) 409 | { 410 | if (updatedTabId === createdTab.id) 411 | { 412 | chrome.tabs.onUpdated.removeListener(DiscardTab); 413 | if (!updatedTab.active) 414 | { 415 | chrome.tabs.discard(updatedTabId); 416 | } 417 | } 418 | }); 419 | }); 420 | }); 421 | }); 422 | 423 | //We added new tabs by restoring a collection, so we refresh the array of tabs ready to be saved. 424 | GetTabsToSave((returnedTabs) => 425 | tabsToSave = returnedTabs); 426 | 427 | if (!removeCollection) 428 | return; 429 | 430 | DeleteCollection(collectionKey); 431 | } 432 | 433 | function RemoveTab (collectionKey, tabIndex) 434 | { 435 | var set = collections[collectionKey]; 436 | if (--set.tabsCount < 1) 437 | { 438 | DeleteCollection(collectionKey); 439 | return; 440 | } 441 | 442 | var urlToRemove = set.links[tabIndex]; 443 | set.titles.splice(tabIndex, 1); 444 | set.links.splice(tabIndex, 1); 445 | 446 | ForEachUnusedUrl([urlToRemove], (url) => delete thumbnails[url]); 447 | UpdateStorages({[collectionKey]: set}); 448 | } 449 | 450 | /** 451 | * Execute a callback for each url in urlsToFilter that is not in any collection urls 452 | * @param {Array} urlsToFilter array of urls to check 453 | * @param {function} callback callback to execute on an url 454 | */ 455 | function ForEachUnusedUrl (urlsToFilter, callback) 456 | { 457 | for (var i = 0; i < urlsToFilter.length; i++) // If the url of the tab n°i is not present among all the collections, we call the callback on it 458 | if (!Object.values(collections).some(collection => collection.links.some(link => link == urlsToFilter[i]))) 459 | callback(urlsToFilter[i]); 460 | } 461 | 462 | // page Captures are not always used in a collection, so we keep them in a specific variable until a collection is saved. 463 | var sessionCaptures = { }; 464 | 465 | function AppendThumbnail (tab) 466 | { 467 | if (!tab.active || !tab.url.startsWith("http")) 468 | return; 469 | 470 | chrome.tabs.captureVisibleTab( 471 | { 472 | format: "jpeg", 473 | quality: 1 474 | }, 475 | (image) => 476 | { 477 | if (!image) 478 | { 479 | console.log("Failed to retrieve thumbnail"); 480 | return; 481 | } 482 | 483 | console.log("Thumbnail retrieved"); 484 | sessionCaptures[tab.url] = image; 485 | } 486 | ); 487 | } 488 | 489 | chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => 490 | { 491 | if (changeInfo.status == "complete") 492 | AppendThumbnail(tab); 493 | }); 494 | -------------------------------------------------------------------------------- /js/lib/lzutf8.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | LZ-UTF8 v0.5.6 3 | 4 | Copyright (c) 2018, Rotem Dan 5 | Released under the MIT license. 6 | 7 | Build date: 2020-08-10 8 | 9 | Please report any issue at https://github.com/rotemdan/lzutf8.js/issues 10 | */ 11 | var IE10SubarrayBugPatcher,LZUTF8;!function(n){n.runningInNodeJS=function(){return"object"==typeof process&&"object"==typeof process.versions&&"string"==typeof process.versions.node},n.runningInMainNodeJSModule=function(){return n.runningInNodeJS()&&require.main===module},n.commonJSAvailable=function(){return"object"==typeof module&&"object"==typeof module.exports},n.runningInWebWorker=function(){return"undefined"==typeof window&&"object"==typeof self&&"function"==typeof self.addEventListener&&"function"==typeof self.close},n.runningInNodeChildProcess=function(){return n.runningInNodeJS()&&"function"==typeof process.send},n.runningInNullOrigin=function(){return"object"==typeof window&&"object"==typeof window.location&&("http:"!==document.location.protocol&&"https:"!==document.location.protocol)},n.webWorkersAvailable=function(){return"function"==typeof Worker&&!n.runningInNullOrigin()&&(!n.runningInNodeJS()&&!(navigator&&navigator.userAgent&&0<=navigator.userAgent.indexOf("Android 4.3")))},n.log=function(e,t){void 0===t&&(t=!1),"object"==typeof console&&(console.log(e),t&&"object"==typeof document&&(document.body.innerHTML+=e+"
"))},n.createErrorMessage=function(e,t){if(void 0===t&&(t="Unhandled exception"),null==e)return t;if(t+=": ","object"==typeof e.content){if(n.runningInNodeJS())return t+e.content.stack;var r=JSON.stringify(e.content);return"{}"!==r?t+r:t+e.content}return"string"==typeof e.content?t+e.content:t+e},n.printExceptionAndStackTraceToConsole=function(e,t){void 0===t&&(t="Unhandled exception"),n.log(n.createErrorMessage(e,t))},n.getGlobalObject=function(){return"object"==typeof global?global:"object"==typeof window?window:"object"==typeof self?self:{}},n.toString=Object.prototype.toString,n.commonJSAvailable()&&(module.exports=n)}(LZUTF8||(LZUTF8={})),function(e){if("function"==typeof Uint8Array&&0!==new Uint8Array(1).subarray(1).byteLength){var t=function(e,t){var r=function(e,t,r){return e>>10)),this.appendCharCode(56320+(e-65536&1023))}},e.prototype.getOutputString=function(){return this.flushBufferToOutputString(),this.outputString},e.prototype.flushBufferToOutputString=function(){this.outputPosition===this.outputBufferCapacity?this.outputString+=String.fromCharCode.apply(null,this.outputBuffer):this.outputString+=String.fromCharCode.apply(null,this.outputBuffer.subarray(0,this.outputPosition)),this.outputPosition=0},e}();e.StringBuilder=t}(LZUTF8||(LZUTF8={})),function(o){var e=function(){function e(){this.restart()}return e.prototype.restart=function(){this.startTime=e.getTimestamp()},e.prototype.getElapsedTime=function(){return e.getTimestamp()-this.startTime},e.prototype.getElapsedTimeAndRestart=function(){var e=this.getElapsedTime();return this.restart(),e},e.prototype.logAndRestart=function(e,t){void 0===t&&(t=!0);var r=this.getElapsedTime(),n=e+": "+r.toFixed(3)+"ms";return o.log(n,t),this.restart(),r},e.getTimestamp=function(){return this.timestampFunc||this.createGlobalTimestampFunction(),this.timestampFunc()},e.getMicrosecondTimestamp=function(){return Math.floor(1e3*e.getTimestamp())},e.createGlobalTimestampFunction=function(){if("object"==typeof process&&"function"==typeof process.hrtime){var r=0;this.timestampFunc=function(){var e=process.hrtime(),t=1e3*e[0]+e[1]/1e6;return r+t},r=Date.now()-this.timestampFunc()}else if("object"==typeof chrome&&chrome.Interval){var e=Date.now(),t=new chrome.Interval;t.start(),this.timestampFunc=function(){return e+t.microseconds()/1e3}}else if("object"==typeof performance&&performance.now){var n=Date.now()-performance.now();this.timestampFunc=function(){return n+performance.now()}}else Date.now?this.timestampFunc=function(){return Date.now()}:this.timestampFunc=function(){return(new Date).getTime()}},e}();o.Timer=e}(LZUTF8||(LZUTF8={})),function(n){var e=function(){function e(e){void 0===e&&(e=!0),this.MinimumSequenceLength=4,this.MaximumSequenceLength=31,this.MaximumMatchDistance=32767,this.PrefixHashTableSize=65537,this.inputBufferStreamOffset=1,e&&"function"==typeof Uint32Array?this.prefixHashTable=new n.CompressorCustomHashTable(this.PrefixHashTableSize):this.prefixHashTable=new n.CompressorSimpleHashTable(this.PrefixHashTableSize)}return e.prototype.compressBlock=function(e){if(null==e)throw new TypeError("compressBlock: undefined or null input received");return"string"==typeof e&&(e=n.encodeUTF8(e)),e=n.BufferTools.convertToUint8ArrayIfNeeded(e),this.compressUtf8Block(e)},e.prototype.compressUtf8Block=function(e){if(!e||0==e.length)return new Uint8Array(0);var t=this.cropAndAddNewBytesToInputBuffer(e),r=this.inputBuffer,n=this.inputBuffer.length;this.outputBuffer=new Uint8Array(e.length);for(var o=this.outputBufferPosition=0,i=t;in-this.MinimumSequenceLength)s||this.outputRawByte(u);else{var a=this.getBucketIndexForPrefix(i);if(!s){var c=this.findLongestMatch(i,a);null!=c&&(this.outputPointerBytes(c.length,c.distance),o=i+c.length,s=!0)}s||this.outputRawByte(u);var f=this.inputBufferStreamOffset+i;this.prefixHashTable.addValueToBucket(a,f)}}return this.outputBuffer.subarray(0,this.outputBufferPosition)},e.prototype.findLongestMatch=function(e,t){var r=this.prefixHashTable.getArraySegmentForBucketIndex(t,this.reusableArraySegmentObject);if(null==r)return null;for(var n,o=this.inputBuffer,i=0,u=0;u>>1):i,a>this.MaximumMatchDistance||c>=this.MaximumSequenceLength||e+c>=o.length)break;if(o[s+c]===o[e+c])for(var f=0;;f++){if(e+f===o.length||o[s+f]!==o[e+f]){c>>8),this.outputRawByte(255&t))},e.prototype.outputRawByte=function(e){this.outputBuffer[this.outputBufferPosition++]=e},e.prototype.cropAndAddNewBytesToInputBuffer=function(e){if(void 0===this.inputBuffer)return this.inputBuffer=e,0;var t=Math.min(this.inputBuffer.length,this.MaximumMatchDistance),r=this.inputBuffer.length-t;return this.inputBuffer=n.CompressionCommon.getCroppedAndAppendedByteArray(this.inputBuffer,r,t,e),this.inputBufferStreamOffset+=r,t},e}();n.Compressor=e}(LZUTF8||(LZUTF8={})),function(s){var e=function(){function e(e){this.minimumBucketCapacity=4,this.maximumBucketCapacity=64,this.bucketLocators=new Uint32Array(2*e),this.storage=new Uint32Array(2*e),this.storageIndex=1}return e.prototype.addValueToBucket=function(e,t){e<<=1,this.storageIndex>=this.storage.length>>>1&&this.compact();var r,n=this.bucketLocators[e];if(0===n)n=this.storageIndex,r=1,this.storage[this.storageIndex]=t,this.storageIndex+=this.minimumBucketCapacity;else{(r=this.bucketLocators[e+1])===this.maximumBucketCapacity-1&&(r=this.truncateBucketToNewerElements(n,r,this.maximumBucketCapacity/2));var o=n+r;0===this.storage[o]?(this.storage[o]=t,o===this.storageIndex&&(this.storageIndex+=r)):(s.ArrayTools.copyElements(this.storage,n,this.storage,this.storageIndex,r),n=this.storageIndex,this.storageIndex+=r,this.storage[this.storageIndex++]=t,this.storageIndex+=r),r++}this.bucketLocators[e]=n,this.bucketLocators[e+1]=r},e.prototype.truncateBucketToNewerElements=function(e,t,r){var n=e+t-r;return s.ArrayTools.copyElements(this.storage,n,this.storage,e,r),s.ArrayTools.zeroElements(this.storage,e+r,t-r),r},e.prototype.compact=function(){var e=this.bucketLocators,t=this.storage;this.bucketLocators=new Uint32Array(this.bucketLocators.length),this.storageIndex=1;for(var r=0;r>>6==3){var i=o>>>5;if(r==n-1||r==n-2&&7==i){this.inputBufferRemainder=e.subarray(r);break}if(e[r+1]>>>7==1)this.outputByte(o);else{var u=31&o,s=void 0;6==i?(s=e[r+1],r+=1):(s=e[r+1]<<8|e[r+2],r+=2);for(var a=this.outputPosition-s,c=0;c>>3==30||e<3&&t>>>4==14||e<2&&t>>>5==6)return this.outputBufferRemainder=this.outputBuffer.subarray(this.outputPosition-e,this.outputPosition),void(this.outputPosition-=e)}},e}();f.Decompressor=e}(LZUTF8||(LZUTF8={})),function(s){var e,t,a,c;e=s.Encoding||(s.Encoding={}),t=e.Base64||(e.Base64={}),a=new Uint8Array([65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,48,49,50,51,52,53,54,55,56,57,43,47]),c=new Uint8Array([255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,62,255,255,255,63,52,53,54,55,56,57,58,59,60,61,255,255,255,0,255,255,255,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,255,255,255,255,255,255,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,255,255,255,255]),t.encode=function(e){return e&&0!=e.length?s.runningInNodeJS()?s.BufferTools.uint8ArrayToBuffer(e).toString("base64"):t.encodeWithJS(e):""},t.decode=function(e){return e?s.runningInNodeJS()?s.BufferTools.bufferToUint8Array(new Buffer(e,"base64")):t.decodeWithJS(e):new Uint8Array(0)},t.encodeWithJS=function(e,t){if(void 0===t&&(t=!0),!e||0==e.length)return"";for(var r,n=a,o=new s.StringBuilder,i=0,u=e.length;i>>18&63]),o.appendCharCode(n[r>>>12&63]),o.appendCharCode(n[r>>>6&63]),o.appendCharCode(n[63&r]),r=0):i===u-2?(r=e[i]<<16|e[i+1]<<8,o.appendCharCode(n[r>>>18&63]),o.appendCharCode(n[r>>>12&63]),o.appendCharCode(n[r>>>6&63]),t&&o.appendCharCode(61)):i===u-1&&(r=e[i]<<16,o.appendCharCode(n[r>>>18&63]),o.appendCharCode(n[r>>>12&63]),t&&(o.appendCharCode(61),o.appendCharCode(61)));return o.getOutputString()},t.decodeWithJS=function(e,t){if(!e||0==e.length)return new Uint8Array(0);var r=e.length%4;if(1===r)throw new Error("Invalid Base64 string: length % 4 == 1");2===r?e+="==":3===r&&(e+="="),t||(t=new Uint8Array(e.length));for(var n=0,o=e.length,i=0;i>>16&255,t[n++]=u>>>8&255,t[n++]=255&u}return 61==e.charCodeAt(o-1)&&n--,61==e.charCodeAt(o-2)&&n--,t.subarray(0,n)}}(LZUTF8||(LZUTF8={})),function(s){var e,t;e=s.Encoding||(s.Encoding={}),(t=e.BinaryString||(e.BinaryString={})).encode=function(e){if(null==e)throw new TypeError("BinaryString.encode: undefined or null input received");if(0===e.length)return"";for(var t=e.length,r=new s.StringBuilder,n=0,o=1,i=0;i>>o),n=u&(1<>>15-i,r[n++]=t>>>8,r[n++]=255&t,o=s&(1<<15-i)-1),15==i?i=0:i+=1)}return r.subarray(0,n)}}(LZUTF8||(LZUTF8={})),function(e){var t,r;t=e.Encoding||(e.Encoding={}),(r=t.CodePoint||(t.CodePoint={})).encodeFromString=function(e,t){var r=e.charCodeAt(t);if(r<55296||56319>>10),56320+(e-65536&1023));throw new Error("getStringFromUnicodeCodePoint: A code point of "+e+" cannot be encoded in UTF-16")}}(LZUTF8||(LZUTF8={})),function(e){var t,r,n;t=e.Encoding||(e.Encoding={}),r=t.DecimalString||(t.DecimalString={}),n=["000","001","002","003","004","005","006","007","008","009","010","011","012","013","014","015","016","017","018","019","020","021","022","023","024","025","026","027","028","029","030","031","032","033","034","035","036","037","038","039","040","041","042","043","044","045","046","047","048","049","050","051","052","053","054","055","056","057","058","059","060","061","062","063","064","065","066","067","068","069","070","071","072","073","074","075","076","077","078","079","080","081","082","083","084","085","086","087","088","089","090","091","092","093","094","095","096","097","098","099","100","101","102","103","104","105","106","107","108","109","110","111","112","113","114","115","116","117","118","119","120","121","122","123","124","125","126","127","128","129","130","131","132","133","134","135","136","137","138","139","140","141","142","143","144","145","146","147","148","149","150","151","152","153","154","155","156","157","158","159","160","161","162","163","164","165","166","167","168","169","170","171","172","173","174","175","176","177","178","179","180","181","182","183","184","185","186","187","188","189","190","191","192","193","194","195","196","197","198","199","200","201","202","203","204","205","206","207","208","209","210","211","212","213","214","215","216","217","218","219","220","221","222","223","224","225","226","227","228","229","230","231","232","233","234","235","236","237","238","239","240","241","242","243","244","245","246","247","248","249","250","251","252","253","254","255"],r.encode=function(e){for(var t=[],r=0;r>>6,t[r++]=128|63&o;else if(o<=65535)t[r++]=224|o>>>12,t[r++]=128|o>>>6&63,t[r++]=128|63&o;else{if(!(o<=1114111))throw new Error("Invalid UTF-16 string: Encountered a character unsupported by UTF-8/16 (RFC 3629)");t[r++]=240|o>>>18,t[r++]=128|o>>>12&63,t[r++]=128|o>>>6&63,t[r++]=128|63&o,n++}}return t.subarray(0,r)},t.decodeWithJS=function(e,t,r){if(void 0===t&&(t=0),!e||0==e.length)return"";void 0===r&&(r=e.length);for(var n,o,i=new a.StringBuilder,u=t,s=r;u>>7==0)n=o,u+=1;else if(o>>>5==6){if(r<=u+1)throw new Error("Invalid UTF-8 stream: Truncated codepoint sequence encountered at position "+u);n=(31&o)<<6|63&e[u+1],u+=2}else if(o>>>4==14){if(r<=u+2)throw new Error("Invalid UTF-8 stream: Truncated codepoint sequence encountered at position "+u);n=(15&o)<<12|(63&e[u+1])<<6|63&e[u+2],u+=3}else{if(o>>>3!=30)throw new Error("Invalid UTF-8 stream: An invalid lead byte value encountered at position "+u);if(r<=u+3)throw new Error("Invalid UTF-8 stream: Truncated codepoint sequence encountered at position "+u);n=(7&o)<<18|(63&e[u+1])<<12|(63&e[u+2])<<6|63&e[u+3],u+=4}i.appendCodePoint(n)}return i.getOutputString()},t.createNativeTextEncoderAndDecoderIfAvailable=function(){return!!r||"function"==typeof TextEncoder&&(r=new TextEncoder("utf-8"),n=new TextDecoder("utf-8"),!0)}}(LZUTF8||(LZUTF8={})),function(o){o.compress=function(e,t){if(void 0===t&&(t={}),null==e)throw new TypeError("compress: undefined or null input received");var r=o.CompressionCommon.detectCompressionSourceEncoding(e);t=o.ObjectTools.override({inputEncoding:r,outputEncoding:"ByteArray"},t);var n=(new o.Compressor).compressBlock(e);return o.CompressionCommon.encodeCompressedBytes(n,t.outputEncoding)},o.decompress=function(e,t){if(void 0===t&&(t={}),null==e)throw new TypeError("decompress: undefined or null input received");t=o.ObjectTools.override({inputEncoding:"ByteArray",outputEncoding:"String"},t);var r=o.CompressionCommon.decodeCompressedBytes(e,t.inputEncoding),n=(new o.Decompressor).decompressBlock(r);return o.CompressionCommon.encodeDecompressedBytes(n,t.outputEncoding)},o.compressAsync=function(e,t,r){var n;null==r&&(r=function(){});try{n=o.CompressionCommon.detectCompressionSourceEncoding(e)}catch(e){return void r(void 0,e)}t=o.ObjectTools.override({inputEncoding:n,outputEncoding:"ByteArray",useWebWorker:!0,blockSize:65536},t),o.enqueueImmediate(function(){t.useWebWorker&&o.WebWorker.createGlobalWorkerIfNeeded()?o.WebWorker.compressAsync(e,t,r):o.AsyncCompressor.compressAsync(e,t,r)})},o.decompressAsync=function(e,t,r){if(null==r&&(r=function(){}),null!=e){t=o.ObjectTools.override({inputEncoding:"ByteArray",outputEncoding:"String",useWebWorker:!0,blockSize:65536},t);var n=o.BufferTools.convertToUint8ArrayIfNeeded(e);o.EventLoop.enqueueImmediate(function(){t.useWebWorker&&o.WebWorker.createGlobalWorkerIfNeeded()?o.WebWorker.decompressAsync(n,t,r):o.AsyncDecompressor.decompressAsync(e,t,r)})}else r(void 0,new TypeError("decompressAsync: undefined or null input received"))},o.createCompressionStream=function(){return o.AsyncCompressor.createCompressionStream()},o.createDecompressionStream=function(){return o.AsyncDecompressor.createDecompressionStream()},o.encodeUTF8=function(e){return o.Encoding.UTF8.encode(e)},o.decodeUTF8=function(e){return o.Encoding.UTF8.decode(e)},o.encodeBase64=function(e){return o.Encoding.Base64.encode(e)},o.decodeBase64=function(e){return o.Encoding.Base64.decode(e)},o.encodeBinaryString=function(e){return o.Encoding.BinaryString.encode(e)},o.decodeBinaryString=function(e){return o.Encoding.BinaryString.decode(e)},o.encodeStorageBinaryString=function(e){return o.Encoding.StorageBinaryString.encode(e)},o.decodeStorageBinaryString=function(e){return o.Encoding.StorageBinaryString.decode(e)}}(LZUTF8||(LZUTF8={})); -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "__MSG_name__", 3 | "version": "2.0.6", 4 | "manifest_version": 2, 5 | "description": "__MSG_description__", 6 | "author": "__MSG_author__", 7 | "default_locale": "en", 8 | 9 | "permissions": 10 | [ 11 | "tabs", 12 | "bookmarks", 13 | "unlimitedStorage", 14 | "storage", 15 | "", 16 | "contextMenus" 17 | ], 18 | 19 | "icons": 20 | { 21 | "128": "icons/icon-128.png", 22 | "48": "icons/icon-48.png", 23 | "32": "icons/icon-32.png", 24 | "16": "icons/icon-16.png" 25 | }, 26 | "browser_action": 27 | { 28 | "default_icon": "icons/icon-32.png" 29 | }, 30 | "web_accessible_resources": [ "*" ], 31 | 32 | "background": 33 | { 34 | "scripts": [ "js/lib/lzutf8.min.js","js/background.js" ], 35 | "persistent": false 36 | }, 37 | 38 | "commands": 39 | { 40 | "set-aside": 41 | { 42 | "description": "__MSG_setAside__", 43 | "suggested_key": 44 | { 45 | "default": "Shift+Alt+Left", 46 | "mac": "MacCtrl+T" 47 | } 48 | }, 49 | "toggle-pane": 50 | { 51 | "description": "__MSG_togglePaneContext__", 52 | "suggested_key": 53 | { 54 | "default": "Alt+P", 55 | "mac": "Command+Shift+P" 56 | } 57 | } 58 | }, 59 | "browser_specific_settings": 60 | { 61 | "gecko": 62 | { 63 | "id": "tabsaside@xfox111.net", 64 | "strict_min_version": "58.0" 65 | } 66 | } 67 | } 68 | --------------------------------------------------------------------------------