mirror of
https://github.com/XFox111/TabsAsideExtension.git
synced 2026-07-02 19:52:47 +03:00
Compare commits
9 Commits
v3.2.0
..
54844c54d5
| Author | SHA1 | Date | |
|---|---|---|---|
| 54844c54d5 | |||
| 46f613f295 | |||
| 01340f6aef | |||
| b4a454f463 | |||
| 8d9864b276 | |||
| 58d8e864e0 | |||
| fdac0c0766 | |||
| 2065ee4637 | |||
| b51dd6083f |
@@ -22,5 +22,5 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
"postCreateCommand": "yarn install"
|
"postCreateCommand": "npm install"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,7 +52,10 @@ updates:
|
|||||||
schedule:
|
schedule:
|
||||||
interval: monthly
|
interval: monthly
|
||||||
rebase-strategy: disabled
|
rebase-strategy: disabled
|
||||||
open-pull-requests-limit: 20
|
groups:
|
||||||
|
actions:
|
||||||
|
patterns:
|
||||||
|
- "*"
|
||||||
|
|
||||||
- package-ecosystem: "devcontainers"
|
- package-ecosystem: "devcontainers"
|
||||||
directory: "/"
|
directory: "/"
|
||||||
@@ -62,4 +65,7 @@ updates:
|
|||||||
schedule:
|
schedule:
|
||||||
interval: monthly
|
interval: monthly
|
||||||
rebase-strategy: disabled
|
rebase-strategy: disabled
|
||||||
open-pull-requests-limit: 20
|
groups:
|
||||||
|
devcontainers:
|
||||||
|
patterns:
|
||||||
|
- "*"
|
||||||
|
|||||||
@@ -51,8 +51,7 @@ jobs:
|
|||||||
echo "WXT_GA4_API_SECRET=${{ secrets.GA4_SECRET }}" >> .env
|
echo "WXT_GA4_API_SECRET=${{ secrets.GA4_SECRET }}" >> .env
|
||||||
echo "WXT_GA4_MEASUREMENT_ID=${{ secrets.GA4_MEASUREMENT_ID }}" >> .env
|
echo "WXT_GA4_MEASUREMENT_ID=${{ secrets.GA4_MEASUREMENT_ID }}" >> .env
|
||||||
|
|
||||||
- run: corepack enable
|
- run: npm install
|
||||||
- run: yarn install
|
|
||||||
|
|
||||||
# Patch for firefox dnd popup (see https://github.com/clauderic/dnd-kit/issues/1043)
|
# Patch for firefox dnd popup (see https://github.com/clauderic/dnd-kit/issues/1043)
|
||||||
- run: grep -v "this.windowListeners.add(EventName.Resize, this.handleCancel);" core.esm.js > core.esm.js.tmp && mv core.esm.js.tmp core.esm.js
|
- run: grep -v "this.windowListeners.add(EventName.Resize, this.handleCancel);" core.esm.js > core.esm.js.tmp && mv core.esm.js.tmp core.esm.js
|
||||||
@@ -64,7 +63,7 @@ jobs:
|
|||||||
working-directory: ./node_modules/@wxt-dev/analytics/dist
|
working-directory: ./node_modules/@wxt-dev/analytics/dist
|
||||||
if: ${{ matrix.target == 'firefox' }}
|
if: ${{ matrix.target == 'firefox' }}
|
||||||
|
|
||||||
- run: yarn zip -b ${{ matrix.target }}
|
- run: npm run zip -- -b ${{ matrix.target }}
|
||||||
|
|
||||||
- name: Drop build artifacts (${{ matrix.target }})
|
- name: Drop build artifacts (${{ matrix.target }})
|
||||||
uses: actions/upload-artifact@main
|
uses: actions/upload-artifact@main
|
||||||
@@ -81,7 +80,7 @@ jobs:
|
|||||||
source: ./.output/firefox-mv3
|
source: ./.output/firefox-mv3
|
||||||
channel: listed
|
channel: listed
|
||||||
|
|
||||||
- run: yarn npm audit
|
- run: npm audit
|
||||||
continue-on-error: ${{ github.event_name != 'release' && github.event.inputs.bypass_audit == 'true' }}
|
continue-on-error: ${{ github.event_name != 'release' && github.event.inputs.bypass_audit == 'true' }}
|
||||||
|
|
||||||
publish-github:
|
publish-github:
|
||||||
|
|||||||
@@ -43,8 +43,7 @@ jobs:
|
|||||||
echo "WXT_GA4_API_SECRET=${{ secrets.GA4_SECRET }}" >> .env
|
echo "WXT_GA4_API_SECRET=${{ secrets.GA4_SECRET }}" >> .env
|
||||||
echo "WXT_GA4_MEASUREMENT_ID=${{ secrets.GA4_MEASUREMENT_ID }}" >> .env
|
echo "WXT_GA4_MEASUREMENT_ID=${{ secrets.GA4_MEASUREMENT_ID }}" >> .env
|
||||||
|
|
||||||
- run: corepack enable
|
- run: npm install
|
||||||
- run: yarn install
|
|
||||||
|
|
||||||
# Patch for firefox dnd popup (see https://github.com/clauderic/dnd-kit/issues/1043)
|
# Patch for firefox dnd popup (see https://github.com/clauderic/dnd-kit/issues/1043)
|
||||||
- run: grep -v "this.windowListeners.add(EventName.Resize, this.handleCancel);" core.esm.js > core.esm.js.tmp && mv core.esm.js.tmp core.esm.js
|
- run: grep -v "this.windowListeners.add(EventName.Resize, this.handleCancel);" core.esm.js > core.esm.js.tmp && mv core.esm.js.tmp core.esm.js
|
||||||
@@ -56,7 +55,7 @@ jobs:
|
|||||||
working-directory: ./node_modules/@wxt-dev/analytics/dist
|
working-directory: ./node_modules/@wxt-dev/analytics/dist
|
||||||
if: ${{ matrix.target == 'firefox' }}
|
if: ${{ matrix.target == 'firefox' }}
|
||||||
|
|
||||||
- run: yarn zip -b ${{ matrix.target }}
|
- run: npm run zip -- -b ${{ matrix.target }}
|
||||||
|
|
||||||
- name: Drop artifacts (${{ matrix.target }})
|
- name: Drop artifacts (${{ matrix.target }})
|
||||||
uses: actions/upload-artifact@main
|
uses: actions/upload-artifact@main
|
||||||
@@ -73,4 +72,4 @@ jobs:
|
|||||||
source: ./.output/firefox-mv3
|
source: ./.output/firefox-mv3
|
||||||
channel: listed
|
channel: listed
|
||||||
|
|
||||||
- run: yarn npm audit
|
- run: npm audit
|
||||||
|
|||||||
-117
@@ -1,117 +0,0 @@
|
|||||||
nodeLinker: node-modules
|
|
||||||
|
|
||||||
packageExtensions:
|
|
||||||
"@wxt-dev/module-react@*":
|
|
||||||
peerDependencies:
|
|
||||||
vite: "*"
|
|
||||||
"@fluentui/react-accordion@*":
|
|
||||||
peerDependencies:
|
|
||||||
scheduler: "0.23.0"
|
|
||||||
"@fluentui/react-avatar@*":
|
|
||||||
peerDependencies:
|
|
||||||
scheduler: "0.23.0"
|
|
||||||
"@fluentui/react-carousel@*":
|
|
||||||
peerDependencies:
|
|
||||||
scheduler: "0.23.0"
|
|
||||||
"@fluentui/react-color-picker@*":
|
|
||||||
peerDependencies:
|
|
||||||
scheduler: "0.23.0"
|
|
||||||
"@fluentui/react-combobox@*":
|
|
||||||
peerDependencies:
|
|
||||||
scheduler: "0.23.0"
|
|
||||||
"@fluentui/react-dialog@*":
|
|
||||||
peerDependencies:
|
|
||||||
scheduler: "0.23.0"
|
|
||||||
"@fluentui/react-field@*":
|
|
||||||
peerDependencies:
|
|
||||||
scheduler: "0.23.0"
|
|
||||||
"@fluentui/react-list@*":
|
|
||||||
peerDependencies:
|
|
||||||
scheduler: "0.23.0"
|
|
||||||
"@fluentui/react-menu@*":
|
|
||||||
peerDependencies:
|
|
||||||
scheduler: "0.23.0"
|
|
||||||
"@fluentui/react-nav@*":
|
|
||||||
peerDependencies:
|
|
||||||
scheduler: "0.23.0"
|
|
||||||
"@fluentui/react-overflow@*":
|
|
||||||
peerDependencies:
|
|
||||||
scheduler: "0.23.0"
|
|
||||||
"@fluentui/react-popover@*":
|
|
||||||
peerDependencies:
|
|
||||||
scheduler: "0.23.0"
|
|
||||||
"@fluentui/react-swatch-picker@*":
|
|
||||||
peerDependencies:
|
|
||||||
scheduler: "0.23.0"
|
|
||||||
"@fluentui/react-table@*":
|
|
||||||
peerDependencies:
|
|
||||||
scheduler: "0.23.0"
|
|
||||||
"@fluentui/react-tabs@*":
|
|
||||||
peerDependencies:
|
|
||||||
scheduler: "0.23.0"
|
|
||||||
"@fluentui/react-tag-picker@*":
|
|
||||||
peerDependencies:
|
|
||||||
scheduler: "0.23.0"
|
|
||||||
"@fluentui/react-teaching-popover@*":
|
|
||||||
peerDependencies:
|
|
||||||
scheduler: "0.23.0"
|
|
||||||
"@fluentui/react-toolbar@*":
|
|
||||||
peerDependencies:
|
|
||||||
scheduler: "0.23.0"
|
|
||||||
"@fluentui/react-tree@*":
|
|
||||||
peerDependencies:
|
|
||||||
scheduler: "0.23.0"
|
|
||||||
"@fluentui/react-alert@*":
|
|
||||||
peerDependencies:
|
|
||||||
scheduler: "0.23.0"
|
|
||||||
"@fluentui/react-checkbox@*":
|
|
||||||
peerDependencies:
|
|
||||||
scheduler: "0.23.0"
|
|
||||||
"@fluentui/react-components@*":
|
|
||||||
peerDependencies:
|
|
||||||
scheduler: "0.23.0"
|
|
||||||
"@fluentui/react-drawer@*":
|
|
||||||
peerDependencies:
|
|
||||||
scheduler: "0.23.0"
|
|
||||||
"@fluentui/react-infobutton@*":
|
|
||||||
peerDependencies:
|
|
||||||
scheduler: "0.23.0"
|
|
||||||
"@fluentui/react-infolabel@*":
|
|
||||||
peerDependencies:
|
|
||||||
scheduler: "0.23.0"
|
|
||||||
"@fluentui/react-input@*":
|
|
||||||
peerDependencies:
|
|
||||||
scheduler: "0.23.0"
|
|
||||||
"@fluentui/react-persona@*":
|
|
||||||
peerDependencies:
|
|
||||||
scheduler: "0.23.0"
|
|
||||||
"@fluentui/react-progress@*":
|
|
||||||
peerDependencies:
|
|
||||||
scheduler: "0.23.0"
|
|
||||||
"@fluentui/react-radio@*":
|
|
||||||
peerDependencies:
|
|
||||||
scheduler: "0.23.0"
|
|
||||||
"@fluentui/react-select@*":
|
|
||||||
peerDependencies:
|
|
||||||
scheduler: "0.23.0"
|
|
||||||
"@fluentui/react-skeleton@*":
|
|
||||||
peerDependencies:
|
|
||||||
scheduler: "0.23.0"
|
|
||||||
"@fluentui/react-slider@*":
|
|
||||||
peerDependencies:
|
|
||||||
scheduler: "0.23.0"
|
|
||||||
"@fluentui/react-spinbutton@*":
|
|
||||||
peerDependencies:
|
|
||||||
scheduler: "0.23.0"
|
|
||||||
"@fluentui/react-switch@*":
|
|
||||||
peerDependencies:
|
|
||||||
scheduler: "0.23.0"
|
|
||||||
"@fluentui/react-tags@*":
|
|
||||||
peerDependencies:
|
|
||||||
scheduler: "0.23.0"
|
|
||||||
"@fluentui/react-textarea@*":
|
|
||||||
peerDependencies:
|
|
||||||
scheduler: "0.23.0"
|
|
||||||
"@fluentui/react-search@*":
|
|
||||||
peerDependencies:
|
|
||||||
scheduler: "0.23.0"
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2025 Eugene Fox
|
Copyright (c) 2026 Eugene Fox
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
@@ -97,4 +97,4 @@ If you are interested in fixing issues and contributing directly to the code bas
|
|||||||
[](https://github.com/xfox111)
|
[](https://github.com/xfox111)
|
||||||
[](https://buymeacoffee.com/xfox111)
|
[](https://buymeacoffee.com/xfox111)
|
||||||
|
|
||||||
> ©2025 Eugene Fox. Licensed under [MIT license](https://github.com/XFox111/TabsAsideExtension/blob/main/LICENSE)
|
> ©2026 Eugene Fox. Licensed under [MIT license](https://github.com/XFox111/TabsAsideExtension/blob/main/LICENSE)
|
||||||
|
|||||||
+1
-1
@@ -20,6 +20,6 @@ export const githubLinks =
|
|||||||
export const storeLink: string =
|
export const storeLink: string =
|
||||||
import.meta.env.FIREFOX
|
import.meta.env.FIREFOX
|
||||||
? "https://addons.mozilla.org/en-US/firefox/addon/ms-edge-tabs-aside/" :
|
? "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://microsoftedge.microsoft.com/addons/detail/tabs-aside/kmnblllmalkiapkfknnlpobmjjdnlhnd" :
|
||||||
"https://chromewebstore.google.com/detail/tabs-aside/mgmjbodjgijnebfgohlnjkegdpbdjgin";
|
"https://chromewebstore.google.com/detail/tabs-aside/mgmjbodjgijnebfgohlnjkegdpbdjgin";
|
||||||
|
|||||||
+26
-24
@@ -1,26 +1,25 @@
|
|||||||
import { track, trackError } from "@/features/analytics";
|
import { track, trackError } from "@/features/analytics";
|
||||||
import { collectionCount, getCollections, saveCollections, thumbnailCaptureEnabled } 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 { migrateStorage } from "@/features/migration";
|
||||||
import { setSettingsReviewNeeded } from "@/features/settingsReview/utils";
|
import { setSettingsReviewNeeded } from "@/features/settingsReview/utils";
|
||||||
import { showWelcomeDialog } from "@/features/v3welcome/utils/showWelcomeDialog";
|
import { showWelcomeDialog } from "@/features/v3welcome/utils/showWelcomeDialog";
|
||||||
import { SettingsValue } from "@/hooks/useSettings";
|
import { SettingsValue } from "@/hooks/useSettings";
|
||||||
import { CollectionItem, GraphicsStorage } from "@/models/CollectionModels";
|
import { CollectionItem, GraphicsStorage } from "@/models/CollectionModels";
|
||||||
|
import { closeTabsAsync } from "@/utils/closeTabsAsync";
|
||||||
|
import { createCollectionFromTabs } from "@/utils/createCollectionFromTabs";
|
||||||
import getLogger from "@/utils/getLogger";
|
import getLogger from "@/utils/getLogger";
|
||||||
|
import { getTabsToSaveAsync } from "@/utils/getTabsToSaveAsync";
|
||||||
import { onMessage, sendMessage } from "@/utils/messaging";
|
import { onMessage, sendMessage } from "@/utils/messaging";
|
||||||
import sendNotification from "@/utils/sendNotification";
|
import sendNotification from "@/utils/sendNotification";
|
||||||
import sendPartialSaveNotification from "@/utils/sendPartialSaveNotification";
|
import sendPartialSaveNotification from "@/utils/sendPartialSaveNotification";
|
||||||
import { settings } from "@/utils/settings";
|
import { settings } from "@/utils/settings";
|
||||||
import watchTabSelection from "@/utils/watchTabSelection";
|
import watchTabSelection from "@/utils/watchTabSelection";
|
||||||
import { RemoveListenerCallback } from "@webext-core/messaging";
|
import { RemoveListenerCallback } from "@webext-core/messaging";
|
||||||
import { Tabs, Windows } from "wxt/browser";
|
import { Unwatch } from "wxt/utils/storage";
|
||||||
import { Unwatch } from "wxt/storage";
|
|
||||||
import { openCollection, openGroup } from "./sidepanel/utils/opener";
|
import { openCollection, openGroup } from "./sidepanel/utils/opener";
|
||||||
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(() =>
|
export default defineBackground(() =>
|
||||||
{
|
{
|
||||||
@@ -99,7 +98,7 @@ export default defineBackground(() =>
|
|||||||
let unwatchAddThumbnail: RemoveListenerCallback | null = null;
|
let unwatchAddThumbnail: RemoveListenerCallback | null = null;
|
||||||
let captureInterval: NodeJS.Timeout | 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)
|
if (!tab.url)
|
||||||
return;
|
return;
|
||||||
@@ -111,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)
|
if (!tab.url || tab.status !== "complete" || !tab.active)
|
||||||
return;
|
return;
|
||||||
@@ -123,7 +122,7 @@ export default defineBackground(() =>
|
|||||||
{
|
{
|
||||||
// We use chrome here because polyfill throws uncatchable errors for some reason
|
// We use chrome here because polyfill throws uncatchable errors for some reason
|
||||||
// It's a compatible API anyway
|
// 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)
|
if (capture)
|
||||||
{
|
{
|
||||||
@@ -287,6 +286,7 @@ export default defineBackground(() =>
|
|||||||
};
|
};
|
||||||
|
|
||||||
const toggleSidebarFirefox = async (): Promise<void> =>
|
const toggleSidebarFirefox = async (): Promise<void> =>
|
||||||
|
// @ts-expect-error Firefox-only API
|
||||||
await browser.sidebarAction.toggle();
|
await browser.sidebarAction.toggle();
|
||||||
|
|
||||||
const updateButton = async (action: SettingsValue<"contextAction">): Promise<void> =>
|
const updateButton = async (action: SettingsValue<"contextAction">): Promise<void> =>
|
||||||
@@ -303,7 +303,7 @@ export default defineBackground(() =>
|
|||||||
unwatchActionTitle?.();
|
unwatchActionTitle?.();
|
||||||
|
|
||||||
if (!import.meta.env.FIREFOX)
|
if (!import.meta.env.FIREFOX)
|
||||||
await chrome.sidePanel.setPanelBehavior({ openPanelOnActionClick: false });
|
await browser.sidePanel.setPanelBehavior({ openPanelOnActionClick: false });
|
||||||
|
|
||||||
// Setup new behavior
|
// Setup new behavior
|
||||||
if (action === "action")
|
if (action === "action")
|
||||||
@@ -322,7 +322,7 @@ export default defineBackground(() =>
|
|||||||
if (import.meta.env.FIREFOX)
|
if (import.meta.env.FIREFOX)
|
||||||
browser.action.onClicked.addListener(toggleSidebarFirefox);
|
browser.action.onClicked.addListener(toggleSidebarFirefox);
|
||||||
else
|
else
|
||||||
chrome.sidePanel.setPanelBehavior({ openPanelOnActionClick: true });
|
browser.sidePanel.setPanelBehavior({ openPanelOnActionClick: true });
|
||||||
}
|
}
|
||||||
else if (location !== "popup")
|
else if (location !== "popup")
|
||||||
browser.action.onClicked.addListener(openCollectionsInTab);
|
browser.action.onClicked.addListener(openCollectionsInTab);
|
||||||
@@ -341,17 +341,17 @@ export default defineBackground(() =>
|
|||||||
{
|
{
|
||||||
logger("enforcePinnedTab");
|
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)
|
for (const openWindow of openWindows)
|
||||||
{
|
{
|
||||||
if (openWindow.incognito || openWindow.type !== "normal")
|
if (openWindow.incognito || openWindow.type !== "normal")
|
||||||
continue;
|
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"));
|
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)
|
if (!targetTab)
|
||||||
await browser.tabs.create({
|
await browser.tabs.create({
|
||||||
@@ -361,7 +361,7 @@ export default defineBackground(() =>
|
|||||||
pinned: true
|
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)
|
if (tabsToClose.length > 0)
|
||||||
await browser.tabs.remove(tabsToClose.map(tab => tab.id!));
|
await browser.tabs.remove(tabsToClose.map(tab => tab.id!));
|
||||||
@@ -373,7 +373,7 @@ export default defineBackground(() =>
|
|||||||
logger("updateView", viewLocation);
|
logger("updateView", viewLocation);
|
||||||
|
|
||||||
browser.tabs.onHighlighted.removeListener(enforcePinnedTab);
|
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")
|
url: browser.runtime.getURL("/sidepanel.html")
|
||||||
});
|
});
|
||||||
await browser.tabs.remove(tabs.map(tab => tab.id!));
|
await browser.tabs.remove(tabs.map(tab => tab.id!));
|
||||||
@@ -383,11 +383,12 @@ export default defineBackground(() =>
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (import.meta.env.FIREFOX)
|
if (import.meta.env.FIREFOX)
|
||||||
|
// @ts-expect-error Firefox-only API
|
||||||
await browser.sidebarAction.setPanel({
|
await browser.sidebarAction.setPanel({
|
||||||
panel: viewLocation === "sidebar" ? browser.runtime.getURL("/sidepanel.html") : ""
|
panel: viewLocation === "sidebar" ? browser.runtime.getURL("/sidepanel.html") : ""
|
||||||
});
|
});
|
||||||
else
|
else
|
||||||
await chrome.sidePanel.setOptions({ enabled: viewLocation === "sidebar" });
|
await browser.sidePanel.setOptions({ enabled: viewLocation === "sidebar" });
|
||||||
|
|
||||||
if (viewLocation === "pinned")
|
if (viewLocation === "pinned")
|
||||||
{
|
{
|
||||||
@@ -418,9 +419,10 @@ export default defineBackground(() =>
|
|||||||
if (view === "sidebar")
|
if (view === "sidebar")
|
||||||
{
|
{
|
||||||
if (import.meta.env.FIREFOX)
|
if (import.meta.env.FIREFOX)
|
||||||
|
// @ts-expect-error Firefox-only API
|
||||||
browser.sidebarAction.open();
|
browser.sidebarAction.open();
|
||||||
else
|
else
|
||||||
chrome.sidePanel.open({ windowId });
|
browser.sidePanel.open({ windowId });
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
browser.action.openPopup();
|
browser.action.openPopup();
|
||||||
@@ -430,11 +432,11 @@ export default defineBackground(() =>
|
|||||||
{
|
{
|
||||||
logger("openCollectionsInTab");
|
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)
|
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 =>
|
availableWindows = availableWindows.filter(window =>
|
||||||
!window.incognito &&
|
!window.incognito &&
|
||||||
@@ -443,7 +445,7 @@ export default defineBackground(() =>
|
|||||||
|
|
||||||
if (availableWindows.length > 0)
|
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")
|
tab => tab.url === browser.runtime.getURL("/sidepanel.html")
|
||||||
)!;
|
)!;
|
||||||
|
|
||||||
@@ -460,7 +462,7 @@ export default defineBackground(() =>
|
|||||||
}
|
}
|
||||||
else
|
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")
|
tab => tab.url === browser.runtime.getURL("/sidepanel.html")
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -41,5 +41,11 @@ export const useOptionsStyles = makeStyles({
|
|||||||
flexFlow: "column",
|
flexFlow: "column",
|
||||||
alignItems: "flex-start",
|
alignItems: "flex-start",
|
||||||
gap: tokens.spacingVerticalSNudge
|
gap: tokens.spacingVerticalSNudge
|
||||||
|
},
|
||||||
|
img:
|
||||||
|
{
|
||||||
|
height: "100px",
|
||||||
|
flexGrow: 1,
|
||||||
|
alignSelf: "flex-end"
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { BuyMeACoffee20Regular } from "@/assets/BuyMeACoffee20";
|
|||||||
import { bskyLink, buyMeACoffeeLink, githubLinks, storeLink, websiteLink } from "@/data/links";
|
import { bskyLink, buyMeACoffeeLink, githubLinks, storeLink, websiteLink } from "@/data/links";
|
||||||
import { useBmcStyles } from "@/hooks/useBmcStyles";
|
import { useBmcStyles } from "@/hooks/useBmcStyles";
|
||||||
import extLink from "@/utils/extLink";
|
import extLink from "@/utils/extLink";
|
||||||
import { Body1, Button, Caption1, Link, Subtitle1, Text } from "@fluentui/react-components";
|
import { Body1, Button, Caption1, Image, Link, Subtitle1, Text } from "@fluentui/react-components";
|
||||||
import { PersonFeedback20Regular } from "@fluentui/react-icons";
|
import { PersonFeedback20Regular } from "@fluentui/react-icons";
|
||||||
import { useOptionsStyles } from "../hooks/useOptionsStyles";
|
import { useOptionsStyles } from "../hooks/useOptionsStyles";
|
||||||
import Package from "@/package.json";
|
import Package from "@/package.json";
|
||||||
@@ -19,25 +19,6 @@ export default function AboutSection(): React.ReactElement
|
|||||||
<sup><Caption1> v{ Package.version }</Caption1></sup>
|
<sup><Caption1> v{ Package.version }</Caption1></sup>
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<Body1 as="p">
|
|
||||||
{ i18n.t("options_page.about.developed_by") } (<Link { ...extLink(bskyLink) }>@xfox111.net</Link>)<br />
|
|
||||||
{ i18n.t("options_page.about.licensed_under") } <Link { ...extLink(githubLinks.license) }>{ i18n.t("options_page.about.mit_license") }</Link>
|
|
||||||
</Body1>
|
|
||||||
|
|
||||||
<Body1 as="p">
|
|
||||||
{ i18n.t("options_page.about.translation_cta.text") }<br />
|
|
||||||
<Link { ...extLink(githubLinks.translationGuide) }>
|
|
||||||
{ i18n.t("options_page.about.translation_cta.button") }
|
|
||||||
</Link>
|
|
||||||
</Body1>
|
|
||||||
|
|
||||||
<Body1 as="p">
|
|
||||||
<Link { ...extLink(websiteLink) }>{ i18n.t("options_page.about.links.website") }</Link><br />
|
|
||||||
<Link { ...extLink(githubLinks.repo) }>{ i18n.t("options_page.about.links.source") }</Link><br />
|
|
||||||
<Link { ...extLink(githubLinks.release) }>{ i18n.t("options_page.about.links.changelog") }</Link><br />
|
|
||||||
<Link { ...extLink(githubLinks.privacy) }>{ i18n.t("options_page.about.links.privacy") }</Link>
|
|
||||||
</Body1>
|
|
||||||
|
|
||||||
<div className={ cls.horizontalButtons }>
|
<div className={ cls.horizontalButtons }>
|
||||||
<Button
|
<Button
|
||||||
as="a" { ...extLink(storeLink) }
|
as="a" { ...extLink(storeLink) }
|
||||||
@@ -54,6 +35,27 @@ export default function AboutSection(): React.ReactElement
|
|||||||
{ i18n.t("common.cta.sponsor") }
|
{ i18n.t("common.cta.sponsor") }
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<Body1 as="p">
|
||||||
|
{ i18n.t("options_page.about.translation_cta.text") }<br />
|
||||||
|
<Link { ...extLink(githubLinks.translationGuide) }>
|
||||||
|
{ i18n.t("options_page.about.translation_cta.button") }
|
||||||
|
</Link>
|
||||||
|
</Body1>
|
||||||
|
|
||||||
|
<Body1 as="p">
|
||||||
|
<Link { ...extLink(websiteLink) }>{ i18n.t("options_page.about.links.website") }</Link><br />
|
||||||
|
<Link { ...extLink(githubLinks.repo) }>{ i18n.t("options_page.about.links.source") }</Link><br />
|
||||||
|
<Link { ...extLink(githubLinks.release) }>{ i18n.t("options_page.about.links.changelog") }</Link><br />
|
||||||
|
<Link { ...extLink(githubLinks.privacy) }>{ i18n.t("options_page.about.links.privacy") }</Link>
|
||||||
|
</Body1>
|
||||||
|
|
||||||
|
<Body1 as="p">
|
||||||
|
{ i18n.t("options_page.about.developed_by") } (<Link { ...extLink(bskyLink) }>@xfox111.net</Link>)<br />
|
||||||
|
{ i18n.t("options_page.about.licensed_under") } <Link { ...extLink(githubLinks.license) }>{ i18n.t("options_page.about.mit_license") }</Link>
|
||||||
|
</Body1>
|
||||||
|
|
||||||
|
<Image className={ cls.img } src="/fox.svg" />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
|
import { analyticsPermission } from "@/features/analytics";
|
||||||
import useSettings, { SettingsValue } from "@/hooks/useSettings";
|
import useSettings, { SettingsValue } from "@/hooks/useSettings";
|
||||||
import { Button, Checkbox, Dropdown, Field, Option, OptionOnSelectData } from "@fluentui/react-components";
|
import { Button, Checkbox, Dropdown, Field, Option, OptionOnSelectData } from "@fluentui/react-components";
|
||||||
import { KeyCommand20Regular } from "@fluentui/react-icons";
|
import { KeyCommand20Regular } from "@fluentui/react-icons";
|
||||||
import { useOptionsStyles } from "../hooks/useOptionsStyles";
|
import { useOptionsStyles } from "../hooks/useOptionsStyles";
|
||||||
import { analyticsPermission } from "@/features/analytics";
|
|
||||||
|
|
||||||
export default function GeneralSection(): React.ReactElement
|
export default function GeneralSection(): React.ReactElement
|
||||||
{
|
{
|
||||||
@@ -45,6 +45,7 @@ export default function GeneralSection(): React.ReactElement
|
|||||||
setContextAction("open");
|
setContextAction("open");
|
||||||
|
|
||||||
if (import.meta.env.FIREFOX && e.optionValue !== "sidebar")
|
if (import.meta.env.FIREFOX && e.optionValue !== "sidebar")
|
||||||
|
// @ts-expect-error Firefox-only API
|
||||||
browser.sidebarAction.close();
|
browser.sidebarAction.close();
|
||||||
|
|
||||||
setListLocation(e.optionValue as ListLocationType);
|
setListLocation(e.optionValue as ListLocationType);
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ import { useDangerStyles } from "@/hooks/useDangerStyles";
|
|||||||
import useStorageInfo from "@/hooks/useStorageInfo";
|
import useStorageInfo from "@/hooks/useStorageInfo";
|
||||||
import { Button, Field, InfoLabel, LabelProps, MessageBar, MessageBarBody, MessageBarTitle, ProgressBar, Switch } from "@fluentui/react-components";
|
import { Button, Field, InfoLabel, LabelProps, MessageBar, MessageBarBody, MessageBarTitle, ProgressBar, Switch } from "@fluentui/react-components";
|
||||||
import { ArrowDownload20Regular, ArrowUpload20Regular } from "@fluentui/react-icons";
|
import { ArrowDownload20Regular, ArrowUpload20Regular } from "@fluentui/react-icons";
|
||||||
|
import { Unwatch } from "wxt/utils/storage";
|
||||||
import { useOptionsStyles } from "../hooks/useOptionsStyles";
|
import { useOptionsStyles } from "../hooks/useOptionsStyles";
|
||||||
import exportData from "../utils/exportData";
|
import exportData from "../utils/exportData";
|
||||||
import importData from "../utils/importData";
|
import importData from "../utils/importData";
|
||||||
import { Unwatch } from "wxt/storage";
|
|
||||||
|
|
||||||
export default function StorageSection(): React.ReactElement
|
export default function StorageSection(): React.ReactElement
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -19,6 +19,11 @@ export const useStyles_CollectionView = makeStyles({
|
|||||||
"&:hover":
|
"&:hover":
|
||||||
{
|
{
|
||||||
boxShadow: tokens.shadow4
|
boxShadow: tokens.shadow4
|
||||||
|
},
|
||||||
|
|
||||||
|
"&:not(:focus-within) .compact":
|
||||||
|
{
|
||||||
|
display: "none"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
color:
|
color:
|
||||||
|
|||||||
@@ -12,7 +12,12 @@ import { useStyles_CollectionView } from "./CollectionView.styles";
|
|||||||
import GroupView from "./GroupView";
|
import GroupView from "./GroupView";
|
||||||
import TabView from "./TabView";
|
import TabView from "./TabView";
|
||||||
|
|
||||||
export default function CollectionView({ collection, index: collectionIndex, dragOverlay }: CollectionViewProps): ReactElement
|
export default function CollectionView({
|
||||||
|
collection,
|
||||||
|
index: collectionIndex,
|
||||||
|
dragOverlay,
|
||||||
|
compact
|
||||||
|
}: CollectionViewProps): ReactElement
|
||||||
{
|
{
|
||||||
const { tilesView } = useCollections();
|
const { tilesView } = useCollections();
|
||||||
const {
|
const {
|
||||||
@@ -53,12 +58,12 @@ export default function CollectionView({ collection, index: collectionIndex, dra
|
|||||||
{ (!activeItem || activeItem.item.type !== "collection") && !dragOverlay &&
|
{ (!activeItem || activeItem.item.type !== "collection") && !dragOverlay &&
|
||||||
<>
|
<>
|
||||||
{ collection.items.length < 1 ?
|
{ collection.items.length < 1 ?
|
||||||
<div className={ cls.empty }>
|
<div className={ mergeClasses(cls.empty, compact === true && "compact") }>
|
||||||
<CollectionsRegular fontSize={ 32 } />
|
<CollectionsRegular fontSize={ 32 } />
|
||||||
<Body1Strong>{ i18n.t("collections.empty") }</Body1Strong>
|
<Body1Strong>{ i18n.t("collections.empty") }</Body1Strong>
|
||||||
</div>
|
</div>
|
||||||
:
|
:
|
||||||
<div className={ mergeClasses(cls.list, !tilesView && cls.verticalList) }>
|
<div className={ mergeClasses(cls.list, !tilesView && cls.verticalList, compact === true && "compact") }>
|
||||||
<SortableContext
|
<SortableContext
|
||||||
items={ collection.items.map((_, index) => [collectionIndex, index].join("/")) }
|
items={ collection.items.map((_, index) => [collectionIndex, index].join("/")) }
|
||||||
strategy={ tilesView ? horizontalListSortingStrategy : verticalListSortingStrategy }
|
strategy={ tilesView ? horizontalListSortingStrategy : verticalListSortingStrategy }
|
||||||
@@ -66,9 +71,12 @@ export default function CollectionView({ collection, index: collectionIndex, dra
|
|||||||
{ collection.items.map((i, index) =>
|
{ collection.items.map((i, index) =>
|
||||||
i.type === "group" ?
|
i.type === "group" ?
|
||||||
<GroupView
|
<GroupView
|
||||||
key={ index } group={ i } indices={ [collectionIndex, index] } />
|
key={ index } group={ i } indices={ [collectionIndex, index] }
|
||||||
|
collectionId={ collection.timestamp } />
|
||||||
:
|
:
|
||||||
<TabView key={ index } tab={ i } indices={ [collectionIndex, index] } />
|
<TabView
|
||||||
|
key={ index } tab={ i } indices={ [collectionIndex, index] }
|
||||||
|
collectionId={ collection.timestamp } />
|
||||||
) }
|
) }
|
||||||
</SortableContext>
|
</SortableContext>
|
||||||
</div>
|
</div>
|
||||||
@@ -85,4 +93,5 @@ export type CollectionViewProps =
|
|||||||
collection: CollectionItem;
|
collection: CollectionItem;
|
||||||
index: number;
|
index: number;
|
||||||
dragOverlay?: boolean;
|
dragOverlay?: boolean;
|
||||||
|
compact?: boolean | null;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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.type === "collection"
|
||||||
? props.collection?.color :
|
? props.collection?.color :
|
||||||
props.group?.pinned === true ? "pinned" : (props.group?.color ?? "blue")
|
props.group?.pinned === true ? "pinned" : (props.group?.color ?? "blue")
|
||||||
@@ -87,7 +87,7 @@ export default function EditDialog(props: GroupEditDialogProps): ReactElement
|
|||||||
value={ color === "pinned" ? i18n.t("groups.pinned") : title }
|
value={ color === "pinned" ? i18n.t("groups.pinned") : title }
|
||||||
onChange={ (_, e) => setTitle(e.value) } />
|
onChange={ (_, e) => setTitle(e.value) } />
|
||||||
</fui.Field>
|
</fui.Field>
|
||||||
<fui.Field label="Color">
|
<fui.Field label={ i18n.t("dialogs.edit.color") }>
|
||||||
<div className={ cls.colorPicker } { ...horizontalNavigationAttributes }>
|
<div className={ cls.colorPicker } { ...horizontalNavigationAttributes }>
|
||||||
{ (props.type === "group" && (!props.hidePinned || props.group?.pinned)) &&
|
{ (props.type === "group" && (!props.hidePinned || props.group?.pinned)) &&
|
||||||
<fui.ToggleButton
|
<fui.ToggleButton
|
||||||
@@ -112,8 +112,8 @@ export default function EditDialog(props: GroupEditDialogProps): ReactElement
|
|||||||
{ Object.keys(colorCls).map(i =>
|
{ Object.keys(colorCls).map(i =>
|
||||||
<fui.ToggleButton
|
<fui.ToggleButton
|
||||||
checked={ color === i }
|
checked={ color === i }
|
||||||
onClick={ () => setColor(i as chrome.tabGroups.ColorEnum) }
|
onClick={ () => setColor(i as `${Browser.tabGroups.Color}`) }
|
||||||
className={ fui.mergeClasses(cls.colorButton, colorCls[i as chrome.tabGroups.ColorEnum]) }
|
className={ fui.mergeClasses(cls.colorButton, colorCls[i as `${Browser.tabGroups.Color}`]) }
|
||||||
icon={ {
|
icon={ {
|
||||||
className: cls.colorButton_icon,
|
className: cls.colorButton_icon,
|
||||||
children: <Circle20Filled />
|
children: <Circle20Filled />
|
||||||
@@ -121,7 +121,7 @@ export default function EditDialog(props: GroupEditDialogProps): ReactElement
|
|||||||
key={ i }
|
key={ i }
|
||||||
shape="circular"
|
shape="circular"
|
||||||
>
|
>
|
||||||
{ i18n.t(`colors.${i as chrome.tabGroups.ColorEnum}`) }
|
{ i18n.t(`colors.${i as `${Browser.tabGroups.Color}`}`) }
|
||||||
</fui.ToggleButton>
|
</fui.ToggleButton>
|
||||||
) }
|
) }
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import GroupMoreMenu from "./collections/GroupMoreMenu";
|
|||||||
import { useStyles_GroupView } from "./GroupView.styles";
|
import { useStyles_GroupView } from "./GroupView.styles";
|
||||||
import TabView from "./TabView";
|
import TabView from "./TabView";
|
||||||
|
|
||||||
export default function GroupView({ group, indices, dragOverlay }: GroupViewProps): ReactElement
|
export default function GroupView({ group, indices, dragOverlay, collectionId }: GroupViewProps): ReactElement
|
||||||
{
|
{
|
||||||
const [alwaysShowToolbars] = useSettings("alwaysShowToolbars");
|
const [alwaysShowToolbars] = useSettings("alwaysShowToolbars");
|
||||||
const { tilesView } = useCollections();
|
const { tilesView } = useCollections();
|
||||||
@@ -101,7 +101,9 @@ export default function GroupView({ group, indices, dragOverlay }: GroupViewProp
|
|||||||
strategy={ !tilesView ? verticalListSortingStrategy : horizontalListSortingStrategy }
|
strategy={ !tilesView ? verticalListSortingStrategy : horizontalListSortingStrategy }
|
||||||
>
|
>
|
||||||
{ group.items.map((i, index) =>
|
{ group.items.map((i, index) =>
|
||||||
<TabView key={ index } tab={ i } indices={ [...indices, index] } />
|
<TabView
|
||||||
|
key={ index } tab={ i } indices={ [...indices, index] }
|
||||||
|
collectionId={ collectionId } />
|
||||||
) }
|
) }
|
||||||
</SortableContext>
|
</SortableContext>
|
||||||
</div>
|
</div>
|
||||||
@@ -117,4 +119,5 @@ export type GroupViewProps =
|
|||||||
group: GroupItem;
|
group: GroupItem;
|
||||||
indices: number[];
|
indices: number[];
|
||||||
dragOverlay?: boolean;
|
dragOverlay?: boolean;
|
||||||
|
collectionId: number;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
import { track } from "@/features/analytics";
|
||||||
|
import { TabItem } from "@/models/CollectionModels";
|
||||||
|
import { Button, DialogActions, DialogBody, DialogContent, DialogSurface, DialogTitle, DialogTrigger, Field, Input, makeStyles, tokens } from "@fluentui/react-components";
|
||||||
|
|
||||||
|
export default function TabEditDialog({ tab, onSave }: TabEditDialogProps): React.ReactElement
|
||||||
|
{
|
||||||
|
const cls = useStyles();
|
||||||
|
|
||||||
|
const [title, setTitle] = useState(tab.title ?? "");
|
||||||
|
const [url, setUrl] = useState(tab.url);
|
||||||
|
const isValid = useMemo(() => url.trim().length > 0, [url]);
|
||||||
|
|
||||||
|
const onSubmit = (e: React.FormEvent<HTMLFormElement>) =>
|
||||||
|
{
|
||||||
|
e.preventDefault();
|
||||||
|
track("item_edited", { type: "tab" });
|
||||||
|
onSave({
|
||||||
|
...tab,
|
||||||
|
title: title.trim().length > 0 ? title : undefined,
|
||||||
|
url: url.trim()
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DialogSurface>
|
||||||
|
<form onSubmit={ onSubmit }>
|
||||||
|
<DialogBody>
|
||||||
|
<DialogTitle>{ i18n.t("dialogs.edit.title.edit_tab") }</DialogTitle>
|
||||||
|
<DialogContent className={ cls.content }>
|
||||||
|
<Input
|
||||||
|
value={ title } onChange={ (_, e) => setTitle(e.value) }
|
||||||
|
placeholder={ i18n.t("dialogs.edit.collection_title") } />
|
||||||
|
<Field validationMessage={ isValid ? undefined : i18n.t("dialogs.edit.url_error") }>
|
||||||
|
<Input
|
||||||
|
value={ url } onChange={ (_, e) => setUrl(e.value) }
|
||||||
|
placeholder="URL" />
|
||||||
|
</Field>
|
||||||
|
</DialogContent>
|
||||||
|
|
||||||
|
<DialogActions>
|
||||||
|
<DialogTrigger disableButtonEnhancement>
|
||||||
|
<Button disabled={ !isValid } appearance="primary" as="button" type="submit">
|
||||||
|
{ i18n.t("common.actions.save") }
|
||||||
|
</Button>
|
||||||
|
</DialogTrigger>
|
||||||
|
<DialogTrigger disableButtonEnhancement>
|
||||||
|
<Button appearance="subtle">{ i18n.t("common.actions.cancel") }</Button>
|
||||||
|
</DialogTrigger>
|
||||||
|
</DialogActions>
|
||||||
|
</DialogBody>
|
||||||
|
</form>
|
||||||
|
</DialogSurface>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const useStyles = makeStyles({
|
||||||
|
content:
|
||||||
|
{
|
||||||
|
display: "flex",
|
||||||
|
flexFlow: "column",
|
||||||
|
gap: tokens.spacingVerticalMNudge
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TabEditDialogProps =
|
||||||
|
{
|
||||||
|
tab: TabItem;
|
||||||
|
onSave: (updatedTab: TabItem) => void;
|
||||||
|
};
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import { useDangerStyles } from "@/hooks/useDangerStyles";
|
||||||
|
import { Button, Menu, MenuItem, MenuList, MenuPopover, MenuTrigger, Tooltip } from "@fluentui/react-components";
|
||||||
|
import { bundleIcon, Delete20Filled, Delete20Regular, Edit20Filled, Edit20Regular, MoreHorizontal20Regular } from "@fluentui/react-icons";
|
||||||
|
import { ButtonHTMLAttributes } from "react";
|
||||||
|
|
||||||
|
export default function TabMoreButton({ onEdit, onDelete, ...props }: TabMoreButtonProps): React.ReactElement
|
||||||
|
{
|
||||||
|
const EditIcon = bundleIcon(Edit20Filled, Edit20Regular);
|
||||||
|
const DeleteIcon = bundleIcon(Delete20Filled, Delete20Regular);
|
||||||
|
const dangerCls = useDangerStyles();
|
||||||
|
|
||||||
|
const onClick = (ev: React.MouseEvent): void =>
|
||||||
|
{
|
||||||
|
ev.stopPropagation();
|
||||||
|
ev.preventDefault();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Menu>
|
||||||
|
<Tooltip relationship="label" content={ i18n.t("common.tooltips.more") }>
|
||||||
|
<MenuTrigger disableButtonEnhancement>
|
||||||
|
<Button
|
||||||
|
appearance="subtle" icon={ <MoreHorizontal20Regular /> }
|
||||||
|
onClick={ onClick }
|
||||||
|
{ ...props } />
|
||||||
|
</MenuTrigger>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
<MenuPopover onClick={ ev => ev.stopPropagation() }>
|
||||||
|
<MenuList>
|
||||||
|
<MenuItem icon={ <EditIcon /> } onClick={ onEdit }>
|
||||||
|
{ i18n.t("dialogs.edit.title.edit_tab") }
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem icon={ <DeleteIcon /> } className={ dangerCls.menuItem } onClick={ onDelete }>
|
||||||
|
{ i18n.t("tabs.delete") }
|
||||||
|
</MenuItem>
|
||||||
|
</MenuList>
|
||||||
|
</MenuPopover>
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TabMoreButtonProps =
|
||||||
|
ButtonHTMLAttributes<HTMLButtonElement> &
|
||||||
|
{
|
||||||
|
onDelete?: () => void;
|
||||||
|
onEdit?: () => void;
|
||||||
|
};
|
||||||
@@ -4,16 +4,17 @@ import { useDialog } from "@/contexts/DialogProvider";
|
|||||||
import { useCollections } from "@/entrypoints/sidepanel/contexts/CollectionsProvider";
|
import { useCollections } from "@/entrypoints/sidepanel/contexts/CollectionsProvider";
|
||||||
import useDndItem from "@/entrypoints/sidepanel/hooks/useDndItem";
|
import useDndItem from "@/entrypoints/sidepanel/hooks/useDndItem";
|
||||||
import useSettings from "@/hooks/useSettings";
|
import useSettings from "@/hooks/useSettings";
|
||||||
import { TabItem } from "@/models/CollectionModels";
|
import { CollectionItem, GroupItem, TabItem } from "@/models/CollectionModels";
|
||||||
import { Button, Caption1, Link, mergeClasses, Tooltip } from "@fluentui/react-components";
|
import { Caption1, Link, mergeClasses, Tooltip } from "@fluentui/react-components";
|
||||||
import { Dismiss20Regular } from "@fluentui/react-icons";
|
|
||||||
import { MouseEventHandler, ReactElement } from "react";
|
import { MouseEventHandler, ReactElement } from "react";
|
||||||
import { useStyles_TabView } from "./TabView.styles";
|
import { useStyles_TabView } from "./TabView.styles";
|
||||||
import CollectionContext, { CollectionContextType } from "../contexts/CollectionContext";
|
import CollectionContext, { CollectionContextType } from "../contexts/CollectionContext";
|
||||||
|
import TabMoreButton from "./TabMoreButton";
|
||||||
|
import TabEditDialog from "./TabEditDialog";
|
||||||
|
|
||||||
export default function TabView({ tab, indices, dragOverlay }: TabViewProps): ReactElement
|
export default function TabView({ tab, indices, dragOverlay, collectionId }: TabViewProps): ReactElement
|
||||||
{
|
{
|
||||||
const { removeItem, graphics, tilesView } = useCollections();
|
const { removeItem, graphics, tilesView, collections, updateCollection } = useCollections();
|
||||||
const { collection } = useContext<CollectionContextType>(CollectionContext);
|
const { collection } = useContext<CollectionContextType>(CollectionContext);
|
||||||
const {
|
const {
|
||||||
setNodeRef, setActivatorNodeRef,
|
setNodeRef, setActivatorNodeRef,
|
||||||
@@ -26,11 +27,8 @@ export default function TabView({ tab, indices, dragOverlay }: TabViewProps): Re
|
|||||||
|
|
||||||
const cls = useStyles_TabView();
|
const cls = useStyles_TabView();
|
||||||
|
|
||||||
const handleDelete: MouseEventHandler<HTMLButtonElement> = (args) =>
|
const handleDelete = (): void =>
|
||||||
{
|
{
|
||||||
args.preventDefault();
|
|
||||||
args.stopPropagation();
|
|
||||||
|
|
||||||
const removeIndex: number[] = [collection.timestamp, ...indices.slice(1)];
|
const removeIndex: number[] = [collection.timestamp, ...indices.slice(1)];
|
||||||
|
|
||||||
if (deletePrompt)
|
if (deletePrompt)
|
||||||
@@ -45,6 +43,26 @@ export default function TabView({ tab, indices, dragOverlay }: TabViewProps): Re
|
|||||||
removeItem(...removeIndex);
|
removeItem(...removeIndex);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleEdit = (): void =>
|
||||||
|
{
|
||||||
|
if (collectionId < 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const updateTab = async (updatedTab: TabItem): Promise<void> =>
|
||||||
|
{
|
||||||
|
const collection: CollectionItem = collections!.find(i => i.timestamp === collectionId)!;
|
||||||
|
|
||||||
|
if (indices.length > 2)
|
||||||
|
(collection.items[indices[1]] as GroupItem).items[indices[2]] = updatedTab;
|
||||||
|
else
|
||||||
|
collection.items[indices[1]] = updatedTab;
|
||||||
|
|
||||||
|
await updateCollection(collection, collection.timestamp);
|
||||||
|
};
|
||||||
|
|
||||||
|
dialog.pushCustom(<TabEditDialog tab={ tab } onSave={ updateTab } />);
|
||||||
|
};
|
||||||
|
|
||||||
const handleClick: MouseEventHandler<HTMLAnchorElement> = (args) =>
|
const handleClick: MouseEventHandler<HTMLAnchorElement> = (args) =>
|
||||||
{
|
{
|
||||||
args.preventDefault();
|
args.preventDefault();
|
||||||
@@ -91,12 +109,10 @@ export default function TabView({ tab, indices, dragOverlay }: TabViewProps): Re
|
|||||||
</Caption1>
|
</Caption1>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
<Tooltip relationship="label" content={ i18n.t("tabs.delete") }>
|
<TabMoreButton
|
||||||
<Button
|
className={ mergeClasses(cls.deleteButton, showToolbar === true && cls.showDeleteButton) }
|
||||||
className={ mergeClasses(cls.deleteButton, showToolbar === true && cls.showDeleteButton) }
|
onEdit={ handleEdit }
|
||||||
appearance="subtle" icon={ <Dismiss20Regular /> }
|
onDelete={ handleDelete } />
|
||||||
onClick={ handleDelete } />
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
@@ -107,4 +123,5 @@ export type TabViewProps =
|
|||||||
tab: TabItem;
|
tab: TabItem;
|
||||||
indices: number[];
|
indices: number[];
|
||||||
dragOverlay?: boolean;
|
dragOverlay?: boolean;
|
||||||
|
collectionId: number;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { getCollectionTitle } from "@/entrypoints/sidepanel/utils/getCollectionT
|
|||||||
import useSettings from "@/hooks/useSettings";
|
import useSettings from "@/hooks/useSettings";
|
||||||
import { TabItem } from "@/models/CollectionModels";
|
import { TabItem } from "@/models/CollectionModels";
|
||||||
import { Button, Caption1, makeStyles, mergeClasses, Subtitle2, tokens, Tooltip } from "@fluentui/react-components";
|
import { Button, Caption1, makeStyles, mergeClasses, Subtitle2, tokens, Tooltip } from "@fluentui/react-components";
|
||||||
import { Add20Filled, Add20Regular, bundleIcon } from "@fluentui/react-icons";
|
import { Add20Filled, Add20Regular, bundleIcon, EyeOff16Regular } from "@fluentui/react-icons";
|
||||||
import CollectionContext, { CollectionContextType } from "../../contexts/CollectionContext";
|
import CollectionContext, { CollectionContextType } from "../../contexts/CollectionContext";
|
||||||
import { useCollections } from "../../contexts/CollectionsProvider";
|
import { useCollections } from "../../contexts/CollectionsProvider";
|
||||||
import CollectionMoreButton from "./CollectionMoreButton";
|
import CollectionMoreButton from "./CollectionMoreButton";
|
||||||
@@ -23,7 +23,7 @@ export default function CollectionHeader({ dragHandleRef, dragHandleProps }: Col
|
|||||||
|
|
||||||
const handleAddSelected = async () =>
|
const handleAddSelected = async () =>
|
||||||
{
|
{
|
||||||
const [newTabs, skipCount] = await getTabsToSaveAsync();
|
const [newTabs, skipCount] = await getTabsToSaveAsync(true);
|
||||||
|
|
||||||
if (newTabs.length > 0)
|
if (newTabs.length > 0)
|
||||||
await updateCollection({
|
await updateCollection({
|
||||||
@@ -45,9 +45,12 @@ export default function CollectionHeader({ dragHandleRef, dragHandleProps }: Col
|
|||||||
content={ getCollectionTitle(collection) }
|
content={ getCollectionTitle(collection) }
|
||||||
positioning="above-start"
|
positioning="above-start"
|
||||||
>
|
>
|
||||||
<Subtitle2 truncate wrap={ false } className={ cls.titleText }>
|
<div className={ cls.titleContainer }>
|
||||||
{ getCollectionTitle(collection) }
|
{ collection.hidden && <EyeOff16Regular /> }
|
||||||
</Subtitle2>
|
<Subtitle2 truncate wrap={ false } className={ cls.titleText }>
|
||||||
|
{ getCollectionTitle(collection) }
|
||||||
|
</Subtitle2>
|
||||||
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
<Caption1>
|
<Caption1>
|
||||||
@@ -112,5 +115,11 @@ const useStyles = makeStyles({
|
|||||||
showToolbar:
|
showToolbar:
|
||||||
{
|
{
|
||||||
display: "flex"
|
display: "flex"
|
||||||
|
},
|
||||||
|
titleContainer:
|
||||||
|
{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: tokens.spacingHorizontalS
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ export default function CollectionMoreButton({ onAddSelected, onOpenChange }: Co
|
|||||||
const EditIcon = ic.bundleIcon(ic.Edit20Filled, ic.Edit20Regular);
|
const EditIcon = ic.bundleIcon(ic.Edit20Filled, ic.Edit20Regular);
|
||||||
const DeleteIcon = ic.bundleIcon(ic.Delete20Filled, ic.Delete20Regular);
|
const DeleteIcon = ic.bundleIcon(ic.Delete20Filled, ic.Delete20Regular);
|
||||||
const BookmarkIcon = ic.bundleIcon(ic.BookmarkAdd20Filled, ic.BookmarkAdd20Regular);
|
const BookmarkIcon = ic.bundleIcon(ic.BookmarkAdd20Filled, ic.BookmarkAdd20Regular);
|
||||||
|
const ShowIcon = ic.bundleIcon(ic.Eye20Filled, ic.Eye20Regular);
|
||||||
|
const HideIcon = ic.bundleIcon(ic.EyeOff20Filled, ic.EyeOff20Regular);
|
||||||
|
|
||||||
const dangerCls = useDangerStyles();
|
const dangerCls = useDangerStyles();
|
||||||
|
|
||||||
@@ -39,6 +41,11 @@ export default function CollectionMoreButton({ onAddSelected, onOpenChange }: Co
|
|||||||
removeItem(collection.timestamp);
|
removeItem(collection.timestamp);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const toggleHidden = () =>
|
||||||
|
{
|
||||||
|
updateCollection({ ...collection, hidden: !collection.hidden }, collection.timestamp);
|
||||||
|
};
|
||||||
|
|
||||||
const handleEdit = () =>
|
const handleEdit = () =>
|
||||||
dialog.pushCustom(
|
dialog.pushCustom(
|
||||||
<EditDialog
|
<EditDialog
|
||||||
@@ -82,6 +89,9 @@ export default function CollectionMoreButton({ onAddSelected, onOpenChange }: Co
|
|||||||
<MenuItem icon={ <EditIcon /> } onClick={ handleEdit }>
|
<MenuItem icon={ <EditIcon /> } onClick={ handleEdit }>
|
||||||
{ i18n.t("collections.menu.edit") }
|
{ i18n.t("collections.menu.edit") }
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
<MenuItem icon={ collection.hidden ? <ShowIcon /> : <HideIcon /> } onClick={ toggleHidden }>
|
||||||
|
{ collection.hidden ? i18n.t("collections.menu.unhide") : i18n.t("collections.menu.hide") }
|
||||||
|
</MenuItem>
|
||||||
<MenuItem icon={ <DeleteIcon /> } className={ dangerCls.menuItem } onClick={ handleDelete }>
|
<MenuItem icon={ <DeleteIcon /> } className={ dangerCls.menuItem } onClick={ handleDelete }>
|
||||||
{ i18n.t("collections.menu.delete") }
|
{ i18n.t("collections.menu.delete") }
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ export default function GroupMoreMenu(): ReactElement
|
|||||||
|
|
||||||
const handleAddSelected = async () =>
|
const handleAddSelected = async () =>
|
||||||
{
|
{
|
||||||
const [newTabs, skipCount] = await getTabsToSaveAsync();
|
const [newTabs, skipCount] = await getTabsToSaveAsync(true);
|
||||||
|
|
||||||
if (newTabs.length > 0)
|
if (newTabs.length > 0)
|
||||||
await updateGroup({
|
await updateGroup({
|
||||||
|
|||||||
@@ -51,5 +51,9 @@ export const useStyles_CollectionListView = makeStyles({
|
|||||||
{
|
{
|
||||||
gridTemplateColumns: "repeat(auto-fit, minmax(360px, 1fr))"
|
gridTemplateColumns: "repeat(auto-fit, minmax(360px, 1fr))"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
compactList:
|
||||||
|
{
|
||||||
|
alignItems: "baseline"
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -18,10 +18,10 @@ import CollectionContext from "../../contexts/CollectionContext";
|
|||||||
import { useCollections } from "../../contexts/CollectionsProvider";
|
import { useCollections } from "../../contexts/CollectionsProvider";
|
||||||
import applyReorder from "../../utils/dnd/applyReorder";
|
import applyReorder from "../../utils/dnd/applyReorder";
|
||||||
import { collisionDetector } from "../../utils/dnd/collisionDetector";
|
import { collisionDetector } from "../../utils/dnd/collisionDetector";
|
||||||
|
import { snapHandleToCursor } from "../../utils/dnd/snapHandleToCursor";
|
||||||
import { useStyles_CollectionListView } from "./CollectionListView.styles";
|
import { useStyles_CollectionListView } from "./CollectionListView.styles";
|
||||||
import SearchBar from "./SearchBar";
|
import SearchBar from "./SearchBar";
|
||||||
import StorageCapacityIssueMessage from "./messages/StorageCapacityIssueMessage";
|
import StorageCapacityIssueMessage from "./messages/StorageCapacityIssueMessage";
|
||||||
import { snapHandleToCursor } from "../../utils/dnd/snapHandleToCursor";
|
|
||||||
|
|
||||||
export default function CollectionListView(): ReactElement
|
export default function CollectionListView(): ReactElement
|
||||||
{
|
{
|
||||||
@@ -30,17 +30,19 @@ export default function CollectionListView(): ReactElement
|
|||||||
const [sortMode, setSortMode] = useSettings("sortMode");
|
const [sortMode, setSortMode] = useSettings("sortMode");
|
||||||
const [query, setQuery] = useState<string>("");
|
const [query, setQuery] = useState<string>("");
|
||||||
const [colors, setColors] = useState<CollectionFilterType["colors"]>([]);
|
const [colors, setColors] = useState<CollectionFilterType["colors"]>([]);
|
||||||
|
const [showHidden, setShowHidden] = useState<boolean>(false);
|
||||||
|
const [compactView] = useSettings("compactView");
|
||||||
|
|
||||||
const [active, setActive] = useState<DndItem | null>(null);
|
const [active, setActive] = useState<DndItem | null>(null);
|
||||||
|
|
||||||
const sensors = useSensors(
|
const sensors = useSensors(
|
||||||
useSensor(MouseSensor, { activationConstraint: { delay: 10, tolerance: 20 } }),
|
useSensor(MouseSensor, { activationConstraint: { delay: 150, tolerance: 20 } }),
|
||||||
useSensor(TouchSensor, { activationConstraint: { delay: 300, tolerance: 20 } })
|
useSensor(TouchSensor, { activationConstraint: { delay: 300, tolerance: 20 } })
|
||||||
);
|
);
|
||||||
|
|
||||||
const resultList = useMemo(
|
const resultList = useMemo(
|
||||||
() => sortCollections(filterCollections(collections, { query, colors }), sortMode),
|
() => sortCollections(filterCollections(collections, { query, colors, showHidden }), sortMode),
|
||||||
[query, colors, sortMode, collections]
|
[query, colors, sortMode, collections, showHidden]
|
||||||
);
|
);
|
||||||
|
|
||||||
const cls = useStyles_CollectionListView();
|
const cls = useStyles_CollectionListView();
|
||||||
@@ -49,6 +51,13 @@ export default function CollectionListView(): ReactElement
|
|||||||
{
|
{
|
||||||
setQuery("");
|
setQuery("");
|
||||||
setColors([]);
|
setColors([]);
|
||||||
|
setShowHidden(false);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const updateFilter = useCallback((newColors: CollectionFilterType["colors"], newShowHidden: boolean) =>
|
||||||
|
{
|
||||||
|
setColors(newColors);
|
||||||
|
setShowHidden(newShowHidden);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleDragStart = (event: DragStartEvent): void =>
|
const handleDragStart = (event: DragStartEvent): void =>
|
||||||
@@ -87,8 +96,9 @@ export default function CollectionListView(): ReactElement
|
|||||||
<article className={ cls.root }>
|
<article className={ cls.root }>
|
||||||
<SearchBar
|
<SearchBar
|
||||||
query={ query } onQueryChange={ setQuery }
|
query={ query } onQueryChange={ setQuery }
|
||||||
filter={ colors } onFilterChange={ setColors }
|
filter={ colors } onFilterChange={ updateFilter }
|
||||||
sort={ sortMode } onSortChange={ setSortMode }
|
sort={ sortMode } onSortChange={ setSortMode }
|
||||||
|
showHidden={ showHidden }
|
||||||
onReset={ resetFilter } />
|
onReset={ resetFilter } />
|
||||||
|
|
||||||
<CtaMessage className={ cls.msgBar } />
|
<CtaMessage className={ cls.msgBar } />
|
||||||
@@ -105,7 +115,7 @@ export default function CollectionListView(): ReactElement
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
:
|
:
|
||||||
<section className={ mergeClasses(cls.collectionList, !tilesView && cls.listView) }>
|
<section className={ mergeClasses(cls.collectionList, !tilesView && cls.listView, !!(!tilesView && compactView) && cls.compactList) }>
|
||||||
<DndContext
|
<DndContext
|
||||||
sensors={ sensors }
|
sensors={ sensors }
|
||||||
collisionDetection={ collisionDetector(!tilesView) }
|
collisionDetection={ collisionDetector(!tilesView) }
|
||||||
@@ -118,7 +128,7 @@ export default function CollectionListView(): ReactElement
|
|||||||
strategy={ tilesView ? verticalListSortingStrategy : rectSortingStrategy }
|
strategy={ tilesView ? verticalListSortingStrategy : rectSortingStrategy }
|
||||||
>
|
>
|
||||||
{ resultList.map((collection, index) =>
|
{ resultList.map((collection, index) =>
|
||||||
<CollectionView key={ index } collection={ collection } index={ index } />
|
<CollectionView key={ index } collection={ collection } index={ index } compact={ compactView } />
|
||||||
) }
|
) }
|
||||||
</SortableContext>
|
</SortableContext>
|
||||||
|
|
||||||
@@ -135,9 +145,9 @@ export default function CollectionListView(): ReactElement
|
|||||||
} }
|
} }
|
||||||
>
|
>
|
||||||
{ active.item.type === "group" ?
|
{ active.item.type === "group" ?
|
||||||
<GroupView group={ active.item } indices={ [-1] } dragOverlay />
|
<GroupView group={ active.item } indices={ [-1] } collectionId={ -1 } dragOverlay />
|
||||||
:
|
:
|
||||||
<TabView tab={ active.item } indices={ [-1] } dragOverlay />
|
<TabView tab={ active.item } indices={ [-1] } collectionId={ -1 } dragOverlay />
|
||||||
}
|
}
|
||||||
</CollectionContext.Provider>
|
</CollectionContext.Provider>
|
||||||
:
|
:
|
||||||
|
|||||||
@@ -3,32 +3,48 @@ import * as fui from "@fluentui/react-components";
|
|||||||
import * as ic from "@fluentui/react-icons";
|
import * as ic from "@fluentui/react-icons";
|
||||||
import { CollectionFilterType } from "../../utils/filterCollections";
|
import { CollectionFilterType } from "../../utils/filterCollections";
|
||||||
|
|
||||||
export default function FilterCollectionsButton({ value, onChange }: FilterCollectionsButtonProps): React.ReactElement
|
export default function FilterCollectionsButton({ value, onChange, showHidden }: FilterCollectionsButtonProps): React.ReactElement
|
||||||
{
|
{
|
||||||
const cls = useStyles();
|
const cls = useStyles();
|
||||||
const colorCls = useGroupColors();
|
const colorCls = useGroupColors();
|
||||||
|
|
||||||
const ColorFilterIcon = ic.bundleIcon(ic.Color20Filled, ic.Color20Regular);
|
const FilterIcon = ic.bundleIcon(ic.Filter20Filled, ic.Filter20Regular);
|
||||||
const ColorIcon = ic.bundleIcon(ic.Circle20Filled, ic.CircleHalfFill20Regular);
|
const ColorIcon = ic.bundleIcon(ic.Circle20Filled, ic.CircleHalfFill20Regular);
|
||||||
const NoColorIcon = ic.bundleIcon(ic.CircleOffFilled, ic.CircleOffRegular);
|
const NoColorIcon = ic.bundleIcon(ic.CircleOffFilled, ic.CircleOffRegular);
|
||||||
const AnyColorIcon = ic.bundleIcon(ic.PhotoFilter20Filled, ic.PhotoFilter20Regular);
|
const AnyColorIcon = ic.bundleIcon(ic.PhotoFilter20Filled, ic.PhotoFilter20Regular);
|
||||||
|
const HiddenIcon = ic.bundleIcon(ic.EyeOff20Filled, ic.EyeOff20Regular);
|
||||||
|
|
||||||
|
const values: Record<string, string[]> = useMemo(() => ({
|
||||||
|
default: !value || value.length < 1 ? ["any"] : [],
|
||||||
|
colors: value || [],
|
||||||
|
hidden: showHidden ? ["show"] : []
|
||||||
|
}), [value, showHidden]);
|
||||||
|
|
||||||
|
const onCheckedValueChange = useCallback((_: fui.MenuCheckedValueChangeEvent, e: fui.MenuCheckedValueChangeData) =>
|
||||||
|
{
|
||||||
|
if (e.name === "hidden")
|
||||||
|
onChange?.(value ?? [], e.checkedItems.includes("show"));
|
||||||
|
else
|
||||||
|
onChange?.(e.checkedItems.includes("any") ? [] : e.checkedItems as CollectionFilterType["colors"], showHidden ?? false);
|
||||||
|
}, [onChange, showHidden, value]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<fui.Menu
|
<fui.Menu
|
||||||
checkedValues={ !value || value.length < 1 ? { default: ["any"] } : { colors: value } }
|
checkedValues={ values }
|
||||||
onCheckedValueChange={ (_, e) =>
|
onCheckedValueChange={ onCheckedValueChange }
|
||||||
onChange?.(e.checkedItems.includes("any") ? [] : e.checkedItems as CollectionFilterType["colors"])
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
|
|
||||||
<fui.Tooltip relationship="label" content={ i18n.t("main.list.searchbar.filter") }>
|
<fui.Tooltip relationship="label" content={ i18n.t("main.list.searchbar.filter") }>
|
||||||
<fui.MenuTrigger disableButtonEnhancement>
|
<fui.MenuTrigger disableButtonEnhancement>
|
||||||
<fui.Button appearance="subtle" icon={ <ColorFilterIcon /> } />
|
<fui.Button appearance="subtle" icon={ <FilterIcon /> } />
|
||||||
</fui.MenuTrigger>
|
</fui.MenuTrigger>
|
||||||
</fui.Tooltip>
|
</fui.Tooltip>
|
||||||
|
|
||||||
<fui.MenuPopover>
|
<fui.MenuPopover>
|
||||||
<fui.MenuList>
|
<fui.MenuList>
|
||||||
|
<fui.MenuItemCheckbox name="hidden" value="show" icon={ <HiddenIcon /> }>
|
||||||
|
{ i18n.t("main.list.searchbar.show_hidden") }
|
||||||
|
</fui.MenuItemCheckbox>
|
||||||
<fui.MenuItemCheckbox name="default" value="any" icon={ <AnyColorIcon /> }>
|
<fui.MenuItemCheckbox name="default" value="any" icon={ <AnyColorIcon /> }>
|
||||||
{ i18n.t("colors.any") }
|
{ i18n.t("colors.any") }
|
||||||
</fui.MenuItemCheckbox>
|
</fui.MenuItemCheckbox>
|
||||||
@@ -44,11 +60,11 @@ export default function FilterCollectionsButton({ value, onChange }: FilterColle
|
|||||||
<ColorIcon
|
<ColorIcon
|
||||||
className={ fui.mergeClasses(
|
className={ fui.mergeClasses(
|
||||||
cls.colorIcon,
|
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.MenuItemCheckbox>
|
||||||
) }
|
) }
|
||||||
</fui.MenuList>
|
</fui.MenuList>
|
||||||
@@ -60,7 +76,8 @@ export default function FilterCollectionsButton({ value, onChange }: FilterColle
|
|||||||
export type FilterCollectionsButtonProps =
|
export type FilterCollectionsButtonProps =
|
||||||
{
|
{
|
||||||
value?: CollectionFilterType["colors"];
|
value?: CollectionFilterType["colors"];
|
||||||
onChange?: (value: CollectionFilterType["colors"]) => void;
|
showHidden?: boolean;
|
||||||
|
onChange?: (value: CollectionFilterType["colors"], showHidden: boolean) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const useStyles = fui.makeStyles({
|
const useStyles = fui.makeStyles({
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export default function SearchBar(props: SearchBarProps): React.ReactElement
|
|||||||
<Button appearance="subtle" icon={ <ResetIcon /> } onClick={ props.onReset } />
|
<Button appearance="subtle" icon={ <ResetIcon /> } onClick={ props.onReset } />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
}
|
}
|
||||||
<FilterCollectionsButton value={ props.filter } onChange={ props.onFilterChange } />
|
<FilterCollectionsButton value={ props.filter } onChange={ props.onFilterChange } showHidden={ props.showHidden } />
|
||||||
<SortCollectionsButton value={ props.sort } onChange={ props.onSortChange } />
|
<SortCollectionsButton value={ props.sort } onChange={ props.onSortChange } />
|
||||||
</>
|
</>
|
||||||
} />
|
} />
|
||||||
@@ -37,8 +37,9 @@ export type SearchBarProps =
|
|||||||
query?: string;
|
query?: string;
|
||||||
onQueryChange?: (query: string) => void;
|
onQueryChange?: (query: string) => void;
|
||||||
filter?: CollectionFilterType["colors"];
|
filter?: CollectionFilterType["colors"];
|
||||||
onFilterChange?: (filter: CollectionFilterType["colors"]) => void;
|
onFilterChange?: (filter: CollectionFilterType["colors"], showHidden: boolean) => void;
|
||||||
sort?: CollectionSortMode;
|
sort?: CollectionSortMode;
|
||||||
|
showHidden?: boolean;
|
||||||
onSortChange?: (sort: CollectionSortMode) => void;
|
onSortChange?: (sort: CollectionSortMode) => void;
|
||||||
onReset?: () => void;
|
onReset?: () => void;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,18 +11,32 @@ import { ReactElement } from "react";
|
|||||||
export default function MoreButton(): ReactElement
|
export default function MoreButton(): ReactElement
|
||||||
{
|
{
|
||||||
const [tilesView, setTilesView] = useSettings("tilesView");
|
const [tilesView, setTilesView] = useSettings("tilesView");
|
||||||
|
const [compactView, setCompactView] = useSettings("compactView");
|
||||||
|
|
||||||
const SettingsIcon: ic.FluentIcon = ic.bundleIcon(ic.Settings20Filled, ic.Settings20Regular);
|
const SettingsIcon: ic.FluentIcon = ic.bundleIcon(ic.Settings20Filled, ic.Settings20Regular);
|
||||||
const ViewIcon: ic.FluentIcon = ic.bundleIcon(ic.GridKanban20Filled, ic.GridKanban20Regular);
|
const GridIcon: ic.FluentIcon = ic.bundleIcon(ic.GridKanban20Filled, ic.GridKanban20Regular);
|
||||||
|
const CompactIcon: ic.FluentIcon = ic.bundleIcon(ic.ArrowMinimizeVerticalFilled, ic.ArrowMinimizeVerticalRegular);
|
||||||
const FeedbackIcon: ic.FluentIcon = ic.bundleIcon(ic.PersonFeedback20Filled, ic.PersonFeedback20Regular);
|
const FeedbackIcon: ic.FluentIcon = ic.bundleIcon(ic.PersonFeedback20Filled, ic.PersonFeedback20Regular);
|
||||||
const LearnIcon: ic.FluentIcon = ic.bundleIcon(ic.QuestionCircle20Filled, ic.QuestionCircle20Regular);
|
const LearnIcon: ic.FluentIcon = ic.bundleIcon(ic.QuestionCircle20Filled, ic.QuestionCircle20Regular);
|
||||||
const BmcIcon: ic.FluentIcon = ic.bundleIcon(BuyMeACoffee20Filled, BuyMeACoffee20Regular);
|
const BmcIcon: ic.FluentIcon = ic.bundleIcon(BuyMeACoffee20Filled, BuyMeACoffee20Regular);
|
||||||
|
|
||||||
|
const checkedValues = useMemo(() => ({
|
||||||
|
view: [
|
||||||
|
tilesView ? "tiles" : "",
|
||||||
|
compactView ? "compact" : ""
|
||||||
|
]
|
||||||
|
}), [tilesView, compactView]);
|
||||||
|
|
||||||
|
const onCheckedValueChange = (_: unknown, e: fui.MenuCheckedValueChangeData) =>
|
||||||
|
{
|
||||||
|
setTilesView(e.checkedItems.includes("tiles"));
|
||||||
|
setCompactView(e.checkedItems.includes("compact"));
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<fui.Menu
|
<fui.Menu
|
||||||
hasIcons hasCheckmarks
|
hasIcons hasCheckmarks
|
||||||
checkedValues={ { tilesView: tilesView ? ["true"] : [] } }
|
checkedValues={ checkedValues } onCheckedValueChange={ onCheckedValueChange }
|
||||||
onCheckedValueChange={ (_, e) => setTilesView(e.checkedItems.length > 0) }
|
|
||||||
>
|
>
|
||||||
<fui.Tooltip relationship="label" content={ i18n.t("common.tooltips.more") }>
|
<fui.Tooltip relationship="label" content={ i18n.t("common.tooltips.more") }>
|
||||||
<fui.MenuTrigger disableButtonEnhancement>
|
<fui.MenuTrigger disableButtonEnhancement>
|
||||||
@@ -36,9 +50,12 @@ export default function MoreButton(): ReactElement
|
|||||||
<fui.MenuItem icon={ <SettingsIcon /> } onClick={ () => browser.runtime.openOptionsPage() }>
|
<fui.MenuItem icon={ <SettingsIcon /> } onClick={ () => browser.runtime.openOptionsPage() }>
|
||||||
{ i18n.t("options_page.title") }
|
{ i18n.t("options_page.title") }
|
||||||
</fui.MenuItem>
|
</fui.MenuItem>
|
||||||
<fui.MenuItemCheckbox name="tilesView" value="true" icon={ <ViewIcon /> }>
|
<fui.MenuItemCheckbox name="view" value="tiles" icon={ <GridIcon /> }>
|
||||||
{ i18n.t("main.header.menu.tiles_view") }
|
{ i18n.t("main.header.menu.tiles_view") }
|
||||||
</fui.MenuItemCheckbox>
|
</fui.MenuItemCheckbox>
|
||||||
|
<fui.MenuItemCheckbox name="view" value="compact" icon={ <CompactIcon /> }>
|
||||||
|
{ i18n.t("main.header.menu.compact_view") }
|
||||||
|
</fui.MenuItemCheckbox>
|
||||||
|
|
||||||
<fui.MenuDivider />
|
<fui.MenuDivider />
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
|
import { track } from "@/features/analytics";
|
||||||
import { CollectionItem, TabItem } from "@/models/CollectionModels";
|
import { CollectionItem, TabItem } from "@/models/CollectionModels";
|
||||||
import sendNotification from "@/utils/sendNotification";
|
import sendNotification from "@/utils/sendNotification";
|
||||||
import { Bookmarks, Permissions } from "wxt/browser";
|
|
||||||
import { getCollectionTitle } from "./getCollectionTitle";
|
import { getCollectionTitle } from "./getCollectionTitle";
|
||||||
import { track } from "@/features/analytics";
|
|
||||||
|
|
||||||
export default async function exportCollectionToBookmarks(collection: CollectionItem)
|
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"))
|
if (!permissions.permissions?.includes("bookmarks"))
|
||||||
{
|
{
|
||||||
@@ -16,7 +15,7 @@ export default async function exportCollectionToBookmarks(collection: Collection
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const rootFolder: Bookmarks.BookmarkTreeNode = await browser.bookmarks.create({
|
const rootFolder: Browser.bookmarks.BookmarkTreeNode = await browser.bookmarks.create({
|
||||||
title: getCollectionTitle(collection)
|
title: getCollectionTitle(collection)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -9,13 +9,16 @@ export default function filterCollections(
|
|||||||
if (!collections || collections.length < 1)
|
if (!collections || collections.length < 1)
|
||||||
return [];
|
return [];
|
||||||
|
|
||||||
if (!filter.query && filter.colors.length < 1)
|
if (!filter.query && filter.colors.length < 1 && filter.showHidden)
|
||||||
return collections;
|
return collections;
|
||||||
|
|
||||||
const query: string = filter.query.toLocaleLowerCase();
|
const query: string = filter.query.toLocaleLowerCase();
|
||||||
|
|
||||||
return collections.filter(collection =>
|
return collections.filter(collection =>
|
||||||
{
|
{
|
||||||
|
if (filter.showHidden === false && collection.hidden === true)
|
||||||
|
return false;
|
||||||
|
|
||||||
let querySatisfied: boolean = query.length < 1 ||
|
let querySatisfied: boolean = query.length < 1 ||
|
||||||
getCollectionTitle(collection).toLocaleLowerCase().includes(query);
|
getCollectionTitle(collection).toLocaleLowerCase().includes(query);
|
||||||
let colorSatisfied: boolean = filter.colors.length < 1 ||
|
let colorSatisfied: boolean = filter.colors.length < 1 ||
|
||||||
@@ -61,5 +64,6 @@ export default function filterCollections(
|
|||||||
export type CollectionFilterType =
|
export type CollectionFilterType =
|
||||||
{
|
{
|
||||||
query: string;
|
query: string;
|
||||||
colors: (chrome.tabGroups.ColorEnum | "none")[];
|
colors: (`${Browser.tabGroups.Color}` | "none")[];
|
||||||
|
showHidden: boolean;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import { TabItem } from "@/models/CollectionModels";
|
import { TabItem } from "@/models/CollectionModels";
|
||||||
import sendNotification from "@/utils/sendNotification";
|
import sendNotification from "@/utils/sendNotification";
|
||||||
import { Tabs } from "wxt/browser";
|
|
||||||
|
|
||||||
export default async function getSelectedTabs(): Promise<TabItem[]>
|
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;
|
const tabCount: number = tabs.length;
|
||||||
|
|
||||||
tabs = tabs.filter(i =>
|
tabs = tabs.filter(i =>
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { getCollectionTitle } from "@/entrypoints/sidepanel/utils/getCollectionTitle";
|
import { getCollectionTitle } from "@/entrypoints/sidepanel/utils/getCollectionTitle";
|
||||||
import { CollectionItem, GroupItem, TabItem } from "@/models/CollectionModels";
|
import { CollectionItem, GroupItem, TabItem } from "@/models/CollectionModels";
|
||||||
import { settings } from "@/utils/settings";
|
import { settings } from "@/utils/settings";
|
||||||
import { Tabs, Windows } from "wxt/browser";
|
|
||||||
|
|
||||||
export async function openCollection(collection: CollectionItem, targetWindow?: "current" | "new" | "incognito"): Promise<void>
|
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>
|
async function createGroup(group: GroupItem, windowId: number, discard?: boolean): Promise<void>
|
||||||
{
|
{
|
||||||
discard ??= await settings.dismissOnLoad.getValue();
|
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)
|
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)
|
if (group.pinned === true)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const groupId: number = await chrome.tabs.group({
|
const groupId: number = await browser.tabs.group({
|
||||||
tabIds: tabs.filter(i => i.windowId === windowId).map(i => i.id!),
|
tabIds: tabs.filter(i => i.windowId === windowId).map(i => i.id!) as [number, ...number[]],
|
||||||
createProperties: { windowId }
|
createProperties: { windowId }
|
||||||
});
|
});
|
||||||
|
|
||||||
await chrome.tabGroups.update(groupId, {
|
await browser.tabGroups.update(groupId, {
|
||||||
title: group.title,
|
title: group.title,
|
||||||
color: group.color
|
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 ?
|
const currentWindow: Browser.windows.Window = windowProps ?
|
||||||
await browser.windows.create({ url: "about:blank", focused: false, ...windowProps }) :
|
(await browser.windows.create({ url: "about:blank", focused: false, ...windowProps }))! :
|
||||||
await browser.windows.getCurrent();
|
await browser.windows.getCurrent();
|
||||||
const windowId: number = currentWindow.id!;
|
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!);
|
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 });
|
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
|
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)
|
if (id !== tabId || !tab.url)
|
||||||
return;
|
return;
|
||||||
|
|||||||
+2
-1
@@ -19,13 +19,14 @@ export default defineConfig([
|
|||||||
{ files: ["**/*.css"], plugins: { css }, language: "css/css", extends: ["css/recommended"] },
|
{ files: ["**/*.css"], plugins: { css }, language: "css/css", extends: ["css/recommended"] },
|
||||||
{
|
{
|
||||||
files: ["**/*.{jsonc,json}"],
|
files: ["**/*.{jsonc,json}"],
|
||||||
|
ignores: [".devcontainer/devcontainer.json", "package-lock.json"],
|
||||||
plugins: { json },
|
plugins: { json },
|
||||||
language: "json/jsonc",
|
language: "json/jsonc",
|
||||||
extends: ["json/recommended"]
|
extends: ["json/recommended"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
files: ["**/*.json"],
|
files: ["**/*.json"],
|
||||||
ignores: [".devcontainer/devcontainer.json"],
|
ignores: [".devcontainer/devcontainer.json", "package-lock.json"],
|
||||||
plugins: { json },
|
plugins: { json },
|
||||||
language: "json/json",
|
language: "json/json",
|
||||||
extends: ["json/recommended"]
|
extends: ["json/recommended"]
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { Unwatch, WatchCallback, WxtStorageItem } from "wxt/storage";
|
import { Unwatch, WatchCallback } from "wxt/utils/storage";
|
||||||
import { analytics } from "./analytics";
|
import { analytics } from "./analytics";
|
||||||
import { Permissions } from "wxt/browser";
|
|
||||||
|
|
||||||
const analyticsPermission: Pick<WxtStorageItem<boolean, Record<string, unknown>>, "getValue" | "watch" | "setValue"> =
|
const analyticsPermission: Pick<WxtStorageItem<boolean, Record<string, unknown>>, "getValue" | "watch" | "setValue"> =
|
||||||
{
|
{
|
||||||
@@ -9,7 +8,7 @@ const analyticsPermission: Pick<WxtStorageItem<boolean, Record<string, unknown>>
|
|||||||
const isGranted: boolean = import.meta.env.FIREFOX
|
const isGranted: boolean = import.meta.env.FIREFOX
|
||||||
? await browser.permissions.contains({
|
? await browser.permissions.contains({
|
||||||
data_collection: ["technicalAndInteraction"]
|
data_collection: ["technicalAndInteraction"]
|
||||||
})
|
} as Browser.permissions.Permissions)
|
||||||
: await allowAnalytics.getValue();
|
: await allowAnalytics.getValue();
|
||||||
|
|
||||||
analytics.setEnabled(isGranted);
|
analytics.setEnabled(isGranted);
|
||||||
@@ -30,11 +29,11 @@ const analyticsPermission: Pick<WxtStorageItem<boolean, Record<string, unknown>>
|
|||||||
if (value)
|
if (value)
|
||||||
result = await browser.permissions.request({
|
result = await browser.permissions.request({
|
||||||
data_collection: ["technicalAndInteraction"]
|
data_collection: ["technicalAndInteraction"]
|
||||||
});
|
} as Browser.permissions.Permissions);
|
||||||
else
|
else
|
||||||
result = await browser.permissions.remove({
|
result = await browser.permissions.remove({
|
||||||
data_collection: ["technicalAndInteraction"]
|
data_collection: ["technicalAndInteraction"]
|
||||||
});
|
} as Browser.permissions.Permissions);
|
||||||
|
|
||||||
if (!result)
|
if (!result)
|
||||||
throw new Error("Permission request was denied");
|
throw new Error("Permission request was denied");
|
||||||
@@ -45,11 +44,14 @@ const analyticsPermission: Pick<WxtStorageItem<boolean, Record<string, unknown>>
|
|||||||
if (!import.meta.env.FIREFOX)
|
if (!import.meta.env.FIREFOX)
|
||||||
return allowAnalytics.watch(cb);
|
return allowAnalytics.watch(cb);
|
||||||
|
|
||||||
const listener = async (permissions: Permissions.Permissions): Promise<void> =>
|
const listener = async (permissions: Browser.permissions.Permissions): Promise<void> =>
|
||||||
{
|
{
|
||||||
|
// @ts-expect-error Firefox-only API
|
||||||
if (permissions.data_collection?.includes("technicalAndInteraction"))
|
if (permissions.data_collection?.includes("technicalAndInteraction"))
|
||||||
{
|
{
|
||||||
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);
|
cb(isGranted, !isGranted);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ function parseCollection(data: string): CollectionItem
|
|||||||
return {
|
return {
|
||||||
type: "collection",
|
type: "collection",
|
||||||
timestamp: parseInt(data.match(/(?<=^c)\d+/)!.toString()),
|
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(),
|
title: data.match(/(?<=^c[\da-z/]*\|).*/)?.toString(),
|
||||||
items: []
|
items: []
|
||||||
};
|
};
|
||||||
@@ -64,7 +64,7 @@ function parseGroup(data: string): GroupItem
|
|||||||
return {
|
return {
|
||||||
type: "group",
|
type: "group",
|
||||||
pinned: false,
|
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(),
|
title: data.match(/(?<=^\tg\/[a-z]+\|).*$/)?.toString(),
|
||||||
items: []
|
items: []
|
||||||
};
|
};
|
||||||
@@ -74,7 +74,7 @@ function parseTab(data: string): TabItem
|
|||||||
{
|
{
|
||||||
return {
|
return {
|
||||||
type: "tab",
|
type: "tab",
|
||||||
url: data.match(/(?<=^(\t){1,2}t\|).*(?=\|)/)!.toString(),
|
url: data.match(/(?<=^\t{1,2}t\|).*(?=\|)/)!.toString(),
|
||||||
title: data.match(/(?<=^(\t){1,2}t\|.*\|).*$/)?.toString()
|
title: data.match(/(?<=^\t{1,2}t\|.*\|).*$/)?.toString()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
|
import { trackError } from "@/features/analytics";
|
||||||
import { CollectionItem } from "@/models/CollectionModels";
|
import { CollectionItem } from "@/models/CollectionModels";
|
||||||
|
import getLogger from "@/utils/getLogger";
|
||||||
|
import sendNotification from "@/utils/sendNotification";
|
||||||
import { compress } from "lzutf8";
|
import { compress } from "lzutf8";
|
||||||
import { WxtStorageItem } from "wxt/storage";
|
|
||||||
import { collectionStorage } from "./collectionStorage";
|
import { collectionStorage } from "./collectionStorage";
|
||||||
import getChunkKeys from "./getChunkKeys";
|
import getChunkKeys from "./getChunkKeys";
|
||||||
import serializeCollections from "./serializeCollections";
|
import serializeCollections from "./serializeCollections";
|
||||||
import { trackError } from "@/features/analytics";
|
|
||||||
import sendNotification from "@/utils/sendNotification";
|
|
||||||
import getLogger from "@/utils/getLogger";
|
|
||||||
|
|
||||||
const logger = getLogger("saveCollectionsToCloud");
|
const logger = getLogger("saveCollectionsToCloud");
|
||||||
|
|
||||||
@@ -70,7 +69,7 @@ function splitIntoChunks(data: string): string[]
|
|||||||
{
|
{
|
||||||
// QUOTA_BYTES_PER_ITEM includes length of key name, length of content and 2 more bytes (for unknown reason).
|
// 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 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[] = [];
|
const chunks: string[] = [];
|
||||||
|
|
||||||
for (let i = 0; i < data.length; i += chunkSize)
|
for (let i = 0; i < data.length; i += chunkSize)
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Permissions } from "wxt/browser";
|
import { Unwatch, WatchCallback } from "wxt/utils/storage";
|
||||||
import { Unwatch, WatchCallback, WxtStorageItem } from "wxt/storage";
|
|
||||||
|
|
||||||
const thumbnailCaptureEnabled: Pick<WxtStorageItem<boolean, Record<string, unknown>>, "getValue" | "watch" | "setValue"> =
|
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 =>
|
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>"))
|
if (permissions.permissions?.includes("scripting") || permissions.origins?.includes("<all_urls>"))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { githubLinks } from "@/data/links";
|
import { githubLinks } from "@/data/links";
|
||||||
import { analyticsPermission } from "@/features/analytics";
|
import { analyticsPermission } from "@/features/analytics";
|
||||||
|
import { thumbnailCaptureEnabled } from "@/features/collectionStorage";
|
||||||
import extLink from "@/utils/extLink";
|
import extLink from "@/utils/extLink";
|
||||||
import * as fui from "@fluentui/react-components";
|
import * as fui from "@fluentui/react-components";
|
||||||
import { settingsForReview } from "../utils/showSettingsReviewDialog";
|
import { Unwatch } from "wxt/utils/storage";
|
||||||
import { reviewSettings } from "../utils/setSettingsReviewNeeded";
|
import { reviewSettings } from "../utils/setSettingsReviewNeeded";
|
||||||
import { Unwatch } from "wxt/storage";
|
import { settingsForReview } from "../utils/showSettingsReviewDialog";
|
||||||
import { thumbnailCaptureEnabled } from "@/features/collectionStorage";
|
|
||||||
|
|
||||||
export default function SettingsReviewDialog(): React.ReactElement
|
export default function SettingsReviewDialog(): React.ReactElement
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { analyticsPermission } from "@/features/analytics";
|
import { analyticsPermission } from "@/features/analytics";
|
||||||
import { Runtime } from "wxt/browser";
|
|
||||||
import { settingsForReview } from "./showSettingsReviewDialog";
|
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();
|
const needsReview: string[] = await settingsForReview.getValue();
|
||||||
|
|
||||||
@@ -25,7 +24,7 @@ export const reviewSettings =
|
|||||||
THUMBNAILS: "thumbnails"
|
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")
|
if (installReason === "install")
|
||||||
return !await analyticsPermission.getValue();
|
return !await analyticsPermission.getValue();
|
||||||
@@ -45,7 +44,7 @@ async function checkAnalyticsReviewNeeded(installReason: Runtime.OnInstalledReas
|
|||||||
return false;
|
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")
|
if (installReason === "install")
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { makeStyles, tokens } from "@fluentui/react-components";
|
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:
|
blue:
|
||||||
{
|
{
|
||||||
"--border": tokens.colorPaletteBlueBorderActive,
|
"--border": tokens.colorPaletteBlueBorderActive,
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ export default function useStorageInfo(): StorageInfoHook
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
bytesInUse,
|
bytesInUse,
|
||||||
storageQuota: chrome.storage.sync.QUOTA_BYTES ?? 102400,
|
storageQuota: browser.storage.sync.QUOTA_BYTES ?? 102400,
|
||||||
usedStorageRatio: bytesInUse / (chrome.storage.sync.QUOTA_BYTES ?? 102400)
|
usedStorageRatio: bytesInUse / (browser.storage.sync.QUOTA_BYTES ?? 102400)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -184,6 +184,8 @@ collections:
|
|||||||
add_group: "Add empty group"
|
add_group: "Add empty group"
|
||||||
export_bookmarks: "Export to bookmarks"
|
export_bookmarks: "Export to bookmarks"
|
||||||
edit: "Edit collection"
|
edit: "Edit collection"
|
||||||
|
hide: "Hide collection"
|
||||||
|
unhide: "Unhide collection"
|
||||||
|
|
||||||
groups:
|
groups:
|
||||||
title: "Group"
|
title: "Group"
|
||||||
@@ -219,21 +221,25 @@ dialogs:
|
|||||||
title:
|
title:
|
||||||
edit_collection: "Edit collection"
|
edit_collection: "Edit collection"
|
||||||
edit_group: "Edit group"
|
edit_group: "Edit group"
|
||||||
|
edit_tab: "Edit tab"
|
||||||
new_group: "New group"
|
new_group: "New group"
|
||||||
new_collection: "New collection"
|
new_collection: "New collection"
|
||||||
collection_title: "Title"
|
collection_title: "Title"
|
||||||
color: "Color"
|
color: "Color"
|
||||||
|
url_error: "URL is required"
|
||||||
|
|
||||||
main:
|
main:
|
||||||
header:
|
header:
|
||||||
create_collection: "Create new collection"
|
create_collection: "Create new collection"
|
||||||
menu:
|
menu:
|
||||||
tiles_view: "Tiles view"
|
tiles_view: "Tiles view"
|
||||||
|
compact_view: "Compact view"
|
||||||
changelog: "What's new?"
|
changelog: "What's new?"
|
||||||
list:
|
list:
|
||||||
searchbar:
|
searchbar:
|
||||||
title: "Search"
|
title: "Search"
|
||||||
filter: "Filter"
|
filter: "Filter"
|
||||||
|
show_hidden: "Show hidden"
|
||||||
sort:
|
sort:
|
||||||
title: "Sort"
|
title: "Sort"
|
||||||
options:
|
options:
|
||||||
|
|||||||
@@ -184,6 +184,8 @@ collections:
|
|||||||
add_group: "Agregar grupo vacío"
|
add_group: "Agregar grupo vacío"
|
||||||
export_bookmarks: "Exportar a marcadores"
|
export_bookmarks: "Exportar a marcadores"
|
||||||
edit: "Editar colección"
|
edit: "Editar colección"
|
||||||
|
hide: "Ocultar colección"
|
||||||
|
unhide: "Mostrar colección"
|
||||||
|
|
||||||
groups:
|
groups:
|
||||||
title: "Grupo"
|
title: "Grupo"
|
||||||
@@ -219,21 +221,25 @@ dialogs:
|
|||||||
title:
|
title:
|
||||||
edit_collection: "Editar colección"
|
edit_collection: "Editar colección"
|
||||||
edit_group: "Editar grupo"
|
edit_group: "Editar grupo"
|
||||||
|
edit_tab: "Editar pestaña"
|
||||||
new_group: "Nuevo grupo"
|
new_group: "Nuevo grupo"
|
||||||
new_collection: "Nueva colección"
|
new_collection: "Nueva colección"
|
||||||
collection_title: "Título"
|
collection_title: "Título"
|
||||||
color: "Color"
|
color: "Color"
|
||||||
|
url_error: "La URL es obligatoria"
|
||||||
|
|
||||||
main:
|
main:
|
||||||
header:
|
header:
|
||||||
create_collection: "Crear nueva colección"
|
create_collection: "Crear nueva colección"
|
||||||
menu:
|
menu:
|
||||||
tiles_view: "Vista de mosaicos"
|
tiles_view: "Vista de mosaicos"
|
||||||
|
compact_view: "Vista compacta"
|
||||||
changelog: "¿Qué hay de nuevo?"
|
changelog: "¿Qué hay de nuevo?"
|
||||||
list:
|
list:
|
||||||
searchbar:
|
searchbar:
|
||||||
title: "Buscar"
|
title: "Buscar"
|
||||||
filter: "Filtrar"
|
filter: "Filtrar"
|
||||||
|
show_hidden: "Mostrar ocultas"
|
||||||
sort:
|
sort:
|
||||||
title: "Ordenar"
|
title: "Ordenar"
|
||||||
options:
|
options:
|
||||||
|
|||||||
@@ -184,6 +184,8 @@ collections:
|
|||||||
add_group: "Aggiungi gruppo vuoto"
|
add_group: "Aggiungi gruppo vuoto"
|
||||||
export_bookmarks: "Esporta nei segnalibri"
|
export_bookmarks: "Esporta nei segnalibri"
|
||||||
edit: "Modifica collezione"
|
edit: "Modifica collezione"
|
||||||
|
hide: "Nascondi collezione"
|
||||||
|
unhide: "Mostra collezione"
|
||||||
|
|
||||||
groups:
|
groups:
|
||||||
title: "Gruppo"
|
title: "Gruppo"
|
||||||
@@ -219,21 +221,25 @@ dialogs:
|
|||||||
title:
|
title:
|
||||||
edit_collection: "Modifica collezione"
|
edit_collection: "Modifica collezione"
|
||||||
edit_group: "Modifica gruppo"
|
edit_group: "Modifica gruppo"
|
||||||
|
edit_tab: "Modifica scheda"
|
||||||
new_group: "Nuovo gruppo"
|
new_group: "Nuovo gruppo"
|
||||||
new_collection: "Nuova collezione"
|
new_collection: "Nuova collezione"
|
||||||
collection_title: "Titolo"
|
collection_title: "Titolo"
|
||||||
color: "Colore"
|
color: "Colore"
|
||||||
|
url_error: "L'URL è obbligatorio"
|
||||||
|
|
||||||
main:
|
main:
|
||||||
header:
|
header:
|
||||||
create_collection: "Crea nuova collezione"
|
create_collection: "Crea nuova collezione"
|
||||||
menu:
|
menu:
|
||||||
tiles_view: "Vista a riquadri"
|
tiles_view: "Vista a riquadri"
|
||||||
|
compact_view: "Vista compatta"
|
||||||
changelog: "Cosa c'è di nuovo?"
|
changelog: "Cosa c'è di nuovo?"
|
||||||
list:
|
list:
|
||||||
searchbar:
|
searchbar:
|
||||||
title: "Cerca"
|
title: "Cerca"
|
||||||
filter: "Filtra"
|
filter: "Filtra"
|
||||||
|
show_hidden: "Mostra nascoste"
|
||||||
sort:
|
sort:
|
||||||
title: "Ordina"
|
title: "Ordina"
|
||||||
options:
|
options:
|
||||||
|
|||||||
@@ -184,6 +184,8 @@ collections:
|
|||||||
add_group: "Dodaj pustą grupę"
|
add_group: "Dodaj pustą grupę"
|
||||||
export_bookmarks: "Eksportuj do zakładek"
|
export_bookmarks: "Eksportuj do zakładek"
|
||||||
edit: "Edytuj kolekcję"
|
edit: "Edytuj kolekcję"
|
||||||
|
hide: "Ukryj kolekcję"
|
||||||
|
unhide: "Pokaż kolekcję"
|
||||||
|
|
||||||
groups:
|
groups:
|
||||||
title: "Grupa"
|
title: "Grupa"
|
||||||
@@ -219,21 +221,25 @@ dialogs:
|
|||||||
title:
|
title:
|
||||||
edit_collection: "Edytuj kolekcję"
|
edit_collection: "Edytuj kolekcję"
|
||||||
edit_group: "Edytuj grupę"
|
edit_group: "Edytuj grupę"
|
||||||
|
edit_tab: "Edytuj zakładkę"
|
||||||
new_group: "Nowa grupa"
|
new_group: "Nowa grupa"
|
||||||
new_collection: "Nowa kolekcja"
|
new_collection: "Nowa kolekcja"
|
||||||
collection_title: "Nazwij"
|
collection_title: "Nazwij"
|
||||||
color: "Kolor"
|
color: "Kolor"
|
||||||
|
url_error: "URL jest wymagany"
|
||||||
|
|
||||||
main:
|
main:
|
||||||
header:
|
header:
|
||||||
create_collection: "Utwórz nową kolekcję"
|
create_collection: "Utwórz nową kolekcję"
|
||||||
menu:
|
menu:
|
||||||
tiles_view: "Kafelki"
|
tiles_view: "Kafelki"
|
||||||
|
compact_view: "Widok kompaktowy"
|
||||||
changelog: "Co nowego?"
|
changelog: "Co nowego?"
|
||||||
list:
|
list:
|
||||||
searchbar:
|
searchbar:
|
||||||
title: "Szukaj"
|
title: "Szukaj"
|
||||||
filter: "Filtr"
|
filter: "Filtr"
|
||||||
|
show_hidden: "Pokaż ukryte"
|
||||||
sort:
|
sort:
|
||||||
title: "Sortowanie"
|
title: "Sortowanie"
|
||||||
options:
|
options:
|
||||||
|
|||||||
@@ -184,6 +184,8 @@ collections:
|
|||||||
add_group: "Adicionar grupo vazio"
|
add_group: "Adicionar grupo vazio"
|
||||||
export_bookmarks: "Exportar para favoritos"
|
export_bookmarks: "Exportar para favoritos"
|
||||||
edit: "Editar coleção"
|
edit: "Editar coleção"
|
||||||
|
hide: "Ocultar coleção"
|
||||||
|
unhide: "Mostrar coleção"
|
||||||
|
|
||||||
groups:
|
groups:
|
||||||
title: "Grupo"
|
title: "Grupo"
|
||||||
@@ -219,21 +221,25 @@ dialogs:
|
|||||||
title:
|
title:
|
||||||
edit_collection: "Editar coleção"
|
edit_collection: "Editar coleção"
|
||||||
edit_group: "Editar grupo"
|
edit_group: "Editar grupo"
|
||||||
|
edit_tab: "Editar aba"
|
||||||
new_group: "Novo grupo"
|
new_group: "Novo grupo"
|
||||||
new_collection: "Nova coleção"
|
new_collection: "Nova coleção"
|
||||||
collection_title: "Título"
|
collection_title: "Título"
|
||||||
color: "Cor"
|
color: "Cor"
|
||||||
|
url_error: "A URL é obrigatória"
|
||||||
|
|
||||||
main:
|
main:
|
||||||
header:
|
header:
|
||||||
create_collection: "Criar nova coleção"
|
create_collection: "Criar nova coleção"
|
||||||
menu:
|
menu:
|
||||||
tiles_view: "Visualização em blocos"
|
tiles_view: "Visualização em blocos"
|
||||||
|
compact_view: "Visualização compacta"
|
||||||
changelog: "O que há de novo?"
|
changelog: "O que há de novo?"
|
||||||
list:
|
list:
|
||||||
searchbar:
|
searchbar:
|
||||||
title: "Pesquisar"
|
title: "Pesquisar"
|
||||||
filter: "Filtrar"
|
filter: "Filtrar"
|
||||||
|
show_hidden: "Mostrar ocultas"
|
||||||
sort:
|
sort:
|
||||||
title: "Ordenar"
|
title: "Ordenar"
|
||||||
options:
|
options:
|
||||||
|
|||||||
@@ -184,6 +184,8 @@ collections:
|
|||||||
add_group: "Добавить пустую группу"
|
add_group: "Добавить пустую группу"
|
||||||
export_bookmarks: "Экспортировать в закладки"
|
export_bookmarks: "Экспортировать в закладки"
|
||||||
edit: "Редактировать коллекцию"
|
edit: "Редактировать коллекцию"
|
||||||
|
hide: "Скрыть коллекцию"
|
||||||
|
unhide: "Показать коллекцию"
|
||||||
|
|
||||||
groups:
|
groups:
|
||||||
title: "Группа"
|
title: "Группа"
|
||||||
@@ -219,21 +221,25 @@ dialogs:
|
|||||||
title:
|
title:
|
||||||
edit_collection: "Редактировать коллекцию"
|
edit_collection: "Редактировать коллекцию"
|
||||||
edit_group: "Редактировать группу"
|
edit_group: "Редактировать группу"
|
||||||
|
edit_tab: "Редактировать вкладку"
|
||||||
new_group: "Новая группа"
|
new_group: "Новая группа"
|
||||||
new_collection: "Новая коллекция"
|
new_collection: "Новая коллекция"
|
||||||
collection_title: "Название"
|
collection_title: "Название"
|
||||||
color: "Цвет"
|
color: "Цвет"
|
||||||
|
url_error: "URL является обязательным"
|
||||||
|
|
||||||
main:
|
main:
|
||||||
header:
|
header:
|
||||||
create_collection: "Создать новую коллекцию"
|
create_collection: "Создать новую коллекцию"
|
||||||
menu:
|
menu:
|
||||||
tiles_view: "Плитки"
|
tiles_view: "Плитки"
|
||||||
|
compact_view: "Компактный вид"
|
||||||
changelog: "Что нового?"
|
changelog: "Что нового?"
|
||||||
list:
|
list:
|
||||||
searchbar:
|
searchbar:
|
||||||
title: "Поиск"
|
title: "Поиск"
|
||||||
filter: "Фильтр"
|
filter: "Фильтр"
|
||||||
|
show_hidden: "Показать скрытые"
|
||||||
sort:
|
sort:
|
||||||
title: "Сортировка"
|
title: "Сортировка"
|
||||||
options:
|
options:
|
||||||
|
|||||||
@@ -184,6 +184,8 @@ collections:
|
|||||||
add_group: "Додати порожню групу"
|
add_group: "Додати порожню групу"
|
||||||
export_bookmarks: "Експортувати в закладки"
|
export_bookmarks: "Експортувати в закладки"
|
||||||
edit: "Редагувати колекцію"
|
edit: "Редагувати колекцію"
|
||||||
|
hide: "Приховати колекцію"
|
||||||
|
unhide: "Показати колекцію"
|
||||||
|
|
||||||
groups:
|
groups:
|
||||||
title: "Група"
|
title: "Група"
|
||||||
@@ -219,21 +221,25 @@ dialogs:
|
|||||||
title:
|
title:
|
||||||
edit_collection: "Редагувати колекцію"
|
edit_collection: "Редагувати колекцію"
|
||||||
edit_group: "Редагувати групу"
|
edit_group: "Редагувати групу"
|
||||||
|
edit_tab: "Редагувати вкладку"
|
||||||
new_group: "Нова група"
|
new_group: "Нова група"
|
||||||
new_collection: "Нова колекція"
|
new_collection: "Нова колекція"
|
||||||
collection_title: "Назва"
|
collection_title: "Назва"
|
||||||
color: "Колір"
|
color: "Колір"
|
||||||
|
url_error: "URL є обов'язковим"
|
||||||
|
|
||||||
main:
|
main:
|
||||||
header:
|
header:
|
||||||
create_collection: "Створити нову колекцію"
|
create_collection: "Створити нову колекцію"
|
||||||
menu:
|
menu:
|
||||||
tiles_view: "Плитки"
|
tiles_view: "Плитки"
|
||||||
|
compact_view: "Компактний вид"
|
||||||
changelog: "Що нового?"
|
changelog: "Що нового?"
|
||||||
list:
|
list:
|
||||||
searchbar:
|
searchbar:
|
||||||
title: "Пошук"
|
title: "Пошук"
|
||||||
filter: "Фільтр"
|
filter: "Фільтр"
|
||||||
|
show_hidden: "Показати приховані"
|
||||||
sort:
|
sort:
|
||||||
title: "Сортування"
|
title: "Сортування"
|
||||||
options:
|
options:
|
||||||
|
|||||||
@@ -184,6 +184,8 @@ collections:
|
|||||||
add_group: "添加空分组"
|
add_group: "添加空分组"
|
||||||
export_bookmarks: "导出到书签"
|
export_bookmarks: "导出到书签"
|
||||||
edit: "编辑收藏"
|
edit: "编辑收藏"
|
||||||
|
hide: "隐藏收藏"
|
||||||
|
unhide: "显示收藏"
|
||||||
|
|
||||||
groups:
|
groups:
|
||||||
title: "分组"
|
title: "分组"
|
||||||
@@ -219,21 +221,25 @@ dialogs:
|
|||||||
title:
|
title:
|
||||||
edit_collection: "编辑收藏"
|
edit_collection: "编辑收藏"
|
||||||
edit_group: "编辑分组"
|
edit_group: "编辑分组"
|
||||||
|
edit_tab: "编辑标签页"
|
||||||
new_group: "新分组"
|
new_group: "新分组"
|
||||||
new_collection: "新收藏"
|
new_collection: "新收藏"
|
||||||
collection_title: "标题"
|
collection_title: "标题"
|
||||||
color: "颜色"
|
color: "颜色"
|
||||||
|
url_error: "需要 URL"
|
||||||
|
|
||||||
main:
|
main:
|
||||||
header:
|
header:
|
||||||
create_collection: "创建新收藏"
|
create_collection: "创建新收藏"
|
||||||
menu:
|
menu:
|
||||||
tiles_view: "平铺视图"
|
tiles_view: "平铺视图"
|
||||||
|
compact_view: "紧凑视图"
|
||||||
changelog: "更新内容"
|
changelog: "更新内容"
|
||||||
list:
|
list:
|
||||||
searchbar:
|
searchbar:
|
||||||
title: "搜索"
|
title: "搜索"
|
||||||
filter: "筛选"
|
filter: "筛选"
|
||||||
|
show_hidden: "显示隐藏项"
|
||||||
sort:
|
sort:
|
||||||
title: "排序"
|
title: "排序"
|
||||||
options:
|
options:
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export type DefaultGroupItem =
|
|||||||
type: "group";
|
type: "group";
|
||||||
pinned?: false;
|
pinned?: false;
|
||||||
title?: string;
|
title?: string;
|
||||||
color: chrome.tabGroups.ColorEnum;
|
color: `${Browser.tabGroups.Color}`;
|
||||||
items: TabItem[];
|
items: TabItem[];
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -28,8 +28,9 @@ export type CollectionItem =
|
|||||||
type: "collection";
|
type: "collection";
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
title?: string;
|
title?: string;
|
||||||
color?: chrome.tabGroups.ColorEnum;
|
color?: `${Browser.tabGroups.Color}`;
|
||||||
items: (TabItem | GroupItem)[];
|
items: (TabItem | GroupItem)[];
|
||||||
|
hidden?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type GraphicsStorage = Record<string, GraphicsItem>;
|
export type GraphicsStorage = Record<string, GraphicsItem>;
|
||||||
|
|||||||
Generated
+10857
File diff suppressed because it is too large
Load Diff
+30
-26
@@ -1,46 +1,50 @@
|
|||||||
{
|
{
|
||||||
"name": "tabs-aside",
|
"name": "tabs-aside",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "3.2.0",
|
"version": "3.3.2",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "wxt",
|
"dev": "wxt",
|
||||||
"build": "yarn lint && wxt build --mv3",
|
"build": "npm run lint && wxt build --mv3",
|
||||||
"zip": "yarn lint && wxt zip --mv3",
|
"zip": "npm run lint && wxt zip --mv3",
|
||||||
"lint": "tsc --noEmit && eslint . -c eslint.config.js",
|
"lint": "tsc --noEmit && eslint . -c eslint.config.js",
|
||||||
"prepare": "wxt prepare",
|
"prepare": "wxt prepare",
|
||||||
"postinstall": "yarn prepare"
|
"postinstall": "wxt prepare"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@dnd-kit/core": "^6.3.1",
|
"@dnd-kit/core": "^6.3.1",
|
||||||
"@dnd-kit/modifiers": "^9.0.0",
|
"@dnd-kit/modifiers": "^9.0.0",
|
||||||
"@dnd-kit/sortable": "^10.0.0",
|
"@dnd-kit/sortable": "^10.0.0",
|
||||||
"@dnd-kit/utilities": "^3.2.2",
|
"@dnd-kit/utilities": "^3.2.2",
|
||||||
"@fluentui/react-components": "^9.72.6",
|
"@fluentui/react-components": "^9.74.1",
|
||||||
"@fluentui/react-icons": "^2.0.313",
|
"@fluentui/react-icons": "^2.0.328",
|
||||||
"@webext-core/messaging": "^2.3.0",
|
"@webext-core/messaging": "^3.0.1",
|
||||||
"@wxt-dev/analytics": "^0.5.1",
|
"@wxt-dev/analytics": "^0.5.4",
|
||||||
"@wxt-dev/i18n": "^0.2.4",
|
"@wxt-dev/i18n": "^0.2.5",
|
||||||
"lzutf8": "^0.6.3",
|
"lzutf8": "^0.6.3",
|
||||||
"react": "~19.2.0",
|
"react": "^19.2.7",
|
||||||
"react-dom": "~19.2.0"
|
"react-dom": "^19.2.7"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/css": "^0.14.1",
|
"@eslint/css": "^1.3.0",
|
||||||
"@eslint/js": "^9.39.1",
|
"@eslint/js": "^9.39.4",
|
||||||
"@eslint/json": "^0.14.0",
|
"@eslint/json": "^2.0.0",
|
||||||
"@stylistic/eslint-plugin": "^5.5.0",
|
"@stylistic/eslint-plugin": "^5.10.0",
|
||||||
"@types/react": "~19.2.2",
|
"@types/react": "^19.2.16",
|
||||||
"@types/react-dom": "~19.2.2",
|
"@types/react-dom": "^19.2.3",
|
||||||
"@wxt-dev/module-react": "^1.1.5",
|
"@wxt-dev/module-react": "^1.2.2",
|
||||||
"eslint": "^9.39.1",
|
"eslint": "^9.39.4",
|
||||||
"eslint-plugin-react": "^7.37.5",
|
"eslint-plugin-react": "^7.37.5",
|
||||||
"globals": "^16.5.0",
|
"typescript": "^6.0.3",
|
||||||
"scheduler": "0.23.0",
|
"typescript-eslint": "^8.60.0",
|
||||||
"typescript": "^5.9.3",
|
"wxt": "^0.20.26"
|
||||||
"typescript-eslint": "^8.46.4",
|
|
||||||
"vite": "^7.2.2",
|
|
||||||
"wxt": "~0.19.29"
|
|
||||||
},
|
},
|
||||||
"packageManager": "yarn@4.9.2"
|
"overrides": {
|
||||||
|
"node-notifier": {
|
||||||
|
"uuid": "^11.1.1"
|
||||||
|
},
|
||||||
|
"web-ext-run": {
|
||||||
|
"tmp": "^0.2.6"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,85 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg id="Layer_5" data-name="Layer 5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2000 1000">
|
||||||
|
<defs>
|
||||||
|
<style>
|
||||||
|
.cls-1,
|
||||||
|
.cls-2,
|
||||||
|
.cls-3 {
|
||||||
|
stroke-width: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-1,
|
||||||
|
.cls-4 {
|
||||||
|
fill: #ff7545;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-2,
|
||||||
|
.cls-6 {
|
||||||
|
fill: #242424;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-5 {
|
||||||
|
stroke-width: 12px;
|
||||||
|
stroke-linejoin: round;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-6,
|
||||||
|
.cls-4 {
|
||||||
|
stroke: #242424;
|
||||||
|
stroke-linejoin: round;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-6,
|
||||||
|
.cls-4 {
|
||||||
|
stroke-linecap: round;
|
||||||
|
stroke-width: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-3 {
|
||||||
|
fill: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.laptop {
|
||||||
|
fill: #424242;
|
||||||
|
stroke: #424242;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.laptop {
|
||||||
|
fill: #d6d6d6;
|
||||||
|
stroke: #d6d6d6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
<g>
|
||||||
|
<path class="cls-1"
|
||||||
|
d="M1656,996.17c-62.9,0-124.32-15.87-177.6-45.9-48.31-27.23-88.43-65.09-116.63-109.96,42.24,16.15,87.02,24.34,133.29,24.34,191.24,0,346.83-142.61,346.83-317.91,0-49.29-17.77-79.71-40.27-118.22-.25-.44-.51-.87-.77-1.31,56.26,25.15,103.09,59.13,135.92,98.71,38.75,46.72,58.4,100.56,58.4,160,0,82.81-35.24,160.68-99.22,219.27-64.08,58.67-149.29,90.99-239.96,90.99Z" />
|
||||||
|
<path class="cls-2"
|
||||||
|
d="M1810.27,435.77c21.37,10.21,41.26,21.71,59.35,34.32,24.99,17.43,46.59,37.03,64.2,58.27,38.17,46.02,57.52,99.03,57.52,157.56,0,41.28-8.83,81.34-26.25,119.04-16.85,36.47-40.98,69.24-71.73,97.4-30.8,28.2-66.67,50.34-106.62,65.82-41.4,16.04-85.39,24.17-130.75,24.17-62.25,0-123.01-15.7-175.72-45.4-44.28-24.95-81.59-58.94-108.99-99.08,39.45,13.69,80.98,20.62,123.77,20.62,193.35,0,350.66-144.33,350.66-321.74,0-46.31-15.25-76.1-35.44-110.97M1791.69,419.07c25.27,43.77,46.37,74.72,46.37,127.67,0,173.46-153.57,314.08-343,314.08-50.83,0-99.07-10.14-142.46-28.3,57.52,99.6,171.79,167.48,303.4,167.48,189.44,0,343-140.62,343-314.08,0-126.92-88.98-217.31-207.31-266.85h0Z" />
|
||||||
|
</g>
|
||||||
|
<path class="cls-4"
|
||||||
|
d="M1850.7,210.11c40.63,49.7,33.28,122.93-16.42,163.56-49.7,40.63-174.15,75.16-214.78,25.46-40.63-49.7,17.94-164.81,67.64-205.44,49.7-40.63,122.93-33.28,163.56,16.42Z" />
|
||||||
|
<g>
|
||||||
|
<path class="cls-1"
|
||||||
|
d="M1141.23,996c-107.8,0-211.68-30.24-292.51-85.15-34.04-23.13-63.19-50-86.62-79.87-30.95-39.44-50.89-82.52-59.27-128.07,2.74-4.13,13.24-18.52,34.99-33.02,23.72-15.81,66.05-35,133.04-36.62,3.24-.07,6.3-.11,9.33-.11,43.77,0,86.56,14.88,130.79,45.49,38.84,26.88,73.68,62.33,104.42,93.61,24.59,25.02,47.83,48.66,69.78,64.67,62.27,45.4,122.66,67.87,162.35,78.74,26.53,7.27,47.34,10.45,59.7,11.84-35.66,20.71-74.9,37.05-116.83,48.62-47.77,13.19-97.96,19.88-149.17,19.88Z" />
|
||||||
|
<path class="cls-2"
|
||||||
|
d="M880.19,637.16c42.93,0,84.97,14.65,128.51,44.78,38.53,26.66,73.23,61.97,103.85,93.12,24.71,25.14,48.06,48.89,70.28,65.1,62.76,45.75,123.63,68.41,163.65,79.36,19.52,5.35,36,8.51,48.36,10.37-32.55,17.79-67.94,32.01-105.51,42.38-47.43,13.09-97.26,19.73-148.11,19.73-54.46,0-107.6-7.59-157.95-22.55-48.57-14.43-93.08-35.26-132.31-61.91-33.7-22.89-62.54-49.48-85.72-79.03-30.18-38.46-49.75-80.41-58.19-124.72,8.21-11.64,51.24-63.8,163.88-66.52,3.21-.07,6.24-.11,9.25-.11M880.19,629.16c-3.2,0-6.34.04-9.43.11-132.17,3.19-172.15,72.82-172.15,72.82,8.48,47.56,29.48,92.03,60.34,131.36,23.74,30.26,53.31,57.47,87.52,80.71,78.67,53.44,181.82,85.84,294.76,85.84,105.42,0,202.3-28.24,278.72-75.46,0,0-28.21-.92-71.36-12.74-43.16-11.81-101.26-34.52-161.05-78.11-76.68-55.9-167.65-204.53-307.35-204.53h0Z" />
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path class="cls-3"
|
||||||
|
d="M760.28,828.65c-29.91-38.81-49.23-81.08-57.46-125.74,2.74-4.13,13.24-18.52,34.99-33.02,23.38-15.59,64.87-34.46,130.24-36.54l51.71,133.53-159.48,61.77Z" />
|
||||||
|
<path class="cls-2"
|
||||||
|
d="M865.35,637.45l49.24,127.14-152.95,59.24c-28.13-37.18-46.48-77.52-54.58-120.04,8.07-11.44,49.8-62.07,158.29-66.35M870.76,629.27c-132.17,3.19-172.15,72.82-172.15,72.82,8.48,47.56,29.48,92.03,60.34,131.36l165.99-64.29-54.18-139.89h0Z" />
|
||||||
|
</g>
|
||||||
|
<rect class="cls-5 laptop" x="1219.11" y="766.83" width="270.32" height="9.95"
|
||||||
|
transform="translate(304.74 -380.67) rotate(18)" />
|
||||||
|
<rect class="cls-5 laptop" x="1059.2" y="596.18" width="270.32" height="9.95"
|
||||||
|
transform="translate(1479.19 -705.28) rotate(75.58)" />
|
||||||
|
<path class="cls-6"
|
||||||
|
d="M1666.04,416.09c1.06-29.87-22.29-54.95-52.17-56.01-2.32-.08-4.6,0-6.85.2.75,15,4.9,28.4,13.47,38.89,10.4,12.71,26.28,19.9,44.99,22.9.29-1.96.48-3.95.55-5.98Z" />
|
||||||
|
<path class="cls-4"
|
||||||
|
d="M1851.96,176.25c-29.01-25.87-78.84-33.24-78.84-33.24,0,0,37.28-38.45,83.99-62.26,46.65-23.78,102.73-32.92,102.73-32.92,0,0-26.34,46.29-43.76,93.12-19.06,51.23-22.35,98.62-22.35,98.62,0,0-13.99-38.55-41.77-63.33Z" />
|
||||||
|
<ellipse class="cls-2" cx="1700.37" cy="301.32" rx="10.19" ry="17.93"
|
||||||
|
transform="translate(271.38 1270.89) rotate(-44.21)" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 4.6 KiB |
+5
-5
@@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"extends": "./.wxt/tsconfig.json",
|
"extends": "./.wxt/tsconfig.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"allowImportingTsExtensions": true,
|
"allowImportingTsExtensions": true,
|
||||||
"jsx": "react-jsx",
|
"jsx": "react-jsx",
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"strictNullChecks": true
|
"strictNullChecks": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
import { Tabs } from "wxt/browser";
|
export async function closeTabsAsync(tabs: Browser.tabs.Tab[]): Promise<void>
|
||||||
|
|
||||||
export async function closeTabsAsync(tabs: Tabs.Tab[]): Promise<void>
|
|
||||||
{
|
{
|
||||||
if (tabs.length < 1)
|
if (tabs.length < 1)
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { CollectionItem, GroupItem } from "@/models/CollectionModels";
|
import { CollectionItem, GroupItem } from "@/models/CollectionModels";
|
||||||
import { Tabs } from "wxt/browser";
|
|
||||||
|
|
||||||
export async function createCollectionFromTabs(tabs: Tabs.Tab[]): Promise<CollectionItem>
|
export async function createCollectionFromTabs(tabs: Browser.tabs.Tab[]): Promise<CollectionItem>
|
||||||
{
|
{
|
||||||
const collection: CollectionItem = {
|
const collection: CollectionItem = {
|
||||||
type: "collection",
|
type: "collection",
|
||||||
@@ -36,7 +35,7 @@ export async function createCollectionFromTabs(tabs: Tabs.Tab[]): Promise<Collec
|
|||||||
tabs.every(i => i.groupId === tabs[0].groupId)
|
tabs.every(i => i.groupId === tabs[0].groupId)
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
const group = await chrome.tabGroups.get(tabs[0].groupId);
|
const group = await browser.tabGroups.get(tabs[0].groupId);
|
||||||
collection.title = group.title;
|
collection.title = group.title;
|
||||||
collection.color = group.color;
|
collection.color = group.color;
|
||||||
|
|
||||||
@@ -63,7 +62,7 @@ export async function createCollectionFromTabs(tabs: Tabs.Tab[]): Promise<Collec
|
|||||||
if (!activeGroup || activeGroup !== tab.groupId)
|
if (!activeGroup || activeGroup !== tab.groupId)
|
||||||
{
|
{
|
||||||
activeGroup = tab.groupId;
|
activeGroup = tab.groupId;
|
||||||
const group = await chrome.tabGroups.get(activeGroup);
|
const group = await browser.tabGroups.get(activeGroup!);
|
||||||
|
|
||||||
collection.items.push({
|
collection.items.push({
|
||||||
type: "group",
|
type: "group",
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
import { Tabs } from "wxt/browser";
|
|
||||||
import { settings } from "./settings";
|
import { settings } from "./settings";
|
||||||
|
|
||||||
export async function getTabsToSaveAsync(): Promise<[Tabs.Tab[], number]>
|
export async function getTabsToSaveAsync(forceSelected: boolean = false): Promise<[Browser.tabs.Tab[], number]>
|
||||||
{
|
{
|
||||||
let tabs: Tabs.Tab[] = await browser.tabs.query({
|
let tabs: Browser.tabs.Tab[] = await browser.tabs.query({
|
||||||
currentWindow: true,
|
currentWindow: true,
|
||||||
highlighted: true
|
highlighted: true
|
||||||
});
|
});
|
||||||
|
|
||||||
if (tabs.length < 2)
|
if (!forceSelected && tabs.length < 2)
|
||||||
{
|
{
|
||||||
const ignorePinned: boolean = await settings.ignorePinned.getValue();
|
const ignorePinned: boolean = await settings.ignorePinned.getValue();
|
||||||
tabs = await browser.tabs.query({
|
tabs = await browser.tabs.query({
|
||||||
|
|||||||
@@ -103,5 +103,13 @@ export const settings = {
|
|||||||
fallback: true,
|
fallback: true,
|
||||||
version: 1
|
version: 1
|
||||||
}
|
}
|
||||||
|
),
|
||||||
|
|
||||||
|
compactView: storage.defineItem<boolean>(
|
||||||
|
"sync:compactView",
|
||||||
|
{
|
||||||
|
fallback: false,
|
||||||
|
version: 1
|
||||||
|
}
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Unwatch } from "wxt/storage";
|
import { Unwatch } from "wxt/utils/storage";
|
||||||
|
|
||||||
export default function watchTabSelection(onChange: TabSelectChangeHandler): Unwatch
|
export default function watchTabSelection(onChange: TabSelectChangeHandler): Unwatch
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -87,7 +87,6 @@ export default defineConfig({
|
|||||||
id: "tabsaside@xfox111.net",
|
id: "tabsaside@xfox111.net",
|
||||||
strict_min_version: "139.0",
|
strict_min_version: "139.0",
|
||||||
|
|
||||||
// @ts-expect-error Introduced in Firefox 139
|
|
||||||
data_collection_permissions: {
|
data_collection_permissions: {
|
||||||
required: ["browsingActivity"],
|
required: ["browsingActivity"],
|
||||||
optional: ["technicalAndInteraction"]
|
optional: ["technicalAndInteraction"]
|
||||||
|
|||||||
Reference in New Issue
Block a user