mirror of
https://github.com/XFox111/TabsAsideExtension.git
synced 2026-07-02 19:52:47 +03:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 41123bd8db | |||
| 9fbc152a91 | |||
| fdac0c0766 | |||
| 2065ee4637 | |||
| b51dd6083f |
@@ -22,5 +22,5 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
"postCreateCommand": "yarn install"
|
"postCreateCommand": "npm install"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
-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,9 @@ export const useOptionsStyles = makeStyles({
|
|||||||
flexFlow: "column",
|
flexFlow: "column",
|
||||||
alignItems: "flex-start",
|
alignItems: "flex-start",
|
||||||
gap: tokens.spacingVerticalSNudge
|
gap: tokens.spacingVerticalSNudge
|
||||||
|
},
|
||||||
|
messageBar:
|
||||||
|
{
|
||||||
|
flexShrink: 0
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -2,12 +2,13 @@ import { useDialog } from "@/contexts/DialogProvider";
|
|||||||
import { clearGraphicsStorage, cloudDisabled, setCloudStorage, thumbnailCaptureEnabled } from "@/features/collectionStorage";
|
import { clearGraphicsStorage, cloudDisabled, setCloudStorage, thumbnailCaptureEnabled } from "@/features/collectionStorage";
|
||||||
import { useDangerStyles } from "@/hooks/useDangerStyles";
|
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, Divider, Field, InfoLabel, LabelProps, MessageBar, MessageBarBody, MessageBarTitle, ProgressBar, Subtitle2, 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";
|
import BookmarksSection from "@/features/netscapeBookmarks/layouts/BookmarksSection";
|
||||||
|
|
||||||
export default function StorageSection(): React.ReactElement
|
export default function StorageSection(): React.ReactElement
|
||||||
{
|
{
|
||||||
@@ -78,6 +79,59 @@ export default function StorageSection(): React.ReactElement
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<Subtitle2>{ i18n.t("options_page.storage.manage_title") }</Subtitle2>
|
||||||
|
|
||||||
|
{ isCloudDisabled === false &&
|
||||||
|
<Field
|
||||||
|
label={ i18n.t("options_page.storage.capacity.title") }
|
||||||
|
hint={ i18n.t("options_page.storage.capacity.description", [(bytesInUse / 1024).toFixed(1), storageQuota / 1024]) }
|
||||||
|
validationState={ usedStorageRatio >= 0.8 ? "error" : undefined }
|
||||||
|
>
|
||||||
|
<ProgressBar value={ usedStorageRatio } thickness="large" />
|
||||||
|
</Field>
|
||||||
|
}
|
||||||
|
|
||||||
|
<div className={ cls.horizontalButtons }>
|
||||||
|
{ isCloudDisabled === true &&
|
||||||
|
<Button appearance="primary" onClick={ () => setCloudStorage(true) }>
|
||||||
|
{ i18n.t("options_page.storage.enable") }
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
|
||||||
|
{ isCloudDisabled === false &&
|
||||||
|
<div className={ cls.horizontalButtons }>
|
||||||
|
<Button
|
||||||
|
appearance="subtle" className={ dangerCls.buttonSubtle }
|
||||||
|
onClick={ handleDisableCloud }
|
||||||
|
>
|
||||||
|
{ i18n.t("options_page.storage.disable") }
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={ cls.horizontalButtons }>
|
||||||
|
<Button icon={ <ArrowDownload20Regular /> } onClick={ exportData }>
|
||||||
|
{ i18n.t("options_page.storage.export") }
|
||||||
|
</Button>
|
||||||
|
<Button icon={ <ArrowUpload20Regular /> } onClick={ handleImport }>
|
||||||
|
{ i18n.t("options_page.storage.import") }
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{ importResult !== null &&
|
||||||
|
<MessageBar intent={ importResult ? "success" : "error" } className={ cls.messageBar }>
|
||||||
|
<MessageBarBody>
|
||||||
|
{ importResult === true ?
|
||||||
|
i18n.t("options_page.storage.import_results.success") :
|
||||||
|
i18n.t("options_page.storage.import_results.error")
|
||||||
|
}
|
||||||
|
</MessageBarBody>
|
||||||
|
</MessageBar>
|
||||||
|
}
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
<Subtitle2>{ i18n.t("options_page.storage.thumbnails_title") }</Subtitle2>
|
||||||
<div className={ cls.group }>
|
<div className={ cls.group }>
|
||||||
<Switch
|
<Switch
|
||||||
checked={ isThumbnailCaptureEnabled ?? true }
|
checked={ isThumbnailCaptureEnabled ?? true }
|
||||||
@@ -101,52 +155,8 @@ export default function StorageSection(): React.ReactElement
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{ isCloudDisabled === false &&
|
<Divider />
|
||||||
<Field
|
<BookmarksSection />
|
||||||
label={ i18n.t("options_page.storage.capacity.title") }
|
|
||||||
hint={ i18n.t("options_page.storage.capacity.description", [(bytesInUse / 1024).toFixed(1), storageQuota / 1024]) }
|
|
||||||
validationState={ usedStorageRatio >= 0.8 ? "error" : undefined }
|
|
||||||
>
|
|
||||||
<ProgressBar value={ usedStorageRatio } thickness="large" />
|
|
||||||
</Field>
|
|
||||||
}
|
|
||||||
|
|
||||||
{ isCloudDisabled === true &&
|
|
||||||
<Button appearance="primary" onClick={ () => setCloudStorage(true) }>
|
|
||||||
{ i18n.t("options_page.storage.enable") }
|
|
||||||
</Button>
|
|
||||||
}
|
|
||||||
|
|
||||||
<div className={ cls.horizontalButtons }>
|
|
||||||
<Button icon={ <ArrowDownload20Regular /> } onClick={ exportData }>
|
|
||||||
{ i18n.t("options_page.storage.export") }
|
|
||||||
</Button>
|
|
||||||
<Button icon={ <ArrowUpload20Regular /> } onClick={ handleImport }>
|
|
||||||
{ i18n.t("options_page.storage.import") }
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{ importResult !== null &&
|
|
||||||
<MessageBar intent={ importResult ? "success" : "error" }>
|
|
||||||
<MessageBarBody>
|
|
||||||
{ importResult === true ?
|
|
||||||
i18n.t("options_page.storage.import_results.success") :
|
|
||||||
i18n.t("options_page.storage.import_results.error")
|
|
||||||
}
|
|
||||||
</MessageBarBody>
|
|
||||||
</MessageBar>
|
|
||||||
}
|
|
||||||
|
|
||||||
{ isCloudDisabled === false &&
|
|
||||||
<div className={ cls.horizontalButtons }>
|
|
||||||
<Button
|
|
||||||
appearance="subtle" className={ dangerCls.buttonSubtle }
|
|
||||||
onClick={ handleDisableCloud }
|
|
||||||
>
|
|
||||||
{ i18n.t("options_page.storage.disable") }
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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")
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -44,11 +44,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>
|
||||||
|
|||||||
@@ -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)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -61,5 +61,5 @@ export default function filterCollections(
|
|||||||
export type CollectionFilterType =
|
export type CollectionFilterType =
|
||||||
{
|
{
|
||||||
query: string;
|
query: string;
|
||||||
colors: (chrome.tabGroups.ColorEnum | "none")[];
|
colors: (`${Browser.tabGroups.Color}` | "none")[];
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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>"))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
import { useDialog } from "@/contexts/DialogProvider";
|
||||||
|
import { Body1, Button, makeStyles, MessageBar, MessageBarBody, Subtitle2, tokens } from "@fluentui/react-components";
|
||||||
|
import { ArrowDownload20Regular, ArrowUpload20Regular } from "@fluentui/react-icons";
|
||||||
|
import importBookmarks from "../utils/importBookmarks";
|
||||||
|
import exportBookmarks from "../utils/exportBookmarks";
|
||||||
|
|
||||||
|
export default function BookmarksSection(): React.ReactElement
|
||||||
|
{
|
||||||
|
const cls = useStyles();
|
||||||
|
const dialog = useDialog();
|
||||||
|
|
||||||
|
const [importResult, setImportResult] = useState<number | null>(null);
|
||||||
|
|
||||||
|
const handleImport = (): void =>
|
||||||
|
dialog.pushPrompt({
|
||||||
|
title: i18n.t("features.netscape_bookmarks.import_dialog.title"),
|
||||||
|
confirmText: i18n.t("options_page.storage.import_prompt.proceed"),
|
||||||
|
onConfirm: () => importBookmarks().then(setImportResult),
|
||||||
|
content: (
|
||||||
|
<Body1 as="p">
|
||||||
|
{ i18n.t("features.netscape_bookmarks.import_dialog.content") }
|
||||||
|
</Body1>
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={ cls.root }>
|
||||||
|
<Subtitle2>{ i18n.t("features.netscape_bookmarks.title") }</Subtitle2>
|
||||||
|
|
||||||
|
{ importResult !== null &&
|
||||||
|
<MessageBar intent={ importResult >= 0 ? "success" : "error" } layout="multiline">
|
||||||
|
<MessageBarBody>
|
||||||
|
{ importResult >= 0 ?
|
||||||
|
i18n.t("features.netscape_bookmarks.import_result.success", [importResult]) :
|
||||||
|
i18n.t("features.netscape_bookmarks.import_result.error")
|
||||||
|
}
|
||||||
|
</MessageBarBody>
|
||||||
|
</MessageBar>
|
||||||
|
}
|
||||||
|
|
||||||
|
<div className={ cls.buttons }>
|
||||||
|
<Button icon={ <ArrowDownload20Regular /> } onClick={ exportBookmarks }>
|
||||||
|
{ i18n.t("features.netscape_bookmarks.export") }
|
||||||
|
</Button>
|
||||||
|
<Button icon={ <ArrowUpload20Regular /> } onClick={ handleImport }>
|
||||||
|
{ i18n.t("features.netscape_bookmarks.import") }
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const useStyles = makeStyles({
|
||||||
|
root:
|
||||||
|
{
|
||||||
|
display: "flex",
|
||||||
|
flexFlow: "column",
|
||||||
|
gap: tokens.spacingVerticalMNudge
|
||||||
|
},
|
||||||
|
buttons:
|
||||||
|
{
|
||||||
|
display: "flex",
|
||||||
|
flexWrap: "wrap",
|
||||||
|
gap: tokens.spacingVerticalSNudge
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
import { CollectionItem, GraphicsStorage, GroupItem, TabItem } from "@/models/CollectionModels";
|
||||||
|
import { Bookmark } from "node-bookmarks-parser/build/interfaces/bookmark";
|
||||||
|
|
||||||
|
export default function convertBookmarks(bookmarks: Bookmark[]): [CollectionItem[], GraphicsStorage, number]
|
||||||
|
{
|
||||||
|
let count: number = 0;
|
||||||
|
const graphics: GraphicsStorage = {};
|
||||||
|
const items: CollectionItem[] = [];
|
||||||
|
const untitled: CollectionItem = {
|
||||||
|
items: [],
|
||||||
|
timestamp: Date.now(),
|
||||||
|
type: "collection"
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const bookmark of bookmarks)
|
||||||
|
{
|
||||||
|
if (bookmark.type === "bookmark")
|
||||||
|
{
|
||||||
|
untitled.items.push(getTab(bookmark, graphics));
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
else if (bookmark.type === "folder")
|
||||||
|
{
|
||||||
|
const collection: CollectionItem = getCollection(bookmark, graphics);
|
||||||
|
items.push(collection);
|
||||||
|
count += collection.items.reduce((acc, item) =>
|
||||||
|
{
|
||||||
|
if (item.type === "tab")
|
||||||
|
return acc + 1;
|
||||||
|
else if (item.type === "group")
|
||||||
|
return acc + item.items.length;
|
||||||
|
return acc;
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (untitled.items.length > 0)
|
||||||
|
items.unshift(untitled);
|
||||||
|
|
||||||
|
return [items, graphics, count];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTab(bookmark: Bookmark, graphics: GraphicsStorage): TabItem
|
||||||
|
{
|
||||||
|
if (bookmark.icon)
|
||||||
|
graphics[bookmark.url!] = {
|
||||||
|
icon: bookmark.icon
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: "tab",
|
||||||
|
url: bookmark.url!,
|
||||||
|
title: bookmark.title || bookmark.url!
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCollection(bookmark: Bookmark, graphics: GraphicsStorage): CollectionItem
|
||||||
|
{
|
||||||
|
const collection: CollectionItem = {
|
||||||
|
items: [],
|
||||||
|
title: bookmark.title,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
type: "collection"
|
||||||
|
};
|
||||||
|
|
||||||
|
if (bookmark.children)
|
||||||
|
for (const child of bookmark.children)
|
||||||
|
{
|
||||||
|
if (child.type === "bookmark")
|
||||||
|
collection.items.push(getTab(child, graphics));
|
||||||
|
else if (child.type === "folder" && child.children)
|
||||||
|
collection.items.push(getGroup(child, graphics));
|
||||||
|
}
|
||||||
|
|
||||||
|
return collection;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getGroup(bookmark: Bookmark, graphics: GraphicsStorage): GroupItem
|
||||||
|
{
|
||||||
|
const group: GroupItem = {
|
||||||
|
items: [],
|
||||||
|
title: bookmark.title,
|
||||||
|
pinned: false,
|
||||||
|
type: "group",
|
||||||
|
color: getRandomColor()
|
||||||
|
};
|
||||||
|
|
||||||
|
if (bookmark.children)
|
||||||
|
for (const child of bookmark.children)
|
||||||
|
{
|
||||||
|
if (child.type === "bookmark")
|
||||||
|
group.items.push(getTab(child, graphics));
|
||||||
|
else if (child.type === "folder")
|
||||||
|
group.items.push(...getGroup(child, graphics).items);
|
||||||
|
}
|
||||||
|
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRandomColor(): "blue" | "cyan" | "green" | "grey" | "orange" | "pink" | "purple" | "red" | "yellow"
|
||||||
|
{
|
||||||
|
const colors = ["blue", "cyan", "green", "grey", "orange", "pink", "purple", "red", "yellow"] as const;
|
||||||
|
return colors[Math.floor(Math.random() * colors.length)];
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
import { getCollectionTitle } from "@/entrypoints/sidepanel/utils/getCollectionTitle";
|
||||||
|
import { getCollections } from "@/features/collectionStorage";
|
||||||
|
import { CollectionItem, GroupItem } from "@/models/CollectionModels";
|
||||||
|
|
||||||
|
export default async function exportBookmarks(): Promise<void>
|
||||||
|
{
|
||||||
|
const [collections] = await getCollections();
|
||||||
|
const lines: string[] = [
|
||||||
|
"<!DOCTYPE NETSCAPE-Bookmark-file-1>",
|
||||||
|
"<!-- This is an automatically generated file.",
|
||||||
|
" It will be read and overwritten.",
|
||||||
|
" DO NOT EDIT! -->",
|
||||||
|
"<META HTTP-EQUIV=\"Content-Type\" CONTENT=\"text/html; charset=UTF-8\">",
|
||||||
|
"<TITLE>Bookmarks</TITLE>",
|
||||||
|
"<H1>Bookmarks</H1>",
|
||||||
|
"<DL><p>"
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const collection of collections)
|
||||||
|
lines.push(...createFolder(collection));
|
||||||
|
|
||||||
|
lines.push("</DL><p>");
|
||||||
|
|
||||||
|
const data: string = lines.join("\n");
|
||||||
|
|
||||||
|
const blob: Blob = new Blob([data], { type: "text/html" });
|
||||||
|
|
||||||
|
const element: HTMLAnchorElement = document.createElement("a");
|
||||||
|
element.style.display = "none";
|
||||||
|
element.href = URL.createObjectURL(blob);
|
||||||
|
element.setAttribute("download", "collections.html");
|
||||||
|
|
||||||
|
document.body.appendChild(element);
|
||||||
|
element.click();
|
||||||
|
|
||||||
|
URL.revokeObjectURL(element.href);
|
||||||
|
document.body.removeChild(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createFolder(item: CollectionItem | GroupItem): string[]
|
||||||
|
{
|
||||||
|
const lines: string[] = [];
|
||||||
|
const title: string = item.type === "collection" ?
|
||||||
|
(item.title ?? getCollectionTitle(item)) :
|
||||||
|
(item.pinned ? i18n.t("groups.pinned") : (item.title ?? ""));
|
||||||
|
|
||||||
|
lines.push(`<DT><H3>${sanitizeString(title)}</H3>`);
|
||||||
|
lines.push("<DL><p>");
|
||||||
|
|
||||||
|
for (const subItem of item.items)
|
||||||
|
{
|
||||||
|
if (subItem.type === "tab")
|
||||||
|
lines.push(`<DT><A HREF="${encodeURI(subItem.url).replace(/"/g, "%22")}">${sanitizeString(subItem.title || subItem.url)}</A>`);
|
||||||
|
else if (subItem.type === "group")
|
||||||
|
lines.push(...createFolder(subItem));
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.push("</DL><p>");
|
||||||
|
return lines;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sanitizeString(str: string): string
|
||||||
|
{
|
||||||
|
return str
|
||||||
|
.replace(/&/g, "&")
|
||||||
|
.replace(/</g, "<")
|
||||||
|
.replace(/>/g, ">")
|
||||||
|
.replace(/"/g, """);
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import { getCollections, saveCollections } from "@/features/collectionStorage";
|
||||||
|
import { sendMessage } from "@/utils/messaging";
|
||||||
|
import parse from "node-bookmarks-parser";
|
||||||
|
import { Bookmark } from "node-bookmarks-parser/build/interfaces/bookmark";
|
||||||
|
import convertBookmarks from "./convertBookmarks";
|
||||||
|
|
||||||
|
export default async function importBookmarks(): Promise<number | null>
|
||||||
|
{
|
||||||
|
const element: HTMLInputElement = document.createElement("input");
|
||||||
|
element.style.display = "none";
|
||||||
|
element.hidden = true;
|
||||||
|
element.type = "file";
|
||||||
|
element.accept = ".html";
|
||||||
|
|
||||||
|
document.body.appendChild(element);
|
||||||
|
element.click();
|
||||||
|
|
||||||
|
await new Promise(resolve =>
|
||||||
|
{
|
||||||
|
const listener = () =>
|
||||||
|
{
|
||||||
|
element.removeEventListener("input", listener);
|
||||||
|
resolve(null);
|
||||||
|
};
|
||||||
|
element.addEventListener("input", listener);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!element.files || element.files.length < 1)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
const file: File = element.files[0];
|
||||||
|
const content: string = await file.text();
|
||||||
|
|
||||||
|
document.body.removeChild(element);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
const bookmarks: Bookmark[] = parse(content);
|
||||||
|
const [data, graphics, tabCount] = convertBookmarks(bookmarks);
|
||||||
|
const [collections, cloudIssues] = await getCollections();
|
||||||
|
|
||||||
|
await saveCollections([...data, ...collections], cloudIssues === null, graphics);
|
||||||
|
sendMessage("refreshCollections", undefined);
|
||||||
|
|
||||||
|
return tabCount;
|
||||||
|
}
|
||||||
|
catch (error)
|
||||||
|
{
|
||||||
|
console.error("Failed to parse bookmarks file", error);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -46,6 +46,17 @@ features:
|
|||||||
p3_text: "See the full list of what we collect"
|
p3_text: "See the full list of what we collect"
|
||||||
p3_link: "here"
|
p3_link: "here"
|
||||||
|
|
||||||
|
netscape_bookmarks:
|
||||||
|
title: "Browser bookmarks"
|
||||||
|
export: "Export collections as bookmarks"
|
||||||
|
import: "Import from bookmarks file"
|
||||||
|
import_dialog:
|
||||||
|
title: "Import bookmarks"
|
||||||
|
content: "Import bookmarks from a Netscape-format bookmarks file exported from your browser."
|
||||||
|
import_result:
|
||||||
|
success: "Successfully imported $1 bookmarks."
|
||||||
|
error: "Failed to import bookmarks. Please ensure the file is a valid bookmarks file."
|
||||||
|
|
||||||
notifications:
|
notifications:
|
||||||
tabs_saved:
|
tabs_saved:
|
||||||
title: "New collection created"
|
title: "New collection created"
|
||||||
@@ -114,6 +125,8 @@ options_page:
|
|||||||
restore: "Open tabs and remove the collection"
|
restore: "Open tabs and remove the collection"
|
||||||
storage:
|
storage:
|
||||||
title: "Storage"
|
title: "Storage"
|
||||||
|
manage_title: "Storage management"
|
||||||
|
thumbnails_title: "Thumbnails & icons"
|
||||||
capacity:
|
capacity:
|
||||||
title: "Cloud storage capacity"
|
title: "Cloud storage capacity"
|
||||||
description: "$1 of $2 KiB"
|
description: "$1 of $2 KiB"
|
||||||
|
|||||||
@@ -46,6 +46,17 @@ features:
|
|||||||
p3_text: "Ver la lista completa de lo que recopilamos"
|
p3_text: "Ver la lista completa de lo que recopilamos"
|
||||||
p3_link: "aquí"
|
p3_link: "aquí"
|
||||||
|
|
||||||
|
netscape_bookmarks:
|
||||||
|
title: "Marcadores del navegador"
|
||||||
|
export: "Exportar colecciones como marcadores"
|
||||||
|
import: "Importar desde archivo de marcadores"
|
||||||
|
import_dialog:
|
||||||
|
title: "Importar marcadores"
|
||||||
|
content: "Importa marcadores desde un archivo de marcadores en formato Netscape exportado desde tu navegador."
|
||||||
|
import_result:
|
||||||
|
success: "Se importaron correctamente $1 marcadores."
|
||||||
|
error: "No se pudieron importar los marcadores. Asegúrate de que el archivo sea un archivo de marcadores válido."
|
||||||
|
|
||||||
notifications:
|
notifications:
|
||||||
tabs_saved:
|
tabs_saved:
|
||||||
title: "Nueva colección creada"
|
title: "Nueva colección creada"
|
||||||
@@ -114,6 +125,8 @@ options_page:
|
|||||||
restore: "Abrir pestañas y eliminar la colección"
|
restore: "Abrir pestañas y eliminar la colección"
|
||||||
storage:
|
storage:
|
||||||
title: "Almacenamiento"
|
title: "Almacenamiento"
|
||||||
|
manage_title: "Administrar almacenamiento"
|
||||||
|
thumbnails_title: "Miniaturas e íconos"
|
||||||
capacity:
|
capacity:
|
||||||
title: "Capacidad de almacenamiento en la nube"
|
title: "Capacidad de almacenamiento en la nube"
|
||||||
description: "$1 de $2 KiB"
|
description: "$1 de $2 KiB"
|
||||||
|
|||||||
@@ -46,6 +46,17 @@ features:
|
|||||||
p3_text: "Vedi l'elenco completo di ciò che raccogliamo"
|
p3_text: "Vedi l'elenco completo di ciò che raccogliamo"
|
||||||
p3_link: "qui"
|
p3_link: "qui"
|
||||||
|
|
||||||
|
netscape_bookmarks:
|
||||||
|
title: "Segnalibri del browser"
|
||||||
|
export: "Esporta collezioni come segnalibri"
|
||||||
|
import: "Importa da file di segnalibri"
|
||||||
|
import_dialog:
|
||||||
|
title: "Importa segnalibri"
|
||||||
|
content: "Importa segnalibri da un file di segnalibri in formato Netscape esportato dal tuo browser."
|
||||||
|
import_result:
|
||||||
|
success: "Importati con successo $1 segnalibri."
|
||||||
|
error: "Impossibile importare i segnalibri. Assicurati che il file sia un file di segnalibri valido."
|
||||||
|
|
||||||
notifications:
|
notifications:
|
||||||
tabs_saved:
|
tabs_saved:
|
||||||
title: "Nuova collezione creata"
|
title: "Nuova collezione creata"
|
||||||
@@ -114,6 +125,8 @@ options_page:
|
|||||||
restore: "Apri le schede e rimuovi la collezione"
|
restore: "Apri le schede e rimuovi la collezione"
|
||||||
storage:
|
storage:
|
||||||
title: "Archiviazione"
|
title: "Archiviazione"
|
||||||
|
manage_title: "Gestisci archiviazione"
|
||||||
|
thumbnails_title: "Miniature e icone"
|
||||||
capacity:
|
capacity:
|
||||||
title: "Capacità di archiviazione cloud"
|
title: "Capacità di archiviazione cloud"
|
||||||
description: "$1 di $2 KiB"
|
description: "$1 di $2 KiB"
|
||||||
|
|||||||
@@ -46,6 +46,17 @@ features:
|
|||||||
p3_text: "Pełną listę zbieranych danych można zobaczyć"
|
p3_text: "Pełną listę zbieranych danych można zobaczyć"
|
||||||
p3_link: "tutaj"
|
p3_link: "tutaj"
|
||||||
|
|
||||||
|
netscape_bookmarks:
|
||||||
|
title: "Import/eksport zakładek"
|
||||||
|
export: "Eksportuj kolekcje jako plik zakładek"
|
||||||
|
import: "Importuj z pliku zakładek"
|
||||||
|
import_dialog:
|
||||||
|
title: "Import zakładek"
|
||||||
|
content: "Importuj zakładki z pliku zakładek w formacie Netscape wyeksportowanego z przeglądarki."
|
||||||
|
import_result:
|
||||||
|
success: "Zakładki zostały pomyślnie zaimportowane ($1)"
|
||||||
|
error: "Nie udało się zaimportować zakładek. Upewnij się, że plik jest poprawnym plikiem zakładek."
|
||||||
|
|
||||||
notifications:
|
notifications:
|
||||||
tabs_saved:
|
tabs_saved:
|
||||||
title: "Utworzono nową kolekcję"
|
title: "Utworzono nową kolekcję"
|
||||||
@@ -114,6 +125,8 @@ options_page:
|
|||||||
restore: "Otwórz karty i usuń kolekcję"
|
restore: "Otwórz karty i usuń kolekcję"
|
||||||
storage:
|
storage:
|
||||||
title: "Magazyn"
|
title: "Magazyn"
|
||||||
|
manage_title: "Zarządzaj magazynem"
|
||||||
|
thumbnails_title: "Podglądy i ikony"
|
||||||
capacity:
|
capacity:
|
||||||
title: "Magazyn w chmurze"
|
title: "Magazyn w chmurze"
|
||||||
description: "$1 z $2 KiB"
|
description: "$1 z $2 KiB"
|
||||||
|
|||||||
@@ -46,6 +46,17 @@ features:
|
|||||||
p3_text: "Veja a lista completa do que coletamos"
|
p3_text: "Veja a lista completa do que coletamos"
|
||||||
p3_link: "aqui"
|
p3_link: "aqui"
|
||||||
|
|
||||||
|
netscape_bookmarks:
|
||||||
|
title: "Favoritos do navegador"
|
||||||
|
export: "Exportar coleções como favoritos"
|
||||||
|
import: "Importar de arquivo de favoritos"
|
||||||
|
import_dialog:
|
||||||
|
title: "Importar favoritos"
|
||||||
|
content: "Importe favoritos de um arquivo de favoritos no formato Netscape exportado do seu navegador."
|
||||||
|
import_result:
|
||||||
|
success: "Importados com sucesso $1 favoritos."
|
||||||
|
error: "Falha ao importar favoritos. Por favor, certifique-se de que o arquivo é um arquivo de favoritos válido."
|
||||||
|
|
||||||
notifications:
|
notifications:
|
||||||
tabs_saved:
|
tabs_saved:
|
||||||
title: "Nova coleção criada"
|
title: "Nova coleção criada"
|
||||||
@@ -114,6 +125,8 @@ options_page:
|
|||||||
restore: "Abrir abas e remover a coleção"
|
restore: "Abrir abas e remover a coleção"
|
||||||
storage:
|
storage:
|
||||||
title: "Armazenamento"
|
title: "Armazenamento"
|
||||||
|
manage_title: "Gerenciar armazenamento"
|
||||||
|
thumbnails_title: "Miniaturas e ícones"
|
||||||
capacity:
|
capacity:
|
||||||
title: "Capacidade de armazenamento na nuvem"
|
title: "Capacidade de armazenamento na nuvem"
|
||||||
description: "$1 de $2 KiB"
|
description: "$1 de $2 KiB"
|
||||||
|
|||||||
@@ -46,6 +46,17 @@ features:
|
|||||||
p3_text: "Полный список собираемых данных можно посмотреть"
|
p3_text: "Полный список собираемых данных можно посмотреть"
|
||||||
p3_link: "здесь"
|
p3_link: "здесь"
|
||||||
|
|
||||||
|
netscape_bookmarks:
|
||||||
|
title: "Импорт/экспорт закладок"
|
||||||
|
export: "Экспортировать коллекции как файл закладок"
|
||||||
|
import: "Импорт из файла закладок"
|
||||||
|
import_dialog:
|
||||||
|
title: "Импорт закладок"
|
||||||
|
content: "Импортируйте закладки из файла закладок в формате Netscape, экспортированного из вашего браузера."
|
||||||
|
import_result:
|
||||||
|
success: "Закладки успешно импортированы ($1 шт.)"
|
||||||
|
error: "Не удалось импортировать закладки. Пожалуйста, убедитесь, что файл является допустимым файлом закладок."
|
||||||
|
|
||||||
notifications:
|
notifications:
|
||||||
tabs_saved:
|
tabs_saved:
|
||||||
title: "Создана новая коллекция"
|
title: "Создана новая коллекция"
|
||||||
@@ -114,6 +125,8 @@ options_page:
|
|||||||
restore: "Открыть вкладки и удалить коллекцию"
|
restore: "Открыть вкладки и удалить коллекцию"
|
||||||
storage:
|
storage:
|
||||||
title: "Хранилище"
|
title: "Хранилище"
|
||||||
|
manage_title: "Управление хранилищем"
|
||||||
|
thumbnails_title: "Превью и иконки"
|
||||||
capacity:
|
capacity:
|
||||||
title: "Объём облачного хранилища"
|
title: "Объём облачного хранилища"
|
||||||
description: "$1 из $2 КиБ"
|
description: "$1 из $2 КиБ"
|
||||||
|
|||||||
+16
-3
@@ -46,6 +46,17 @@ features:
|
|||||||
p3_text: "Повний список зібраних даних можна подивитися"
|
p3_text: "Повний список зібраних даних можна подивитися"
|
||||||
p3_link: "тут"
|
p3_link: "тут"
|
||||||
|
|
||||||
|
netscape_bookmarks:
|
||||||
|
title: "Імпорт/експорт закладок"
|
||||||
|
export: "Експортувати колекції як файл закладок"
|
||||||
|
import: "Імпорт із файлу закладок"
|
||||||
|
import_dialog:
|
||||||
|
title: "Імпорт закладок"
|
||||||
|
content: "Імпортуйте закладки з файлу закладок у форматі Netscape, експортованого з вашого браузера."
|
||||||
|
import_result:
|
||||||
|
success: "Закладки успішно імпортовані ($1 шт.)"
|
||||||
|
error: "Не вдалося імпортувати закладки. Будь ласка, переконайтеся, що файл є коректним файлом закладок."
|
||||||
|
|
||||||
notifications:
|
notifications:
|
||||||
tabs_saved:
|
tabs_saved:
|
||||||
title: "Створено нову колекцію"
|
title: "Створено нову колекцію"
|
||||||
@@ -114,6 +125,8 @@ options_page:
|
|||||||
restore: "Відкрити вкладки та видалити колекцію"
|
restore: "Відкрити вкладки та видалити колекцію"
|
||||||
storage:
|
storage:
|
||||||
title: "Сховище"
|
title: "Сховище"
|
||||||
|
manage_title: "Керування сховищем"
|
||||||
|
thumbnails_title: "Прев'ю та іконки"
|
||||||
capacity:
|
capacity:
|
||||||
title: "Хмарне сховище"
|
title: "Хмарне сховище"
|
||||||
description: "$1 з $2 КіБ"
|
description: "$1 з $2 КіБ"
|
||||||
@@ -132,13 +145,13 @@ options_page:
|
|||||||
disable_prompt:
|
disable_prompt:
|
||||||
text: "Ця дія вимкне синхронізацію колекцій між вашими пристроями. Налаштування продовжать зберігатися у хмарі."
|
text: "Ця дія вимкне синхронізацію колекцій між вашими пристроями. Налаштування продовжать зберігатися у хмарі."
|
||||||
action: "Вимкнути та перезавантажити розширення"
|
action: "Вимкнути та перезавантажити розширення"
|
||||||
thumbnail_capture: "Зберігати превью і іконки для збережених вкладок"
|
thumbnail_capture: "Зберігати прев'ю і іконки для збережених вкладок"
|
||||||
thumbnail_capture_notice1: "Необхідний доступ до вмісту відвіданих веб-сайтів"
|
thumbnail_capture_notice1: "Необхідний доступ до вмісту відвіданих веб-сайтів"
|
||||||
thumbnail_capture_notice2: "Вимкнення цієї функції може покращити продуктивність при великій кількості збережених вкладок"
|
thumbnail_capture_notice2: "Вимкнення цієї функції може покращити продуктивність при великій кількості збережених вкладок"
|
||||||
clear_thumbnails:
|
clear_thumbnails:
|
||||||
action: "Видалити збережені іконки"
|
action: "Видалити збережені іконки"
|
||||||
title: "Видалити превью і іконки?"
|
title: "Видалити прев'ю і іконки?"
|
||||||
prompt: "Ця дія видалить всі превью і іконки у ваших збережених вкладках. Цю дію не можна скасувати."
|
prompt: "Ця дія видалить всі прев'ю і іконки у ваших збережених вкладках. Цю дію не можна скасувати."
|
||||||
about:
|
about:
|
||||||
title: "О розширенні"
|
title: "О розширенні"
|
||||||
developed_by: "Розробник: Євген Лис"
|
developed_by: "Розробник: Євген Лис"
|
||||||
|
|||||||
@@ -46,6 +46,17 @@ features:
|
|||||||
p3_text: "请参阅我们收集内容的"
|
p3_text: "请参阅我们收集内容的"
|
||||||
p3_link: "完整列表"
|
p3_link: "完整列表"
|
||||||
|
|
||||||
|
netscape_bookmarks:
|
||||||
|
title: "浏览器书签"
|
||||||
|
export: "将收藏导出为书签"
|
||||||
|
import: "从书签文件导入"
|
||||||
|
import_dialog:
|
||||||
|
title: "导入书签"
|
||||||
|
content: "从您的浏览器导出的 Netscape 格式书签文件中导入书签。"
|
||||||
|
import_result:
|
||||||
|
success: "成功导入 $1 个书签。"
|
||||||
|
error: "导入书签失败。请确保该文件是有效的书签文件。"
|
||||||
|
|
||||||
notifications:
|
notifications:
|
||||||
tabs_saved:
|
tabs_saved:
|
||||||
title: "已创建新收藏"
|
title: "已创建新收藏"
|
||||||
@@ -114,6 +125,8 @@ options_page:
|
|||||||
restore: "打开标签页并删除收藏"
|
restore: "打开标签页并删除收藏"
|
||||||
storage:
|
storage:
|
||||||
title: "存储"
|
title: "存储"
|
||||||
|
manage_title: "存储管理"
|
||||||
|
thumbnails_title: "缩略图和图标"
|
||||||
capacity:
|
capacity:
|
||||||
title: "云存储容量"
|
title: "云存储容量"
|
||||||
description: "$1 / $2 KiB"
|
description: "$1 / $2 KiB"
|
||||||
|
|||||||
@@ -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,7 +28,7 @@ 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)[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Generated
+11095
File diff suppressed because it is too large
Load Diff
+15
-18
@@ -1,46 +1,43 @@
|
|||||||
{
|
{
|
||||||
"name": "tabs-aside",
|
"name": "tabs-aside",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "3.2.0",
|
"version": "3.2.3",
|
||||||
"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.72.8",
|
||||||
"@fluentui/react-icons": "^2.0.313",
|
"@fluentui/react-icons": "^2.0.316",
|
||||||
"@webext-core/messaging": "^2.3.0",
|
"@webext-core/messaging": "^2.3.0",
|
||||||
"@wxt-dev/analytics": "^0.5.1",
|
"@wxt-dev/analytics": "^0.5.1",
|
||||||
"@wxt-dev/i18n": "^0.2.4",
|
"@wxt-dev/i18n": "^0.2.4",
|
||||||
"lzutf8": "^0.6.3",
|
"lzutf8": "^0.6.3",
|
||||||
"react": "~19.2.0",
|
"node-bookmarks-parser": "^2.0.0",
|
||||||
"react-dom": "~19.2.0"
|
"react": "^19.2.1",
|
||||||
|
"react-dom": "^19.2.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/css": "^0.14.1",
|
"@eslint/css": "^0.14.1",
|
||||||
"@eslint/js": "^9.39.1",
|
"@eslint/js": "^9.39.1",
|
||||||
"@eslint/json": "^0.14.0",
|
"@eslint/json": "^0.14.0",
|
||||||
"@stylistic/eslint-plugin": "^5.5.0",
|
"@stylistic/eslint-plugin": "^5.6.1",
|
||||||
"@types/react": "~19.2.2",
|
"@types/react": "^19.2.7",
|
||||||
"@types/react-dom": "~19.2.2",
|
"@types/react-dom": "^19.2.3",
|
||||||
"@wxt-dev/module-react": "^1.1.5",
|
"@wxt-dev/module-react": "^1.1.5",
|
||||||
"eslint": "^9.39.1",
|
"eslint": "^9.39.1",
|
||||||
"eslint-plugin-react": "^7.37.5",
|
"eslint-plugin-react": "^7.37.5",
|
||||||
"globals": "^16.5.0",
|
|
||||||
"scheduler": "0.23.0",
|
|
||||||
"typescript": "^5.9.3",
|
"typescript": "^5.9.3",
|
||||||
"typescript-eslint": "^8.46.4",
|
"typescript-eslint": "^8.49.0",
|
||||||
"vite": "^7.2.2",
|
"wxt": "^0.20.11"
|
||||||
"wxt": "~0.19.29"
|
}
|
||||||
},
|
|
||||||
"packageManager": "yarn@4.9.2"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,9 +1,8 @@
|
|||||||
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(): 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
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user