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

Compare commits

...

30 Commits

Author SHA1 Message Date
xfox111 e803636c35 chore: release candidate 7 2025-07-13 16:54:54 +03:00
xfox111 c8b4ef3e15 fix: tabs in collection list view overlap on overflow on Firefox 2025-07-13 16:50:02 +03:00
xfox111 eeefd1feff fix: collection context menus flickering in Firefox popup view 2025-07-13 16:20:17 +03:00
xfox111 53adbd4f75 fix: adding forbidden tabs via "Add selected tabs" 2025-07-11 13:53:27 +03:00
xfox111 3eed3b4b01 fix: list view horizontal overflow in sidebar on firefox 2025-07-09 00:49:09 +03:00
xfox111 525130b7e9 fix: collection collapses on tab/group drag 2025-07-06 12:34:02 +03:00
xfox111 1a274348e0 fix!: incorrect byte calculation for cloud storage (changed encoding) 2025-07-06 12:17:17 +03:00
xfox111 2c8cfa1583 chore: release candidate 6 2025-07-03 12:58:37 +03:00
xfox111 e844a68f49 chore(ui): remove fixed collection height for list view 2025-07-03 12:56:58 +03:00
xfox111 794f6e3af0 chore(locale): clarify action icon option for slaivc locales (ru, uk, pl) 2025-07-03 12:06:25 +03:00
xfox111 d85d10dc58 chore(ui): increase tab's dnd handle area 2025-07-03 11:46:53 +03:00
xfox111 213cc84602 chore: reduce delay and increase tolerance for mouse drag and drop 2025-07-03 11:33:05 +03:00
xfox111 9675b65e81 fix: pinned tab opens and closes infinitely if pwa window is opened 2025-07-03 11:24:53 +03:00
xfox111 6bce330a8f chore: dependabot reviewers deprecation 2025-06-03 18:55:01 +03:00
xfox111 405f9163f2 !feat: tabGroups API for Firefox 139 2025-05-23 16:45:50 +03:00
xfox111 e498e25c57 chore: version update 2025-05-08 10:07:18 +03:00
xfox111 d07c99e3a1 !feat: partial tab groups support on firefox 2025-05-08 10:06:47 +03:00
xfox111 0ff1d63cde fix: close firefox sidebar when list location changes 2025-05-08 10:05:53 +03:00
xfox111 dfcafae2b1 fix: options page crush on cloud storage disabling 2025-05-08 09:05:53 +03:00
xfox111 8f4cd4198a fix: groupig fails on private windows when group contains restricted tabs 2025-05-08 09:05:21 +03:00
xfox111 1b64f65e9f fix: empty collection/group title 2025-05-08 09:04:34 +03:00
xfox111 d249e07eca fix: corrupted json export 2025-05-08 09:04:08 +03:00
xfox111 4070907240 chore(loc): english locale fix 2025-05-08 09:03:15 +03:00
xfox111 9d250dc01d fix: MAX_CAPTURE_VISIBLE_TAB_CALLS_PER_SECOND exceeded 2025-05-08 06:59:55 +03:00
xfox111 24bf0e88ca Fix: window closes if all tabs were set aside 2025-05-08 06:56:06 +03:00
xfox111 ef94842066 fix: collections removed by their sorted index 2025-05-08 06:55:35 +03:00
xfox111 40490aec2d fix: open collection in a new window doesn't work 2025-05-08 02:26:08 +03:00
xfox111 06aca3d3ca fix: show timestamp-based title in edit dialog title placeholder 2025-05-08 02:20:44 +03:00
xfox111 a6a5c236c6 fix: broken chrome store link 2025-05-08 02:09:22 +03:00
xfox111 a144221e33 chore: chunk max size warning 2025-05-07 20:38:18 +03:00
32 changed files with 201 additions and 143 deletions
+2
View File
@@ -0,0 +1,2 @@
* @XFox111
locales/pt_BR.yml @maisondasilva @XFox111
+3 -9
View File
@@ -12,9 +12,7 @@ updates:
directory: "/" # Location of package manifests directory: "/" # Location of package manifests
target-branch: "next" target-branch: "next"
assignees: assignees:
- "xfox111" - "XFox111"
reviewers:
- "xfox111"
schedule: schedule:
interval: monthly interval: monthly
rebase-strategy: disabled rebase-strategy: disabled
@@ -24,9 +22,7 @@ updates:
directory: "/" directory: "/"
target-branch: "next" target-branch: "next"
assignees: assignees:
- "xfox111" - "XFox111"
reviewers:
- "xfox111"
schedule: schedule:
interval: monthly interval: monthly
rebase-strategy: disabled rebase-strategy: disabled
@@ -36,9 +32,7 @@ updates:
directory: "/" directory: "/"
target-branch: "next" target-branch: "next"
assignees: assignees:
- "xfox111" - "XFox111"
reviewers:
- "xfox111"
schedule: schedule:
interval: monthly interval: monthly
rebase-strategy: disabled rebase-strategy: disabled
+1 -1
View File
@@ -21,4 +21,4 @@ export const storeLink: string =
? "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/") ? chrome.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://chrome.google.com/webstore/detail/mgmjbodjgijnebfgohlnjkegdbdjgin"; "https://chromewebstore.google.com/detail/tabs-aside/mgmjbodjgijnebfgohlnjkegdpbdjgin";
+13 -2
View File
@@ -68,6 +68,7 @@ export default defineBackground(() =>
icon: graphicsCache[data.url]?.icon icon: graphicsCache[data.url]?.icon
}; };
}); });
onMessage("refreshCollections", () => {});
setupTabCaputre(); setupTabCaputre();
async function setupTabCaputre(): Promise<void> async function setupTabCaputre(): Promise<void>
@@ -77,6 +78,9 @@ export default defineBackground(() =>
if (!tab.url || tab.status !== "complete" || !tab.active) if (!tab.url || tab.status !== "complete" || !tab.active)
return; return;
if (graphicsCache[tab.url]?.capture || graphicsCache[tab.url]?.capture === null)
return;
try try
{ {
// We use chrome here because polyfill throws uncatchable errors for some reason // We use chrome here because polyfill throws uncatchable errors for some reason
@@ -92,7 +96,14 @@ export default defineBackground(() =>
}; };
} }
} }
catch { } catch
{
graphicsCache[tab.url] = {
capture: null!,
preview: graphicsCache[tab.url]?.preview,
icon: graphicsCache[tab.url]?.icon
};
}
}; };
setInterval(() => setInterval(() =>
@@ -246,7 +257,7 @@ export default defineBackground(() =>
for (const openWindow of openWindows) for (const openWindow of openWindows)
{ {
if (openWindow.incognito) if (openWindow.incognito || openWindow.type !== "normal")
continue; continue;
const activeTabs: Tabs.Tab[] = openWindow.tabs!.filter(tab => const activeTabs: Tabs.Tab[] = openWindow.tabs!.filter(tab =>
@@ -27,6 +27,9 @@ export default function GeneralSection(): React.ReactElement
if (e.optionValue === "popup" && contextAction !== "open") if (e.optionValue === "popup" && contextAction !== "open")
setContextAction("open"); setContextAction("open");
if (import.meta.env.FIREFOX && e.optionValue !== "sidebar")
browser.sidebarAction.close();
setListLocation(e.optionValue as ListLocationType); setListLocation(e.optionValue as ListLocationType);
}; };
+4 -1
View File
@@ -4,13 +4,16 @@ export default async function exportData(): Promise<void>
local: await browser.storage.local.get(null), local: await browser.storage.local.get(null),
sync: await browser.storage.sync.get(null) sync: await browser.storage.sync.get(null)
}); });
const blob: Blob = new Blob([data], { type: "application/json" });
const element: HTMLAnchorElement = document.createElement("a"); const element: HTMLAnchorElement = document.createElement("a");
element.style.display = "none"; element.style.display = "none";
element.href = `data:application/json;charset=utf-8,${data}`; element.href = URL.createObjectURL(blob);
element.setAttribute("download", "tabs-aside_data.json"); element.setAttribute("download", "tabs-aside_data.json");
document.body.appendChild(element); document.body.appendChild(element);
element.click(); element.click();
URL.revokeObjectURL(element.href);
document.body.removeChild(element); document.body.removeChild(element);
}; };
@@ -27,7 +27,7 @@ export const useStyles_CollectionView = makeStyles({
}, },
verticalRoot: verticalRoot:
{ {
height: "560px" maxHeight: "560px"
}, },
empty: empty:
{ {
@@ -74,7 +74,8 @@ export const useStyles_CollectionView = makeStyles({
{ {
gridAutoFlow: "row", gridAutoFlow: "row",
width: "100%", width: "100%",
paddingBottom: tokens.spacingVerticalS paddingBottom: tokens.spacingVerticalS,
gridAutoRows: import.meta.env.FIREFOX ? "min-content" : undefined
}, },
dragOverlay: dragOverlay:
{ {
@@ -33,7 +33,7 @@ export default function CollectionView({ collection, index: collectionIndex, dra
const colorCls = useGroupColors(); const colorCls = useGroupColors();
return ( return (
<CollectionContext.Provider value={ { collection, collectionIndex, tabCount, hasPinnedGroup } }> <CollectionContext.Provider value={ { collection, tabCount, hasPinnedGroup } }>
<div <div
ref={ setNodeRef } { ...nodeProps } ref={ setNodeRef } { ...nodeProps }
className={ mergeClasses( className={ mergeClasses(
@@ -50,26 +50,30 @@ export default function CollectionView({ collection, index: collectionIndex, dra
<CollectionHeader dragHandleProps={ activatorProps } dragHandleRef={ setActivatorNodeRef } /> <CollectionHeader dragHandleProps={ activatorProps } dragHandleRef={ setActivatorNodeRef } />
{ collection.items.length < 1 ? { (!activeItem || activeItem.item.type !== "collection") && !dragOverlay &&
<div className={ cls.empty }> <>
<CollectionsRegular fontSize={ 32 } /> { collection.items.length < 1 ?
<Body1Strong>{ i18n.t("collections.empty") }</Body1Strong> <div className={ cls.empty }>
</div> <CollectionsRegular fontSize={ 32 } />
: <Body1Strong>{ i18n.t("collections.empty") }</Body1Strong>
<div className={ mergeClasses(cls.list, !tilesView && cls.verticalList) }> </div>
<SortableContext :
items={ collection.items.map((_, index) => [collectionIndex, index].join("/")) } <div className={ mergeClasses(cls.list, !tilesView && cls.verticalList) }>
strategy={ tilesView ? horizontalListSortingStrategy : verticalListSortingStrategy } <SortableContext
> items={ collection.items.map((_, index) => [collectionIndex, index].join("/")) }
{ collection.items.map((i, index) => strategy={ tilesView ? horizontalListSortingStrategy : verticalListSortingStrategy }
i.type === "group" ? >
<GroupView { collection.items.map((i, index) =>
key={ index } group={ i } indices={ [collectionIndex, index] } /> i.type === "group" ?
: <GroupView
<TabView key={ index } tab={ i } indices={ [collectionIndex, index] } /> key={ index } group={ i } indices={ [collectionIndex, index] } />
) } :
</SortableContext> <TabView key={ index } tab={ i } indices={ [collectionIndex, index] } />
</div> ) }
</SortableContext>
</div>
}
</>
} }
</div > </div >
</CollectionContext.Provider> </CollectionContext.Provider>
@@ -37,7 +37,7 @@ export default function EditDialog(props: GroupEditDialogProps): ReactElement
type: "collection", type: "collection",
timestamp: props.collection?.timestamp ?? Date.now(), timestamp: props.collection?.timestamp ?? Date.now(),
color: (color === "pinned") ? undefined : color!, color: (color === "pinned") ? undefined : color!,
title, title: title ? title : undefined,
items: props.collection?.items ?? [] items: props.collection?.items ?? []
}); });
else if (color === "pinned") else if (color === "pinned")
@@ -51,7 +51,7 @@ export default function EditDialog(props: GroupEditDialogProps): ReactElement
type: "group", type: "group",
pinned: false, pinned: false,
color: color!, color: color!,
title, title: title ? title : undefined,
items: props.group?.items ?? [] items: props.group?.items ?? []
}); });
}; };
@@ -74,7 +74,7 @@ export default function EditDialog(props: GroupEditDialogProps): ReactElement
contentBefore={ <Rename20Regular /> } contentBefore={ <Rename20Regular /> }
disabled={ color === "pinned" } disabled={ color === "pinned" }
placeholder={ placeholder={
props.type === "collection" ? getCollectionTitle(props.collection) : "" props.type === "collection" ? getCollectionTitle(props.collection, true) : ""
} }
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) } />
@@ -58,8 +58,6 @@ export const useStyles_TabView = makeStyles({
display: "grid", display: "grid",
gridTemplateColumns: "auto 1fr auto", gridTemplateColumns: "auto 1fr auto",
alignItems: "center", alignItems: "center",
gap: tokens.spacingHorizontalSNudge,
paddingLeft: tokens.spacingHorizontalS,
borderBottomLeftRadius: tokens.borderRadiusMedium, borderBottomLeftRadius: tokens.borderRadiusMedium,
borderBottomRightRadius: tokens.borderRadiusMedium, borderBottomRightRadius: tokens.borderRadiusMedium,
@@ -72,6 +70,9 @@ export const useStyles_TabView = makeStyles({
icon: icon:
{ {
cursor: "grab", cursor: "grab",
padding: `${tokens.spacingVerticalSNudge} ${tokens.spacingHorizontalSNudge}`,
height: "32px",
boxSizing: "border-box",
"&:active": "&:active":
{ {
+6 -3
View File
@@ -9,10 +9,12 @@ import { Button, Caption1, Link, mergeClasses, Tooltip } from "@fluentui/react-c
import { Dismiss20Regular } from "@fluentui/react-icons"; 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";
export default function TabView({ tab, indices, dragOverlay }: TabViewProps): ReactElement export default function TabView({ tab, indices, dragOverlay }: TabViewProps): ReactElement
{ {
const { removeItem, graphics, tilesView } = useCollections(); const { removeItem, graphics, tilesView } = useCollections();
const { collection } = useContext<CollectionContextType>(CollectionContext);
const { const {
setNodeRef, setActivatorNodeRef, setNodeRef, setActivatorNodeRef,
nodeProps, activatorProps, isBeingDragged nodeProps, activatorProps, isBeingDragged
@@ -29,16 +31,18 @@ export default function TabView({ tab, indices, dragOverlay }: TabViewProps): Re
args.preventDefault(); args.preventDefault();
args.stopPropagation(); args.stopPropagation();
const removeIndex: number[] = [collection.timestamp, ...indices.slice(1)];
if (deletePrompt) if (deletePrompt)
dialog.pushPrompt({ dialog.pushPrompt({
title: i18n.t("tabs.delete"), title: i18n.t("tabs.delete"),
content: i18n.t("common.delete_prompt"), content: i18n.t("common.delete_prompt"),
destructive: true, destructive: true,
confirmText: i18n.t("common.actions.delete"), confirmText: i18n.t("common.actions.delete"),
onConfirm: () => removeItem(...indices) onConfirm: () => removeItem(...removeIndex)
}); });
else else
removeItem(...indices); removeItem(...removeIndex);
}; };
const handleClick: MouseEventHandler<HTMLAnchorElement> = (args) => const handleClick: MouseEventHandler<HTMLAnchorElement> = (args) =>
@@ -79,7 +83,6 @@ export default function TabView({ tab, indices, dragOverlay }: TabViewProps): Re
ref={ setActivatorNodeRef } { ...activatorProps } ref={ setActivatorNodeRef } { ...activatorProps }
src={ graphics[tab.url]?.icon ?? faviconPlaceholder } src={ graphics[tab.url]?.icon ?? faviconPlaceholder }
onError={ e => e.currentTarget.src = faviconPlaceholder } onError={ e => e.currentTarget.src = faviconPlaceholder }
height={ 20 } width={ 20 }
className={ cls.icon } draggable={ false } /> className={ cls.icon } draggable={ false } />
<Tooltip relationship="description" content={ tab.title ?? tab.url }> <Tooltip relationship="description" content={ tab.title ?? tab.url }>
@@ -12,10 +12,11 @@ import saveTabsToCollection from "@/utils/saveTabsToCollection";
export default function CollectionHeader({ dragHandleRef, dragHandleProps }: CollectionHeaderProps): React.ReactElement export default function CollectionHeader({ dragHandleRef, dragHandleProps }: CollectionHeaderProps): React.ReactElement
{ {
const [contextOpen, setContextOpen] = useState<boolean>(false);
const [listLocation] = useSettings("listLocation"); const [listLocation] = useSettings("listLocation");
const isTab: boolean = listLocation === "tab" || listLocation === "pinned"; const isTab: boolean = listLocation === "tab" || listLocation === "pinned";
const { updateCollection } = useCollections(); const { updateCollection } = useCollections();
const { tabCount, collection, collectionIndex } = useContext<CollectionContextType>(CollectionContext); const { tabCount, collection } = useContext<CollectionContextType>(CollectionContext);
const [alwaysShowToolbars] = useSettings("alwaysShowToolbars"); const [alwaysShowToolbars] = useSettings("alwaysShowToolbars");
const AddIcon = bundleIcon(Add20Filled, Add20Regular); const AddIcon = bundleIcon(Add20Filled, Add20Regular);
@@ -25,7 +26,7 @@ export default function CollectionHeader({ dragHandleRef, dragHandleProps }: Col
const newTabs: (TabItem | GroupItem)[] = isTab ? const newTabs: (TabItem | GroupItem)[] = isTab ?
(await saveTabsToCollection(false)).items : (await saveTabsToCollection(false)).items :
await getSelectedTabs(); await getSelectedTabs();
updateCollection({ ...collection, items: [...collection.items, ...newTabs] }, collectionIndex); updateCollection({ ...collection, items: [...collection.items, ...newTabs] }, collection.timestamp);
}; };
const cls = useStyles(); const cls = useStyles();
@@ -53,7 +54,7 @@ export default function CollectionHeader({ dragHandleRef, dragHandleProps }: Col
mergeClasses( mergeClasses(
cls.toolbar, cls.toolbar,
"CollectionView__toolbar", "CollectionView__toolbar",
alwaysShowToolbars === true && cls.showToolbar (alwaysShowToolbars === true || contextOpen) && cls.showToolbar
) } ) }
> >
{ tabCount < 1 ? { tabCount < 1 ?
@@ -61,10 +62,10 @@ export default function CollectionHeader({ dragHandleRef, dragHandleProps }: Col
{ isTab ? i18n.t("collections.menu.add_all") : i18n.t("collections.menu.add_selected") } { isTab ? i18n.t("collections.menu.add_all") : i18n.t("collections.menu.add_selected") }
</Button> </Button>
: :
<OpenCollectionButton /> <OpenCollectionButton onOpenChange={ (_, e) => setContextOpen(e.open) } />
} }
<CollectionMoreButton onAddSelected={ handleAddSelected } /> <CollectionMoreButton onAddSelected={ handleAddSelected } onOpenChange={ (_, e) => setContextOpen(e.open) } />
</div> </div>
</div> </div>
); );
@@ -1,19 +1,19 @@
import { useDialog } from "@/contexts/DialogProvider"; import { useDialog } from "@/contexts/DialogProvider";
import { useDangerStyles } from "@/hooks/useDangerStyles"; import { useDangerStyles } from "@/hooks/useDangerStyles";
import useSettings from "@/hooks/useSettings"; import useSettings from "@/hooks/useSettings";
import { Button, Menu, MenuDivider, MenuItem, MenuList, MenuPopover, MenuTrigger, Tooltip } from "@fluentui/react-components"; import { Button, Menu, MenuDivider, MenuItem, MenuList, MenuOpenChangeData, MenuOpenEvent, MenuPopover, MenuTrigger, Tooltip } from "@fluentui/react-components";
import * as ic from "@fluentui/react-icons"; import * as ic 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 exportCollectionToBookmarks from "../../utils/exportCollectionToBookmarks"; import exportCollectionToBookmarks from "../../utils/exportCollectionToBookmarks";
import EditDialog from "../EditDialog"; import EditDialog from "../EditDialog";
export default function CollectionMoreButton({ onAddSelected }: CollectionMoreButtonProps): React.ReactElement export default function CollectionMoreButton({ onAddSelected, onOpenChange }: CollectionMoreButtonProps): React.ReactElement
{ {
const [listLocation] = useSettings("listLocation"); const [listLocation] = useSettings("listLocation");
const isTab: boolean = listLocation === "tab" || listLocation === "pinned"; const isTab: boolean = listLocation === "tab" || listLocation === "pinned";
const { removeItem, updateCollection } = useCollections(); const { removeItem, updateCollection } = useCollections();
const { tabCount, hasPinnedGroup, collection, collectionIndex } = useContext<CollectionContextType>(CollectionContext); const { tabCount, hasPinnedGroup, collection } = useContext<CollectionContextType>(CollectionContext);
const dialog = useDialog(); const dialog = useDialog();
const [deletePrompt] = useSettings("deletePrompt"); const [deletePrompt] = useSettings("deletePrompt");
@@ -33,10 +33,10 @@ export default function CollectionMoreButton({ onAddSelected }: CollectionMoreBu
content: i18n.t("common.delete_prompt"), content: i18n.t("common.delete_prompt"),
destructive: true, destructive: true,
confirmText: i18n.t("common.actions.delete"), confirmText: i18n.t("common.actions.delete"),
onConfirm: () => removeItem(collectionIndex) onConfirm: () => removeItem(collection.timestamp)
}); });
else else
removeItem(collectionIndex); removeItem(collection.timestamp);
}; };
const handleEdit = () => const handleEdit = () =>
@@ -44,7 +44,7 @@ export default function CollectionMoreButton({ onAddSelected }: CollectionMoreBu
<EditDialog <EditDialog
type="collection" type="collection"
collection={ collection } collection={ collection }
onSave={ item => updateCollection(item, collectionIndex) } /> onSave={ item => updateCollection(item, collection.timestamp) } />
); );
const handleCreateGroup = () => const handleCreateGroup = () =>
@@ -52,11 +52,11 @@ export default function CollectionMoreButton({ onAddSelected }: CollectionMoreBu
<EditDialog <EditDialog
type="group" type="group"
hidePinned={ hasPinnedGroup } hidePinned={ hasPinnedGroup }
onSave={ group => updateCollection({ ...collection, items: [...collection.items, group] }, collectionIndex) } /> onSave={ group => updateCollection({ ...collection, items: [...collection.items, group] }, collection.timestamp) } />
); );
return ( return (
<Menu> <Menu onOpenChange={ onOpenChange }>
<Tooltip relationship="label" content={ i18n.t("common.tooltips.more") }> <Tooltip relationship="label" content={ i18n.t("common.tooltips.more") }>
<MenuTrigger> <MenuTrigger>
<Button appearance="subtle" icon={ <ic.MoreVertical20Regular /> } /> <Button appearance="subtle" icon={ <ic.MoreVertical20Regular /> } />
@@ -94,4 +94,5 @@ export default function CollectionMoreButton({ onAddSelected }: CollectionMoreBu
export type CollectionMoreButtonProps = export type CollectionMoreButtonProps =
{ {
onAddSelected?: () => void; onAddSelected?: () => void;
onOpenChange?: (e: MenuOpenEvent, data: MenuOpenChangeData) => void;
}; };
@@ -18,7 +18,7 @@ export default function GroupMoreMenu(): ReactElement
const [listLocation] = useSettings("listLocation"); const [listLocation] = useSettings("listLocation");
const isTab: boolean = listLocation === "tab" || listLocation === "pinned"; const isTab: boolean = listLocation === "tab" || listLocation === "pinned";
const { group, indices } = useContext<GroupContextType>(GroupContext); const { group, indices } = useContext<GroupContextType>(GroupContext);
const { hasPinnedGroup } = useContext<CollectionContextType>(CollectionContext); const { hasPinnedGroup, collection } = useContext<CollectionContextType>(CollectionContext);
const [deletePrompt] = useSettings("deletePrompt"); const [deletePrompt] = useSettings("deletePrompt");
const dialog = useDialog(); const dialog = useDialog();
const { updateGroup, removeItem, ungroup } = useCollections(); const { updateGroup, removeItem, ungroup } = useCollections();
@@ -33,16 +33,18 @@ export default function GroupMoreMenu(): ReactElement
const handleDelete = () => const handleDelete = () =>
{ {
const removeIndex: number[] = [collection.timestamp, ...indices.slice(1)];
if (deletePrompt) if (deletePrompt)
dialog.pushPrompt({ dialog.pushPrompt({
title: i18n.t("groups.menu.delete"), title: i18n.t("groups.menu.delete"),
content: i18n.t("common.delete_prompt"), content: i18n.t("common.delete_prompt"),
confirmText: i18n.t("common.actions.delete"), confirmText: i18n.t("common.actions.delete"),
destructive: true, destructive: true,
onConfirm: () => removeItem(...indices) onConfirm: () => removeItem(...removeIndex)
}); });
else else
removeItem(...indices); removeItem(...removeIndex);
}; };
const handleEdit = () => const handleEdit = () =>
@@ -51,7 +53,7 @@ export default function GroupMoreMenu(): ReactElement
type="group" type="group"
group={ group } group={ group }
hidePinned={ hasPinnedGroup } hidePinned={ hasPinnedGroup }
onSave={ item => updateGroup(item, indices[0], indices[1]) } /> onSave={ item => updateGroup(item, collection.timestamp, indices[1]) } />
); );
const handleAddSelected = async () => const handleAddSelected = async () =>
@@ -59,7 +61,7 @@ export default function GroupMoreMenu(): ReactElement
const newTabs: TabItem[] = isTab ? const newTabs: TabItem[] = isTab ?
(await saveTabsToCollection(false)).items.flatMap(i => i.type === "tab" ? i : i.items) : (await saveTabsToCollection(false)).items.flatMap(i => i.type === "tab" ? i : i.items) :
await getSelectedTabs(); await getSelectedTabs();
updateGroup({ ...group, items: [...group.items, ...newTabs] }, indices[0], indices[1]); updateGroup({ ...group, items: [...group.items, ...newTabs] }, collection.timestamp, indices[1]);
}; };
return ( return (
@@ -89,7 +91,7 @@ export default function GroupMoreMenu(): ReactElement
<MenuItem <MenuItem
className={ dangerCls.menuItem } className={ dangerCls.menuItem }
icon={ <UngroupIcon /> } icon={ <UngroupIcon /> }
onClick={ () => ungroup(indices[0], indices[1]) } onClick={ () => ungroup(collection.timestamp, indices[1]) }
> >
{ i18n.t("groups.menu.ungroup") } { i18n.t("groups.menu.ungroup") }
</MenuItem> </MenuItem>
@@ -1,18 +1,18 @@
import { useDialog } from "@/contexts/DialogProvider"; import { useDialog } from "@/contexts/DialogProvider";
import useSettings from "@/hooks/useSettings"; import useSettings from "@/hooks/useSettings";
import browserLocaleKey from "@/utils/browserLocaleKey"; import browserLocaleKey from "@/utils/browserLocaleKey";
import { Menu, MenuButtonProps, MenuItem, MenuList, MenuPopover, MenuTrigger, SplitButton } from "@fluentui/react-components"; import { Menu, MenuButtonProps, MenuItem, MenuList, MenuOpenChangeData, MenuOpenEvent, MenuPopover, MenuTrigger, SplitButton } from "@fluentui/react-components";
import * as ic from "@fluentui/react-icons"; import * as ic 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 { openCollection } from "../../utils/opener"; import { openCollection } from "../../utils/opener";
export default function OpenCollectionButton(): React.ReactElement export default function OpenCollectionButton({ onOpenChange }: OpenCollectionButtonProps): React.ReactElement
{ {
const [defaultAction] = useSettings("defaultRestoreAction"); const [defaultAction] = useSettings("defaultRestoreAction");
const { removeItem } = useCollections(); const { removeItem } = useCollections();
const dialog = useDialog(); const dialog = useDialog();
const { collection, collectionIndex } = useContext<CollectionContextType>(CollectionContext); const { collection } = useContext<CollectionContextType>(CollectionContext);
const OpenIcon = ic.bundleIcon(ic.Open20Filled, ic.Open20Regular); const OpenIcon = ic.bundleIcon(ic.Open20Filled, ic.Open20Regular);
const RestoreIcon = ic.bundleIcon(ic.ArrowExportRtl20Filled, ic.ArrowExportRtl20Regular); const RestoreIcon = ic.bundleIcon(ic.ArrowExportRtl20Filled, ic.ArrowExportRtl20Regular);
@@ -50,11 +50,11 @@ export default function OpenCollectionButton(): React.ReactElement
const handleRestore = async () => const handleRestore = async () =>
{ {
await openCollection(collection); await openCollection(collection);
removeItem(collectionIndex); removeItem(collection.timestamp);
}; };
return ( return (
<Menu> <Menu onOpenChange={ onOpenChange }>
<MenuTrigger disableButtonEnhancement> <MenuTrigger disableButtonEnhancement>
{ (triggerProps: MenuButtonProps) => defaultAction === "restore" ? { (triggerProps: MenuButtonProps) => defaultAction === "restore" ?
<SplitButton <SplitButton
@@ -84,7 +84,7 @@ export default function OpenCollectionButton(): React.ReactElement
{ i18n.t("collections.actions.restore") } { i18n.t("collections.actions.restore") }
</MenuItem> </MenuItem>
} }
<MenuItem icon={ <NewWindowIcon /> } onClick={ () => handleOpen("new") }> <MenuItem icon={ <NewWindowIcon /> } onClick={ handleOpen("new") }>
{ i18n.t("collections.actions.new_window") } { i18n.t("collections.actions.new_window") }
</MenuItem> </MenuItem>
<MenuItem icon={ <InPrivateIcon /> } onClick={ handleIncognito }> <MenuItem icon={ <InPrivateIcon /> } onClick={ handleIncognito }>
@@ -95,3 +95,8 @@ export default function OpenCollectionButton(): React.ReactElement
</Menu> </Menu>
); );
} }
export type OpenCollectionButtonProps =
{
onOpenChange?: (e: MenuOpenEvent, data: MenuOpenChangeData) => void;
};
@@ -8,7 +8,6 @@ export default CollectionContext;
export type CollectionContextType = export type CollectionContextType =
{ {
collection: CollectionItem; collection: CollectionItem;
collectionIndex: number;
tabCount: number; tabCount: number;
hasPinnedGroup: boolean; hasPinnedGroup: boolean;
}; };
@@ -50,12 +50,14 @@ export default function CollectionsProvider({ children }: React.PropsWithChildre
const removeItem = (...indices: number[]): void => const removeItem = (...indices: number[]): void =>
{ {
const collectionIndex: number = collections.findIndex(i => i.timestamp === indices[0]);
if (indices.length > 2) if (indices.length > 2)
(collections[indices[0]].items[indices[1]] as GroupItem).items.splice(indices[2], 1); (collections[collectionIndex].items[indices[1]] as GroupItem).items.splice(indices[2], 1);
else if (indices.length > 1) else if (indices.length > 1)
collections[indices[0]].items.splice(indices[1], 1); collections[collectionIndex].items.splice(indices[1], 1);
else else
collections.splice(indices[0], 1); collections.splice(collectionIndex, 1);
updateStorage(collections); updateStorage(collections);
}; };
@@ -65,20 +67,23 @@ export default function CollectionsProvider({ children }: React.PropsWithChildre
updateStorage(collectionList); updateStorage(collectionList);
}; };
const updateCollection = (collection: CollectionItem, index: number): void => const updateCollection = (collection: CollectionItem, id: number): void =>
{ {
const index: number = collections.findIndex(i => i.timestamp === id);
collections[index] = collection; collections[index] = collection;
updateStorage(collections); updateStorage(collections);
}; };
const updateGroup = (group: GroupItem, collectionIndex: number, groupIndex: number): void => const updateGroup = (group: GroupItem, collectionId: number, groupIndex: number): void =>
{ {
const collectionIndex: number = collections.findIndex(i => i.timestamp === collectionId);
collections[collectionIndex].items[groupIndex] = group; collections[collectionIndex].items[groupIndex] = group;
updateStorage(collections); updateStorage(collections);
}; };
const ungroup = (collectionIndex: number, groupIndex: number): void => const ungroup = (collectionId: number, groupIndex: number): void =>
{ {
const collectionIndex: number = collections.findIndex(i => i.timestamp === collectionId);
const group = collections[collectionIndex].items[groupIndex] as GroupItem; const group = collections[collectionIndex].items[groupIndex] as GroupItem;
collections[collectionIndex].items.splice(groupIndex, 1, ...group.items); collections[collectionIndex].items.splice(groupIndex, 1, ...group.items);
updateStorage(collections); updateStorage(collections);
@@ -108,9 +113,9 @@ export type CollectionsContextType =
addCollection: (collection: CollectionItem) => void; addCollection: (collection: CollectionItem) => void;
updateCollections: (collections: CollectionItem[]) => void; updateCollections: (collections: CollectionItem[]) => void;
updateCollection: (collection: CollectionItem, index: number) => void; updateCollection: (collection: CollectionItem, id: number) => void;
updateGroup: (group: GroupItem, collectionIndex: number, groupIndex: number) => void; updateGroup: (group: GroupItem, collectionId: number, groupIndex: number) => void;
ungroup: (collectionIndex: number, groupIndex: number) => void; ungroup: (collectionId: number, groupIndex: number) => void;
removeItem: (...indices: number[]) => void; removeItem: (...indices: number[]) => void;
}; };
@@ -46,6 +46,10 @@ export const useStyles_CollectionListView = makeStyles({
listView: listView:
{ {
display: "grid", display: "grid",
gridTemplateColumns: "repeat(auto-fit, minmax(360px, 1fr))"
"@media screen and (min-width: 360px)":
{
gridTemplateColumns: "repeat(auto-fit, minmax(360px, 1fr))"
}
} }
}); });
@@ -33,7 +33,7 @@ export default function CollectionListView(): ReactElement
const [active, setActive] = useState<DndItem | null>(null); const [active, setActive] = useState<DndItem | null>(null);
const sensors = useSensors( const sensors = useSensors(
useSensor(MouseSensor, { activationConstraint: { delay: 100, tolerance: 0 } }), useSensor(MouseSensor, { activationConstraint: { delay: 10, tolerance: 20 } }),
useSensor(TouchSensor, { activationConstraint: { delay: 300, tolerance: 20 } }) useSensor(TouchSensor, { activationConstraint: { delay: 300, tolerance: 20 } })
); );
@@ -121,27 +121,25 @@ export default function CollectionListView(): ReactElement
</SortableContext> </SortableContext>
<DragOverlay dropAnimation={ null }> <DragOverlay dropAnimation={ null }>
{ active && { active !== null ?
<> active.item.type === "collection" ?
{ active.item.type === "collection" && <CollectionView collection={ active.item } index={ -1 } dragOverlay />
<CollectionView collection={ active.item } index={ -1 } dragOverlay /> :
} <CollectionContext.Provider
{ active.item.type === "group" && value={ {
<CollectionContext.Provider tabCount: 0,
value={ { collection: resultList[active.indices[0]],
tabCount: 0, hasPinnedGroup: true
collectionIndex: active.indices[0], } }
collection: resultList[active.indices[0]], >
hasPinnedGroup: true { active.item.type === "group" ?
} }
>
<GroupView group={ active.item } indices={ [-1] } dragOverlay /> <GroupView group={ active.item } indices={ [-1] } dragOverlay />
</CollectionContext.Provider> :
} <TabView tab={ active.item } indices={ [-1] } dragOverlay />
{ active.item.type === "tab" && }
<TabView tab={ active.item } indices={ [-1] } dragOverlay /> </CollectionContext.Provider>
} :
</> <></>
} }
</DragOverlay> </DragOverlay>
</DndContext> </DndContext>
@@ -1,8 +1,10 @@
import { CollectionItem } from "@/models/CollectionModels"; import { CollectionItem } from "@/models/CollectionModels";
export function getCollectionTitle(collection?: CollectionItem): string export function getCollectionTitle(collection?: CollectionItem, useTimestamp?: boolean): string
{ {
return collection?.title if (collection?.title !== undefined && useTimestamp !== true)
|| new Date(collection?.timestamp ?? Date.now()) return collection.title;
.toLocaleDateString(browser.i18n.getUILanguage(), { year: "numeric", month: "short", day: "numeric" });
return new Date(collection?.timestamp ?? Date.now())
.toLocaleDateString(browser.i18n.getUILanguage(), { year: "numeric", month: "short", day: "numeric" });
} }
+18 -2
View File
@@ -1,8 +1,24 @@
import { TabItem } from "@/models/CollectionModels"; import { TabItem } from "@/models/CollectionModels";
import sendNotification from "@/utils/sendNotification";
import { Tabs } from "wxt/browser"; import { Tabs } from "wxt/browser";
export default async function getSelectedTabs(): Promise<TabItem[]> export default async function getSelectedTabs(): Promise<TabItem[]>
{ {
const tabs: Tabs.Tab[] = await browser.tabs.query({ currentWindow: true, highlighted: true }); let tabs: Tabs.Tab[] = await browser.tabs.query({ currentWindow: true, highlighted: true });
return tabs.filter(i => i.url).map(i => ({ type: "tab", url: i.url!, title: i.title })); const tabCount: number = tabs.length;
tabs = tabs.filter(i =>
i.url
&& new URL(i.url).protocol !== "about:"
&& new URL(i.url).hostname !== "newtab"
);
if (tabs.length < tabCount)
await sendNotification({
title: i18n.t("notifications.partial_save.title"),
message: i18n.t("notifications.partial_save.message"),
icon: "/notification_icons/save_warning.png"
});
return tabs.map(i => ({ type: "tab", url: i.url!, title: i.title }));
} }
+8 -8
View File
@@ -55,19 +55,17 @@ 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 tabIds: number[] = await Promise.all(group.items.map(async i => const tabs: Tabs.Tab[] = await Promise.all(group.items.map(async i =>
(await createTab(i.url, windowId, discard, group.pinned)).id! await createTab(i.url, windowId, discard, group.pinned)
)); ));
// "Pinned" group is technically not a group, so not much else to do here // "Pinned" group is technically not a group, so not much else to do here
// and Firefox doesn't even support tab groups if (group.pinned === true)
if (group.pinned === true || import.meta.env.FIREFOX)
return; return;
const groupId: number = await chrome.tabs.group({ const groupId: number = await chrome.tabs.group({
tabIds, createProperties: { tabIds: tabs.filter(i => i.windowId === windowId).map(i => i.id!),
windowId createProperties: { windowId }
}
}); });
await chrome.tabGroups.update(groupId, { await chrome.tabGroups.update(groupId, {
@@ -79,12 +77,14 @@ async function createGroup(group: GroupItem, windowId: number, discard?: boolean
async function manageWindow(handle: (windowId: number) => Promise<void>, windowProps?: Windows.CreateCreateDataType): Promise<void> async function manageWindow(handle: (windowId: number) => Promise<void>, windowProps?: Windows.CreateCreateDataType): Promise<void>
{ {
const currentWindow: Windows.Window = windowProps ? const currentWindow: Windows.Window = windowProps ?
await browser.windows.create({ url: "about:blank", focused: true, ...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!;
await handle(windowId); await handle(windowId);
await browser.windows.update(windowId, { focused: true });
if (windowProps) if (windowProps)
// Close "about:blank" tab // Close "about:blank" tab
await browser.tabs.remove(currentWindow.tabs![0].id!); await browser.tabs.remove(currentWindow.tabs![0].id!);
@@ -14,7 +14,7 @@ export default async function getCollectionsFromCloud(): Promise<CollectionItem[
const chunks: Record<string, string> = const chunks: Record<string, string> =
await browser.storage.sync.get(getChunkKeys(0, chunkCount)) as Record<string, string>; await browser.storage.sync.get(getChunkKeys(0, chunkCount)) as Record<string, string>;
const data: string = decompress(Object.values(chunks).join(), { inputEncoding: "StorageBinaryString" }); const data: string = decompress(Object.values(chunks).join(), { inputEncoding: "Base64" });
return parseCollections(data); return parseCollections(data);
} }
@@ -14,7 +14,7 @@ export default async function saveCollectionsToCloud(collections: CollectionItem
return; return;
} }
const data: string = compress(serializeCollections(collections), { outputEncoding: "StorageBinaryString" }); const data: string = compress(serializeCollections(collections), { outputEncoding: "Base64" });
const chunks: string[] = splitIntoChunks(data); const chunks: string[] = splitIntoChunks(data);
if (chunks.length > collectionStorage.maxChunkCount) if (chunks.length > collectionStorage.maxChunkCount)
@@ -1,3 +1,4 @@
import { sendMessage } from "@/utils/messaging";
import { collectionStorage } from "./collectionStorage"; import { collectionStorage } from "./collectionStorage";
import saveCollectionsToCloud from "./saveCollectionsToCloud"; import saveCollectionsToCloud from "./saveCollectionsToCloud";
@@ -14,6 +15,6 @@ export default async function setCloudStorage(enable: boolean): Promise<void>
{ {
await collectionStorage.disableCloud.setValue(true); await collectionStorage.disableCloud.setValue(true);
await saveCollectionsToCloud([], 0); await saveCollectionsToCloud([], 0);
browser.runtime.reload(); await sendMessage("refreshCollections", undefined);
} }
} }
+2 -2
View File
@@ -161,7 +161,7 @@ collections:
menu: menu:
delete: "Delete collection" delete: "Delete collection"
add_selected: "Add selected tabs" add_selected: "Add selected tabs"
add_all: "Add selected tabs" add_all: "Add all tabs"
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"
@@ -174,7 +174,7 @@ groups:
menu: menu:
new_window: "Open in new window" new_window: "Open in new window"
add_selected: "Add selected tabs" add_selected: "Add selected tabs"
add_all: "Add selected tabs" add_all: "Add all tabs"
edit: "Edit group" edit: "Edit group"
ungroup: "Ungroup" ungroup: "Ungroup"
delete: "Delete group" delete: "Delete group"
+1 -1
View File
@@ -84,7 +84,7 @@ options_page:
icon_action: icon_action:
title: "Po kliknięciu ikony rozszerzenia:" title: "Po kliknięciu ikony rozszerzenia:"
options: options:
action: "Wykonaj domyślną akcję" action: "Zapisz karty (domyślna akcja)"
context: "Pokaż menu kontekstowe" context: "Pokaż menu kontekstowe"
open: "Otwórz listę kolekcji" open: "Otwórz listę kolekcji"
change_shortcuts: "Zmień skróty klawiszowe" change_shortcuts: "Zmień skróty klawiszowe"
+1 -1
View File
@@ -84,7 +84,7 @@ options_page:
icon_action: icon_action:
title: "При нажатии на иконку расширения:" title: "При нажатии на иконку расширения:"
options: options:
action: "Выполнить действие по умолчанию" action: "Сохранить вкладки (действие по умолчанию)"
context: "Показать контекстное меню" context: "Показать контекстное меню"
open: "Открыть список коллекций" open: "Открыть список коллекций"
change_shortcuts: "Изменить горячие клавиши" change_shortcuts: "Изменить горячие клавиши"
+1 -1
View File
@@ -84,7 +84,7 @@ options_page:
icon_action: icon_action:
title: "При натисканні на іконку розширення:" title: "При натисканні на іконку розширення:"
options: options:
action: "Виконати дію за замовчуванням" action: "Зберегти вкладки (дія за замовчуванням)"
context: "Показати контекстне меню" context: "Показати контекстне меню"
open: "Відкрити список колекцій" open: "Відкрити список колекцій"
change_shortcuts: "Змінити гарячі клавіші" change_shortcuts: "Змінити гарячі клавіші"
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "tabs-aside", "name": "tabs-aside",
"private": true, "private": true,
"version": "3.0.0-rc3", "version": "3.0.0-rc7",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "wxt", "dev": "wxt",
+10 -12
View File
@@ -23,7 +23,13 @@ export default async function saveTabsToCollection(closeTabs: boolean): Promise<
const [collection, tabsToClose] = await createCollectionFromTabs(tabs); const [collection, tabsToClose] = await createCollectionFromTabs(tabs);
if (closeTabs) if (closeTabs)
{
await browser.tabs.create({
active: true,
windowId: tabs[0].windowId
});
await browser.tabs.remove(tabsToClose.map(i => i.id!)); await browser.tabs.remove(tabsToClose.map(i => i.id!));
}
track(closeTabs ? "set_aside" : "save"); track(closeTabs ? "set_aside" : "save");
@@ -77,20 +83,12 @@ async function createCollectionFromTabs(tabs: Tabs.Tab[]): Promise<[CollectionIt
} }
} }
if (import.meta.env.FIREFOX)
{
for (; tabIndex < tabs.length; tabIndex++)
collection.items.push({ type: "tab", url: tabs[tabIndex].url!, title: tabs[tabIndex].title });
return [collection, tabs];
}
// Special case, if all tabs are in the same group, create a collection with the group title // Special case, if all tabs are in the same group, create a collection with the group title
if (tabs[0].groupId && tabs[0].groupId !== chrome.tabGroups.TAB_GROUP_ID_NONE && if (tabs[0].groupId && tabs[0].groupId !== -1 &&
tabs.every(i => i.groupId === tabs[0].groupId) tabs.every(i => i.groupId === tabs[0].groupId)
) )
{ {
const group = await browser.tabGroups!.get(tabs[0].groupId); const group = await chrome.tabGroups.get(tabs[0].groupId);
collection.title = group.title; collection.title = group.title;
collection.color = group.color; collection.color = group.color;
@@ -107,7 +105,7 @@ async function createCollectionFromTabs(tabs: Tabs.Tab[]): Promise<[CollectionIt
{ {
const tab = tabs[tabIndex]; const tab = tabs[tabIndex];
if (!tab.groupId || tab.groupId === chrome.tabGroups.TAB_GROUP_ID_NONE) if (!tab.groupId || tab.groupId === -1)
{ {
collection.items.push({ type: "tab", url: tab.url!, title: tab.title }); collection.items.push({ type: "tab", url: tab.url!, title: tab.title });
activeGroup = null; activeGroup = null;
@@ -117,7 +115,7 @@ async function createCollectionFromTabs(tabs: Tabs.Tab[]): Promise<[CollectionIt
if (!activeGroup || activeGroup !== tab.groupId) if (!activeGroup || activeGroup !== tab.groupId)
{ {
activeGroup = tab.groupId; activeGroup = tab.groupId;
const group = await browser.tabGroups!.get(activeGroup); const group = await chrome.tabGroups.get(activeGroup);
collection.items.push({ collection.items.push({
type: "group", type: "group",
+11 -7
View File
@@ -3,6 +3,12 @@ import { ConfigEnv, defineConfig, UserManifest } from "wxt";
// See https://wxt.dev/api/config.html // See https://wxt.dev/api/config.html
export default defineConfig({ export default defineConfig({
modules: ["@wxt-dev/module-react", "@wxt-dev/i18n/module", "@wxt-dev/analytics/module"], modules: ["@wxt-dev/module-react", "@wxt-dev/i18n/module", "@wxt-dev/analytics/module"],
vite: () => ({
build:
{
chunkSizeWarningLimit: 1000
}
}),
imports: { imports: {
dirsScanOptions: dirsScanOptions:
{ {
@@ -18,7 +24,8 @@ export default defineConfig({
description: "__MSG_manifest_description__", description: "__MSG_manifest_description__",
homepage_url: "https://github.com/xfox111/TabsAsideExtension/", homepage_url: "https://github.com/xfox111/TabsAsideExtension/",
action: { action:
{
default_title: "__MSG_manifest_name__" default_title: "__MSG_manifest_name__"
}, },
@@ -30,7 +37,8 @@ export default defineConfig({
"tabs", "tabs",
"notifications", "notifications",
"contextMenus", "contextMenus",
"bookmarks" "bookmarks",
"tabGroups"
], ],
commands: commands:
@@ -72,17 +80,13 @@ export default defineConfig({
gecko: gecko:
{ {
id: "tabsaside@xfox111.net", id: "tabsaside@xfox111.net",
strict_min_version: "109.0" strict_min_version: "139.0"
} }
}; };
// @ts-expect-error author key in Firefox has a different format // @ts-expect-error author key in Firefox has a different format
manifest.author = "__MSG_manifest_author__"; manifest.author = "__MSG_manifest_author__";
} }
else
{
manifest.permissions!.push("tabGroups");
}
return manifest; return manifest;
} }