1
0
mirror of https://github.com/XFox111/TabsAsideExtension.git synced 2026-07-02 19:52:47 +03:00

Compare commits

...

7 Commits

Author SHA1 Message Date
xfox111 2065ee4637 fix: ",\t" adds to tab's title and URL on each cloud synchronization 2025-11-14 04:48:54 +03:00
xfox111 b51dd6083f chore(deps): WXT 0.20.0 bump + lockfile regen (#199)
* chore(deps): wxt 0.20.0 bump #134

* chore: 3.2.1 manifest bump
2025-11-14 02:16:57 +03:00
xfox111 3cd3c4453d feat: Minor 3.2.0 (#197)
* chore(loc): zh_CN translation improvements (#153)

* chore(deps): Bump typescript-eslint from 8.43.0 to 8.45.0 (#166)

Bumps [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) from 8.43.0 to 8.45.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.45.0/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: typescript-eslint
  dependency-version: 8.45.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): Bump @wxt-dev/analytics from 0.4.1 to 0.5.1 (#164)

Bumps [@wxt-dev/analytics](https://github.com/wxt-dev/wxt/tree/HEAD/packages/analytics) from 0.4.1 to 0.5.1.
- [Release notes](https://github.com/wxt-dev/wxt/releases)
- [Changelog](https://github.com/wxt-dev/wxt/blob/main/packages/analytics/CHANGELOG.md)
- [Commits](https://github.com/wxt-dev/wxt/commits/v0.5.1/packages/analytics)

---
updated-dependencies:
- dependency-name: "@wxt-dev/analytics"
  dependency-version: 0.5.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): Bump eslint from 9.35.0 to 9.36.0 (#163)

Bumps [eslint](https://github.com/eslint/eslint) from 9.35.0 to 9.36.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v9.35.0...v9.36.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-version: 9.36.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): Bump globals from 16.3.0 to 16.4.0 (#158)

Bumps [globals](https://github.com/sindresorhus/globals) from 16.3.0 to 16.4.0.
- [Release notes](https://github.com/sindresorhus/globals/releases)
- [Commits](https://github.com/sindresorhus/globals/compare/v16.3.0...v16.4.0)

---
updated-dependencies:
- dependency-name: globals
  dependency-version: 16.4.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): Bump @eslint/css from 0.11.0 to 0.11.1 (#157)

Bumps [@eslint/css](https://github.com/eslint/css) from 0.11.0 to 0.11.1.
- [Release notes](https://github.com/eslint/css/releases)
- [Changelog](https://github.com/eslint/css/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/css/compare/css-v0.11.0...css-v0.11.1)

---
updated-dependencies:
- dependency-name: "@eslint/css"
  dependency-version: 0.11.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): Bump @fluentui/react-icons from 2.0.309 to 2.0.311 (#156)

Bumps [@fluentui/react-icons](https://github.com/microsoft/fluentui-system-icons) from 2.0.309 to 2.0.311.
- [Changelog](https://github.com/microsoft/fluentui-system-icons/blob/main/fluentui-android-system-icons-release.yml)
- [Commits](https://github.com/microsoft/fluentui-system-icons/commits)

---
updated-dependencies:
- dependency-name: "@fluentui/react-icons"
  dependency-version: 2.0.311
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): Bump typescript from 5.9.2 to 5.9.3 (#165)

Bumps [typescript](https://github.com/microsoft/TypeScript) from 5.9.2 to 5.9.3.
- [Release notes](https://github.com/microsoft/TypeScript/releases)
- [Changelog](https://github.com/microsoft/TypeScript/blob/main/azure-pipelines.release-publish.yml)
- [Commits](https://github.com/microsoft/TypeScript/compare/v5.9.2...v5.9.3)

---
updated-dependencies:
- dependency-name: typescript
  dependency-version: 5.9.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): Bump vite from 7.1.5 to 7.1.7 (#161)

Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 7.1.5 to 7.1.7.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v7.1.7/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 7.1.7
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): Bump @eslint/js from 9.35.0 to 9.36.0 (#160)

Bumps [@eslint/js](https://github.com/eslint/eslint/tree/HEAD/packages/js) from 9.35.0 to 9.36.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/commits/v9.36.0/packages/js)

---
updated-dependencies:
- dependency-name: "@eslint/js"
  dependency-version: 9.36.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): Bump @stylistic/eslint-plugin from 5.3.1 to 5.4.0 (#159)

Bumps [@stylistic/eslint-plugin](https://github.com/eslint-stylistic/eslint-stylistic/tree/HEAD/packages/eslint-plugin) from 5.3.1 to 5.4.0.
- [Release notes](https://github.com/eslint-stylistic/eslint-stylistic/releases)
- [Changelog](https://github.com/eslint-stylistic/eslint-stylistic/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint-stylistic/eslint-stylistic/commits/v5.4.0/packages/eslint-plugin)

---
updated-dependencies:
- dependency-name: "@stylistic/eslint-plugin"
  dependency-version: 5.4.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): Bump react-dom and @types/react-dom (#169)

Bumps [react-dom](https://github.com/facebook/react/tree/HEAD/packages/react-dom) and [@types/react-dom](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react-dom). These dependencies needed to be updated together.

Updates `react-dom` from 18.3.1 to 19.2.0
- [Release notes](https://github.com/facebook/react/releases)
- [Changelog](https://github.com/facebook/react/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/react/commits/v19.2.0/packages/react-dom)

Updates `@types/react-dom` from 18.3.7 to 19.2.0
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react-dom)

---
updated-dependencies:
- dependency-name: react-dom
  dependency-version: 19.2.0
  dependency-type: direct:production
  update-type: version-update:semver-major
- dependency-name: "@types/react-dom"
  dependency-version: 19.2.0
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump @fluentui/react-components from 9.70.0 to 9.72.0 (#171)

Bumps [@fluentui/react-components](https://github.com/microsoft/fluentui) from 9.70.0 to 9.72.0.
- [Release notes](https://github.com/microsoft/fluentui/releases)
- [Changelog](https://github.com/microsoft/fluentui/blob/master/azure-pipelines.release.yml)
- [Commits](https://github.com/microsoft/fluentui/compare/@fluentui/react-components_v9.70.0...@fluentui/react-components_v9.72.0)

---
updated-dependencies:
- dependency-name: "@fluentui/react-components"
  dependency-version: 9.72.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump eslint from 9.36.0 to 9.37.0 (#170)

Bumps [eslint](https://github.com/eslint/eslint) from 9.36.0 to 9.37.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/compare/v9.36.0...v9.37.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-version: 9.37.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Revert "chore(deps): Bump @wxt-dev/analytics from 0.4.1 to 0.5.1 (#164)"

This reverts commit 88178035cb.

* chore: update return type for a component

* Bump react and @types/react (#167)

Bumps [react](https://github.com/facebook/react/tree/HEAD/packages/react) and [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react). These dependencies needed to be updated together.

Updates `react` from 18.3.1 to 19.2.0
- [Release notes](https://github.com/facebook/react/releases)
- [Changelog](https://github.com/facebook/react/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/react/commits/v19.2.0/packages/react)

Updates `@types/react` from 18.3.24 to 19.2.0
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react)

---
updated-dependencies:
- dependency-name: react
  dependency-version: 19.2.0
  dependency-type: direct:production
  update-type: version-update:semver-major
- dependency-name: "@types/react"
  dependency-version: 19.2.0
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore: 3.1.1 version update

* fix(dev): regenerate lockfile

* fix: remove ts-expect-error

* fix: tabs closing before saved #178

* fix: cloud conflict error appears each time when saving a collection after removing all saved ones #180

* feat: option to disable partial save notifications #181

* fix: cloud collection storage retreival fails #180

* chore: 3.2.0 manifest version

* Bump github/codeql-action from 3 to 4 (#183)

Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3 to 4.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): Bump the deps group with 11 updates (#195)

Bumps the deps group with 11 updates:

| Package | From | To |
| --- | --- | --- |
| [@fluentui/react-components](https://github.com/microsoft/fluentui) | `9.72.0` | `9.72.6` |
| [@fluentui/react-icons](https://github.com/microsoft/fluentui-system-icons) | `2.0.311` | `2.0.313` |
| [@wxt-dev/analytics](https://github.com/wxt-dev/wxt/tree/HEAD/packages/analytics) | `0.4.1` | `0.5.1` |
| [@eslint/css](https://github.com/eslint/css) | `0.11.1` | `0.14.1` |
| [@eslint/js](https://github.com/eslint/eslint/tree/HEAD/packages/js) | `9.37.0` | `9.39.1` |
| [@eslint/json](https://github.com/eslint/json) | `0.13.2` | `0.14.0` |
| [@stylistic/eslint-plugin](https://github.com/eslint-stylistic/eslint-stylistic/tree/HEAD/packages/eslint-plugin) | `5.4.0` | `5.5.0` |
| [eslint](https://github.com/eslint/eslint) | `9.37.0` | `9.39.1` |
| [globals](https://github.com/sindresorhus/globals) | `16.4.0` | `16.5.0` |
| [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.45.0` | `8.46.4` |
| [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) | `7.1.9` | `7.2.2` |


Updates `@fluentui/react-components` from 9.72.0 to 9.72.6
- [Release notes](https://github.com/microsoft/fluentui/releases)
- [Changelog](https://github.com/microsoft/fluentui/blob/master/azure-pipelines.release.yml)
- [Commits](https://github.com/microsoft/fluentui/compare/@fluentui/react-components_v9.72.0...@fluentui/react-components_v9.72.6)

Updates `@fluentui/react-icons` from 2.0.311 to 2.0.313
- [Changelog](https://github.com/microsoft/fluentui-system-icons/blob/main/fluentui-android-system-icons-release.yml)
- [Commits](https://github.com/microsoft/fluentui-system-icons/commits)

Updates `@wxt-dev/analytics` from 0.4.1 to 0.5.1
- [Release notes](https://github.com/wxt-dev/wxt/releases)
- [Changelog](https://github.com/wxt-dev/wxt/blob/main/packages/analytics/CHANGELOG.md)
- [Commits](https://github.com/wxt-dev/wxt/commits/v0.5.1/packages/analytics)

Updates `@eslint/css` from 0.11.1 to 0.14.1
- [Release notes](https://github.com/eslint/css/releases)
- [Changelog](https://github.com/eslint/css/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/css/compare/css-v0.11.1...css-v0.14.1)

Updates `@eslint/js` from 9.37.0 to 9.39.1
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/commits/v9.39.1/packages/js)

Updates `@eslint/json` from 0.13.2 to 0.14.0
- [Release notes](https://github.com/eslint/json/releases)
- [Changelog](https://github.com/eslint/json/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/json/compare/json-v0.13.2...json-v0.14.0)

Updates `@stylistic/eslint-plugin` from 5.4.0 to 5.5.0
- [Release notes](https://github.com/eslint-stylistic/eslint-stylistic/releases)
- [Changelog](https://github.com/eslint-stylistic/eslint-stylistic/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint-stylistic/eslint-stylistic/commits/v5.5.0/packages/eslint-plugin)

Updates `eslint` from 9.37.0 to 9.39.1
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/compare/v9.37.0...v9.39.1)

Updates `globals` from 16.4.0 to 16.5.0
- [Release notes](https://github.com/sindresorhus/globals/releases)
- [Commits](https://github.com/sindresorhus/globals/compare/v16.4.0...v16.5.0)

Updates `typescript-eslint` from 8.45.0 to 8.46.4
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.46.4/packages/typescript-eslint)

Updates `vite` from 7.1.9 to 7.2.2
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v7.2.2/packages/vite)

---
updated-dependencies:
- dependency-name: "@fluentui/react-components"
  dependency-version: 9.72.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: deps
- dependency-name: "@fluentui/react-icons"
  dependency-version: 2.0.313
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: deps
- dependency-name: "@wxt-dev/analytics"
  dependency-version: 0.5.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: deps
- dependency-name: "@eslint/css"
  dependency-version: 0.14.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: deps
- dependency-name: "@eslint/js"
  dependency-version: 9.39.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: deps
- dependency-name: "@eslint/json"
  dependency-version: 0.14.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: deps
- dependency-name: "@stylistic/eslint-plugin"
  dependency-version: 5.5.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: deps
- dependency-name: eslint
  dependency-version: 9.39.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: deps
- dependency-name: globals
  dependency-version: 16.5.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: deps
- dependency-name: typescript-eslint
  dependency-version: 8.46.4
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: deps
- dependency-name: vite
  dependency-version: 7.2.2
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: deps
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): Bump the react group with 2 updates (#196)

Bumps the react group with 2 updates: [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react) and [@types/react-dom](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react-dom).


Updates `@types/react` from 19.2.0 to 19.2.2
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react)

Updates `@types/react-dom` from 19.2.0 to 19.2.2
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react-dom)

---
updated-dependencies:
- dependency-name: "@types/react"
  dependency-version: 19.2.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: react
- dependency-name: "@types/react-dom"
  dependency-version: 19.2.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: react
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Dustin Jiang <dustinjiang@outlook.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-11 16:05:50 +03:00
xfox111 816c8bf28c chore: update dependabot config to combine npm bumps 2025-11-11 15:37:55 +03:00
xfox111 c27c9f7b33 chore: Patch 3.1.1 (#172)
* chore(loc): zh_CN translation improvements (#153)

* chore(deps): Bump typescript-eslint from 8.43.0 to 8.45.0 (#166)

Bumps [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) from 8.43.0 to 8.45.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.45.0/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: typescript-eslint
  dependency-version: 8.45.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): Bump @wxt-dev/analytics from 0.4.1 to 0.5.1 (#164)

Bumps [@wxt-dev/analytics](https://github.com/wxt-dev/wxt/tree/HEAD/packages/analytics) from 0.4.1 to 0.5.1.
- [Release notes](https://github.com/wxt-dev/wxt/releases)
- [Changelog](https://github.com/wxt-dev/wxt/blob/main/packages/analytics/CHANGELOG.md)
- [Commits](https://github.com/wxt-dev/wxt/commits/v0.5.1/packages/analytics)

---
updated-dependencies:
- dependency-name: "@wxt-dev/analytics"
  dependency-version: 0.5.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): Bump eslint from 9.35.0 to 9.36.0 (#163)

Bumps [eslint](https://github.com/eslint/eslint) from 9.35.0 to 9.36.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v9.35.0...v9.36.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-version: 9.36.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): Bump globals from 16.3.0 to 16.4.0 (#158)

Bumps [globals](https://github.com/sindresorhus/globals) from 16.3.0 to 16.4.0.
- [Release notes](https://github.com/sindresorhus/globals/releases)
- [Commits](https://github.com/sindresorhus/globals/compare/v16.3.0...v16.4.0)

---
updated-dependencies:
- dependency-name: globals
  dependency-version: 16.4.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): Bump @eslint/css from 0.11.0 to 0.11.1 (#157)

Bumps [@eslint/css](https://github.com/eslint/css) from 0.11.0 to 0.11.1.
- [Release notes](https://github.com/eslint/css/releases)
- [Changelog](https://github.com/eslint/css/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/css/compare/css-v0.11.0...css-v0.11.1)

---
updated-dependencies:
- dependency-name: "@eslint/css"
  dependency-version: 0.11.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): Bump @fluentui/react-icons from 2.0.309 to 2.0.311 (#156)

Bumps [@fluentui/react-icons](https://github.com/microsoft/fluentui-system-icons) from 2.0.309 to 2.0.311.
- [Changelog](https://github.com/microsoft/fluentui-system-icons/blob/main/fluentui-android-system-icons-release.yml)
- [Commits](https://github.com/microsoft/fluentui-system-icons/commits)

---
updated-dependencies:
- dependency-name: "@fluentui/react-icons"
  dependency-version: 2.0.311
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): Bump typescript from 5.9.2 to 5.9.3 (#165)

Bumps [typescript](https://github.com/microsoft/TypeScript) from 5.9.2 to 5.9.3.
- [Release notes](https://github.com/microsoft/TypeScript/releases)
- [Changelog](https://github.com/microsoft/TypeScript/blob/main/azure-pipelines.release-publish.yml)
- [Commits](https://github.com/microsoft/TypeScript/compare/v5.9.2...v5.9.3)

---
updated-dependencies:
- dependency-name: typescript
  dependency-version: 5.9.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): Bump vite from 7.1.5 to 7.1.7 (#161)

Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 7.1.5 to 7.1.7.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v7.1.7/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 7.1.7
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): Bump @eslint/js from 9.35.0 to 9.36.0 (#160)

Bumps [@eslint/js](https://github.com/eslint/eslint/tree/HEAD/packages/js) from 9.35.0 to 9.36.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/commits/v9.36.0/packages/js)

---
updated-dependencies:
- dependency-name: "@eslint/js"
  dependency-version: 9.36.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): Bump @stylistic/eslint-plugin from 5.3.1 to 5.4.0 (#159)

Bumps [@stylistic/eslint-plugin](https://github.com/eslint-stylistic/eslint-stylistic/tree/HEAD/packages/eslint-plugin) from 5.3.1 to 5.4.0.
- [Release notes](https://github.com/eslint-stylistic/eslint-stylistic/releases)
- [Changelog](https://github.com/eslint-stylistic/eslint-stylistic/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint-stylistic/eslint-stylistic/commits/v5.4.0/packages/eslint-plugin)

---
updated-dependencies:
- dependency-name: "@stylistic/eslint-plugin"
  dependency-version: 5.4.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): Bump react-dom and @types/react-dom (#169)

Bumps [react-dom](https://github.com/facebook/react/tree/HEAD/packages/react-dom) and [@types/react-dom](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react-dom). These dependencies needed to be updated together.

Updates `react-dom` from 18.3.1 to 19.2.0
- [Release notes](https://github.com/facebook/react/releases)
- [Changelog](https://github.com/facebook/react/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/react/commits/v19.2.0/packages/react-dom)

Updates `@types/react-dom` from 18.3.7 to 19.2.0
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react-dom)

---
updated-dependencies:
- dependency-name: react-dom
  dependency-version: 19.2.0
  dependency-type: direct:production
  update-type: version-update:semver-major
- dependency-name: "@types/react-dom"
  dependency-version: 19.2.0
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump @fluentui/react-components from 9.70.0 to 9.72.0 (#171)

Bumps [@fluentui/react-components](https://github.com/microsoft/fluentui) from 9.70.0 to 9.72.0.
- [Release notes](https://github.com/microsoft/fluentui/releases)
- [Changelog](https://github.com/microsoft/fluentui/blob/master/azure-pipelines.release.yml)
- [Commits](https://github.com/microsoft/fluentui/compare/@fluentui/react-components_v9.70.0...@fluentui/react-components_v9.72.0)

---
updated-dependencies:
- dependency-name: "@fluentui/react-components"
  dependency-version: 9.72.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump eslint from 9.36.0 to 9.37.0 (#170)

Bumps [eslint](https://github.com/eslint/eslint) from 9.36.0 to 9.37.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/compare/v9.36.0...v9.37.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-version: 9.37.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Revert "chore(deps): Bump @wxt-dev/analytics from 0.4.1 to 0.5.1 (#164)"

This reverts commit 88178035cb.

* chore: update return type for a component

* Bump react and @types/react (#167)

Bumps [react](https://github.com/facebook/react/tree/HEAD/packages/react) and [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react). These dependencies needed to be updated together.

Updates `react` from 18.3.1 to 19.2.0
- [Release notes](https://github.com/facebook/react/releases)
- [Changelog](https://github.com/facebook/react/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/react/commits/v19.2.0/packages/react)

Updates `@types/react` from 18.3.24 to 19.2.0
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react)

---
updated-dependencies:
- dependency-name: react
  dependency-version: 19.2.0
  dependency-type: direct:production
  update-type: version-update:semver-major
- dependency-name: "@types/react"
  dependency-version: 19.2.0
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore: 3.1.1 version update

* fix(dev): regenerate lockfile

* fix: remove ts-expect-error

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Dustin Jiang <dustinjiang@outlook.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-05 18:47:38 +05:00
Gitoffthelawn def52278ff docs: i18n for AMO link (#152) 2025-09-09 17:07:23 +03:00
xfox111 101a72e6e3 fix(ci): switch web-ext linting action 2025-09-09 12:57:37 +03:00
49 changed files with 2151 additions and 2685 deletions
+27 -1
View File
@@ -16,7 +16,33 @@ updates:
schedule:
interval: monthly
rebase-strategy: disabled
open-pull-requests-limit: 20
groups:
deps:
patterns:
- "*"
exclude-patterns:
- "react"
- "react-dom"
- "@types/react"
- "@types/react-dom"
- "scheduler"
react:
patterns:
- "react"
- "react-dom"
- "@types/react"
- "@types/react-dom"
update-types:
- minor
- patch
react-next:
patterns:
- "react"
- "react-dom"
- "@types/react"
- "@types/react-dom"
update-types:
- major
- package-ecosystem: "github-actions"
directory: "/"
+4 -3
View File
@@ -75,10 +75,11 @@ jobs:
- name: web-ext lint
if: ${{ matrix.target == 'firefox' }}
uses: freaktechnik/web-ext-lint@main
uses: kewisch/action-web-ext@main
with:
extension-root: ./.output/firefox-mv3
self-hosted: false
cmd: lint
source: ./.output/firefox-mv3
channel: listed
- run: yarn npm audit
continue-on-error: ${{ github.event_name != 'release' && github.event.inputs.bypass_audit == 'true' }}
+3 -3
View File
@@ -56,7 +56,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
uses: github/codeql-action/init@v4
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -70,7 +70,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v3
uses: github/codeql-action/autobuild@v4
# ️ Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
@@ -83,4 +83,4 @@ jobs:
# ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
uses: github/codeql-action/analyze@v4
+1 -1
View File
@@ -37,7 +37,7 @@ Check out our [latest blog post](https://at.xfox111.net/tabs-aside-3-0) regardin
- [Google Chrome Webstore](https://chrome.google.com/webstore/detail/mgmjbodjgijnebfgohlnjkegdpbdjgin)
- [Microsoft Edge Add-ons Webstore](https://microsoftedge.microsoft.com/addons/detail/kmnblllmalkiapkfknnlpobmjjdnlhnd)
- [Firefox Add-ons](https://addons.mozilla.org/en-US/firefox/addon/ms-edge-tabs-aside/)
- [Firefox Add-ons](https://addons.mozilla.org/firefox/addon/ms-edge-tabs-aside/)
- [GitHub Releases](https://github.com/xfox111/TabsAsideExtension/releases/latest)
### Sideloading (for testing purposes only)
+1 -1
View File
@@ -20,6 +20,6 @@ export const githubLinks =
export const storeLink: string =
import.meta.env.FIREFOX
? "https://addons.mozilla.org/en-US/firefox/addon/ms-edge-tabs-aside/" :
chrome.runtime.getManifest().update_url?.startsWith("https://edge.microsoft.com/") ?
browser.runtime.getManifest().update_url?.startsWith("https://edge.microsoft.com/") ?
"https://microsoftedge.microsoft.com/addons/detail/tabs-aside/kmnblllmalkiapkfknnlpobmjjdnlhnd" :
"https://chromewebstore.google.com/detail/tabs-aside/mgmjbodjgijnebfgohlnjkegdpbdjgin";
+74 -26
View File
@@ -1,20 +1,25 @@
import { track, trackError } from "@/features/analytics";
import { collectionCount, getCollections, thumbnailCaptureEnabled, saveCollections } from "@/features/collectionStorage";
import { collectionCount, getCollections, saveCollections, thumbnailCaptureEnabled } from "@/features/collectionStorage";
import { collectionStorage } from "@/features/collectionStorage/utils/collectionStorage";
import getCollectionsFromCloud from "@/features/collectionStorage/utils/getCollectionsFromCloud";
import getCollectionsFromLocal from "@/features/collectionStorage/utils/getCollectionsFromLocal";
import { migrateStorage } from "@/features/migration";
import { setSettingsReviewNeeded } from "@/features/settingsReview/utils";
import { showWelcomeDialog } from "@/features/v3welcome/utils/showWelcomeDialog";
import { SettingsValue } from "@/hooks/useSettings";
import { CollectionItem, GraphicsStorage } from "@/models/CollectionModels";
import { closeTabsAsync } from "@/utils/closeTabsAsync";
import { createCollectionFromTabs } from "@/utils/createCollectionFromTabs";
import getLogger from "@/utils/getLogger";
import { getTabsToSaveAsync } from "@/utils/getTabsToSaveAsync";
import { onMessage, sendMessage } from "@/utils/messaging";
import saveTabsToCollection from "@/utils/saveTabsToCollection";
import sendNotification from "@/utils/sendNotification";
import sendPartialSaveNotification from "@/utils/sendPartialSaveNotification";
import { settings } from "@/utils/settings";
import watchTabSelection from "@/utils/watchTabSelection";
import { Tabs, Windows } from "wxt/browser";
import { Unwatch } from "wxt/storage";
import { openCollection, openGroup } from "./sidepanel/utils/opener";
import { setSettingsReviewNeeded } from "@/features/settingsReview/utils";
import { RemoveListenerCallback } from "@webext-core/messaging";
import { Unwatch } from "wxt/utils/storage";
import { openCollection, openGroup } from "./sidepanel/utils/opener";
export default defineBackground(() =>
{
@@ -36,16 +41,42 @@ export default defineBackground(() =>
logger("onInstalled", reason, previousVersion);
track("extension_installed", { reason, previousVersion: previousVersion ?? "none" });
const previousMajor: number = previousVersion ? parseInt(previousVersion.split(".")[0]) : 0;
const [major, minor, patch] = (previousVersion ?? "0.0.0").split(".").map(parseInt);
const cumulative: number = major * 10000 + minor * 100 + patch;
await setSettingsReviewNeeded(reason, previousVersion);
if (reason === "update" && previousMajor < 3)
if (reason === "update" && cumulative < 30000) // < 3.0.0
{
await migrateStorage();
await showWelcomeDialog.setValue(true);
browser.runtime.reload();
}
if (reason === "update" && cumulative >= 30000 && cumulative < 30200) // >= 3.0.0 && < 3.2.0
{
// Merge cloud and local storage if they are out of sync
const localTimestamp: number = await collectionStorage.localLastUpdated.getValue();
const syncTimestamp: number = await collectionStorage.syncLastUpdated.getValue();
if (localTimestamp === syncTimestamp)
return;
try
{
const localCollections: CollectionItem[] = await getCollectionsFromLocal();
const cloudCollections: CollectionItem[] = await getCollectionsFromCloud();
const mergedCollections: CollectionItem[] = [...cloudCollections, ...localCollections];
await saveCollections(mergedCollections, true, graphicsCache);
}
catch (ex)
{
logger("Failed to merge cloud and local storage during update");
trackError("cloud_sync_merge_error", ex as Error);
console.error(ex);
}
}
});
browser.commands.onCommand.addListener(
@@ -67,7 +98,7 @@ export default defineBackground(() =>
let unwatchAddThumbnail: RemoveListenerCallback | null = null;
let captureInterval: NodeJS.Timeout | null = null;
const captureFavicon = (_: any, __: any, tab: Tabs.Tab): void =>
const captureFavicon = (_: any, __: any, tab: Browser.tabs.Tab): void =>
{
if (!tab.url)
return;
@@ -79,7 +110,7 @@ export default defineBackground(() =>
};
};
const tryCaptureTab = async (tab: Tabs.Tab): Promise<void> =>
const tryCaptureTab = async (tab: Browser.tabs.Tab): Promise<void> =>
{
if (!tab.url || tab.status !== "complete" || !tab.active)
return;
@@ -91,7 +122,7 @@ export default defineBackground(() =>
{
// We use chrome here because polyfill throws uncatchable errors for some reason
// It's a compatible API anyway
const capture: string = await chrome.tabs.captureVisibleTab(tab.windowId!, { format: "jpeg", quality: 1 });
const capture: string = await browser.tabs.captureVisibleTab(tab.windowId!, { format: "jpeg", quality: 1 });
if (capture)
{
@@ -255,6 +286,7 @@ export default defineBackground(() =>
};
const toggleSidebarFirefox = async (): Promise<void> =>
// @ts-expect-error Firefox-only API
await browser.sidebarAction.toggle();
const updateButton = async (action: SettingsValue<"contextAction">): Promise<void> =>
@@ -271,7 +303,7 @@ export default defineBackground(() =>
unwatchActionTitle?.();
if (!import.meta.env.FIREFOX)
await chrome.sidePanel.setPanelBehavior({ openPanelOnActionClick: false });
await browser.sidePanel.setPanelBehavior({ openPanelOnActionClick: false });
// Setup new behavior
if (action === "action")
@@ -290,7 +322,7 @@ export default defineBackground(() =>
if (import.meta.env.FIREFOX)
browser.action.onClicked.addListener(toggleSidebarFirefox);
else
chrome.sidePanel.setPanelBehavior({ openPanelOnActionClick: true });
browser.sidePanel.setPanelBehavior({ openPanelOnActionClick: true });
}
else if (location !== "popup")
browser.action.onClicked.addListener(openCollectionsInTab);
@@ -309,17 +341,17 @@ export default defineBackground(() =>
{
logger("enforcePinnedTab");
const openWindows: Windows.Window[] = await browser.windows.getAll({ populate: true });
const openWindows: Browser.windows.Window[] = await browser.windows.getAll({ populate: true });
for (const openWindow of openWindows)
{
if (openWindow.incognito || openWindow.type !== "normal")
continue;
const activeTabs: Tabs.Tab[] = openWindow.tabs!.filter(tab =>
const activeTabs: Browser.tabs.Tab[] = openWindow.tabs!.filter(tab =>
tab.url === browser.runtime.getURL("/sidepanel.html"));
const targetTab: Tabs.Tab | undefined = activeTabs.find(tab => tab.pinned);
const targetTab: Browser.tabs.Tab | undefined = activeTabs.find(tab => tab.pinned);
if (!targetTab)
await browser.tabs.create({
@@ -329,7 +361,7 @@ export default defineBackground(() =>
pinned: true
});
const tabsToClose: Tabs.Tab[] = activeTabs.filter(tab => tab.id !== targetTab?.id);
const tabsToClose: Browser.tabs.Tab[] = activeTabs.filter(tab => tab.id !== targetTab?.id);
if (tabsToClose.length > 0)
await browser.tabs.remove(tabsToClose.map(tab => tab.id!));
@@ -341,7 +373,7 @@ export default defineBackground(() =>
logger("updateView", viewLocation);
browser.tabs.onHighlighted.removeListener(enforcePinnedTab);
const tabs: Tabs.Tab[] = await browser.tabs.query({
const tabs: Browser.tabs.Tab[] = await browser.tabs.query({
url: browser.runtime.getURL("/sidepanel.html")
});
await browser.tabs.remove(tabs.map(tab => tab.id!));
@@ -351,11 +383,12 @@ export default defineBackground(() =>
});
if (import.meta.env.FIREFOX)
// @ts-expect-error Firefox-only API
await browser.sidebarAction.setPanel({
panel: viewLocation === "sidebar" ? browser.runtime.getURL("/sidepanel.html") : ""
});
else
await chrome.sidePanel.setOptions({ enabled: viewLocation === "sidebar" });
await browser.sidePanel.setOptions({ enabled: viewLocation === "sidebar" });
if (viewLocation === "pinned")
{
@@ -386,9 +419,10 @@ export default defineBackground(() =>
if (view === "sidebar")
{
if (import.meta.env.FIREFOX)
// @ts-expect-error Firefox-only API
browser.sidebarAction.open();
else
chrome.sidePanel.open({ windowId });
browser.sidePanel.open({ windowId });
}
else
browser.action.openPopup();
@@ -398,11 +432,11 @@ export default defineBackground(() =>
{
logger("openCollectionsInTab");
const currentWindow: Windows.Window = await browser.windows.getCurrent({ populate: true });
const currentWindow: Browser.windows.Window = await browser.windows.getCurrent({ populate: true });
if (currentWindow.incognito)
{
let availableWindows: Windows.Window[] = await browser.windows.getAll({ populate: true });
let availableWindows: Browser.windows.Window[] = await browser.windows.getAll({ populate: true });
availableWindows = availableWindows.filter(window =>
!window.incognito &&
@@ -411,7 +445,7 @@ export default defineBackground(() =>
if (availableWindows.length > 0)
{
const availableTab: Tabs.Tab = availableWindows[0].tabs!.find(
const availableTab: Browser.tabs.Tab = availableWindows[0].tabs!.find(
tab => tab.url === browser.runtime.getURL("/sidepanel.html")
)!;
@@ -428,7 +462,7 @@ export default defineBackground(() =>
}
else
{
const collectionTab: Tabs.Tab | undefined = currentWindow.tabs!.find(
const collectionTab: Browser.tabs.Tab | undefined = currentWindow.tabs!.find(
tab => tab.url === browser.runtime.getURL("/sidepanel.html")
);
@@ -447,14 +481,28 @@ export default defineBackground(() =>
{
logger("saveTabs", closeAfterSave);
const collection: CollectionItem = await saveTabsToCollection(closeAfterSave);
const [tabs, skipCount] = await getTabsToSaveAsync();
if (tabs.length < 1)
{
await sendPartialSaveNotification();
return;
}
const collection: CollectionItem = await createCollectionFromTabs(tabs);
const [savedCollections, cloudIssue] = await getCollections();
const newList = [collection, ...savedCollections];
await saveCollections(newList, cloudIssue === null, graphicsCache);
track(closeAfterSave ? "set_aside" : "save");
sendMessage("refreshCollections", undefined);
if (skipCount > 0)
await sendPartialSaveNotification();
if (closeAfterSave)
await closeTabsAsync(tabs);
if (await settings.notifyOnSave.getValue())
await sendNotification({
title: i18n.t("notifications.tabs_saved.title"),
@@ -1,8 +1,8 @@
import { analyticsPermission } from "@/features/analytics";
import useSettings, { SettingsValue } from "@/hooks/useSettings";
import { Button, Checkbox, Dropdown, Field, Option, OptionOnSelectData } from "@fluentui/react-components";
import { KeyCommand20Regular } from "@fluentui/react-icons";
import { useOptionsStyles } from "../hooks/useOptionsStyles";
import { analyticsPermission } from "@/features/analytics";
export default function GeneralSection(): React.ReactElement
{
@@ -14,6 +14,7 @@ export default function GeneralSection(): React.ReactElement
const [dismissOnLoad, setDismissOnLoad] = useSettings("dismissOnLoad");
const [listLocation, setListLocation] = useSettings("listLocation");
const [contextAction, setContextAction] = useSettings("contextAction");
const [showPartialSaveNotification, setShowPartialSaveNotification] = useSettings("showPartialSaveNotification");
const [allowAnalytics, setAllowAnalytics] = useState<boolean | null>(null);
@@ -44,6 +45,7 @@ export default function GeneralSection(): React.ReactElement
setContextAction("open");
if (import.meta.env.FIREFOX && e.optionValue !== "sidebar")
// @ts-expect-error Firefox-only API
browser.sidebarAction.close();
setListLocation(e.optionValue as ListLocationType);
@@ -72,6 +74,10 @@ export default function GeneralSection(): React.ReactElement
label={ i18n.t("options_page.general.options.show_notification") }
checked={ notifyOnSave ?? false }
onChange={ (_, e) => setNotifyOnSave(e.checked as boolean) } />
<Checkbox
label={ i18n.t("options_page.general.options.show_partial_save_notification") }
checked={ showPartialSaveNotification ?? false }
onChange={ (_, e) => setShowPartialSaveNotification(e.checked as boolean) } />
<Checkbox
label={ i18n.t("options_page.general.options.unload_tabs") }
checked={ dismissOnLoad ?? false }
@@ -4,10 +4,10 @@ import { useDangerStyles } from "@/hooks/useDangerStyles";
import useStorageInfo from "@/hooks/useStorageInfo";
import { Button, Field, InfoLabel, LabelProps, MessageBar, MessageBarBody, MessageBarTitle, ProgressBar, Switch } from "@fluentui/react-components";
import { ArrowDownload20Regular, ArrowUpload20Regular } from "@fluentui/react-icons";
import { Unwatch } from "wxt/utils/storage";
import { useOptionsStyles } from "../hooks/useOptionsStyles";
import exportData from "../utils/exportData";
import importData from "../utils/importData";
import { Unwatch } from "wxt/storage";
export default function StorageSection(): React.ReactElement
{
@@ -16,7 +16,7 @@ export default function EditDialog(props: GroupEditDialogProps): ReactElement
?? ""
);
const [color, setColor] = useState<chrome.tabGroups.ColorEnum | undefined | "pinned">(
const [color, setColor] = useState<`${Browser.tabGroups.Color}` | undefined | "pinned">(
props.type === "collection"
? props.collection?.color :
props.group?.pinned === true ? "pinned" : (props.group?.color ?? "blue")
@@ -112,8 +112,8 @@ export default function EditDialog(props: GroupEditDialogProps): ReactElement
{ Object.keys(colorCls).map(i =>
<fui.ToggleButton
checked={ color === i }
onClick={ () => setColor(i as chrome.tabGroups.ColorEnum) }
className={ fui.mergeClasses(cls.colorButton, colorCls[i as chrome.tabGroups.ColorEnum]) }
onClick={ () => setColor(i as `${Browser.tabGroups.Color}`) }
className={ fui.mergeClasses(cls.colorButton, colorCls[i as `${Browser.tabGroups.Color}`]) }
icon={ {
className: cls.colorButton_icon,
children: <Circle20Filled />
@@ -121,7 +121,7 @@ export default function EditDialog(props: GroupEditDialogProps): ReactElement
key={ i }
shape="circular"
>
{ i18n.t(`colors.${i as chrome.tabGroups.ColorEnum}`) }
{ i18n.t(`colors.${i as `${Browser.tabGroups.Color}`}`) }
</fui.ToggleButton>
) }
</div>
@@ -1,20 +1,20 @@
import { getCollectionTitle } from "@/entrypoints/sidepanel/utils/getCollectionTitle";
import getSelectedTabs from "@/entrypoints/sidepanel/utils/getSelectedTabs";
import useSettings from "@/hooks/useSettings";
import { GroupItem, TabItem } from "@/models/CollectionModels";
import { TabItem } from "@/models/CollectionModels";
import { Button, Caption1, makeStyles, mergeClasses, Subtitle2, tokens, Tooltip } from "@fluentui/react-components";
import { Add20Filled, Add20Regular, bundleIcon } from "@fluentui/react-icons";
import CollectionContext, { CollectionContextType } from "../../contexts/CollectionContext";
import { useCollections } from "../../contexts/CollectionsProvider";
import CollectionMoreButton from "./CollectionMoreButton";
import OpenCollectionButton from "./OpenCollectionButton";
import saveTabsToCollection from "@/utils/saveTabsToCollection";
import sendPartialSaveNotification from "@/utils/sendPartialSaveNotification";
import { getTabsToSaveAsync } from "@/utils/getTabsToSaveAsync";
export default function CollectionHeader({ dragHandleRef, dragHandleProps }: CollectionHeaderProps): React.ReactElement
{
const [contextOpen, setContextOpen] = useState<boolean>(false);
const [listLocation] = useSettings("listLocation");
const isTab: boolean = listLocation === "tab" || listLocation === "pinned";
const isTabView: boolean = listLocation === "tab" || listLocation === "pinned";
const { updateCollection } = useCollections();
const { tabCount, collection } = useContext<CollectionContextType>(CollectionContext);
const [alwaysShowToolbars] = useSettings("alwaysShowToolbars");
@@ -23,10 +23,16 @@ export default function CollectionHeader({ dragHandleRef, dragHandleProps }: Col
const handleAddSelected = async () =>
{
const newTabs: (TabItem | GroupItem)[] = isTab ?
(await saveTabsToCollection(false)).items :
await getSelectedTabs();
updateCollection({ ...collection, items: [...collection.items, ...newTabs] }, collection.timestamp);
const [newTabs, skipCount] = await getTabsToSaveAsync();
if (newTabs.length > 0)
await updateCollection({
...collection,
items: [...collection.items, ...newTabs.map<TabItem>(i => ({ type: "tab", url: i.url!, title: i.title }))]
}, collection.timestamp);
if (skipCount > 0)
await sendPartialSaveNotification();
};
const cls = useStyles();
@@ -59,7 +65,7 @@ export default function CollectionHeader({ dragHandleRef, dragHandleProps }: Col
>
{ tabCount < 1 ?
<Button icon={ <AddIcon /> } appearance="subtle" onClick={ handleAddSelected }>
{ isTab ? i18n.t("collections.menu.add_all") : i18n.t("collections.menu.add_selected") }
{ isTabView ? i18n.t("collections.menu.add_all") : i18n.t("collections.menu.add_selected") }
</Button>
:
<OpenCollectionButton onOpenChange={ (_, e) => setContextOpen(e.open) } />
@@ -3,21 +3,21 @@ import EditDialog from "@/entrypoints/sidepanel/components/EditDialog";
import CollectionContext, { CollectionContextType } from "@/entrypoints/sidepanel/contexts/CollectionContext";
import { useCollections } from "@/entrypoints/sidepanel/contexts/CollectionsProvider";
import GroupContext, { GroupContextType } from "@/entrypoints/sidepanel/contexts/GroupContext";
import getSelectedTabs from "@/entrypoints/sidepanel/utils/getSelectedTabs";
import { useDangerStyles } from "@/hooks/useDangerStyles";
import useSettings from "@/hooks/useSettings";
import { TabItem } from "@/models/CollectionModels";
import { sendMessage } from "@/utils/messaging";
import saveTabsToCollection from "@/utils/saveTabsToCollection";
import { Button, Menu, MenuItem, MenuList, MenuPopover, MenuTrigger, Tooltip } from "@fluentui/react-components";
import * as ic from "@fluentui/react-icons";
import { ReactElement } from "react";
import { openGroup } from "../../utils/opener";
import { getTabsToSaveAsync } from "@/utils/getTabsToSaveAsync";
import sendPartialSaveNotification from "@/utils/sendPartialSaveNotification";
export default function GroupMoreMenu(): ReactElement
{
const [listLocation] = useSettings("listLocation");
const isTab: boolean = listLocation === "tab" || listLocation === "pinned";
const isTabView: boolean = listLocation === "tab" || listLocation === "pinned";
const { group, indices } = useContext<GroupContextType>(GroupContext);
const { hasPinnedGroup, collection } = useContext<CollectionContextType>(CollectionContext);
const [deletePrompt] = useSettings("deletePrompt");
@@ -67,10 +67,16 @@ export default function GroupMoreMenu(): ReactElement
const handleAddSelected = async () =>
{
const newTabs: TabItem[] = isTab ?
(await saveTabsToCollection(false)).items.flatMap(i => i.type === "tab" ? i : i.items) :
await getSelectedTabs();
updateGroup({ ...group, items: [...group.items, ...newTabs] }, collection.timestamp, indices[1]);
const [newTabs, skipCount] = await getTabsToSaveAsync();
if (newTabs.length > 0)
await updateGroup({
...group,
items: [...group.items, ...newTabs.map<TabItem>(i => ({ type: "tab", url: i.url!, title: i.title }))]
}, collection.timestamp, indices[1]);
if (skipCount > 0)
await sendPartialSaveNotification();
};
return (
@@ -90,7 +96,7 @@ export default function GroupMoreMenu(): ReactElement
}
<MenuItem icon={ <AddIcon /> } onClick={ handleAddSelected }>
{ isTab ? i18n.t("groups.menu.add_all") : i18n.t("groups.menu.add_selected") }
{ isTabView ? i18n.t("groups.menu.add_all") : i18n.t("groups.menu.add_selected") }
</MenuItem>
<MenuItem icon={ <EditIcon /> } onClick={ handleEdit }>
@@ -43,12 +43,12 @@ export default function CollectionsProvider({ children }: React.PropsWithChildre
sendMessage("refreshCollections", undefined);
};
const addCollection = (collection: CollectionItem): void =>
const addCollection = async (collection: CollectionItem): Promise<void> =>
{
updateStorage([collection, ...collections]);
await updateStorage([collection, ...collections]);
};
const removeItem = (...indices: number[]): void =>
const removeItem = async (...indices: number[]): Promise<void> =>
{
const collectionIndex: number = collections.findIndex(i => i.timestamp === indices[0]);
@@ -59,34 +59,34 @@ export default function CollectionsProvider({ children }: React.PropsWithChildre
else
collections.splice(collectionIndex, 1);
updateStorage(collections);
await updateStorage(collections);
};
const updateCollections = (collectionList: CollectionItem[]): void =>
const updateCollections = async (collectionList: CollectionItem[]): Promise<void> =>
{
updateStorage(collectionList);
await updateStorage(collectionList);
};
const updateCollection = (collection: CollectionItem, id: number): void =>
const updateCollection = async (collection: CollectionItem, id: number): Promise<void> =>
{
const index: number = collections.findIndex(i => i.timestamp === id);
collections[index] = collection;
updateStorage(collections);
await updateStorage(collections);
};
const updateGroup = (group: GroupItem, collectionId: number, groupIndex: number): void =>
const updateGroup = async (group: GroupItem, collectionId: number, groupIndex: number): Promise<void> =>
{
const collectionIndex: number = collections.findIndex(i => i.timestamp === collectionId);
collections[collectionIndex].items[groupIndex] = group;
updateStorage(collections);
await updateStorage(collections);
};
const ungroup = (collectionId: number, groupIndex: number): void =>
const ungroup = async (collectionId: number, groupIndex: number): Promise<void> =>
{
const collectionIndex: number = collections.findIndex(i => i.timestamp === collectionId);
const group = collections[collectionIndex].items[groupIndex] as GroupItem;
collections[collectionIndex].items.splice(groupIndex, 1, ...group.items);
updateStorage(collections);
await updateStorage(collections);
};
return (
@@ -110,12 +110,12 @@ export type CollectionsContextType =
tilesView: boolean;
refreshCollections: () => Promise<void>;
addCollection: (collection: CollectionItem) => void;
addCollection: (collection: CollectionItem) => Promise<void>;
updateCollections: (collections: CollectionItem[]) => void;
updateCollection: (collection: CollectionItem, id: number) => void;
updateGroup: (group: GroupItem, collectionId: number, groupIndex: number) => void;
ungroup: (collectionId: number, groupIndex: number) => void;
updateCollections: (collections: CollectionItem[]) => Promise<void>;
updateCollection: (collection: CollectionItem, id: number) => Promise<void>;
updateGroup: (group: GroupItem, collectionId: number, groupIndex: number) => Promise<void>;
ungroup: (collectionId: number, groupIndex: number) => Promise<void>;
removeItem: (...indices: number[]) => void;
removeItem: (...indices: number[]) => Promise<void>;
};
@@ -44,11 +44,11 @@ export default function FilterCollectionsButton({ value, onChange }: FilterColle
<ColorIcon
className={ fui.mergeClasses(
cls.colorIcon,
colorCls[i as chrome.tabGroups.ColorEnum]
colorCls[i as `${Browser.tabGroups.Color}`]
) } />
}
>
{ i18n.t(`colors.${i as chrome.tabGroups.ColorEnum}`) }
{ i18n.t(`colors.${i as `${Browser.tabGroups.Color}`}`) }
</fui.MenuItemCheckbox>
) }
</fui.MenuList>
@@ -1,7 +1,8 @@
import resolveConflict from "@/features/collectionStorage/utils/resolveConflict";
import { Button, MessageBar, MessageBarActions, MessageBarBody, MessageBarProps, MessageBarTitle } from "@fluentui/react-components";
import { ArrowUpload20Regular, CloudArrowDown20Regular, Wrench20Regular } from "@fluentui/react-icons";
import { ArrowDownload20Regular, ArrowUpload20Regular, CloudArrowDown20Regular, Wrench20Regular } from "@fluentui/react-icons";
import { useCollections } from "../../../contexts/CollectionsProvider";
import exportData from "@/entrypoints/options/utils/exportData";
export default function CloudIssueMessages(props: MessageBarProps): React.ReactElement
{
@@ -36,6 +37,9 @@ export default function CloudIssueMessages(props: MessageBarProps): React.ReactE
{ i18n.t("merge_conflict_message.message") }
</MessageBarBody>
<MessageBarActions>
<Button icon={ <ArrowDownload20Regular /> } onClick={ exportData }>
{ i18n.t("options_page.storage.export") }
</Button>
<Button icon={ <ArrowUpload20Regular /> } onClick={ () => overrideStorageWith("local") }>
{ i18n.t("merge_conflict_message.accept_local") }
</Button>
@@ -1,7 +1,7 @@
import useStorageInfo from "@/hooks/useStorageInfo";
import { MessageBar, MessageBarBody, MessageBarProps, MessageBarTitle } from "@fluentui/react-components";
export default function StorageCapacityIssueMessage(props: MessageBarProps): JSX.Element
export default function StorageCapacityIssueMessage(props: MessageBarProps): React.ReactElement
{
const { usedStorageRatio } = useStorageInfo();
@@ -1,6 +1,11 @@
import { useCollections } from "@/entrypoints/sidepanel/contexts/CollectionsProvider";
import { track } from "@/features/analytics";
import useSettings, { SettingsValue } from "@/hooks/useSettings";
import saveTabsToCollection from "@/utils/saveTabsToCollection";
import { CollectionItem } from "@/models/CollectionModels";
import { closeTabsAsync } from "@/utils/closeTabsAsync";
import { createCollectionFromTabs } from "@/utils/createCollectionFromTabs";
import { getTabsToSaveAsync } from "@/utils/getTabsToSaveAsync";
import sendPartialSaveNotification from "@/utils/sendPartialSaveNotification";
import watchTabSelection from "@/utils/watchTabSelection";
import { Menu, MenuButtonProps, MenuItem, MenuList, MenuPopover, MenuTrigger, SplitButton } from "@fluentui/react-components";
import * as ic from "@fluentui/react-icons";
@@ -14,8 +19,26 @@ export default function ActionButton(): ReactElement
const handleAction = async (primary: boolean) =>
{
const colection = await saveTabsToCollection(primary === (defaultAction === "set_aside"));
addCollection(colection);
const [tabs, skipCount] = await getTabsToSaveAsync();
if (tabs.length < 1)
{
await sendPartialSaveNotification();
return;
}
const collection: CollectionItem = await createCollectionFromTabs(tabs);
await addCollection(collection);
if (skipCount > 0)
await sendPartialSaveNotification();
const closeTabs: boolean = primary === (defaultAction === "set_aside");
if (closeTabs)
await closeTabsAsync(tabs);
track(closeTabs ? "set_aside" : "save");
};
useEffect(() =>
@@ -1,12 +1,11 @@
import { track } from "@/features/analytics";
import { CollectionItem, TabItem } from "@/models/CollectionModels";
import sendNotification from "@/utils/sendNotification";
import { Bookmarks, Permissions } from "wxt/browser";
import { getCollectionTitle } from "./getCollectionTitle";
import { track } from "@/features/analytics";
export default async function exportCollectionToBookmarks(collection: CollectionItem)
{
const permissions: Permissions.AnyPermissions = await browser.permissions.getAll();
const permissions: Browser.permissions.Permissions = await browser.permissions.getAll();
if (!permissions.permissions?.includes("bookmarks"))
{
@@ -16,7 +15,7 @@ export default async function exportCollectionToBookmarks(collection: Collection
return;
}
const rootFolder: Bookmarks.BookmarkTreeNode = await browser.bookmarks.create({
const rootFolder: Browser.bookmarks.BookmarkTreeNode = await browser.bookmarks.create({
title: getCollectionTitle(collection)
});
@@ -61,5 +61,5 @@ export default function filterCollections(
export type CollectionFilterType =
{
query: string;
colors: (chrome.tabGroups.ColorEnum | "none")[];
colors: (`${Browser.tabGroups.Color}` | "none")[];
};
@@ -1,10 +1,9 @@
import { TabItem } from "@/models/CollectionModels";
import sendNotification from "@/utils/sendNotification";
import { Tabs } from "wxt/browser";
export default async function getSelectedTabs(): Promise<TabItem[]>
{
let tabs: Tabs.Tab[] = await browser.tabs.query({ currentWindow: true, highlighted: true });
let tabs: Browser.tabs.Tab[] = await browser.tabs.query({ currentWindow: true, highlighted: true });
const tabCount: number = tabs.length;
tabs = tabs.filter(i =>
+9 -10
View File
@@ -1,7 +1,6 @@
import { getCollectionTitle } from "@/entrypoints/sidepanel/utils/getCollectionTitle";
import { CollectionItem, GroupItem, TabItem } from "@/models/CollectionModels";
import { settings } from "@/utils/settings";
import { Tabs, Windows } from "wxt/browser";
export async function openCollection(collection: CollectionItem, targetWindow?: "current" | "new" | "incognito"): Promise<void>
{
@@ -55,7 +54,7 @@ export async function openGroup(group: GroupItem, newWindow: boolean = false): P
async function createGroup(group: GroupItem, windowId: number, discard?: boolean): Promise<void>
{
discard ??= await settings.dismissOnLoad.getValue();
const tabs: Tabs.Tab[] = await Promise.all(group.items.map(async i =>
const tabs: Browser.tabs.Tab[] = await Promise.all(group.items.map(async i =>
await createTab(i.url, windowId, discard, group.pinned)
));
@@ -63,21 +62,21 @@ async function createGroup(group: GroupItem, windowId: number, discard?: boolean
if (group.pinned === true)
return;
const groupId: number = await chrome.tabs.group({
tabIds: tabs.filter(i => i.windowId === windowId).map(i => i.id!),
const groupId: number = await browser.tabs.group({
tabIds: tabs.filter(i => i.windowId === windowId).map(i => i.id!) as [number, ...number[]],
createProperties: { windowId }
});
await chrome.tabGroups.update(groupId, {
await browser.tabGroups.update(groupId, {
title: group.title,
color: group.color
});
}
async function manageWindow(handle: (windowId: number) => Promise<void>, windowProps?: Windows.CreateCreateDataType): Promise<void>
async function manageWindow(handle: (windowId: number) => Promise<void>, windowProps?: Browser.windows.CreateData): Promise<void>
{
const currentWindow: Windows.Window = windowProps ?
await browser.windows.create({ url: "about:blank", focused: false, ...windowProps }) :
const currentWindow: Browser.windows.Window = windowProps ?
(await browser.windows.create({ url: "about:blank", focused: false, ...windowProps }))! :
await browser.windows.getCurrent();
const windowId: number = currentWindow.id!;
@@ -90,7 +89,7 @@ async function manageWindow(handle: (windowId: number) => Promise<void>, windowP
await browser.tabs.remove(currentWindow.tabs![0].id!);
}
async function createTab(url: string, windowId: number, discard: boolean, pinned?: boolean): Promise<Tabs.Tab>
async function createTab(url: string, windowId: number, discard: boolean, pinned?: boolean): Promise<Browser.tabs.Tab>
{
const tab = await browser.tabs.create({ url, windowId: windowId, active: false, pinned });
@@ -102,7 +101,7 @@ async function createTab(url: string, windowId: number, discard: boolean, pinned
function discardOnLoad(tabId: number): void
{
const handleTabUpdated = (id: number, _: any, tab: Tabs.Tab) =>
const handleTabUpdated = (id: number, _: any, tab: Browser.tabs.Tab) =>
{
if (id !== tabId || !tab.url)
return;
@@ -1,6 +1,5 @@
import { Unwatch, WatchCallback, WxtStorageItem } from "wxt/storage";
import { Unwatch, WatchCallback } from "wxt/utils/storage";
import { analytics } from "./analytics";
import { Permissions } from "wxt/browser";
const analyticsPermission: Pick<WxtStorageItem<boolean, Record<string, unknown>>, "getValue" | "watch" | "setValue"> =
{
@@ -8,9 +7,8 @@ const analyticsPermission: Pick<WxtStorageItem<boolean, Record<string, unknown>>
{
const isGranted: boolean = import.meta.env.FIREFOX
? await browser.permissions.contains({
// @ts-expect-error Introduced in Firefox 139
data_collection: ["technicalAndInteraction"]
})
} as Browser.permissions.Permissions)
: await allowAnalytics.getValue();
analytics.setEnabled(isGranted);
@@ -30,14 +28,12 @@ const analyticsPermission: Pick<WxtStorageItem<boolean, Record<string, unknown>>
if (value)
result = await browser.permissions.request({
// @ts-expect-error Introduced in Firefox 139
data_collection: ["technicalAndInteraction"]
});
} as Browser.permissions.Permissions);
else
result = await browser.permissions.remove({
// @ts-expect-error Introduced in Firefox 139
data_collection: ["technicalAndInteraction"]
});
} as Browser.permissions.Permissions);
if (!result)
throw new Error("Permission request was denied");
@@ -48,13 +44,14 @@ const analyticsPermission: Pick<WxtStorageItem<boolean, Record<string, unknown>>
if (!import.meta.env.FIREFOX)
return allowAnalytics.watch(cb);
const listener = async (permissions: Permissions.Permissions): Promise<void> =>
const listener = async (permissions: Browser.permissions.Permissions): Promise<void> =>
{
// @ts-expect-error Introduced in Firefox 139
// @ts-expect-error Firefox-only API
if (permissions.data_collection?.includes("technicalAndInteraction"))
{
// @ts-expect-error Introduced in Firefox 139
const isGranted: boolean = await browser.permissions.contains({ data_collection: ["technicalAndInteraction"] });
const isGranted: boolean = await browser.permissions.contains({
data_collection: ["technicalAndInteraction"]
} as Browser.permissions.Permissions);
cb(isGranted, !isGranted);
}
};
@@ -14,7 +14,7 @@ export default async function getCollectionsFromCloud(): Promise<CollectionItem[
const chunks: Record<string, string> =
await browser.storage.sync.get(getChunkKeys(0, chunkCount)) as Record<string, string>;
const data: string = decompress(Object.values(chunks).join(), { inputEncoding: "Base64" });
const data: string = decompress(Object.values(chunks).join(""), { inputEncoding: "Base64" });
return parseCollections(data);
}
@@ -44,7 +44,7 @@ function parseCollection(data: string): CollectionItem
return {
type: "collection",
timestamp: parseInt(data.match(/(?<=^c)\d+/)!.toString()),
color: data.match(/(?<=^c\d+\/)[a-z]+/)?.toString() as chrome.tabGroups.ColorEnum,
color: data.match(/(?<=^c\d+\/)[a-z]+/)?.toString() as `${Browser.tabGroups.Color}`,
title: data.match(/(?<=^c[\da-z/]*\|).*/)?.toString(),
items: []
};
@@ -64,7 +64,7 @@ function parseGroup(data: string): GroupItem
return {
type: "group",
pinned: false,
color: data.match(/(?<=^\tg\/)[a-z]+/)?.toString() as chrome.tabGroups.ColorEnum,
color: data.match(/(?<=^\tg\/)[a-z]+/)?.toString() as `${Browser.tabGroups.Color}`,
title: data.match(/(?<=^\tg\/[a-z]+\|).*$/)?.toString(),
items: []
};
@@ -74,7 +74,7 @@ function parseTab(data: string): TabItem
{
return {
type: "tab",
url: data.match(/(?<=^(\t){1,2}t\|).*(?=\|)/)!.toString(),
title: data.match(/(?<=^(\t){1,2}t\|.*\|).*$/)?.toString()
url: data.match(/(?<=^\t{1,2}t\|).*(?=\|)/)!.toString(),
title: data.match(/(?<=^\t{1,2}t\|.*\|).*$/)?.toString()
};
}
@@ -1,7 +1,5 @@
import { trackError } from "@/features/analytics";
import { CollectionItem, GraphicsStorage } from "@/models/CollectionModels";
import getLogger from "@/utils/getLogger";
import sendNotification from "@/utils/sendNotification";
import { collectionStorage } from "./collectionStorage";
import saveCollectionsToCloud from "./saveCollectionsToCloud";
import saveCollectionsToLocal from "./saveCollectionsToLocal";
@@ -19,29 +17,8 @@ export default async function saveCollections(
await saveCollectionsToLocal(collections, timestamp);
if (updateCloud && await collectionStorage.disableCloud.getValue() !== true)
try
{
await saveCollectionsToCloud(collections, timestamp);
}
catch (ex)
{
logger("Failed to save cloud storage");
console.error(ex);
trackError("cloud_save_error", ex as Error);
if ((ex as Error).message.includes("MAX_WRITE_OPERATIONS_PER_MINUTE"))
await sendNotification({
title: i18n.t("notifications.error_quota_exceeded.title"),
message: i18n.t("notifications.error_quota_exceeded.message"),
icon: "/notification_icons/cloud_error.png"
});
else
await sendNotification({
title: i18n.t("notifications.error_storage_full.title"),
message: i18n.t("notifications.error_storage_full.message"),
icon: "/notification_icons/cloud_error.png"
});
}
await updateGraphics(collections, graphicsCache);
logger("Save complete");
};
@@ -1,15 +1,24 @@
import { trackError } from "@/features/analytics";
import { CollectionItem } from "@/models/CollectionModels";
import getLogger from "@/utils/getLogger";
import sendNotification from "@/utils/sendNotification";
import { compress } from "lzutf8";
import { WxtStorageItem } from "wxt/storage";
import { collectionStorage } from "./collectionStorage";
import getChunkKeys from "./getChunkKeys";
import serializeCollections from "./serializeCollections";
const logger = getLogger("saveCollectionsToCloud");
export default async function saveCollectionsToCloud(collections: CollectionItem[], timestamp: number): Promise<void>
{
try
{
if (!collections || collections.length < 1)
{
await collectionStorage.chunkCount.setValue(0);
await browser.storage.sync.set({
[getStorageKey(collectionStorage.chunkCount)]: 0,
[getStorageKey(collectionStorage.syncLastUpdated)]: timestamp
});
await browser.storage.sync.remove(getChunkKeys());
return;
}
@@ -34,13 +43,33 @@ export default async function saveCollectionsToCloud(collections: CollectionItem
if (chunks.length < collectionStorage.maxChunkCount)
await browser.storage.sync.remove(getChunkKeys(chunks.length));
}
catch (ex)
{
logger("Failed to save cloud storage");
console.error(ex);
trackError("cloud_save_error", ex as Error);
if ((ex as Error).message.includes("MAX_WRITE_OPERATIONS_PER_MINUTE"))
await sendNotification({
title: i18n.t("notifications.error_quota_exceeded.title"),
message: i18n.t("notifications.error_quota_exceeded.message"),
icon: "/notification_icons/cloud_error.png"
});
else
await sendNotification({
title: i18n.t("notifications.error_storage_full.title"),
message: i18n.t("notifications.error_storage_full.message"),
icon: "/notification_icons/cloud_error.png"
});
}
}
function splitIntoChunks(data: string): string[]
{
// QUOTA_BYTES_PER_ITEM includes length of key name, length of content and 2 more bytes (for unknown reason).
const chunkKey: string = getChunkKeys(collectionStorage.maxChunkCount - 1)[0];
const chunkSize = (chrome.storage.sync.QUOTA_BYTES_PER_ITEM ?? 8192) - chunkKey.length - 2;
const chunkSize = (browser.storage.sync.QUOTA_BYTES_PER_ITEM ?? 8192) - chunkKey.length - 2;
const chunks: string[] = [];
for (let i = 0; i < data.length; i += chunkSize)
@@ -1,5 +1,4 @@
import { Permissions } from "wxt/browser";
import { Unwatch, WatchCallback, WxtStorageItem } from "wxt/storage";
import { Unwatch, WatchCallback } from "wxt/utils/storage";
const thumbnailCaptureEnabled: Pick<WxtStorageItem<boolean, Record<string, unknown>>, "getValue" | "watch" | "setValue"> =
{
@@ -8,7 +7,7 @@ const thumbnailCaptureEnabled: Pick<WxtStorageItem<boolean, Record<string, unkno
watch: (cb: WatchCallback<boolean>): Unwatch =>
{
const listener = async (permissions: Permissions.Permissions): Promise<void> =>
const listener = async (permissions: Browser.permissions.Permissions): Promise<void> =>
{
if (permissions.permissions?.includes("scripting") || permissions.origins?.includes("<all_urls>"))
{
@@ -1,11 +1,11 @@
import { githubLinks } from "@/data/links";
import { analyticsPermission } from "@/features/analytics";
import { thumbnailCaptureEnabled } from "@/features/collectionStorage";
import extLink from "@/utils/extLink";
import * as fui from "@fluentui/react-components";
import { settingsForReview } from "../utils/showSettingsReviewDialog";
import { Unwatch } from "wxt/utils/storage";
import { reviewSettings } from "../utils/setSettingsReviewNeeded";
import { Unwatch } from "wxt/storage";
import { thumbnailCaptureEnabled } from "@/features/collectionStorage";
import { settingsForReview } from "../utils/showSettingsReviewDialog";
export default function SettingsReviewDialog(): React.ReactElement
{
@@ -1,8 +1,7 @@
import { analyticsPermission } from "@/features/analytics";
import { Runtime } from "wxt/browser";
import { settingsForReview } from "./showSettingsReviewDialog";
export default async function setSettingsReviewNeeded(installReason: Runtime.OnInstalledReason, previousVersion?: string): Promise<void>
export default async function setSettingsReviewNeeded(installReason: `${Browser.runtime.OnInstalledReason}`, previousVersion?: string): Promise<void>
{
const needsReview: string[] = await settingsForReview.getValue();
@@ -25,7 +24,7 @@ export const reviewSettings =
THUMBNAILS: "thumbnails"
};
async function checkAnalyticsReviewNeeded(installReason: Runtime.OnInstalledReason, previousVersion?: string): Promise<boolean>
async function checkAnalyticsReviewNeeded(installReason: `${Browser.runtime.OnInstalledReason}`, previousVersion?: string): Promise<boolean>
{
if (installReason === "install")
return !await analyticsPermission.getValue();
@@ -45,7 +44,7 @@ async function checkAnalyticsReviewNeeded(installReason: Runtime.OnInstalledReas
return false;
}
async function checkThumbnailsReviewNeeded(installReason: Runtime.OnInstalledReason, previousVersion?: string): Promise<boolean>
async function checkThumbnailsReviewNeeded(installReason: `${Browser.runtime.OnInstalledReason}`, previousVersion?: string): Promise<boolean>
{
if (installReason === "install")
return true;
+1 -1
View File
@@ -1,6 +1,6 @@
import { makeStyles, tokens } from "@fluentui/react-components";
export const useGroupColors: () => Record<chrome.tabGroups.ColorEnum, string> = makeStyles({
export const useGroupColors: () => Record<`${Browser.tabGroups.Color}`, string> = makeStyles({
blue:
{
"--border": tokens.colorPaletteBlueBorderActive,
+2 -2
View File
@@ -14,8 +14,8 @@ export default function useStorageInfo(): StorageInfoHook
return {
bytesInUse,
storageQuota: chrome.storage.sync.QUOTA_BYTES ?? 102400,
usedStorageRatio: bytesInUse / (chrome.storage.sync.QUOTA_BYTES ?? 102400)
storageQuota: browser.storage.sync.QUOTA_BYTES ?? 102400,
usedStorageRatio: bytesInUse / (browser.storage.sync.QUOTA_BYTES ?? 102400)
};
}
+1
View File
@@ -82,6 +82,7 @@ options_page:
show_delete_prompt: "Ask for confirmation when deleting an item"
show_badge: "Show counter badge"
show_notification: "Show notification when saving tabs using context menu"
show_partial_save_notification: "Show notification when some tabs couldn't be saved"
unload_tabs: "Do not load tabs after opening"
allow_analytics: "Allow collection of anonymous statistics"
list_locations:
+1
View File
@@ -82,6 +82,7 @@ options_page:
show_delete_prompt: "Pedir confirmación al eliminar un elemento"
show_badge: "Mostrar insignia de contador"
show_notification: "Mostrar notificación al guardar pestañas usando el menú contextual"
show_partial_save_notification: "Mostrar notificación cuando algunas pestañas no se pudieron guardar"
unload_tabs: "No cargar pestañas después de abrir"
allow_analytics: "Permitir la recopilación de estadísticas anónimas"
list_locations:
+1
View File
@@ -82,6 +82,7 @@ options_page:
show_delete_prompt: "Chiedi conferma quando elimini un elemento"
show_badge: "Mostra il badge del contatore"
show_notification: "Mostra notifica quando salvi le schede usando il menu contestuale"
show_partial_save_notification: "Mostra notifica quando alcune schede non sono state salvate"
unload_tabs: "Non caricare le schede dopo l'apertura"
allow_analytics: "Consenti la raccolta di statistiche anonime"
list_locations:
+1
View File
@@ -82,6 +82,7 @@ options_page:
show_delete_prompt: "Pytaj o potwierdzenie przy usuwaniu elementów"
show_badge: "Pokaż licznik"
show_notification: "Pokaż powiadomienie przy zapisywaniu przez menu kontekstowe"
show_partial_save_notification: "Pokaż powiadomienie, jeśli niektóre karty nie zostały zapisane"
unload_tabs: "Nie ładuj kart po otwarciu"
allow_analytics: "Zezwól na zbieranie anonimowej statystyki"
list_locations:
+1
View File
@@ -82,6 +82,7 @@ options_page:
show_delete_prompt: "Pedir confirmação ao excluir um item"
show_badge: "Mostrar contador no ícone"
show_notification: "Mostrar notificação ao salvar abas pelo menu de contexto"
show_partial_save_notification: "Mostrar notificação quando algumas abas não puderam ser salvas"
unload_tabs: "Não carregar abas após abrir"
allow_analytics: "Permitir coleta de estatísticas anônimas"
list_locations:
+1
View File
@@ -82,6 +82,7 @@ options_page:
show_delete_prompt: "Спрашивать подтверждение при удалении элементов"
show_badge: "Показывать счетчик"
show_notification: "Показывать уведомление при сохранении через контекстное меню"
show_partial_save_notification: "Показывать уведомление, если некоторые вкладки не были сохранены"
unload_tabs: "Не загружать вкладки после открытия"
allow_analytics: "Разрешить сбор анонимной статистики"
list_locations:
+1
View File
@@ -82,6 +82,7 @@ options_page:
show_delete_prompt: "Запитувати підтвердження при видаленні елементів"
show_badge: "Показувати лічильник"
show_notification: "Показувати сповіщення при збереженні через контекстне меню"
show_partial_save_notification: "Показувати сповіщення, якщо деякі вкладки не були збережені"
unload_tabs: "Не завантажувати вкладки після відкриття"
allow_analytics: "Дозволити збір анонімної статистики"
list_locations:
+53 -52
View File
@@ -5,8 +5,8 @@ manifest:
shortcuts:
toggle_sidebar: "打开收藏列表"
set_aside: "将标签放到一边"
save_tabs: "保存标签不关闭"
set_aside: "搁置标签页"
save_tabs: "保存标签页但不关闭"
common:
actions:
@@ -14,10 +14,10 @@ common:
save: "保存"
close: "关闭"
delete: "删除"
reset_filters: "重置筛选"
reset_filters: "重置筛选"
cta:
feedback: "留下反馈"
sponsor: "请我喝咖啡"
sponsor: "请我喝咖啡"
tooltips:
more: "更多"
delete_prompt: "您确定吗?此操作无法撤销。"
@@ -25,13 +25,13 @@ common:
features:
v3welcome:
title: "欢迎使用搁置的标签页 3.0"
text1: "我们很高兴宣布搁置的标签页扩展新重大更新!"
text1: "我们很高兴宣布搁置的标签页扩展新重大更新!"
text2: "此更新带来了全新的用户界面,以及许多新功能,包括:"
list:
item1: "支持标签组"
item2: "收藏自定义"
item3: "拖放重新排序和组织"
item4: "从开始手动创建收藏"
item3: "拖放排序和整理"
item4: "从开始创建收藏"
item5: "以及更多!"
text3: "访问我们的开发博客以了解有关此更新及其所有功能的更多信息!"
actions:
@@ -43,33 +43,33 @@ features:
title: "这些统计数据将帮助我们改进扩展"
p1: "我们只收集使用统计数据(收藏数量、使用的功能等)"
p2: "我们不会收集您的任何数据!"
p3_text: ""
p3_link: "请参阅我们收集内容的完整列表"
p3_text: "请参阅我们收集内容的"
p3_link: "完整列表"
notifications:
tabs_saved:
title: "新收藏已创建"
message: "您的标签已保存到新收藏中"
title: "已创建新收藏"
message: "您的标签已保存到新收藏中"
error_quota_exceeded:
title: "超出最大云写入操作"
message: "我们已将您的标签保存到本地存储。您需要手动更新云存储"
title: "超出最大云储存写入操作"
message: "我们已将您的标签保存到本地存储。您需要手动更新云存储"
error_storage_full:
title: "您的云存储已满"
message: "我们已将您的标签保存到本地存储。请清理一些云存储空间"
message: "我们已将您的标签保存到本地存储。请清理一些云存储空间"
bookmark_saved:
title: "已导出到书签"
message: "您的收藏已导出到书签"
partial_save:
title: "某些标签无法保存"
message: "某些标签是我们无法访问的系统标签。它们已被跳过"
title: "部分标签无法保存"
message: "部分标签页是无法访问的系统标签。它们已被跳过"
actions:
save:
all: "保存所有标签"
selected: "保存选定的标签"
all: "保存所有标签"
selected: "保存选定的标签"
set_aside:
all: "所有标签放到一边"
selected: "选定的标签放到一边"
all: "搁置所有标签"
selected: "搁置选定的标签"
show_collections: "显示收藏"
options_page:
@@ -78,11 +78,12 @@ options_page:
title: "常规"
options:
always_show_toolbars: "始终显示工具栏"
include_pinned: "保存所有标签时包括固定标签"
include_pinned: "保存所有标签时包括固定标签"
show_delete_prompt: "删除项目时要求确认"
show_badge: "显示计数徽章"
show_notification: "使用上下文菜单保存标签时显示通知"
unload_tabs: "打开后不加载标签"
show_badge: "显示计数角标"
show_notification: "使用上下文菜单保存标签时显示通知"
show_partial_save_notification: "如果某些标签页无法保存则显示通知"
unload_tabs: "打开后不加载标签页"
allow_analytics: "允许收集匿名统计数据"
list_locations:
title: "在以下位置打开收藏列表:"
@@ -102,15 +103,15 @@ options_page:
title: "默认操作"
options:
save_actions:
title: "保存标签时的默认操作"
title: "保存标签时的默认操作"
options:
set_aside: "保存并关闭标签"
save: "保存标签而不关闭"
set_aside: "保存并关闭标签"
save: "保存标签而不关闭"
restore_actions:
title: "打开收藏时的默认操作"
options:
open: "仅打开标签"
restore: "打开标签并删除收藏"
open: "仅打开标签"
restore: "打开标签并删除收藏"
storage:
title: "存储"
capacity:
@@ -124,16 +125,16 @@ options_page:
import_prompt:
title: "导入数据"
warning_title: "这是不可逆的操作"
warning_text: "这将覆盖您的所有数据请确保选择了正确的文件,否则可能会导致数据损坏或丢失。建议先导出数据。"
warning_text: "这将覆盖您的所有数据请确保选择了正确的文件,否则可能会导致数据损坏或丢失。建议先导出数据。"
proceed: "选择文件"
enable: "启用云存储"
disable: "禁用云存储"
disable_prompt:
text: "此操作将禁用设备之间的收藏同步。扩展设置仍将同步。"
action: "禁用并重新加载扩展"
thumbnail_capture: "为已保存的标签保存缩略图和图标"
thumbnail_capture: "为已保存的标签保存缩略图和图标"
thumbnail_capture_notice1: "需要访问已访问网站内容的权限"
thumbnail_capture_notice2: "当你有大量集合时,禁用此功能可能会提高性能"
thumbnail_capture_notice2: "有大量收藏时,禁用此功能可能会提高性能"
clear_thumbnails:
action: "删除已保存的图标"
title: "删除缩略图和图标?"
@@ -142,10 +143,10 @@ options_page:
title: "关于"
developed_by: "由尤金·福克斯开发"
licensed_under: "许可协议"
mit_license: "MIT 许可协议"
mit_license: "MIT 协议"
translation_cta:
text: "发现错别字或想为您的语言提供翻译?"
button: "从这里开始"
button: "快速入门"
links:
website: "我的网站"
source: "源代码"
@@ -154,7 +155,7 @@ options_page:
collections:
empty: "此收藏为空"
tabs_count: "$1 个标签"
tabs_count: "$1 个标签"
actions:
open: "打开所有"
restore: "恢复所有"
@@ -170,35 +171,35 @@ collections:
p1: "扩展需要权限才能在 InPrivate 窗口中打开标签"
p2: "为此,请单击“设置”,然后勾选“允许在 InPrivate 中”选项"
firefox:
p1: "扩展需要权限才能在隐私窗口中打开标签"
p1: "扩展需要权限才能在隐私窗口中打开标签"
p2: "为此,请单击“设置”,转到“详细信息”并将“在隐私窗口中运行”设置为“允许”"
chrome:
p1: "扩展需要权限才能在隐身窗口中打开标签"
p1: "扩展需要权限才能在隐身窗口中打开标签"
p2: "为此,请单击“设置”,然后勾选“允许在隐身中”选项"
action: "设置"
menu:
delete: "删除收藏"
add_selected: "添加选定的标签"
add_all: "添加所有标签"
add_group: "添加空组"
add_selected: "添加选定的标签"
add_all: "添加所有标签"
add_group: "添加空组"
export_bookmarks: "导出到书签"
edit: "编辑收藏"
groups:
title: "组"
title: "组"
pinned: "已固定"
open: "打开所有"
empty: "此组为空"
empty: "此组为空"
menu:
new_window: "在新窗口中打开"
add_selected: "添加选定的标签"
add_all: "添加所有标签"
edit: "编辑组"
add_selected: "添加选定的标签"
add_all: "添加所有标签"
edit: "编辑组"
ungroup: "取消分组"
delete: "删除组"
delete: "删除组"
tabs:
delete: "删除标签"
delete: "删除标签"
colors:
none: "无颜色"
@@ -217,8 +218,8 @@ dialogs:
edit:
title:
edit_collection: "编辑收藏"
edit_group: "编辑组"
new_group: "新组"
edit_group: "编辑组"
new_group: "新组"
new_collection: "新收藏"
collection_title: "标题"
color: "颜色"
@@ -228,7 +229,7 @@ main:
create_collection: "创建新收藏"
menu:
tiles_view: "平铺视图"
changelog: "更新内容"
changelog: "更新内容"
list:
searchbar:
title: "搜索"
@@ -243,7 +244,7 @@ main:
custom: "自定义"
empty:
title: "这里还没有内容"
message: "当前标签放到一边,或创建新收藏"
message: "搁置当前标签,或创建新收藏"
empty_search:
title: "未找到任何内容"
message: "尝试更改搜索查询"
@@ -265,5 +266,5 @@ parse_error_message:
merge_conflict_message:
title: "您的本地和云存储有冲突的更改。"
message: "要解决此问题,您可以将本地副本上传到云端,或接受云端更改。"
accept_local: "用本地替换"
accept_local: "用本地替换云端"
accept_cloud: "接受云端更改"
+2 -2
View File
@@ -17,7 +17,7 @@ export type DefaultGroupItem =
type: "group";
pinned?: false;
title?: string;
color: chrome.tabGroups.ColorEnum;
color: `${Browser.tabGroups.Color}`;
items: TabItem[];
};
@@ -28,7 +28,7 @@ export type CollectionItem =
type: "collection";
timestamp: number;
title?: string;
color?: chrome.tabGroups.ColorEnum;
color?: `${Browser.tabGroups.Color}`;
items: (TabItem | GroupItem)[];
};
+18 -18
View File
@@ -1,7 +1,7 @@
{
"name": "tabs-aside",
"private": true,
"version": "3.1.0",
"version": "3.2.2",
"type": "module",
"scripts": {
"dev": "wxt",
@@ -16,31 +16,31 @@
"@dnd-kit/modifiers": "^9.0.0",
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@fluentui/react-components": "^9.70.0",
"@fluentui/react-icons": "^2.0.309",
"@fluentui/react-components": "^9.72.6",
"@fluentui/react-icons": "^2.0.313",
"@webext-core/messaging": "^2.3.0",
"@wxt-dev/analytics": "^0.4.1",
"@wxt-dev/analytics": "^0.5.1",
"@wxt-dev/i18n": "^0.2.4",
"lzutf8": "^0.6.3",
"react": "~18.3.1",
"react-dom": "~18.3.1"
"react": "~19.2.0",
"react-dom": "~19.2.0"
},
"devDependencies": {
"@eslint/css": "^0.11.0",
"@eslint/js": "^9.35.0",
"@eslint/json": "^0.13.2",
"@stylistic/eslint-plugin": "^5.3.1",
"@types/react": "~18.3.1",
"@types/react-dom": "~18.3.1",
"@eslint/css": "^0.14.1",
"@eslint/js": "^9.39.1",
"@eslint/json": "^0.14.0",
"@stylistic/eslint-plugin": "^5.5.0",
"@types/react": "~19.2.2",
"@types/react-dom": "~19.2.2",
"@wxt-dev/module-react": "^1.1.5",
"eslint": "^9.35.0",
"eslint": "^9.39.1",
"eslint-plugin-react": "^7.37.5",
"globals": "^16.3.0",
"globals": "^16.5.0",
"scheduler": "0.23.0",
"typescript": "^5.9.2",
"typescript-eslint": "^8.42.0",
"vite": "^7.1.5",
"wxt": "~0.19.29"
"typescript": "^5.9.3",
"typescript-eslint": "^8.46.4",
"vite": "^7.2.2",
"wxt": "^0.20.11"
},
"packageManager": "yarn@4.9.2"
}
+11
View File
@@ -0,0 +1,11 @@
export async function closeTabsAsync(tabs: Browser.tabs.Tab[]): Promise<void>
{
if (tabs.length < 1)
return;
await browser.tabs.create({
active: true,
windowId: tabs[0].windowId
});
await browser.tabs.remove(tabs.map(i => i.id!));
}
+83
View File
@@ -0,0 +1,83 @@
import { CollectionItem, GroupItem } from "@/models/CollectionModels";
export async function createCollectionFromTabs(tabs: Browser.tabs.Tab[]): Promise<CollectionItem>
{
const collection: CollectionItem = {
type: "collection",
timestamp: Date.now(),
items: []
};
if (tabs.length < 1)
return collection;
let tabIndex: number = 0;
if (tabs[tabIndex].pinned)
{
collection.items.push({ type: "group", pinned: true, items: [] });
for (; tabIndex < tabs.length; tabIndex++)
{
if (!tabs[tabIndex].pinned)
break;
(collection.items[0] as GroupItem).items.push({
type: "tab",
url: tabs[tabIndex].url!,
title: tabs[tabIndex].title
});
}
}
// Special case, if all tabs are in the same group, create a collection with the group title
if (tabs[0].groupId && tabs[0].groupId !== -1 &&
tabs.every(i => i.groupId === tabs[0].groupId)
)
{
const group = await browser.tabGroups.get(tabs[0].groupId);
collection.title = group.title;
collection.color = group.color;
tabs.forEach(i =>
collection.items.push({ type: "tab", url: i.url!, title: i.title })
);
return collection;
}
let activeGroup: number | null = null;
for (; tabIndex < tabs.length; tabIndex++)
{
const tab = tabs[tabIndex];
if (!tab.groupId || tab.groupId === -1)
{
collection.items.push({ type: "tab", url: tab.url!, title: tab.title });
activeGroup = null;
continue;
}
if (!activeGroup || activeGroup !== tab.groupId)
{
activeGroup = tab.groupId;
const group = await browser.tabGroups.get(activeGroup!);
collection.items.push({
type: "group",
color: group.color,
title: group.title,
items: []
});
}
(collection.items[collection.items.length - 1] as GroupItem).items.push({
type: "tab",
url: tab.url!,
title: tab.title
});
}
return collection;
}
+30
View File
@@ -0,0 +1,30 @@
import { settings } from "./settings";
export async function getTabsToSaveAsync(): Promise<[Browser.tabs.Tab[], number]>
{
let tabs: Browser.tabs.Tab[] = await browser.tabs.query({
currentWindow: true,
highlighted: true
});
if (tabs.length < 2)
{
const ignorePinned: boolean = await settings.ignorePinned.getValue();
tabs = await browser.tabs.query({
currentWindow: true,
pinned: ignorePinned ? false : undefined
});
}
const tabsCount: number = tabs.length;
const extension_prefix: string = browser.runtime.getURL("/");
tabs = tabs.filter(i =>
i.url
&& new URL(i.url).protocol !== "about:"
&& new URL(i.url).hostname !== "newtab"
&& !i.url!.startsWith(extension_prefix)
);
return [tabs, tabsCount - tabs.length];
}
-136
View File
@@ -1,136 +0,0 @@
import { track } from "@/features/analytics";
import { CollectionItem, GroupItem } from "@/models/CollectionModels";
import { Tabs } from "wxt/browser";
import sendNotification from "./sendNotification";
import { settings } from "./settings";
export default async function saveTabsToCollection(closeTabs: boolean): Promise<CollectionItem>
{
let tabs: Tabs.Tab[] = await browser.tabs.query({
currentWindow: true,
highlighted: true
});
if (tabs.length < 2)
{
const ignorePinned: boolean = await settings.ignorePinned.getValue();
tabs = await browser.tabs.query({
currentWindow: true,
pinned: ignorePinned ? false : undefined
});
}
const [collection, tabsToClose] = await createCollectionFromTabs(tabs);
if (closeTabs)
{
await browser.tabs.create({
active: true,
windowId: tabs[0].windowId
});
await browser.tabs.remove(tabsToClose.map(i => i.id!));
}
track(closeTabs ? "set_aside" : "save");
return collection;
}
async function createCollectionFromTabs(tabs: Tabs.Tab[]): Promise<[CollectionItem, Tabs.Tab[]]>
{
if (tabs.length < 1)
return [{ type: "collection", timestamp: Date.now(), items: [] }, []];
const tabCount: number = tabs.length;
tabs = tabs.filter(i =>
i.url
&& new URL(i.url).protocol !== "about:"
&& new URL(i.url).hostname !== "newtab"
);
if (tabs.length < tabCount)
await sendNotification({
title: i18n.t("notifications.partial_save.title"),
message: i18n.t("notifications.partial_save.message"),
icon: "/notification_icons/save_warning.png"
});
tabs = tabs.filter(i => !i.url!.startsWith(browser.runtime.getURL("/")));
const collection: CollectionItem = {
type: "collection",
timestamp: Date.now(),
items: []
};
let tabIndex: number = 0;
if (tabs[tabIndex].pinned)
{
collection.items.push({ type: "group", pinned: true, items: [] });
for (; tabIndex < tabs.length; tabIndex++)
{
if (!tabs[tabIndex].pinned)
break;
(collection.items[0] as GroupItem).items.push({
type: "tab",
url: tabs[tabIndex].url!,
title: tabs[tabIndex].title
});
}
}
// Special case, if all tabs are in the same group, create a collection with the group title
if (tabs[0].groupId && tabs[0].groupId !== -1 &&
tabs.every(i => i.groupId === tabs[0].groupId)
)
{
const group = await chrome.tabGroups.get(tabs[0].groupId);
collection.title = group.title;
collection.color = group.color;
tabs.forEach(i =>
collection.items.push({ type: "tab", url: i.url!, title: i.title })
);
return [collection, tabs];
}
let activeGroup: number | null = null;
for (; tabIndex < tabs.length; tabIndex++)
{
const tab = tabs[tabIndex];
if (!tab.groupId || tab.groupId === -1)
{
collection.items.push({ type: "tab", url: tab.url!, title: tab.title });
activeGroup = null;
continue;
}
if (!activeGroup || activeGroup !== tab.groupId)
{
activeGroup = tab.groupId;
const group = await chrome.tabGroups.get(activeGroup);
collection.items.push({
type: "group",
color: group.color,
title: group.title,
items: []
});
}
(collection.items[collection.items.length - 1] as GroupItem).items.push({
type: "tab",
url: tab.url!,
title: tab.title
});
}
return [collection, tabs];
}
+12
View File
@@ -0,0 +1,12 @@
import sendNotification from "./sendNotification";
import { settings } from "./settings";
export default async function sendPartialSaveNotification(): Promise<void>
{
if (await settings.showPartialSaveNotification.getValue())
await sendNotification({
title: i18n.t("notifications.partial_save.title"),
message: i18n.t("notifications.partial_save.message"),
icon: "/notification_icons/save_warning.png"
});
}
+8
View File
@@ -95,5 +95,13 @@ export const settings = {
fallback: true,
version: 1
}
),
showPartialSaveNotification: storage.defineItem<boolean>(
"sync:showPartialSaveNotification",
{
fallback: true,
version: 1
}
)
};
+1 -1
View File
@@ -1,4 +1,4 @@
import { Unwatch } from "wxt/storage";
import { Unwatch } from "wxt/utils/storage";
export default function watchTabSelection(onChange: TabSelectChangeHandler): Unwatch
{
+1623 -2291
View File
File diff suppressed because it is too large Load Diff