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:
@@ -0,0 +1,8 @@
|
||||
const browserLocaleKey: "firefox" | "edge" | "chrome" =
|
||||
import.meta.env.FIREFOX ?
|
||||
"firefox" :
|
||||
import.meta.env.EDGE ?
|
||||
"edge" :
|
||||
"chrome";
|
||||
|
||||
export default browserLocaleKey;
|
||||
@@ -0,0 +1,7 @@
|
||||
const extLink = (href: string) => ({
|
||||
href,
|
||||
target: "_blank",
|
||||
rel: "noopener"
|
||||
});
|
||||
|
||||
export default extLink;
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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 });
|
||||
@@ -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];
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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
|
||||
}
|
||||
)
|
||||
};
|
||||
@@ -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;
|
||||
Reference in New Issue
Block a user