mirror of
https://github.com/XFox111/TabsAsideExtension.git
synced 2026-07-02 19:52:47 +03:00
Compare commits
30 Commits
v3.0.0-rc3
...
v3.0.0-rc7
| Author | SHA1 | Date | |
|---|---|---|---|
| e803636c35 | |||
| c8b4ef3e15 | |||
| eeefd1feff | |||
| 53adbd4f75 | |||
| 3eed3b4b01 | |||
| 525130b7e9 | |||
| 1a274348e0 | |||
| 2c8cfa1583 | |||
| e844a68f49 | |||
| 794f6e3af0 | |||
| d85d10dc58 | |||
| 213cc84602 | |||
| 9675b65e81 | |||
| 6bce330a8f | |||
| 405f9163f2 | |||
| e498e25c57 | |||
| d07c99e3a1 | |||
| 0ff1d63cde | |||
| dfcafae2b1 | |||
| 8f4cd4198a | |||
| 1b64f65e9f | |||
| d249e07eca | |||
| 4070907240 | |||
| 9d250dc01d | |||
| 24bf0e88ca | |||
| ef94842066 | |||
| 40490aec2d | |||
| 06aca3d3ca | |||
| a6a5c236c6 | |||
| a144221e33 |
@@ -0,0 +1,2 @@
|
|||||||
|
* @XFox111
|
||||||
|
locales/pt_BR.yml @maisondasilva @XFox111
|
||||||
@@ -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
@@ -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";
|
||||||
|
|||||||
@@ -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,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":
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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" });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 }));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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",
|
||||||
|
|||||||
@@ -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
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user