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

Compare commits

..

16 Commits

21 changed files with 120 additions and 90 deletions
+1 -1
View File
@@ -21,4 +21,4 @@ export const storeLink: string =
? "https://addons.mozilla.org/en-US/firefox/addon/ms-edge-tabs-aside/" :
chrome.runtime.getManifest().update_url?.startsWith("https://edge.microsoft.com/") ?
"https://microsoftedge.microsoft.com/addons/detail/tabs-aside/kmnblllmalkiapkfknnlpobmjjdnlhnd" :
"https://chrome.google.com/webstore/detail/mgmjbodjgijnebfgohlnjkegdbdjgin";
"https://chromewebstore.google.com/detail/tabs-aside/mgmjbodjgijnebfgohlnjkegdpbdjgin";
+12 -1
View File
@@ -68,6 +68,7 @@ export default defineBackground(() =>
icon: graphicsCache[data.url]?.icon
};
});
onMessage("refreshCollections", () => {});
setupTabCaputre();
async function setupTabCaputre(): Promise<void>
@@ -77,6 +78,9 @@ export default defineBackground(() =>
if (!tab.url || tab.status !== "complete" || !tab.active)
return;
if (graphicsCache[tab.url]?.capture || graphicsCache[tab.url]?.capture === null)
return;
try
{
// 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(() =>
@@ -27,6 +27,9 @@ export default function GeneralSection(): React.ReactElement
if (e.optionValue === "popup" && contextAction !== "open")
setContextAction("open");
if (import.meta.env.FIREFOX && e.optionValue !== "sidebar")
browser.sidebarAction.close();
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),
sync: await browser.storage.sync.get(null)
});
const blob: Blob = new Blob([data], { type: "application/json" });
const element: HTMLAnchorElement = document.createElement("a");
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");
document.body.appendChild(element);
element.click();
URL.revokeObjectURL(element.href);
document.body.removeChild(element);
};
@@ -33,7 +33,7 @@ export default function CollectionView({ collection, index: collectionIndex, dra
const colorCls = useGroupColors();
return (
<CollectionContext.Provider value={ { collection, collectionIndex, tabCount, hasPinnedGroup } }>
<CollectionContext.Provider value={ { collection, tabCount, hasPinnedGroup } }>
<div
ref={ setNodeRef } { ...nodeProps }
className={ mergeClasses(
@@ -37,7 +37,7 @@ export default function EditDialog(props: GroupEditDialogProps): ReactElement
type: "collection",
timestamp: props.collection?.timestamp ?? Date.now(),
color: (color === "pinned") ? undefined : color!,
title,
title: title ? title : undefined,
items: props.collection?.items ?? []
});
else if (color === "pinned")
@@ -51,7 +51,7 @@ export default function EditDialog(props: GroupEditDialogProps): ReactElement
type: "group",
pinned: false,
color: color!,
title,
title: title ? title : undefined,
items: props.group?.items ?? []
});
};
@@ -74,7 +74,7 @@ export default function EditDialog(props: GroupEditDialogProps): ReactElement
contentBefore={ <Rename20Regular /> }
disabled={ color === "pinned" }
placeholder={
props.type === "collection" ? getCollectionTitle(props.collection) : ""
props.type === "collection" ? getCollectionTitle(props.collection, true) : ""
}
value={ color === "pinned" ? i18n.t("groups.pinned") : title }
onChange={ (_, e) => setTitle(e.value) } />
+6 -2
View File
@@ -9,10 +9,12 @@ import { Button, Caption1, Link, mergeClasses, Tooltip } from "@fluentui/react-c
import { Dismiss20Regular } from "@fluentui/react-icons";
import { MouseEventHandler, ReactElement } from "react";
import { useStyles_TabView } from "./TabView.styles";
import CollectionContext, { CollectionContextType } from "../contexts/CollectionContext";
export default function TabView({ tab, indices, dragOverlay }: TabViewProps): ReactElement
{
const { removeItem, graphics, tilesView } = useCollections();
const { collection } = useContext<CollectionContextType>(CollectionContext);
const {
setNodeRef, setActivatorNodeRef,
nodeProps, activatorProps, isBeingDragged
@@ -29,16 +31,18 @@ export default function TabView({ tab, indices, dragOverlay }: TabViewProps): Re
args.preventDefault();
args.stopPropagation();
const removeIndex: number[] = [collection.timestamp, ...indices.slice(1)];
if (deletePrompt)
dialog.pushPrompt({
title: i18n.t("tabs.delete"),
content: i18n.t("common.delete_prompt"),
destructive: true,
confirmText: i18n.t("common.actions.delete"),
onConfirm: () => removeItem(...indices)
onConfirm: () => removeItem(...removeIndex)
});
else
removeItem(...indices);
removeItem(...removeIndex);
};
const handleClick: MouseEventHandler<HTMLAnchorElement> = (args) =>
@@ -15,7 +15,7 @@ export default function CollectionHeader({ dragHandleRef, dragHandleProps }: Col
const [listLocation] = useSettings("listLocation");
const isTab: boolean = listLocation === "tab" || listLocation === "pinned";
const { updateCollection } = useCollections();
const { tabCount, collection, collectionIndex } = useContext<CollectionContextType>(CollectionContext);
const { tabCount, collection } = useContext<CollectionContextType>(CollectionContext);
const [alwaysShowToolbars] = useSettings("alwaysShowToolbars");
const AddIcon = bundleIcon(Add20Filled, Add20Regular);
@@ -25,7 +25,7 @@ export default function CollectionHeader({ dragHandleRef, dragHandleProps }: Col
const newTabs: (TabItem | GroupItem)[] = isTab ?
(await saveTabsToCollection(false)).items :
await getSelectedTabs();
updateCollection({ ...collection, items: [...collection.items, ...newTabs] }, collectionIndex);
updateCollection({ ...collection, items: [...collection.items, ...newTabs] }, collection.timestamp);
};
const cls = useStyles();
@@ -13,7 +13,7 @@ export default function CollectionMoreButton({ onAddSelected }: CollectionMoreBu
const [listLocation] = useSettings("listLocation");
const isTab: boolean = listLocation === "tab" || listLocation === "pinned";
const { removeItem, updateCollection } = useCollections();
const { tabCount, hasPinnedGroup, collection, collectionIndex } = useContext<CollectionContextType>(CollectionContext);
const { tabCount, hasPinnedGroup, collection } = useContext<CollectionContextType>(CollectionContext);
const dialog = useDialog();
const [deletePrompt] = useSettings("deletePrompt");
@@ -33,10 +33,10 @@ export default function CollectionMoreButton({ onAddSelected }: CollectionMoreBu
content: i18n.t("common.delete_prompt"),
destructive: true,
confirmText: i18n.t("common.actions.delete"),
onConfirm: () => removeItem(collectionIndex)
onConfirm: () => removeItem(collection.timestamp)
});
else
removeItem(collectionIndex);
removeItem(collection.timestamp);
};
const handleEdit = () =>
@@ -44,7 +44,7 @@ export default function CollectionMoreButton({ onAddSelected }: CollectionMoreBu
<EditDialog
type="collection"
collection={ collection }
onSave={ item => updateCollection(item, collectionIndex) } />
onSave={ item => updateCollection(item, collection.timestamp) } />
);
const handleCreateGroup = () =>
@@ -52,7 +52,7 @@ export default function CollectionMoreButton({ onAddSelected }: CollectionMoreBu
<EditDialog
type="group"
hidePinned={ hasPinnedGroup }
onSave={ group => updateCollection({ ...collection, items: [...collection.items, group] }, collectionIndex) } />
onSave={ group => updateCollection({ ...collection, items: [...collection.items, group] }, collection.timestamp) } />
);
return (
@@ -18,7 +18,7 @@ export default function GroupMoreMenu(): ReactElement
const [listLocation] = useSettings("listLocation");
const isTab: boolean = listLocation === "tab" || listLocation === "pinned";
const { group, indices } = useContext<GroupContextType>(GroupContext);
const { hasPinnedGroup } = useContext<CollectionContextType>(CollectionContext);
const { hasPinnedGroup, collection } = useContext<CollectionContextType>(CollectionContext);
const [deletePrompt] = useSettings("deletePrompt");
const dialog = useDialog();
const { updateGroup, removeItem, ungroup } = useCollections();
@@ -33,16 +33,18 @@ export default function GroupMoreMenu(): ReactElement
const handleDelete = () =>
{
const removeIndex: number[] = [collection.timestamp, ...indices.slice(1)];
if (deletePrompt)
dialog.pushPrompt({
title: i18n.t("groups.menu.delete"),
content: i18n.t("common.delete_prompt"),
confirmText: i18n.t("common.actions.delete"),
destructive: true,
onConfirm: () => removeItem(...indices)
onConfirm: () => removeItem(...removeIndex)
});
else
removeItem(...indices);
removeItem(...removeIndex);
};
const handleEdit = () =>
@@ -51,7 +53,7 @@ export default function GroupMoreMenu(): ReactElement
type="group"
group={ group }
hidePinned={ hasPinnedGroup }
onSave={ item => updateGroup(item, indices[0], indices[1]) } />
onSave={ item => updateGroup(item, collection.timestamp, indices[1]) } />
);
const handleAddSelected = async () =>
@@ -59,7 +61,7 @@ export default function GroupMoreMenu(): ReactElement
const newTabs: TabItem[] = isTab ?
(await saveTabsToCollection(false)).items.flatMap(i => i.type === "tab" ? i : i.items) :
await getSelectedTabs();
updateGroup({ ...group, items: [...group.items, ...newTabs] }, indices[0], indices[1]);
updateGroup({ ...group, items: [...group.items, ...newTabs] }, collection.timestamp, indices[1]);
};
return (
@@ -89,7 +91,7 @@ export default function GroupMoreMenu(): ReactElement
<MenuItem
className={ dangerCls.menuItem }
icon={ <UngroupIcon /> }
onClick={ () => ungroup(indices[0], indices[1]) }
onClick={ () => ungroup(collection.timestamp, indices[1]) }
>
{ i18n.t("groups.menu.ungroup") }
</MenuItem>
@@ -12,7 +12,7 @@ export default function OpenCollectionButton(): React.ReactElement
const [defaultAction] = useSettings("defaultRestoreAction");
const { removeItem } = useCollections();
const dialog = useDialog();
const { collection, collectionIndex } = useContext<CollectionContextType>(CollectionContext);
const { collection } = useContext<CollectionContextType>(CollectionContext);
const OpenIcon = ic.bundleIcon(ic.Open20Filled, ic.Open20Regular);
const RestoreIcon = ic.bundleIcon(ic.ArrowExportRtl20Filled, ic.ArrowExportRtl20Regular);
@@ -50,7 +50,7 @@ export default function OpenCollectionButton(): React.ReactElement
const handleRestore = async () =>
{
await openCollection(collection);
removeItem(collectionIndex);
removeItem(collection.timestamp);
};
return (
@@ -84,7 +84,7 @@ export default function OpenCollectionButton(): React.ReactElement
{ i18n.t("collections.actions.restore") }
</MenuItem>
}
<MenuItem icon={ <NewWindowIcon /> } onClick={ () => handleOpen("new") }>
<MenuItem icon={ <NewWindowIcon /> } onClick={ handleOpen("new") }>
{ i18n.t("collections.actions.new_window") }
</MenuItem>
<MenuItem icon={ <InPrivateIcon /> } onClick={ handleIncognito }>
@@ -8,7 +8,6 @@ export default CollectionContext;
export type CollectionContextType =
{
collection: CollectionItem;
collectionIndex: number;
tabCount: number;
hasPinnedGroup: boolean;
};
@@ -50,12 +50,14 @@ export default function CollectionsProvider({ children }: React.PropsWithChildre
const removeItem = (...indices: number[]): void =>
{
const collectionIndex: number = collections.findIndex(i => i.timestamp === indices[0]);
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)
collections[indices[0]].items.splice(indices[1], 1);
collections[collectionIndex].items.splice(indices[1], 1);
else
collections.splice(indices[0], 1);
collections.splice(collectionIndex, 1);
updateStorage(collections);
};
@@ -65,20 +67,23 @@ export default function CollectionsProvider({ children }: React.PropsWithChildre
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;
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;
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;
collections[collectionIndex].items.splice(groupIndex, 1, ...group.items);
updateStorage(collections);
@@ -108,9 +113,9 @@ export type CollectionsContextType =
addCollection: (collection: CollectionItem) => void;
updateCollections: (collections: CollectionItem[]) => void;
updateCollection: (collection: CollectionItem, index: number) => void;
updateGroup: (group: GroupItem, collectionIndex: number, groupIndex: number) => void;
ungroup: (collectionIndex: number, groupIndex: number) => void;
updateCollection: (collection: CollectionItem, id: number) => void;
updateGroup: (group: GroupItem, collectionId: number, groupIndex: number) => void;
ungroup: (collectionId: number, groupIndex: number) => void;
removeItem: (...indices: number[]) => void;
};
@@ -121,27 +121,25 @@ export default function CollectionListView(): ReactElement
</SortableContext>
<DragOverlay dropAnimation={ null }>
{ active &&
<>
{ active.item.type === "collection" &&
<CollectionView collection={ active.item } index={ -1 } dragOverlay />
}
{ active.item.type === "group" &&
<CollectionContext.Provider
value={ {
tabCount: 0,
collectionIndex: active.indices[0],
collection: resultList[active.indices[0]],
hasPinnedGroup: true
} }
>
{ active !== null ?
active.item.type === "collection" ?
<CollectionView collection={ active.item } index={ -1 } dragOverlay />
:
<CollectionContext.Provider
value={ {
tabCount: 0,
collection: resultList[active.indices[0]],
hasPinnedGroup: true
} }
>
{ active.item.type === "group" ?
<GroupView group={ active.item } indices={ [-1] } dragOverlay />
</CollectionContext.Provider>
}
{ active.item.type === "tab" &&
<TabView tab={ active.item } indices={ [-1] } dragOverlay />
}
</>
:
<TabView tab={ active.item } indices={ [-1] } dragOverlay />
}
</CollectionContext.Provider>
:
<></>
}
</DragOverlay>
</DndContext>
@@ -1,8 +1,10 @@
import { CollectionItem } from "@/models/CollectionModels";
export function getCollectionTitle(collection?: CollectionItem): string
export function getCollectionTitle(collection?: CollectionItem, useTimestamp?: boolean): string
{
return collection?.title
|| new Date(collection?.timestamp ?? Date.now())
.toLocaleDateString(browser.i18n.getUILanguage(), { year: "numeric", month: "short", day: "numeric" });
if (collection?.title !== undefined && useTimestamp !== true)
return collection.title;
return new Date(collection?.timestamp ?? Date.now())
.toLocaleDateString(browser.i18n.getUILanguage(), { year: "numeric", month: "short", day: "numeric" });
}
+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>
{
discard ??= await settings.dismissOnLoad.getValue();
const tabIds: number[] = await Promise.all(group.items.map(async i =>
(await createTab(i.url, windowId, discard, group.pinned)).id!
const tabs: Tabs.Tab[] = await Promise.all(group.items.map(async i =>
await createTab(i.url, windowId, discard, group.pinned)
));
// "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 || import.meta.env.FIREFOX)
if (group.pinned === true)
return;
const groupId: number = await chrome.tabs.group({
tabIds, createProperties: {
windowId
}
tabIds: tabs.filter(i => i.windowId === windowId).map(i => i.id!),
createProperties: { windowId }
});
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>
{
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();
const windowId: number = currentWindow.id!;
await handle(windowId);
await browser.windows.update(windowId, { focused: true });
if (windowProps)
// Close "about:blank" tab
await browser.tabs.remove(currentWindow.tabs![0].id!);
@@ -1,3 +1,4 @@
import { sendMessage } from "@/utils/messaging";
import { collectionStorage } from "./collectionStorage";
import saveCollectionsToCloud from "./saveCollectionsToCloud";
@@ -14,6 +15,6 @@ export default async function setCloudStorage(enable: boolean): Promise<void>
{
await collectionStorage.disableCloud.setValue(true);
await saveCollectionsToCloud([], 0);
browser.runtime.reload();
await sendMessage("refreshCollections", undefined);
}
}
+2 -2
View File
@@ -161,7 +161,7 @@ collections:
menu:
delete: "Delete collection"
add_selected: "Add selected tabs"
add_all: "Add selected tabs"
add_all: "Add all tabs"
add_group: "Add empty group"
export_bookmarks: "Export to bookmarks"
edit: "Edit collection"
@@ -174,7 +174,7 @@ groups:
menu:
new_window: "Open in new window"
add_selected: "Add selected tabs"
add_all: "Add selected tabs"
add_all: "Add all tabs"
edit: "Edit group"
ungroup: "Ungroup"
delete: "Delete group"
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "tabs-aside",
"private": true,
"version": "3.0.0-rc3",
"version": "3.0.0-rc5",
"type": "module",
"scripts": {
"dev": "wxt",
+10 -12
View File
@@ -23,7 +23,13 @@ export default async function saveTabsToCollection(closeTabs: boolean): Promise<
const [collection, tabsToClose] = await createCollectionFromTabs(tabs);
if (closeTabs)
{
await browser.tabs.create({
active: true,
windowId: tabs[0].windowId
});
await browser.tabs.remove(tabsToClose.map(i => i.id!));
}
track(closeTabs ? "set_aside" : "save");
@@ -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
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)
)
{
const group = await browser.tabGroups!.get(tabs[0].groupId);
const group = await chrome.tabGroups.get(tabs[0].groupId);
collection.title = group.title;
collection.color = group.color;
@@ -107,7 +105,7 @@ async function createCollectionFromTabs(tabs: Tabs.Tab[]): Promise<[CollectionIt
{
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 });
activeGroup = null;
@@ -117,7 +115,7 @@ async function createCollectionFromTabs(tabs: Tabs.Tab[]): Promise<[CollectionIt
if (!activeGroup || activeGroup !== tab.groupId)
{
activeGroup = tab.groupId;
const group = await browser.tabGroups!.get(activeGroup);
const group = await chrome.tabGroups.get(activeGroup);
collection.items.push({
type: "group",
+11 -7
View File
@@ -3,6 +3,12 @@ import { ConfigEnv, defineConfig, UserManifest } from "wxt";
// See https://wxt.dev/api/config.html
export default defineConfig({
modules: ["@wxt-dev/module-react", "@wxt-dev/i18n/module", "@wxt-dev/analytics/module"],
vite: () => ({
build:
{
chunkSizeWarningLimit: 1000
}
}),
imports: {
dirsScanOptions:
{
@@ -18,7 +24,8 @@ export default defineConfig({
description: "__MSG_manifest_description__",
homepage_url: "https://github.com/xfox111/TabsAsideExtension/",
action: {
action:
{
default_title: "__MSG_manifest_name__"
},
@@ -30,7 +37,8 @@ export default defineConfig({
"tabs",
"notifications",
"contextMenus",
"bookmarks"
"bookmarks",
"tabGroups"
],
commands:
@@ -72,17 +80,13 @@ export default defineConfig({
gecko:
{
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
manifest.author = "__MSG_manifest_author__";
}
else
{
manifest.permissions!.push("tabGroups");
}
return manifest;
}