mirror of
https://github.com/XFox111/TabsAsideExtension.git
synced 2026-04-22 07:58:01 +03:00
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>
This commit is contained in:
@@ -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,20 +1,26 @@
|
||||
import { track, trackError } from "@/features/analytics";
|
||||
import { collectionCount, getCollections, thumbnailCaptureEnabled, saveCollections } from "@/features/collectionStorage";
|
||||
import { collectionCount, getCollections, saveCollections, thumbnailCaptureEnabled } from "@/features/collectionStorage";
|
||||
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 getLogger from "@/utils/getLogger";
|
||||
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 { RemoveListenerCallback } from "@webext-core/messaging";
|
||||
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 { closeTabsAsync } from "@/utils/closeTabsAsync";
|
||||
import { getTabsToSaveAsync } from "@/utils/getTabsToSaveAsync";
|
||||
import { createCollectionFromTabs } from "@/utils/createCollectionFromTabs";
|
||||
import getCollectionsFromLocal from "@/features/collectionStorage/utils/getCollectionsFromLocal";
|
||||
import { collectionStorage } from "@/features/collectionStorage/utils/collectionStorage";
|
||||
import getCollectionsFromCloud from "@/features/collectionStorage/utils/getCollectionsFromCloud";
|
||||
|
||||
export default defineBackground(() =>
|
||||
{
|
||||
@@ -36,16 +42,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(
|
||||
@@ -447,14 +479,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"),
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -72,6 +73,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 }
|
||||
|
||||
@@ -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>;
|
||||
};
|
||||
|
||||
@@ -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,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(() =>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 saveCollectionsToCloud(collections, timestamp);
|
||||
|
||||
await updateGraphics(collections, graphicsCache);
|
||||
logger("Save complete");
|
||||
};
|
||||
|
||||
@@ -4,36 +4,66 @@ import { WxtStorageItem } from "wxt/storage";
|
||||
import { collectionStorage } from "./collectionStorage";
|
||||
import getChunkKeys from "./getChunkKeys";
|
||||
import serializeCollections from "./serializeCollections";
|
||||
import { trackError } from "@/features/analytics";
|
||||
import sendNotification from "@/utils/sendNotification";
|
||||
import getLogger from "@/utils/getLogger";
|
||||
|
||||
const logger = getLogger("saveCollectionsToCloud");
|
||||
|
||||
export default async function saveCollectionsToCloud(collections: CollectionItem[], timestamp: number): Promise<void>
|
||||
{
|
||||
if (!collections || collections.length < 1)
|
||||
try
|
||||
{
|
||||
await collectionStorage.chunkCount.setValue(0);
|
||||
await browser.storage.sync.remove(getChunkKeys());
|
||||
return;
|
||||
if (!collections || collections.length < 1)
|
||||
{
|
||||
await browser.storage.sync.set({
|
||||
[getStorageKey(collectionStorage.chunkCount)]: 0,
|
||||
[getStorageKey(collectionStorage.syncLastUpdated)]: timestamp
|
||||
});
|
||||
await browser.storage.sync.remove(getChunkKeys());
|
||||
return;
|
||||
}
|
||||
|
||||
const data: string = compress(serializeCollections(collections), { outputEncoding: "Base64" });
|
||||
const chunks: string[] = splitIntoChunks(data);
|
||||
|
||||
if (chunks.length > collectionStorage.maxChunkCount)
|
||||
throw new Error("Data is too large to be stored in sync storage.");
|
||||
|
||||
// Since there's a limit for cloud write operations, we need to write all chunks in one go.
|
||||
const newRecords: Record<string, string | number> =
|
||||
{
|
||||
[getStorageKey(collectionStorage.chunkCount)]: chunks.length,
|
||||
[getStorageKey(collectionStorage.syncLastUpdated)]: timestamp
|
||||
};
|
||||
|
||||
for (let i = 0; i < chunks.length; i++)
|
||||
newRecords[`c${i}`] = chunks[i];
|
||||
|
||||
await browser.storage.sync.set(newRecords);
|
||||
|
||||
if (chunks.length < collectionStorage.maxChunkCount)
|
||||
await browser.storage.sync.remove(getChunkKeys(chunks.length));
|
||||
}
|
||||
|
||||
const data: string = compress(serializeCollections(collections), { outputEncoding: "Base64" });
|
||||
const chunks: string[] = splitIntoChunks(data);
|
||||
|
||||
if (chunks.length > collectionStorage.maxChunkCount)
|
||||
throw new Error("Data is too large to be stored in sync storage.");
|
||||
|
||||
// Since there's a limit for cloud write operations, we need to write all chunks in one go.
|
||||
const newRecords: Record<string, string | number> =
|
||||
catch (ex)
|
||||
{
|
||||
[getStorageKey(collectionStorage.chunkCount)]: chunks.length,
|
||||
[getStorageKey(collectionStorage.syncLastUpdated)]: timestamp
|
||||
};
|
||||
logger("Failed to save cloud storage");
|
||||
console.error(ex);
|
||||
trackError("cloud_save_error", ex as Error);
|
||||
|
||||
for (let i = 0; i < chunks.length; i++)
|
||||
newRecords[`c${i}`] = chunks[i];
|
||||
|
||||
await browser.storage.sync.set(newRecords);
|
||||
|
||||
if (chunks.length < collectionStorage.maxChunkCount)
|
||||
await browser.storage.sync.remove(getChunkKeys(chunks.length));
|
||||
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[]
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -82,6 +82,7 @@ options_page:
|
||||
show_delete_prompt: "Спрашивать подтверждение при удалении элементов"
|
||||
show_badge: "Показывать счетчик"
|
||||
show_notification: "Показывать уведомление при сохранении через контекстное меню"
|
||||
show_partial_save_notification: "Показывать уведомление, если некоторые вкладки не были сохранены"
|
||||
unload_tabs: "Не загружать вкладки после открытия"
|
||||
allow_analytics: "Разрешить сбор анонимной статистики"
|
||||
list_locations:
|
||||
|
||||
@@ -82,6 +82,7 @@ options_page:
|
||||
show_delete_prompt: "Запитувати підтвердження при видаленні елементів"
|
||||
show_badge: "Показувати лічильник"
|
||||
show_notification: "Показувати сповіщення при збереженні через контекстне меню"
|
||||
show_partial_save_notification: "Показувати сповіщення, якщо деякі вкладки не були збережені"
|
||||
unload_tabs: "Не завантажувати вкладки після відкриття"
|
||||
allow_analytics: "Дозволити збір анонімної статистики"
|
||||
list_locations:
|
||||
|
||||
@@ -82,6 +82,7 @@ options_page:
|
||||
show_delete_prompt: "删除项目时要求确认"
|
||||
show_badge: "显示计数角标"
|
||||
show_notification: "使用上下文菜单保存标签页时显示通知"
|
||||
show_partial_save_notification: "如果某些标签页无法保存则显示通知"
|
||||
unload_tabs: "打开后不加载标签页"
|
||||
allow_analytics: "允许收集匿名统计数据"
|
||||
list_locations:
|
||||
|
||||
+14
-14
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "tabs-aside",
|
||||
"private": true,
|
||||
"version": "3.1.1",
|
||||
"version": "3.2.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "wxt",
|
||||
@@ -16,30 +16,30 @@
|
||||
"@dnd-kit/modifiers": "^9.0.0",
|
||||
"@dnd-kit/sortable": "^10.0.0",
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
"@fluentui/react-components": "^9.72.0",
|
||||
"@fluentui/react-icons": "^2.0.311",
|
||||
"@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": "~19.2.0",
|
||||
"react-dom": "~19.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/css": "^0.11.1",
|
||||
"@eslint/js": "^9.37.0",
|
||||
"@eslint/json": "^0.13.2",
|
||||
"@stylistic/eslint-plugin": "^5.4.0",
|
||||
"@types/react": "~19.2.0",
|
||||
"@types/react-dom": "~19.2.0",
|
||||
"@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.37.0",
|
||||
"eslint": "^9.39.1",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"globals": "^16.4.0",
|
||||
"globals": "^16.5.0",
|
||||
"scheduler": "0.23.0",
|
||||
"typescript": "^5.9.3",
|
||||
"typescript-eslint": "^8.45.0",
|
||||
"vite": "^7.1.9",
|
||||
"typescript-eslint": "^8.46.4",
|
||||
"vite": "^7.2.2",
|
||||
"wxt": "~0.19.29"
|
||||
},
|
||||
"packageManager": "yarn@4.9.2"
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import { Tabs } from "wxt/browser";
|
||||
|
||||
export async function closeTabsAsync(tabs: 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!));
|
||||
}
|
||||
@@ -1,69 +1,17 @@
|
||||
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>
|
||||
export async function createCollectionFromTabs(tabs: Tabs.Tab[]): 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: []
|
||||
};
|
||||
|
||||
if (tabs.length < 1)
|
||||
return collection;
|
||||
|
||||
let tabIndex: number = 0;
|
||||
|
||||
if (tabs[tabIndex].pinned)
|
||||
@@ -96,7 +44,7 @@ async function createCollectionFromTabs(tabs: Tabs.Tab[]): Promise<[CollectionIt
|
||||
collection.items.push({ type: "tab", url: i.url!, title: i.title })
|
||||
);
|
||||
|
||||
return [collection, tabs];
|
||||
return collection;
|
||||
}
|
||||
|
||||
let activeGroup: number | null = null;
|
||||
@@ -132,5 +80,5 @@ async function createCollectionFromTabs(tabs: Tabs.Tab[]): Promise<[CollectionIt
|
||||
});
|
||||
}
|
||||
|
||||
return [collection, tabs];
|
||||
return collection;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { Tabs } from "wxt/browser";
|
||||
import { settings } from "./settings";
|
||||
|
||||
export async function getTabsToSaveAsync(): Promise<[Tabs.Tab[], number]>
|
||||
{
|
||||
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 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];
|
||||
}
|
||||
@@ -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"
|
||||
});
|
||||
}
|
||||
@@ -95,5 +95,13 @@ export const settings = {
|
||||
fallback: true,
|
||||
version: 1
|
||||
}
|
||||
),
|
||||
|
||||
showPartialSaveNotification: storage.defineItem<boolean>(
|
||||
"sync:showPartialSaveNotification",
|
||||
{
|
||||
fallback: true,
|
||||
version: 1
|
||||
}
|
||||
)
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user