1
0
mirror of https://github.com/XFox111/TabsAsideExtension.git synced 2026-04-22 07:58:01 +03:00

Major 3.0 (#118)

Co-authored-by: Maison da Silva <maisonmdsgreen@hotmail.com>
This commit is contained in:
2025-07-30 15:02:26 +03:00
committed by GitHub
parent d6996031b6
commit 2bd9337e63
200 changed files with 19452 additions and 3339 deletions
+8
View File
@@ -0,0 +1,8 @@
const browserLocaleKey: "firefox" | "edge" | "chrome" =
import.meta.env.FIREFOX ?
"firefox" :
import.meta.env.EDGE ?
"edge" :
"chrome";
export default browserLocaleKey;
+7
View File
@@ -0,0 +1,7 @@
const extLink = (href: string) => ({
href,
target: "_blank",
rel: "noopener"
});
export default extLink;
+13
View File
@@ -0,0 +1,13 @@
/**
* Creates a logger function for a specific component.
* The logger prepends a standardized prefix to all log messages,
* indicating the component name for easier debugging.
*
* @param component - The name of the component to include in the log prefix.
* @returns A logging function that accepts any number of arguments and logs them
* to the console with the component-specific prefix.
*/
export default function getLogger(component: string): (...data: any[]) => void
{
return (...data: any[]): void => console.log(`[TabsAside.${component}]`, ...data);
}
+42
View File
@@ -0,0 +1,42 @@
import { trackError } from "@/features/analytics";
import { CollectionItem, GraphicsStorage, GroupItem } from "@/models/CollectionModels";
import { defineExtensionMessaging, ExtensionMessagingConfig, ExtensionMessenger, ExtensionSendMessageArgs, GetDataType, GetReturnType } from "@webext-core/messaging";
type ProtocolMap =
{
addThumbnail(data: { url: string; thumbnail: string; }): void;
getGraphicsCache(): GraphicsStorage;
refreshCollections(): void;
openCollection(data: { collection: CollectionItem; targetWindow: "new" | "incognito"; }): void;
openGroup(data: { group: GroupItem; newWindow: boolean; }): void;
};
function defineMessaging(config?: ExtensionMessagingConfig): ExtensionMessenger<ProtocolMap>
{
const { onMessage, sendMessage, removeAllListeners }: ExtensionMessenger<ProtocolMap> = defineExtensionMessaging<ProtocolMap>(config);
return {
onMessage,
removeAllListeners,
async sendMessage<TType extends keyof ProtocolMap>(
type: TType,
data: GetDataType<ProtocolMap[TType]>,
...args: ExtensionSendMessageArgs
): Promise<GetReturnType<ProtocolMap[TType]>>
{
try
{
return await sendMessage(type, data, ...args);
}
catch (ex)
{
console.error(ex);
trackError("messaging_error", ex as Error);
return undefined!;
}
}
};
}
export const { onMessage, sendMessage } = defineMessaging({ logger: console });
+136
View File
@@ -0,0 +1,136 @@
import { track } from "@/features/analytics";
import { CollectionItem, GroupItem } from "@/models/CollectionModels";
import { Tabs } from "wxt/browser";
import sendNotification from "./sendNotification";
import { settings } from "./settings";
export default async function saveTabsToCollection(closeTabs: boolean): Promise<CollectionItem>
{
let tabs: Tabs.Tab[] = await browser.tabs.query({
currentWindow: true,
highlighted: true
});
if (tabs.length < 2)
{
const ignorePinned: boolean = await settings.ignorePinned.getValue();
tabs = await browser.tabs.query({
currentWindow: true,
pinned: ignorePinned ? false : undefined
});
}
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");
return collection;
}
async function createCollectionFromTabs(tabs: Tabs.Tab[]): Promise<[CollectionItem, Tabs.Tab[]]>
{
if (tabs.length < 1)
return [{ type: "collection", timestamp: Date.now(), items: [] }, []];
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"
});
tabs = tabs.filter(i => !i.url!.startsWith(browser.runtime.getURL("/")));
const collection: CollectionItem = {
type: "collection",
timestamp: Date.now(),
items: []
};
let tabIndex: number = 0;
if (tabs[tabIndex].pinned)
{
collection.items.push({ type: "group", pinned: true, items: [] });
for (; tabIndex < tabs.length; tabIndex++)
{
if (!tabs[tabIndex].pinned)
break;
(collection.items[0] as GroupItem).items.push({
type: "tab",
url: tabs[tabIndex].url!,
title: tabs[tabIndex].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 !== -1 &&
tabs.every(i => i.groupId === tabs[0].groupId)
)
{
const group = await chrome.tabGroups.get(tabs[0].groupId);
collection.title = group.title;
collection.color = group.color;
tabs.forEach(i =>
collection.items.push({ type: "tab", url: i.url!, title: i.title })
);
return [collection, tabs];
}
let activeGroup: number | null = null;
for (; tabIndex < tabs.length; tabIndex++)
{
const tab = tabs[tabIndex];
if (!tab.groupId || tab.groupId === -1)
{
collection.items.push({ type: "tab", url: tab.url!, title: tab.title });
activeGroup = null;
continue;
}
if (!activeGroup || activeGroup !== tab.groupId)
{
activeGroup = tab.groupId;
const group = await chrome.tabGroups.get(activeGroup);
collection.items.push({
type: "group",
color: group.color,
title: group.title,
items: []
});
}
(collection.items[collection.items.length - 1] as GroupItem).items.push({
type: "tab",
url: tab.url!,
title: tab.title
});
}
return [collection, tabs];
}
+28
View File
@@ -0,0 +1,28 @@
import { trackError } from "@/features/analytics";
import { PublicPath } from "wxt/browser";
export default async function sendNotification(props: NotificationProps): Promise<void>
{
try
{
await browser.notifications.create({
type: "basic",
title: props.title,
message: props.message,
iconUrl: browser.runtime.getURL(props.icon)
});
}
catch (ex)
{
console.error("Error while showing notification (probably because of user restrictions)");
console.error(ex);
trackError("notification_error", ex as Error);
}
}
export type NotificationProps =
{
title: string;
message: string;
icon: PublicPath;
};
+99
View File
@@ -0,0 +1,99 @@
import { CollectionSortMode } from "@/entrypoints/sidepanel/utils/sortCollections";
export const settings = {
defaultRestoreAction: storage.defineItem<"open" | "restore">(
"sync:defaultRestoreAction",
{
fallback: "open",
version: 1
}
),
defaultSaveAction: storage.defineItem<"save" | "set_aside">(
"sync:defaultSaveAction",
{
fallback: "set_aside",
version: 1
}
),
dismissOnLoad: storage.defineItem<boolean>(
"sync:dismissOnLoad",
{
fallback: false,
version: 1
}
),
deletePrompt: storage.defineItem<boolean>(
"sync:deletePrompt",
{
fallback: true,
version: 1
}
),
tilesView: storage.defineItem<boolean>(
"sync:tilesView",
{
fallback: true,
version: 1
}
),
sortMode: storage.defineItem<CollectionSortMode>(
"sync:sortMode",
{
fallback: "custom",
version: 1
}
),
ignorePinned: storage.defineItem<boolean>(
"sync:ignorePinned",
{
fallback: true,
version: 1
}
),
alwaysShowToolbars: storage.defineItem<boolean>(
"sync:alwaysShowToolbars",
{
fallback: false,
version: 1
}
),
showBadge: storage.defineItem<boolean>(
"sync:showBadge",
{
fallback: true,
version: 1
}
),
contextAction: storage.defineItem<"action" | "context" | "open">(
"sync:contextAction",
{
fallback: "open",
version: 1
}
),
listLocation: storage.defineItem<"sidebar" | "popup" | "tab" | "pinned">(
"sync:listLocation",
{
fallback: "sidebar",
version: 1
}
),
notifyOnSave: storage.defineItem<boolean>(
"sync:notifyOnSave",
{
fallback: true,
version: 1
}
)
};
+20
View File
@@ -0,0 +1,20 @@
import { Unwatch } from "wxt/storage";
export default function watchTabSelection(onChange: TabSelectChangeHandler): Unwatch
{
const handleTabSelection = async () =>
{
const highlightedTabs = await browser.tabs.query({
currentWindow: true,
highlighted: true
});
onChange(highlightedTabs.length > 1 ? "selected" : "all");
};
handleTabSelection();
browser.tabs.onHighlighted.addListener(handleTabSelection);
return () => browser.tabs.onHighlighted.removeListener(handleTabSelection);
}
export type TabSelectChangeHandler = (selection: "all" | "selected") => void;