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

Compare commits

...

35 Commits

Author SHA1 Message Date
xfox111 e498e25c57 chore: version update 2025-05-08 10:07:18 +03:00
xfox111 d07c99e3a1 !feat: partial tab groups support on firefox 2025-05-08 10:06:47 +03:00
xfox111 0ff1d63cde fix: close firefox sidebar when list location changes 2025-05-08 10:05:53 +03:00
xfox111 dfcafae2b1 fix: options page crush on cloud storage disabling 2025-05-08 09:05:53 +03:00
xfox111 8f4cd4198a fix: groupig fails on private windows when group contains restricted tabs 2025-05-08 09:05:21 +03:00
xfox111 1b64f65e9f fix: empty collection/group title 2025-05-08 09:04:34 +03:00
xfox111 d249e07eca fix: corrupted json export 2025-05-08 09:04:08 +03:00
xfox111 4070907240 chore(loc): english locale fix 2025-05-08 09:03:15 +03:00
xfox111 9d250dc01d fix: MAX_CAPTURE_VISIBLE_TAB_CALLS_PER_SECOND exceeded 2025-05-08 06:59:55 +03:00
xfox111 24bf0e88ca Fix: window closes if all tabs were set aside 2025-05-08 06:56:06 +03:00
xfox111 ef94842066 fix: collections removed by their sorted index 2025-05-08 06:55:35 +03:00
xfox111 40490aec2d fix: open collection in a new window doesn't work 2025-05-08 02:26:08 +03:00
xfox111 06aca3d3ca fix: show timestamp-based title in edit dialog title placeholder 2025-05-08 02:20:44 +03:00
xfox111 a6a5c236c6 fix: broken chrome store link 2025-05-08 02:09:22 +03:00
xfox111 a144221e33 chore: chunk max size warning 2025-05-07 20:38:18 +03:00
xfox111 e6a69980c2 docs: privacy policy 2025-05-07 20:26:24 +03:00
xfox111 59b0547ec6 chore: version update 2025-05-07 20:01:17 +03:00
xfox111 eed5159a56 chore(loc): simplified chinese translation 2025-05-07 19:55:28 +03:00
xfox111 7effe309dd chore: minor updates 2025-05-07 19:40:03 +03:00
xfox111 6728a50056 chore(loc): spanish translation 2025-05-07 19:39:12 +03:00
xfox111 0cb036c69a chore(loc): italian translation 2025-05-07 19:18:48 +03:00
xfox111 4ef336da5b chore(loc): polish translation 2025-05-07 18:43:50 +03:00
xfox111 00492ad710 chore: tab list location logic improvement #117 2025-05-07 00:50:54 +03:00
xfox111 a706c3bc89 fix: when listLocation is "tab" or "pinned" add all tabs instead of selected #117 2025-05-07 00:28:48 +03:00
xfox111 4ef9e2651c fix: partial save notification appears when listLocation is "tab" or "pinned" 2025-05-07 00:27:16 +03:00
xfox111 bfb849fbdf fix: collection list refresh across multiple list views 2025-05-07 00:09:50 +03:00
xfox111 297a6aa95c feat: allow to create any group on firefox 2025-05-07 00:05:12 +03:00
xfox111 aa2ee02c79 chore: messenger refactoring 2025-05-07 00:04:19 +03:00
xfox111 8b77159abe hotfix: wrapped analytics into try/catch to prevent failing on firefox 2025-05-07 00:03:43 +03:00
xfox111 f5bf0db039 !feat: remove "pointer: coarse" media #117 2025-05-06 22:58:52 +03:00
xfox111 5d4a59153a feat: ga4 analytics #117 2025-05-05 19:25:25 +03:00
Maison da Silva b6be86aac9 locales pt_BR.yml
locales pt_BR.yml
2025-05-04 19:51:19 +03:00
Maison da Silva d872515b8b Update pt_BR.txt 2025-05-04 19:50:59 +03:00
Maison da Silva 8693e8d563 Create pt_BR.txt 2025-05-04 19:50:59 +03:00
xfox111 9c4121ea79 chore(ci): exclude locales from pr check 2025-05-04 19:50:29 +03:00
57 changed files with 1941 additions and 215 deletions
+4
View File
@@ -47,6 +47,10 @@ jobs:
steps:
- uses: actions/checkout@main
- run: |
echo "WXT_GA4_API_SECRET=${{ secrets.GA4_SECRET }}" >> .env
echo "WXT_GA4_MEASUREMENT_ID=${{ secrets.GA4_MEASUREMENT_ID }}" >> .env
- run: yarn install
- run: yarn zip -b ${{ matrix.target }}
+7 -1
View File
@@ -5,9 +5,11 @@ on:
branches: [ "main", "next" ]
paths-ignore:
- '**.md'
- '**.txt'
- "locales/*"
- 'LICENSE'
- 'PRIVACY'
- '**/cd_pipeline.yaml'
- '**/cd_pipeline.yml'
- '**/dependabot.yml'
- '**/codeql-analysis.yml'
- '**/pr_next.yaml'
@@ -37,6 +39,10 @@ jobs:
steps:
- uses: actions/checkout@main
- run: |
echo "WXT_GA4_API_SECRET=${{ secrets.GA4_SECRET }}" >> .env
echo "WXT_GA4_MEASUREMENT_ID=${{ secrets.GA4_MEASUREMENT_ID }}" >> .env
- run: yarn install
- run: yarn zip -b ${{ matrix.target }}
+2
View File
@@ -26,3 +26,5 @@ web-ext.config.ts
*.sw?
web-ext.config.js
.env*
+16 -4
View File
@@ -1,8 +1,20 @@
# Tabs aside extension Privacy policy
1. Developers of the extension don't affiliate with Google LLC, Mozilla Foundation or Microsoft Corporation in any way.
2. This extension only stores user data related to its core functionality. This includes:
2. This extension stores user data only related to its core functionality. This includes:
- User settings
- User saved collections of tabs
2. This extension doesn't use any tracking software, nor does it collect, sell or share any personal data with any third parties.
3. This extension uses cloud storage built into your browser to store its data.
4. Refer to your browser's developer regarding the privacy policy of the cloud storage used by your browser.
- Thumbnails of saved tabs
3. This extension uses Google Analytics to collect usage statistics and improve the extension.
4. This extension uses analytics to collect following data:
- Random UUID to identify the user
- Browser name and version
- Operating system name and version
- System architecture
- Screen resolution
- Extension language
- User settings
- Number of saved collections
- Action identifiers (e.g. "page_view", "extension_installed", "item_created", etc.)
4. This extension does not collect or use any personally identifiable information (PII) or sensitive data for purposes other than its core functionality.
5. This extension uses cloud storage built into your browser to store its data.
6. Refer to your browser's developer regarding the privacy policy of the cloud storage used by your browser.
+1 -1
View File
@@ -26,7 +26,7 @@ Check out our [latest blog post](https://at.xfox111.net/tabs-aside-3-0) regardin
- English
- Italian
- Polish
- Portuguese (Brazil)
- Portuguese (Brazil) by [@maisondasilva](https://github.com/maisondasilva)
- Russian
- Spanish
- Ukrainian
+24
View File
@@ -0,0 +1,24 @@
import { googleAnalytics4 } from "@wxt-dev/analytics/providers/google-analytics-4";
import { WxtAppConfig } from "wxt/sandbox";
import { userPropertiesStorage } from "./features/analytics";
export default defineAppConfig({
analytics:
{
debug: import.meta.env.DEV,
enabled: storage.defineItem("local:analytics", {
fallback: true
}),
userId: storage.defineItem("local:userId", {
init: () => crypto.randomUUID()
}),
userProperties: userPropertiesStorage,
providers:
[
googleAnalytics4({
apiSecret: import.meta.env.WXT_GA4_API_SECRET,
measurementId: import.meta.env.WXT_GA4_MEASUREMENT_ID
})
]
}
} as WxtAppConfig);
+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";
+60 -23
View File
@@ -1,3 +1,4 @@
import { track, trackError } from "@/features/analytics";
import { collectionCount, getCollections, saveCollections } from "@/features/collectionStorage";
import { migrateStorage } from "@/features/migration";
import { showWelcomeDialog } from "@/features/v3welcome/utils/showWelcomeDialog";
@@ -30,6 +31,7 @@ export default defineBackground(() =>
browser.runtime.onInstalled.addListener(async ({ reason, previousVersion }) =>
{
logger("onInstalled", reason, previousVersion);
track("extension_installed", { reason, previousVersion: previousVersion ?? "none" });
const previousMajor: number = previousVersion ? parseInt(previousVersion.split(".")[0]) : 0;
@@ -66,6 +68,7 @@ export default defineBackground(() =>
icon: graphicsCache[data.url]?.icon
};
});
onMessage("refreshCollections", () => {});
setupTabCaputre();
async function setupTabCaputre(): Promise<void>
@@ -75,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
@@ -88,11 +94,16 @@ export default defineBackground(() =>
preview: graphicsCache[tab.url]?.preview,
icon: graphicsCache[tab.url]?.icon
};
logger("Captured tab", tab.url);
}
}
catch (ex) { logger(ex); }
catch
{
graphicsCache[tab.url] = {
capture: null!,
preview: graphicsCache[tab.url]?.preview,
icon: graphicsCache[tab.url]?.icon
};
}
};
setInterval(() =>
@@ -238,24 +249,35 @@ export default defineBackground(() =>
setupCollectionView();
async function setupCollectionView(): Promise<void>
{
const enforcePinnedTab = async (info: Tabs.OnHighlightedHighlightInfoType): Promise<void> =>
const enforcePinnedTab = async (): Promise<void> =>
{
logger("enforcePinnedTab", info);
logger("enforcePinnedTab");
const activeWindow: Windows.Window = await browser.windows.getCurrent({ populate: true });
const openWindows: Windows.Window[] = await browser.windows.getAll({ populate: true });
if (activeWindow.incognito)
return;
for (const openWindow of openWindows)
{
if (openWindow.incognito)
continue;
if (!activeWindow.tabs!.some(tab =>
[tab.url, tab.pendingUrl].includes(browser.runtime.getURL("/sidepanel.html")))
)
await browser.tabs.create({
url: browser.runtime.getURL("/sidepanel.html"),
windowId: activeWindow.id,
active: false,
pinned: true
});
const activeTabs: Tabs.Tab[] = openWindow.tabs!.filter(tab =>
tab.url === browser.runtime.getURL("/sidepanel.html"));
const targetTab: Tabs.Tab | undefined = activeTabs.find(tab => tab.pinned);
if (!targetTab)
await browser.tabs.create({
url: browser.runtime.getURL("/sidepanel.html"),
windowId: openWindow.id,
active: false,
pinned: true
});
const tabsToClose: Tabs.Tab[] = activeTabs.filter(tab => tab.id !== targetTab?.id);
if (tabsToClose.length > 0)
await browser.tabs.remove(tabsToClose.map(tab => tab.id!));
}
};
const updateView = async (viewLocation: SettingsValue<"listLocation">): Promise<void> =>
@@ -264,7 +286,6 @@ export default defineBackground(() =>
browser.tabs.onHighlighted.removeListener(enforcePinnedTab);
const tabs: Tabs.Tab[] = await browser.tabs.query({
currentWindow: true,
url: browser.runtime.getURL("/sidepanel.html")
});
await browser.tabs.remove(tabs.map(tab => tab.id!));
@@ -282,11 +303,7 @@ export default defineBackground(() =>
if (viewLocation === "pinned")
{
await browser.tabs.create({
url: browser.runtime.getURL("/sidepanel.html"),
active: false,
pinned: true
});
enforcePinnedTab();
browser.tabs.onHighlighted.addListener(enforcePinnedTab);
}
};
@@ -329,6 +346,25 @@ export default defineBackground(() =>
if (currentWindow.incognito)
{
let availableWindows: Windows.Window[] = await browser.windows.getAll({ populate: true });
availableWindows = availableWindows.filter(window =>
!window.incognito &&
window.tabs?.some(i => i.url === browser.runtime.getURL("/sidepanel.html"))
);
if (availableWindows.length > 0)
{
const availableTab: Tabs.Tab = availableWindows[0].tabs!.find(
tab => tab.url === browser.runtime.getURL("/sidepanel.html")
)!;
await browser.tabs.update(availableTab.id, { active: true });
await browser.windows.update(availableWindows[0].id!, { focused: true });
return;
}
await browser.windows.create({
url: browser.runtime.getURL("/sidepanel.html"),
focused: true
@@ -374,5 +410,6 @@ export default defineBackground(() =>
catch (ex)
{
console.error(ex);
trackError("background_error", ex as Error);
}
});
@@ -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);
};
+2
View File
@@ -14,6 +14,8 @@ ReactDOM.createRoot(document.getElementById("root")!).render(
</App>
);
analytics.page("options_page");
function OptionsPage(): React.ReactElement
{
const [selection, setSelection] = useState<SelectionType>("general");
+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(
@@ -1,4 +1,5 @@
import { getCollectionTitle } from "@/entrypoints/sidepanel/utils/getCollectionTitle";
import { track } from "@/features/analytics";
import { useGroupColors } from "@/hooks/useGroupColors";
import { CollectionItem, GroupItem } from "@/models/CollectionModels";
import * as fui from "@fluentui/react-components";
@@ -26,12 +27,17 @@ export default function EditDialog(props: GroupEditDialogProps): ReactElement
const handleSave = () =>
{
if (props.type === "collection" ? props.collection !== null : props.group !== null)
track("item_edited", { type: props.type });
else
track("item_created", { type: props.type });
if (props.type === "collection")
props.onSave({
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")
@@ -45,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 ?? []
});
};
@@ -68,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) } />
@@ -68,12 +68,7 @@ export const useStyles_GroupView = makeStyles({
{
display: "flex",
gap: tokens.spacingHorizontalS,
visibility: "hidden",
"@media (pointer: coarse)":
{
visibility: "visible"
}
visibility: "hidden"
},
showToolbar:
{
@@ -86,12 +86,7 @@ export const useStyles_TabView = makeStyles({
},
deleteButton:
{
display: "none",
"@media (pointer: coarse)":
{
display: "inline-flex"
}
display: "none"
},
showDeleteButton:
{
+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) =>
@@ -1,26 +1,31 @@
import { getCollectionTitle } from "@/entrypoints/sidepanel/utils/getCollectionTitle";
import getSelectedTabs from "@/entrypoints/sidepanel/utils/getSelectedTabs";
import useSettings from "@/hooks/useSettings";
import { TabItem } from "@/models/CollectionModels";
import { GroupItem, TabItem } from "@/models/CollectionModels";
import { Button, Caption1, makeStyles, mergeClasses, Subtitle2, tokens, Tooltip } from "@fluentui/react-components";
import { Add20Filled, Add20Regular, bundleIcon } from "@fluentui/react-icons";
import CollectionContext, { CollectionContextType } from "../../contexts/CollectionContext";
import { useCollections } from "../../contexts/CollectionsProvider";
import CollectionMoreButton from "./CollectionMoreButton";
import OpenCollectionButton from "./OpenCollectionButton";
import saveTabsToCollection from "@/utils/saveTabsToCollection";
export default function CollectionHeader({ dragHandleRef, dragHandleProps }: CollectionHeaderProps): React.ReactElement
{
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);
const handleAddSelected = async () =>
{
const newTabs: TabItem[] = await getSelectedTabs();
updateCollection({ ...collection, items: [...collection.items, ...newTabs] }, collectionIndex);
const newTabs: (TabItem | GroupItem)[] = isTab ?
(await saveTabsToCollection(false)).items :
await getSelectedTabs();
updateCollection({ ...collection, items: [...collection.items, ...newTabs] }, collection.timestamp);
};
const cls = useStyles();
@@ -53,7 +58,7 @@ export default function CollectionHeader({ dragHandleRef, dragHandleProps }: Col
>
{ tabCount < 1 ?
<Button icon={ <AddIcon /> } appearance="subtle" onClick={ handleAddSelected }>
{ i18n.t("collections.menu.add_selected") }
{ isTab ? i18n.t("collections.menu.add_all") : i18n.t("collections.menu.add_selected") }
</Button>
:
<OpenCollectionButton />
@@ -95,12 +100,7 @@ const useStyles = makeStyles({
{
display: "none",
gap: tokens.spacingHorizontalS,
alignItems: "flex-start",
"@media (pointer: coarse)":
{
display: "flex"
}
alignItems: "flex-start"
},
showToolbar:
{
@@ -10,8 +10,10 @@ import EditDialog from "../EditDialog";
export default function CollectionMoreButton({ onAddSelected }: CollectionMoreButtonProps): React.ReactElement
{
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");
@@ -19,7 +21,6 @@ export default function CollectionMoreButton({ onAddSelected }: CollectionMoreBu
const GroupIcon = ic.bundleIcon(ic.GroupList20Filled, ic.GroupList20Regular);
const EditIcon = ic.bundleIcon(ic.Edit20Filled, ic.Edit20Regular);
const DeleteIcon = ic.bundleIcon(ic.Delete20Filled, ic.Delete20Regular);
const PinnedIcon = ic.bundleIcon(ic.Pin20Filled, ic.Pin20Regular);
const BookmarkIcon = ic.bundleIcon(ic.BookmarkAdd20Filled, ic.BookmarkAdd20Regular);
const dangerCls = useDangerStyles();
@@ -32,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 = () =>
@@ -43,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 = () =>
@@ -51,20 +52,9 @@ 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) } />
);
const handleAddPinnedGroup = () =>
{
updateCollection({
...collection,
items: [
{ type: "group", pinned: true, items: [] },
...collection.items
]
}, collectionIndex);
};
return (
<Menu>
<Tooltip relationship="label" content={ i18n.t("common.tooltips.more") }>
@@ -77,19 +67,12 @@ export default function CollectionMoreButton({ onAddSelected }: CollectionMoreBu
<MenuList>
{ tabCount > 0 &&
<MenuItem icon={ <AddIcon /> } onClick={ () => onAddSelected?.() }>
{ i18n.t("collections.menu.add_selected") }
</MenuItem>
}
{ !import.meta.env.FIREFOX &&
<MenuItem icon={ <GroupIcon /> } onClick={ handleCreateGroup }>
{ i18n.t("collections.menu.add_group") }
</MenuItem>
}
{ (import.meta.env.FIREFOX && !hasPinnedGroup) &&
<MenuItem icon={ <PinnedIcon /> } onClick={ handleAddPinnedGroup }>
{ i18n.t("collections.menu.add_pinned") }
{ isTab ? i18n.t("collections.menu.add_all") : i18n.t("collections.menu.add_selected") }
</MenuItem>
}
<MenuItem icon={ <GroupIcon /> } onClick={ handleCreateGroup }>
{ i18n.t("collections.menu.add_group") }
</MenuItem>
{ tabCount > 0 &&
<MenuItem icon={ <BookmarkIcon /> } onClick={ () => exportCollectionToBookmarks(collection) }>
{ i18n.t("collections.menu.export_bookmarks") }
@@ -11,11 +11,14 @@ import { Button, Menu, MenuItem, MenuList, MenuPopover, MenuTrigger, Tooltip } f
import * as ic from "@fluentui/react-icons";
import { ReactElement } from "react";
import { openGroup } from "../../utils/opener";
import saveTabsToCollection from "@/utils/saveTabsToCollection";
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();
@@ -30,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 = () =>
@@ -48,13 +53,15 @@ 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 () =>
{
const newTabs: TabItem[] = await getSelectedTabs();
updateGroup({ ...group, items: [...group.items, ...newTabs] }, indices[0], indices[1]);
const newTabs: TabItem[] = isTab ?
(await saveTabsToCollection(false)).items.flatMap(i => i.type === "tab" ? i : i.items) :
await getSelectedTabs();
updateGroup({ ...group, items: [...group.items, ...newTabs] }, collection.timestamp, indices[1]);
};
return (
@@ -74,19 +81,17 @@ export default function GroupMoreMenu(): ReactElement
}
<MenuItem icon={ <AddIcon /> } onClick={ handleAddSelected }>
{ i18n.t("groups.menu.add_selected") }
{ isTab ? i18n.t("groups.menu.add_all") : i18n.t("groups.menu.add_selected") }
</MenuItem>
{ (!import.meta.env.FIREFOX || group.pinned !== true) &&
<MenuItem icon={ <EditIcon /> } onClick={ handleEdit }>
{ i18n.t("groups.menu.edit") }
</MenuItem>
}
<MenuItem icon={ <EditIcon /> } onClick={ handleEdit }>
{ i18n.t("groups.menu.edit") }
</MenuItem>
{ group.items.length > 0 &&
<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;
};
@@ -2,7 +2,7 @@ import { CloudStorageIssueType, getCollections, graphics as graphicsStorage, sav
import useSettings from "@/hooks/useSettings";
import { CollectionItem, GraphicsStorage, GroupItem } from "@/models/CollectionModels";
import getLogger from "@/utils/getLogger";
import { onMessage } from "@/utils/messaging";
import { onMessage, sendMessage } from "@/utils/messaging";
import { createContext } from "react";
import mergePinnedGroups from "../utils/mergePinnedGroups";
@@ -40,6 +40,7 @@ export default function CollectionsProvider({ children }: React.PropsWithChildre
setCollections([...collectionList]);
await saveCollections(collectionList, cloudIssue === null);
setGraphics(await graphicsStorage.getValue());
sendMessage("refreshCollections", undefined);
};
const addCollection = (collection: CollectionItem): void =>
@@ -49,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);
};
@@ -64,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);
@@ -107,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;
};
@@ -5,6 +5,7 @@ import CloudIssueMessages from "@/entrypoints/sidepanel/layouts/collections/mess
import CtaMessage from "@/entrypoints/sidepanel/layouts/collections/messages/CtaMessage";
import filterCollections, { CollectionFilterType } from "@/entrypoints/sidepanel/utils/filterCollections";
import sortCollections from "@/entrypoints/sidepanel/utils/sortCollections";
import { track } from "@/features/analytics";
import useSettings from "@/hooks/useSettings";
import { CollectionItem } from "@/models/CollectionModels";
import { DndContext, DragEndEvent, DragOverlay, DragStartEvent, MouseSensor, TouchSensor, useSensor, useSensors } from "@dnd-kit/core";
@@ -64,6 +65,8 @@ export default function CollectionListView(): ReactElement
updateCollections(result);
if (sortMode !== "custom")
setSortMode("custom");
track("used_drag_and_drop");
}
};
@@ -118,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,5 +1,6 @@
import { BuyMeACoffee20Regular } from "@/assets/BuyMeACoffee20";
import { buyMeACoffeeLink, storeLink } from "@/data/links";
import { track } from "@/features/analytics";
import { useBmcStyles } from "@/hooks/useBmcStyles";
import extLink from "@/utils/extLink";
import { Button, Link, MessageBar, MessageBarActions, MessageBarBody, MessageBarProps, MessageBarTitle } from "@fluentui/react-components";
@@ -27,6 +28,11 @@ export default function CtaMessage(props: MessageBarProps): ReactElement
{
await ctaCounter.setValue(counter);
setCounter(counter);
if (counter === -1)
track("bmc_clicked");
else
track("cta_dismissed");
};
if (counter < 50)
@@ -36,7 +42,7 @@ export default function CtaMessage(props: MessageBarProps): ReactElement
<MessageBar layout="multiline" icon={ <HeartFilled color="red" /> } { ...props }>
<MessageBarBody>
<MessageBarTitle>{ i18n.t("cta_message.title") }</MessageBarTitle>
{ i18n.t("cta_message.message") } <Link { ...extLink(storeLink) }>{ i18n.t("cta_message.feedback") }</Link>
{ i18n.t("cta_message.message") } <Link { ...extLink(storeLink) } onClick={ () => track("feedback_clicked") }>{ i18n.t("cta_message.feedback") }</Link>
</MessageBarBody>
<MessageBarActions
containerAction={
@@ -1,5 +1,6 @@
import { BuyMeACoffee20Filled, BuyMeACoffee20Regular } from "@/assets/BuyMeACoffee20";
import { buyMeACoffeeLink, githubLinks, storeLink } from "@/data/links";
import { track } from "@/features/analytics";
import useSettings from "@/hooks/useSettings";
import extLink from "@/utils/extLink";
import sendNotification from "@/utils/sendNotification";
@@ -41,10 +42,10 @@ export default function MoreButton(): ReactElement
<fui.MenuDivider />
<fui.MenuItemLink icon={ <BmcIcon /> } { ...extLink(buyMeACoffeeLink) }>
<fui.MenuItemLink icon={ <BmcIcon /> } { ...extLink(buyMeACoffeeLink) } onClick={ () => track("feedback_clicked") }>
{ i18n.t("common.cta.sponsor") }
</fui.MenuItemLink>
<fui.MenuItemLink icon={ <FeedbackIcon /> } { ...extLink(storeLink) } >
<fui.MenuItemLink icon={ <FeedbackIcon /> } { ...extLink(storeLink) } onClick={ () => track("bmc_clicked") }>
{ i18n.t("common.cta.feedback") }
</fui.MenuItemLink>
<fui.MenuItemLink icon={ <LearnIcon /> } { ...extLink(githubLinks.release) } >
+1
View File
@@ -15,6 +15,7 @@ ReactDOM.createRoot(document.getElementById("root")!).render(
);
document.title = i18n.t("manifest.name");
analytics.page("collection_list");
function MainPage(): React.ReactElement
{
@@ -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" });
}
+15 -12
View File
@@ -55,36 +55,39 @@ 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, {
title: group.title,
color: group.color
});
// Grouping support came in 138, tabGroups is expected to be in 139
// TODO: Remove this check once the API is available
if (!import.meta.env.FIREFOX)
await chrome.tabGroups.update(groupId, {
title: group.title,
color: group.color
});
}
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!);
+2 -1
View File
@@ -88,7 +88,8 @@ export default defineConfig([
"@typescript-eslint/no-unused-vars": ["warn"],
"prefer-const": ["warn"],
"@stylistic/padded-blocks": ["warn"],
"no-empty": ["off"]
"no-empty": ["off"],
"@stylistic/eol-last": ["warn"]
}
},
{
+3
View File
@@ -0,0 +1,3 @@
export { default as userPropertiesStorage } from "./utils/userPropertiesStorage";
export { default as trackError } from "./utils/trackError";
export { default as track } from "./utils/track";
+11
View File
@@ -0,0 +1,11 @@
export default function track(eventName: string, eventProperties?: Record<string, string>): void
{
try
{
analytics.track(eventName, eventProperties);
}
catch (ex)
{
console.error("Failed to send analytics event", ex);
}
}
+15
View File
@@ -0,0 +1,15 @@
export default function trackError(name: string, error: Error): void
{
try
{
analytics.track(name, {
name: error.name,
message: error.message,
stack: error.stack ?? "no_stack"
});
}
catch (ex)
{
console.error("Failed to send error report", ex);
}
}
@@ -0,0 +1,35 @@
import { cloudDisabled, collectionCount } from "@/features/collectionStorage";
import { settings } from "@/utils/settings";
import { WxtStorageItem } from "wxt/storage";
// @ts-expect-error we don't need to implement a full storage item
const userPropertiesStorage: WxtStorageItem<Record<string, string>, any> =
{
getValue: async (): Promise<UserProperties> =>
{
console.log("userPropertiesStorage.getValue");
const properties: UserProperties =
{
cloud_used: await cloudDisabled.getValue() ? "-1" : (await browser.storage.sync.getBytesInUse() / 102400).toString(),
collection_count: (await collectionCount.getValue()).toString()
};
for (const key of Object.keys(settings))
{
const value = await settings[key as keyof typeof settings].getValue();
properties[`option_${key}`] = value.valueOf().toString();
}
return properties;
},
setValue: async () => { }
};
export default userPropertiesStorage;
export type UserProperties =
{
collection_count: string;
cloud_used: string;
[key: `option_${string}`]: string;
};
@@ -1,3 +1,4 @@
import { trackError } from "@/features/analytics";
import { CollectionItem } from "@/models/CollectionModels";
import getLogger from "@/utils/getLogger";
import { collectionStorage } from "./collectionStorage";
@@ -32,6 +33,7 @@ export default async function getCollections(): Promise<[CollectionItem[], Cloud
{
logger("Failed to get cloud storage");
console.error(ex);
trackError("cloud_get_error", ex as Error);
return [await getCollectionsFromLocal(), "parse_error"];
}
}
@@ -1,3 +1,4 @@
import { trackError } from "@/features/analytics";
import { CollectionItem } from "@/models/CollectionModels";
import getLogger from "@/utils/getLogger";
import { collectionStorage } from "./collectionStorage";
@@ -37,5 +38,6 @@ async function replaceLocalWithCloud(): Promise<void>
{
logger("Failed to get cloud storage");
console.error(ex);
trackError("conflict_resolve_with_cloud_error", ex as Error);
}
}
@@ -1,3 +1,4 @@
import { trackError } from "@/features/analytics";
import { CollectionItem, GraphicsStorage } from "@/models/CollectionModels";
import getLogger from "@/utils/getLogger";
import sendNotification from "@/utils/sendNotification";
@@ -26,6 +27,7 @@ export default async function saveCollections(
{
logger("Failed to save cloud storage");
console.error(ex);
trackError("cloud_save_error", ex as Error);
if ((ex as Error).message.includes("MAX_WRITE_OPERATIONS_PER_MINUTE"))
await sendNotification({
@@ -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);
}
}
@@ -1,5 +1,6 @@
import { useTheme } from "@/contexts/ThemeProvider";
import { v3blogPost } from "@/data/links";
import { track } from "@/features/analytics";
import extLink from "@/utils/extLink";
import * as fui from "@fluentui/react-components";
@@ -23,9 +24,7 @@ export default function WelcomeDialog(): React.ReactElement
{ i18n.t("features.v3welcome.text2") }
</fui.Body1>
<ul>
{ !import.meta.env.FIREFOX &&
<li>{ i18n.t("features.v3welcome.list.item1") }</li>
}
<li>{ i18n.t("features.v3welcome.list.item1") }</li>
<li>{ i18n.t("features.v3welcome.list.item2") }</li>
<li>{ i18n.t("features.v3welcome.list.item3") }</li>
<li>{ i18n.t("features.v3welcome.list.item4") }</li>
@@ -39,7 +38,10 @@ export default function WelcomeDialog(): React.ReactElement
<fui.DialogActions>
<fui.DialogTrigger disableButtonEnhancement>
<fui.Button appearance="primary" as="a" { ...extLink(v3blogPost) }>
<fui.Button
appearance="primary" as="a" { ...extLink(v3blogPost) }
onClick={ () => track("visit_blog_button_click") }
>
{ i18n.t("features.v3welcome.actions.visit_blog") }
</fui.Button>
</fui.DialogTrigger>
+2 -1
View File
@@ -161,8 +161,8 @@ collections:
menu:
delete: "Delete collection"
add_selected: "Add selected tabs"
add_all: "Add all tabs"
add_group: "Add empty group"
add_pinned: "Add pinned group"
export_bookmarks: "Export to bookmarks"
edit: "Edit collection"
@@ -174,6 +174,7 @@ groups:
menu:
new_window: "Open in new window"
add_selected: "Add selected tabs"
add_all: "Add all tabs"
edit: "Edit group"
ungroup: "Ungroup"
delete: "Delete group"
+251
View File
@@ -0,0 +1,251 @@
manifest:
name: "Pestañas a un lado"
description: "Guarda y organiza tus pestañas para más tarde. Retoma donde lo dejaste"
author: "Eugene Fox"
shortcuts:
toggle_sidebar: "Abrir lista de colecciones"
set_aside: "Apartar pestañas"
save_tabs: "Guardar pestañas sin cerrar"
common:
actions:
cancel: "Cancelar"
save: "Guardar"
close: "Cerrar"
delete: "Eliminar"
reset_filters: "Restablecer filtros"
cta:
feedback: "Dejar comentarios"
sponsor: "Invítame un café"
tooltips:
more: "Más"
delete_prompt: "¿Estás seguro? Esta acción no se puede deshacer."
features:
v3welcome:
title: "Bienvenido a Pestañas a un lado 3.0"
text1: "¡Estamos felices de anunciar nuestra nueva actualización principal para la extensión Pestañas a un lado!"
text2: "Esta actualización trae una nueva interfaz de usuario y muchas características nuevas, incluyendo:"
list:
item1: "Soporte para grupos de pestañas"
item2: "Personalización de colecciones"
item3: "Reordenamiento y organización mediante arrastrar y soltar"
item4: "Creación manual de colecciones desde cero"
item5: "¡Y más!"
text3: "¡Visita nuestro blog de desarrollo para aprender más sobre esta actualización y todas sus características!"
actions:
visit_blog: "Leer el blog de desarrollo"
notifications:
tabs_saved:
title: "Nueva colección creada"
message: "Tus pestañas se han guardado en una nueva colección"
error_quota_exceeded:
title: "Se excedió el máximo de operaciones de escritura en la nube"
message: "Guardamos tus pestañas en el almacenamiento local. Tendrás que actualizar manualmente tu almacenamiento en la nube"
error_storage_full:
title: "Tu almacenamiento en la nube está lleno"
message: "Guardamos tus pestañas en el almacenamiento local. Por favor, libera espacio en tu almacenamiento en la nube"
bookmark_saved:
title: "Exportado a marcadores"
message: "Tu colección ha sido exportada a marcadores"
partial_save:
title: "Algunas pestañas no se pudieron guardar"
message: "Algunas de las pestañas eran pestañas del sistema a las que no pudimos acceder. Fueron omitidas"
actions:
save:
all: "Guardar todas las pestañas"
selected: "Guardar pestañas seleccionadas"
set_aside:
all: "Apartar todas las pestañas"
selected: "Apartar pestañas seleccionadas"
show_collections: "Mostrar colecciones"
options_page:
title: "Configuración"
general:
title: "General"
options:
always_show_toolbars: "Mostrar siempre las barras de herramientas"
include_pinned: "Incluir pestañas fijadas al guardar todas las pestañas"
show_delete_prompt: "Pedir confirmación al eliminar un elemento"
show_badge: "Mostrar insignia de contador"
show_notification: "Mostrar notificación al guardar pestañas usando el menú contextual"
unload_tabs: "No cargar pestañas después de abrir"
list_locations:
title: "Abrir lista de colecciones en:"
options:
sidebar: "Barra lateral"
popup: "Ventana emergente"
tab: "Pestaña separada"
pinned: "Pestaña separada fijada"
icon_action:
title: "Al hacer clic en el icono de la extensión:"
options:
action: "Realizar la acción de guardado predeterminada"
context: "Mostrar menú contextual"
open: "Abrir lista de colecciones"
change_shortcuts: "Cambiar atajos de la extensión"
actions:
title: "Acciones predeterminadas"
options:
save_actions:
title: "Acción predeterminada al guardar pestañas"
options:
set_aside: "Guardar y cerrar pestañas"
save: "Guardar pestañas sin cerrar"
restore_actions:
title: "Acción predeterminada al abrir colecciones"
options:
open: "Solo abrir las pestañas"
restore: "Abrir pestañas y eliminar la colección"
storage:
title: "Almacenamiento"
capacity:
title: "Capacidad de almacenamiento en la nube"
description: "$1 de $2 KiB"
import: "Importar datos"
export: "Exportar datos"
import_results:
success: "Datos importados con éxito"
error: "El archivo proporcionado parece estar corrupto. No se importó nada"
import_prompt:
title: "Importar datos"
warning_title: "Esta es una acción irreversible"
warning_text: "Esto sobrescribirá todos tus datos. Asegúrate de haber elegido el archivo correcto, de lo contrario, podría ocurrir corrupción o pérdida de datos. Se recomienda exportar los datos primero."
proceed: "Seleccionar un archivo"
enable: "Habilitar almacenamiento en la nube"
disable: "Deshabilitar almacenamiento en la nube"
disable_prompt:
text: "Esta acción deshabilitará la sincronización de colecciones entre tus dispositivos. La configuración de la extensión seguirá sincronizándose."
action: "Deshabilitar y recargar la extensión"
about:
title: "Acerca de"
developed_by: "Desarrollado por Eugene Fox"
licensed_under: "Licenciado bajo"
mit_license: "Licencia MIT"
translation_cta:
text: "¿Encontraste un error tipográfico o quieres una traducción para tu idioma?"
button: "Comienza aquí"
links:
website: "Mi sitio web"
source: "Código fuente"
changelog: "Registro de cambios"
collections:
empty: "Esta colección está vacía"
tabs_count: "$1 pestañas"
actions:
open: "Abrir todo"
restore: "Restaurar todo"
new_window: "Abrir todo en una ventana nueva"
incognito:
edge: "Abrir todo en ventana InPrivate"
firefox: "Abrir todo en una nueva ventana privada"
chrome: "Abrir todo en una ventana de incógnito"
incognito_check:
title: "Permisos requeridos"
message:
edge:
p1: "La extensión necesita permiso para abrir pestañas en ventana InPrivate"
p2: "Para hacerlo, haz clic en \"Configuración\" y luego selecciona la opción \"Permitir en InPrivate\""
firefox:
p1: "La extensión necesita permiso para abrir pestañas en una ventana privada"
p2: "Para hacerlo, haz clic en \"Configuración\", ve a \"Detalles\" y configura \"Ejecutar en ventana privada\" en \"Permitir\""
chrome:
p1: "La extensión necesita permiso para abrir pestañas en una ventana de incógnito"
p2: "Para hacerlo, haz clic en \"Configuración\" y luego selecciona la opción \"Permitir en incógnito\""
action: "Configuración"
menu:
delete: "Eliminar colección"
add_selected: "Agregar pestañas seleccionadas"
add_all: "Agregar todas las pestañas"
add_group: "Agregar grupo vacío"
export_bookmarks: "Exportar a marcadores"
edit: "Editar colección"
groups:
title: "Grupo"
pinned: "Fijado"
open: "Abrir todo"
empty: "Este grupo está vacío"
menu:
new_window: "Abrir en una nueva ventana"
add_selected: "Agregar pestañas seleccionadas"
add_all: "Agregar todas las pestañas"
edit: "Editar grupo"
ungroup: "Desagrupar"
delete: "Eliminar grupo"
tabs:
delete: "Eliminar pestaña"
colors:
none: "Sin color"
any: "Cualquier color"
grey: "Gris"
blue: "Azul"
red: "Rojo"
yellow: "Amarillo"
green: "Verde"
pink: "Rosa"
purple: "Morado"
cyan: "Cian"
orange: "Naranja"
dialogs:
edit:
title:
edit_collection: "Editar colección"
edit_group: "Editar grupo"
new_group: "Nuevo grupo"
new_collection: "Nueva colección"
collection_title: "Título"
color: "Color"
main:
header:
create_collection: "Crear nueva colección"
menu:
tiles_view: "Vista de mosaicos"
changelog: "¿Qué hay de nuevo?"
list:
searchbar:
title: "Buscar"
filter: "Filtrar"
sort:
title: "Ordenar"
options:
newest: "Más recientes primero"
oldest: "Más antiguos primero"
ascending: "De la A a la Z"
descending: "De la Z a la A"
custom: "Personalizado"
empty:
title: "Nada que mostrar aquí todavía"
message: "Aparta tus pestañas actuales o crea una nueva colección"
empty_search:
title: "No se encontró nada"
message: "Intenta cambiar tu consulta de búsqueda"
cta_message:
title: "¿Te gusta esta extensión?"
message: "Considera apoyar al autor con una donación o"
feedback: "dejando un comentario"
storage_full_message:
title: "Tu almacenamiento en la nube está casi lleno ($1%)"
message: "Puedes liberar espacio eliminando colecciones no utilizadas."
parse_error_message:
title: "No pudimos obtener colecciones de tu almacenamiento en la nube."
message: "Tu almacenamiento en la nube parece estar corrupto. Puedes solucionarlo reemplazándolo con tu copia local."
action: "Solucionar con copia local"
merge_conflict_message:
title: "Tus almacenamientos local y en la nube tienen cambios en conflicto."
message: "Para solucionarlo, puedes cargar tu copia local en la nube o aceptar los cambios de la nube."
accept_local: "Reemplazar con local"
accept_cloud: "Aceptar cambios de la nube"
+251
View File
@@ -0,0 +1,251 @@
manifest:
name: "Schede a parte"
description: "Salva e organizza le tue schede per dopo. Riprendi da dove avevi lasciato"
author: "Eugene Fox"
shortcuts:
toggle_sidebar: "Apri elenco delle collezioni"
set_aside: "Metti da parte le schede"
save_tabs: "Salva le schede senza chiuderle"
common:
actions:
cancel: "Annulla"
save: "Salva"
close: "Chiudi"
delete: "Elimina"
reset_filters: "Reimposta filtri"
cta:
feedback: "Lascia un feedback"
sponsor: "Offrimi un caffè"
tooltips:
more: "Altro"
delete_prompt: "Sei sicuro? Questa azione non può essere annullata."
features:
v3welcome:
title: "Benvenuto in Schede a parte 3.0"
text1: "Siamo felici di annunciare il nostro nuovo aggiornamento principale per l'estensione Schede a parte!"
text2: "Questo aggiornamento porta una nuova interfaccia utente e molte nuove funzionalità, tra cui:"
list:
item1: "Supporto per gruppi di schede"
item2: "Personalizzazione delle collezioni"
item3: "Riordino e organizzazione tramite drag and drop"
item4: "Creazione manuale di collezioni da zero"
item5: "E altro ancora!"
text3: "Visita il nostro blog per saperne di più su questo aggiornamento e tutte le sue funzionalità!"
actions:
visit_blog: "Leggi il blog degli sviluppatori"
notifications:
tabs_saved:
title: "Nuova collezione creata"
message: "Le tue schede sono state salvate in una nuova collezione"
error_quota_exceeded:
title: "Superato il limite massimo di operazioni di scrittura sul cloud"
message: "Abbiamo salvato le tue schede nella memoria locale. Dovrai aggiornare manualmente il tuo spazio cloud"
error_storage_full:
title: "Il tuo spazio cloud è pieno"
message: "Abbiamo salvato le tue schede nella memoria locale. Libera spazio nel tuo cloud storage"
bookmark_saved:
title: "Esportato nei segnalibri"
message: "La tua collezione è stata esportata nei segnalibri"
partial_save:
title: "Alcune schede non sono state salvate"
message: "Alcune schede erano schede di sistema a cui non potevamo accedere. Sono state saltate"
actions:
save:
all: "Salva tutte le schede"
selected: "Salva le schede selezionate"
set_aside:
all: "Metti da parte tutte le schede"
selected: "Metti da parte le schede selezionate"
show_collections: "Mostra collezioni"
options_page:
title: "Impostazioni"
general:
title: "Generale"
options:
always_show_toolbars: "Mostra sempre le barre degli strumenti"
include_pinned: "Includi schede bloccate quando salvi tutte le schede"
show_delete_prompt: "Chiedi conferma quando elimini un elemento"
show_badge: "Mostra il badge del contatore"
show_notification: "Mostra notifica quando salvi le schede usando il menu contestuale"
unload_tabs: "Non caricare le schede dopo l'apertura"
list_locations:
title: "Apri elenco delle collezioni in:"
options:
sidebar: "Barra laterale"
popup: "Popup"
tab: "Scheda separata"
pinned: "Scheda separata bloccata"
icon_action:
title: "Quando clicchi sull'icona dell'estensione:"
options:
action: "Esegui l'azione di salvataggio predefinita"
context: "Mostra menu contestuale"
open: "Apri elenco delle collezioni"
change_shortcuts: "Modifica le scorciatoie dell'estensione"
actions:
title: "Azioni predefinite"
options:
save_actions:
title: "Azione predefinita quando salvi le schede"
options:
set_aside: "Salva e chiudi le schede"
save: "Salva le schede senza chiuderle"
restore_actions:
title: "Azione predefinita quando apri le collezioni"
options:
open: "Apri solo le schede"
restore: "Apri le schede e rimuovi la collezione"
storage:
title: "Archiviazione"
capacity:
title: "Capacità di archiviazione cloud"
description: "$1 di $2 KiB"
import: "Importa dati"
export: "Esporta dati"
import_results:
success: "Dati importati con successo"
error: "Il file fornito sembra essere corrotto. Non è stato importato nulla"
import_prompt:
title: "Importa dati"
warning_title: "Questa è un'azione irreversibile"
warning_text: "Questo sovrascriverà tutti i tuoi dati. Assicurati di aver scelto il file corretto, altrimenti potrebbero verificarsi corruzioni o perdite di dati. Si consiglia di esportare prima i dati."
proceed: "Scegli un file"
enable: "Abilita archiviazione cloud"
disable: "Disabilita archiviazione cloud"
disable_prompt:
text: "Questa azione disabiliterà la sincronizzazione delle collezioni tra i tuoi dispositivi. Le impostazioni dell'estensione saranno comunque sincronizzate."
action: "Disabilita e ricarica l'estensione"
about:
title: "Informazioni"
developed_by: "Sviluppato da Eugene Fox"
licensed_under: "Concesso in licenza sotto"
mit_license: "Licenza MIT"
translation_cta:
text: "Hai trovato un errore di battitura o vuoi una traduzione per la tua lingua?"
button: "Inizia qui"
links:
website: "Il mio sito web"
source: "Codice sorgente"
changelog: "Registro delle modifiche"
collections:
empty: "Questa collezione è vuota"
tabs_count: "$1 schede"
actions:
open: "Apri tutto"
restore: "Ripristina tutto"
new_window: "Apri tutto in una nova finestra"
incognito:
edge: "Apri tutto in una nuova finestra InPrivate"
firefox: "Apri tutto in nuova finestra anonima"
chrome: "Apri tutto in finestra di navigazione in incognito"
incognito_check:
title: "Permessi richiesti"
message:
edge:
p1: "L'estensione necessita del permesso per aprire schede in finestra InPrivate"
p2: "Per farlo, clicca su \"Impostazioni\" e poi seleziona l'opzione \"Consenti in InPrivate\""
firefox:
p1: "L'estensione necessita del permesso per aprire schede in finestra anonima"
p2: "Per farlo, clicca su \"Impostazioni\", vai su \"Dettagli\" e imposta \"Funzionamento in finestre anonime\" su \"Consenti\""
chrome:
p1: "L'estensione necessita del permesso per aprire schede in finestra di navigazione in incognito"
p2: "Per farlo, clicca su \"Impostazioni\" e poi seleziona l'opzione \"Consenti modalità di navigazione in incognito\""
action: "Impostazioni"
menu:
delete: "Elimina collezione"
add_selected: "Aggiungi schede selezionate"
add_all: "Aggiungi tutte le schede"
add_group: "Aggiungi gruppo vuoto"
export_bookmarks: "Esporta nei segnalibri"
edit: "Modifica collezione"
groups:
title: "Gruppo"
pinned: "Bloccato"
open: "Apri tutto"
empty: "Questo gruppo è vuoto"
menu:
new_window: "Apri in una nuova finestra"
add_selected: "Aggiungi schede selezionate"
add_all: "Aggiungi tutte le schede"
edit: "Modifica gruppo"
ungroup: "Rimuovi dal gruppo"
delete: "Elimina gruppo"
tabs:
delete: "Elimina scheda"
colors:
none: "Nessun colore"
any: "Qualsiasi colore"
grey: "Grigio"
blue: "Blu"
red: "Rosso"
yellow: "Giallo"
green: "Verde"
pink: "Rosa"
purple: "Viola"
cyan: "Ciano"
orange: "Arancione"
dialogs:
edit:
title:
edit_collection: "Modifica collezione"
edit_group: "Modifica gruppo"
new_group: "Nuovo gruppo"
new_collection: "Nuova collezione"
collection_title: "Titolo"
color: "Colore"
main:
header:
create_collection: "Crea nuova collezione"
menu:
tiles_view: "Vista a riquadri"
changelog: "Cosa c'è di nuovo?"
list:
searchbar:
title: "Cerca"
filter: "Filtra"
sort:
title: "Ordina"
options:
newest: "Più recenti prima"
oldest: "Più vecchi prima"
ascending: "Dalla A alla Z"
descending: "Dalla Z alla A"
custom: "Personalizzato"
empty:
title: "Niente da mostrare qui per ora"
message: "Metti da parte le tue schede attuali o crea una nuova collezione"
empty_search:
title: "Non è stato trovato nulla"
message: "Prova a cambiare la tua query di ricerca"
cta_message:
title: "Ti piace questa estensione?"
message: "Considera di supportare l'autore con una donazione o"
feedback: "lasciando un feedback"
storage_full_message:
title: "Il tuo spazio cloud è quasi pieno ($1%)"
message: "Puoi liberare spazio eliminando collezioni inutilizzate."
parse_error_message:
title: "Non siamo riusciti a ottenere le collezioni dal tuo spazio cloud."
message: "Il tuo spazio cloud sembra essere corrotto. Puoi risolvere sostituendolo con la tua copia locale."
action: "Risolvere con copia locale"
merge_conflict_message:
title: "Le tue memorie locali e cloud hanno modifiche in conflitto."
message: "Per risolvere, puoi caricare la tua copia locale nel cloud o accettare le modifiche del cloud."
accept_local: "Sostituisci con locale"
accept_cloud: "Accetta modifiche del cloud"
+251
View File
@@ -0,0 +1,251 @@
manifest:
name: "Odłożone karty"
description: "Odkładaj i organizuj swoje karty. Kontynuuj tam, gdzie przerwałeś"
author: "Eugeniusz Lis"
shortcuts:
toggle_sidebar: "Otwórz listę kolekcji"
set_aside: "Odłóż karty"
save_tabs: "Zapisz karty"
common:
actions:
cancel: "Anuluj"
save: "Zapisz"
close: "Zamknij"
delete: "Usuń"
reset_filters: "Resetuj filtry"
cta:
feedback: "Zostaw opinię"
sponsor: "Wesprzyj"
tooltips:
more: "Więcej"
delete_prompt: "Czy jesteś pewien? Tej akcji nie można cofnąć."
features:
v3welcome:
title: "Witamy w Odłożonych kartach 3.0"
text1: "Z radością przedstawiamy nową dużą aktualizację rozszerzenia!"
text2: "Ta aktualizacja zawiera zupełnie nowy interfejs i wiele nowych funkcji, takich jak:"
list:
item1: "Obsługa grupowania kart"
item2: "Personalizacja kolekcji"
item3: "Przeciąganie kolekcji i elementów"
item4: "Tworzenie kolekcji od zera"
item5: "I wiele więcej!"
text3: "Odwiedź blog dewelopera (tylko w języku angielskim), aby dowiedzieć się więcej o tej aktualizacji i wszystkich jej funkcjach!"
actions:
visit_blog: "Czytaj blog"
notifications:
tabs_saved:
title: "Utworzono nową kolekcję"
message: "Twoje karty zostały zapisane w nowej kolekcji"
error_quota_exceeded:
title: "Przekroczono limit operacji zapisu w chmurze"
message: "Twoje karty zostały zapisane w lokalnym magazynie. Musisz ręcznie zaktualizować magazyn w chmurze"
error_storage_full:
title: "Magazyn w chmurze jest pełny"
message: "Twoje karty zostały zapisane w lokalnym magazynie. Proszę zwolnić miejsce w magazynie w chmurze"
bookmark_saved:
title: "Wyeksportowano do zakładek"
message: "Twoja kolekcja została wyeksportowana do zakładek"
partial_save:
title: "Niektóre karty nie zostały zapisane"
message: "Niektóre z kart są systemowe i nie mogą być zapisane"
actions:
save:
all: "Zapisz wszystkie karty"
selected: "Zapisz wybrane karty"
set_aside:
all: "Odłóż wszystkie karty"
selected: "Odłóż wybrane karty"
show_collections: "Pokaż listę kolekcji"
options_page:
title: "Ustawienia"
general:
title: "Ogólne"
options:
always_show_toolbars: "Zawsze pokazuj paski narzędzi"
include_pinned: "Zapisuj przypięte karty przy zapisywaniu wszystkich kart"
show_delete_prompt: "Pytaj o potwierdzenie przy usuwaniu elementów"
show_badge: "Pokaż licznik"
show_notification: "Pokaż powiadomienie przy zapisywaniu przez menu kontekstowe"
unload_tabs: "Nie ładuj kart po otwarciu"
list_locations:
title: "Otwieraj listę kolekcji w:"
options:
sidebar: "Panel boczny"
popup: "Okno popup"
tab: "Osobna karta"
pinned: "Przypięta karta"
icon_action:
title: "Po kliknięciu ikony rozszerzenia:"
options:
action: "Wykonaj domyślną akcję"
context: "Pokaż menu kontekstowe"
open: "Otwórz listę kolekcji"
change_shortcuts: "Zmień skróty klawiszowe"
actions:
title: "Akcje"
options:
save_actions:
title: "Domyślna akcja przy zapisywaniu kart"
options:
set_aside: "Zapisz i zamknij karty"
save: "Zapisz karty bez ich zamykania"
restore_actions:
title: "Domyślna akcja przy otwieraniu kolekcji"
options:
open: "Po prostu otwórz karty"
restore: "Otwórz karty i usuń kolekcję"
storage:
title: "Magazyn"
capacity:
title: "Magazyn w chmurze"
description: "$1 z $2 KiB"
import: "Importuj dane"
export: "Eksportuj dane"
import_results:
success: "Dane zostały pomyślnie zaimportowane"
error: "Wygląda na to, że wybrany plik jest uszkodzony. Nic nie zostało zaimportowane"
import_prompt:
title: "Import danych"
warning_title: "To jest nieodwracalna akcja!"
warning_text: "Zastąpi wszystkie twoje dane. Upewnij się, że wybrałeś właściwy plik, w przeciwnym razie może to prowadzić do uszkodzenia lub utraty danych. Zaleca się najpierw wyeksportować dane."
proceed: "Wybierz plik"
disable: "Wyłącz magazyn w chmurze"
enable: "Włącz magazyn w chmurze"
disable_prompt:
text: "Ta akcja wyłączy synchronizację kolekcji między twoimi urządzeniami. Ustawienia nadal będą przechowywane w chmurze."
action: "Wyłącz i przeładuj rozszerzenie"
about:
title: "O rozszerzeniu"
developed_by: "Wywoływacz: Eugeniusz Lis"
licensed_under: ""
mit_license: "Licencja MIT"
translation_cta:
text: "Znalazłeś błąd lub chcesz tłumaczenie na swój język?"
button: "Zacznij tutaj"
links:
website: "Moja strona internetowa"
source: "Kod źródłowy"
changelog: "Lista zmian"
collections:
empty: "Ta kolekcja jest pusta"
tabs_count: "Karty: $1"
actions:
open: "Otwórz wszystkie"
restore: "Przywróć wszystkie"
new_window: "Otwórz w nowym oknie"
incognito:
edge: "Otwórz w oknie InPrivate"
firefox: "Otwórz w nowym oknie w trybie prywatnym"
chrome: "Otwórz w oknie incognito"
incognito_check:
title: "Wymagane uprawnienie"
message:
edge:
p1: "Rozszerzenie wymaga dodatkowego uprawnienia, aby otworzyć karty w oknie InPrivate"
p2: "Aby to zrobić, kliknij \"Ustawienia\" i zaznacz opcję \"Zezwalaj w trybie InPrivate\""
firefox:
p1: "Rozszerzenie wymaga dodatkowego uprawnienia, aby otworzyć karty w trybie prywatnym"
p2: "Aby to zrobić, kliknij \"Ustawienia\", przejdź do \"Szczegóły\" i zezwól na \"Działanie w oknach prywatnych\""
chrome:
p1: "Rozszerzenie wymaga dodatkowego uprawnienia, aby otworzyć karty w oknie incognito"
p2: "Aby to zrobić, kliknij \"Ustawienia\" i zaznacz opcję \"Zezwalaj w trybie incognito\""
action: "Ustawienia"
menu:
delete: "Usuń kolekcję"
add_selected: "Dodaj wybrane karty"
add_all: "Dodaj wszystkie karty"
add_group: "Dodaj pustą grupę"
export_bookmarks: "Eksportuj do zakładek"
edit: "Edytuj kolekcję"
groups:
title: "Grupa"
pinned: "Przypięte"
open: "Otwórz"
empty: "Ta grupa jest pusta"
menu:
new_window: "Otwórz w nowym oknie"
add_selected: "Dodaj wybrane karty"
add_all: "Dodaj wszystkie karty"
edit: "Edytuj grupę"
ungroup: "Rozgrupuj"
delete: "Usuń grupę"
tabs:
delete: "Usuń zakładkę"
colors:
none: "Bez koloru"
any: "Dowolny kolor"
grey: "Szary"
blue: "Niebieski"
red: "Czerwony"
yellow: "Żółty"
green: "Zielony"
pink: "Różowy"
purple: "Purpurowy"
cyan: "Cyjan"
orange: "Pomarańczowy"
dialogs:
edit:
title:
edit_collection: "Edytuj kolekcję"
edit_group: "Edytuj grupę"
new_group: "Nowa grupa"
new_collection: "Nowa kolekcja"
collection_title: "Nazwij"
color: "Kolor"
main:
header:
create_collection: "Utwórz nową kolekcję"
menu:
tiles_view: "Kafelki"
changelog: "Co nowego?"
list:
searchbar:
title: "Szukaj"
filter: "Filtr"
sort:
title: "Sortowanie"
options:
newest: "Najpierw nowe"
oldest: "Najpierw stare"
ascending: "Od A do Z"
descending: "Od Z do A"
custom: "Niestandardowe"
empty:
title: "Na razie nic tu nie ma"
message: "Odłóż bieżące zakładki lub utwórz nową kolekcję"
empty_search:
title: "Nic nie znaleziono"
message: "Spróbuj zmienić zapytanie wyszukiwania"
cta_message:
title: "Podoba Ci się rozszerzenie?"
message: "Wesprzyj autora darowizną lub"
feedback: "zostaw opinię"
storage_full_message:
title: "Magazyn w chmurze prawie pełny ($1%)"
message: "Możesz zwolnić miejsce, usuwając nieużywane kolekcje."
parse_error_message:
title: "Nie udało się pobrać kolekcji z magazynu w chmurze."
message: "Wygląda na to, że magazyn w chmurze jest uszkodzony. Aby to naprawić, możesz zastąpić go lokalną kopią."
action: "Użyj lokalnej kopii"
merge_conflict_message:
title: "W lokalnym i chmurowym magazynie są konfliktujące zmiany."
message: "Aby to naprawić, możesz zapisać lokalną kopię w chmurze lub zaakceptować zmiany z chmury."
accept_local: "Zastąp lokalną"
accept_cloud: "Zaakceptuj zmiany z chmury"
+251
View File
@@ -0,0 +1,251 @@
manifest:
name: "Tabs aside"
description: "Salve e organize suas abas para depois. Continue de onde parou"
author: "Eugene Fox"
shortcuts:
toggle_sidebar: "Abrir lista de coleções"
set_aside: "Colocar abas de lado"
save_tabs: "Salvar abas sem fechar"
common:
actions:
cancel: "Cancelar"
save: "Salvar"
close: "Fechar"
delete: "Excluir"
reset_filters: "Limpar filtros"
cta:
feedback: "Deixar feedback"
sponsor: "Me pague um café"
tooltips:
more: "Mais"
delete_prompt: "Tem certeza? Esta ação não pode ser desfeita."
features:
v3welcome:
title: "Bem-vindo ao Tabs aside 3.0"
text1: "Estamos felizes em anunciar nossa nova grande atualização para a extensão Tabs aside!"
text2: "Esta atualização traz uma nova interface e muitos novos recursos, incluindo:"
list:
item1: "Suporte a grupos de abas"
item2: "Personalização de coleções"
item3: "Reordenação e organização por arrastar e soltar"
item4: "Criação manual de coleções do zero"
item5: "E mais!"
text3: "Visite nosso blog de desenvolvimento para saber mais sobre esta atualização e todos os seus recursos!"
actions:
visit_blog: "Ler blog de desenvolvimento"
notifications:
tabs_saved:
title: "Nova coleção criada"
message: "Suas abas foram salvas em uma nova coleção"
error_quota_exceeded:
title: "Limite máximo de operações na nuvem excedido"
message: "Salvamos suas abas no armazenamento local. Você precisará atualizar o armazenamento na nuvem manualmente"
error_storage_full:
title: "Seu armazenamento na nuvem está cheio"
message: "Salvamos suas abas no armazenamento local. Por favor, libere espaço na nuvem"
bookmark_saved:
title: "Exportado para favoritos"
message: "Sua coleção foi exportada para os favoritos"
partial_save:
title: "Algumas abas não puderam ser salvas"
message: "Algumas abas eram abas do sistema que não pudemos acessar. Elas foram ignoradas"
actions:
save:
all: "Salvar todas as abas"
selected: "Salvar abas selecionadas"
set_aside:
all: "Colocar todas as abas de lado"
selected: "Colocar abas selecionadas de lado"
show_collections: "Mostrar coleções"
options_page:
title: "Configurações"
general:
title: "Geral"
options:
always_show_toolbars: "Sempre mostrar barras de ferramentas"
include_pinned: "Incluir abas fixadas ao salvar todas as abas"
show_delete_prompt: "Pedir confirmação ao excluir um item"
show_badge: "Mostrar contador no ícone"
show_notification: "Mostrar notificação ao salvar abas pelo menu de contexto"
unload_tabs: "Não carregar abas após abrir"
list_locations:
title: "Abrir lista de coleções em:"
options:
sidebar: "Barra lateral"
popup: "Popup"
tab: "Aba separada"
pinned: "Aba fixada separada"
icon_action:
title: "Ao clicar no ícone da extensão:"
options:
action: "Executar ação padrão de salvar"
context: "Mostrar menu de contexto"
open: "Abrir lista de coleções"
change_shortcuts: "Alterar atalhos da extensão"
actions:
title: "Ações padrão"
options:
save_actions:
title: "Ação padrão ao salvar abas"
options:
set_aside: "Salvar e fechar abas"
save: "Salvar abas sem fechar"
restore_actions:
title: "Ação padrão ao abrir coleções"
options:
open: "Apenas abrir abas"
restore: "Abrir abas e remover a coleção"
storage:
title: "Armazenamento"
capacity:
title: "Capacidade de armazenamento na nuvem"
description: "$1 de $2 KiB"
import: "Importar dados"
export: "Exportar dados"
import_results:
success: "Dados importados com sucesso"
error: "O arquivo fornecido parece estar corrompido. Nada foi importado"
import_prompt:
title: "Importar dados"
warning_title: "Esta é uma ação irreversível"
warning_text: "Isso irá sobrescrever todos os seus dados. Certifique-se de ter escolhido o arquivo correto, caso contrário pode ocorrer corrupção ou perda de dados. Recomenda-se exportar os dados antes."
proceed: "Escolher um arquivo"
enable: "Ativar armazenamento na nuvem"
disable: "Desativar armazenamento na nuvem"
disable_prompt:
text: "Esta ação desativará a sincronização de coleções entre seus dispositivos. As configurações da extensão ainda serão sincronizadas."
action: "Desativar e recarregar a extensão"
about:
title: "Sobre"
developed_by: "Desenvolvido por Eugene Fox"
licensed_under: "Licenciado sob"
mit_license: "Licença MIT"
translation_cta:
text: "Encontrou um erro ou quer uma tradução para seu idioma?"
button: "Comece aqui"
links:
website: "Meu site"
source: "Código-fonte"
changelog: "Registro de alterações"
collections:
empty: "Esta coleção está vazia"
tabs_count: "$1 abas"
actions:
open: "Abrir todas"
restore: "Restaurar todas"
new_window: "Abrir todas em nova janela"
incognito:
edge: "Abrir todas em nova janela InPrivate"
firefox: "Abrir todas em nova janela privativa"
chrome: "Abrir todas em janela anônima"
incognito_check:
title: "Permissões necessárias"
message:
edge:
p1: "A extensão precisa de permissão para abrir abas em janela InPrivate"
p2: "Para isso, clique em \"Configurações\" e marque a opção \"Permitir em InPrivate\""
firefox:
p1: "A extensão precisa de permissão para abrir abas em janela privativa"
p2: "Para isso, clique em \"Configurações\", vá em \"Detalhes\" e defina \"Executar em janelas privadas\" como \"Permitir\""
chrome:
p1: "A extensão precisa de permissão para abrir abas em janela anônima"
p2: "Para isso, clique em \"Configurações\" e marque a opção \"Permitir em modo anônimo\""
action: "Configurações"
menu:
delete: "Excluir coleção"
add_selected: "Adicionar abas selecionadas"
add_all: "Adicionar todas as abas"
add_group: "Adicionar grupo vazio"
export_bookmarks: "Exportar para favoritos"
edit: "Editar coleção"
groups:
title: "Grupo"
pinned: "Fixado"
open: "Abrir todas"
empty: "Este grupo está vazio"
menu:
new_window: "Abrir em nova janela"
add_selected: "Adicionar abas selecionadas"
add_all: "Adicionar todas as abas"
edit: "Editar grupo"
ungroup: "Desagrupar"
delete: "Excluir grupo"
tabs:
delete: "Excluir aba"
colors:
none: "Sem cor"
any: "Qualquer cor"
grey: "Cinza"
blue: "Azul"
red: "Vermelho"
yellow: "Amarelo"
green: "Verde"
pink: "Rosa"
purple: "Roxo"
cyan: "Ciano"
orange: "Laranja"
dialogs:
edit:
title:
edit_collection: "Editar coleção"
edit_group: "Editar grupo"
new_group: "Novo grupo"
new_collection: "Nova coleção"
collection_title: "Título"
color: "Cor"
main:
header:
create_collection: "Criar nova coleção"
menu:
tiles_view: "Visualização em blocos"
changelog: "O que há de novo?"
list:
searchbar:
title: "Pesquisar"
filter: "Filtrar"
sort:
title: "Ordenar"
options:
newest: "Mais recentes primeiro"
oldest: "Mais antigas primeiro"
ascending: "De A a Z"
descending: "De Z a A"
custom: "Personalizado"
empty:
title: "Nada para mostrar aqui ainda"
message: "Coloque suas abas atuais de lado ou crie uma nova coleção"
empty_search:
title: "Nada encontrado"
message: "Tente alterar sua busca"
cta_message:
title: "Gostou desta extensão?"
message: "Considere apoiar o autor com uma doação ou"
feedback: "deixando um feedback"
storage_full_message:
title: "Seu armazenamento na nuvem está quase cheio ($1%)"
message: "Você pode liberar espaço excluindo coleções não utilizadas."
parse_error_message:
title: "Não foi possível obter coleções do seu armazenamento na nuvem."
message: "Seu armazenamento na nuvem parece estar corrompido. Você pode corrigir isso substituindo pela sua cópia local."
action: "Corrigir com cópia local"
merge_conflict_message:
title: "Seu armazenamento local e na nuvem possuem alterações conflitantes."
message: "Para corrigir, você pode enviar sua cópia local para a nuvem ou aceitar as alterações da nuvem."
accept_local: "Substituir pela local"
accept_cloud: "Aceitar alterações da nuvem"
+2 -1
View File
@@ -161,8 +161,8 @@ collections:
menu:
delete: "Удалить коллекцию"
add_selected: "Добавить выбранные вкладки"
add_all: "Добавить все вкладки"
add_group: "Добавить пустую группу"
add_pinned: "Добавить закрепленную группу"
export_bookmarks: "Экспортировать в закладки"
edit: "Редактировать коллекцию"
@@ -174,6 +174,7 @@ groups:
menu:
new_window: "Открыть в новом окне"
add_selected: "Добавить выбранные вкладки"
add_all: "Добавить все вкладки"
edit: "Редактировать группу"
ungroup: "Разгруппировать"
delete: "Удалить группу"
+5 -4
View File
@@ -161,8 +161,8 @@ collections:
menu:
delete: "Видалити колекцію"
add_selected: "Додати вибрані вкладки"
add_all: "Додати всі вкладки"
add_group: "Додати порожню групу"
add_pinned: "Додати закріплену групу"
export_bookmarks: "Експортувати в закладки"
edit: "Редагувати колекцію"
@@ -174,6 +174,7 @@ groups:
menu:
new_window: "Відкрити у новому вікні"
add_selected: "Додати вибрані вкладки"
add_all: "Додати всі вкладки"
edit: "Редагувати групу"
ungroup: "Розгрупувати"
delete: "Видалити групу"
@@ -190,9 +191,9 @@ colors:
yellow: "Жовтий"
green: "Зелений"
pink: "Рожевий"
purple: "Фіолетовий"
cyan: "Голубий"
orange: "Помаранчевий"
purple: "Пурпуровий"
cyan: "Бірюзовий"
orange: "Оранжевий"
dialogs:
edit:
+251
View File
@@ -0,0 +1,251 @@
manifest:
name: "搁置的标签页"
description: "保存并组织您的标签以备后用。从您离开的地方继续"
author: "尤金·福克斯"
shortcuts:
toggle_sidebar: "打开收藏列表"
set_aside: "将标签放到一边"
save_tabs: "保存标签而不关闭"
common:
actions:
cancel: "取消"
save: "保存"
close: "关闭"
delete: "删除"
reset_filters: "重置筛选器"
cta:
feedback: "留下反馈"
sponsor: "请我喝咖啡"
tooltips:
more: "更多"
delete_prompt: "您确定吗?此操作无法撤销。"
features:
v3welcome:
title: "欢迎使用搁置的标签页 3.0"
text1: "我们很高兴宣布搁置的标签页扩展的新重大更新!"
text2: "此更新带来了全新的用户界面,以及许多新功能,包括:"
list:
item1: "支持标签组"
item2: "收藏自定义"
item3: "拖放重新排序和组织"
item4: "从头开始手动创建收藏"
item5: "以及更多!"
text3: "访问我们的开发博客以了解有关此更新及其所有功能的更多信息!"
actions:
visit_blog: "阅读开发博客"
notifications:
tabs_saved:
title: "新收藏已创建"
message: "您的标签已保存到新收藏中"
error_quota_exceeded:
title: "超出最大云写入操作"
message: "我们已将您的标签保存到本地存储。您需要手动更新云存储"
error_storage_full:
title: "您的云存储已满"
message: "我们已将您的标签保存到本地存储。请清理一些云存储空间"
bookmark_saved:
title: "已导出到书签"
message: "您的收藏已导出到书签"
partial_save:
title: "某些标签无法保存"
message: "某些标签是我们无法访问的系统标签。它们已被跳过"
actions:
save:
all: "保存所有标签"
selected: "保存选定的标签"
set_aside:
all: "将所有标签放到一边"
selected: "将选定的标签放到一边"
show_collections: "显示收藏"
options_page:
title: "设置"
general:
title: "常规"
options:
always_show_toolbars: "始终显示工具栏"
include_pinned: "保存所有标签时包括固定标签"
show_delete_prompt: "删除项目时要求确认"
show_badge: "显示计数徽章"
show_notification: "使用上下文菜单保存标签时显示通知"
unload_tabs: "打开后不加载标签"
list_locations:
title: "在以下位置打开收藏列表:"
options:
sidebar: "侧边栏"
popup: "弹出窗口"
tab: "单独的标签页"
pinned: "单独的固定标签页"
icon_action:
title: "单击扩展图标时:"
options:
action: "执行默认保存操作"
context: "显示上下文菜单"
open: "打开收藏列表"
change_shortcuts: "更改扩展快捷方式"
actions:
title: "默认操作"
options:
save_actions:
title: "保存标签时的默认操作"
options:
set_aside: "保存并关闭标签"
save: "保存标签而不关闭"
restore_actions:
title: "打开收藏时的默认操作"
options:
open: "仅打开标签"
restore: "打开标签并删除收藏"
storage:
title: "存储"
capacity:
title: "云存储容量"
description: "$1 / $2 KiB"
import: "导入数据"
export: "导出数据"
import_results:
success: "数据已成功导入"
error: "提供的文件似乎已损坏。未导入任何内容"
import_prompt:
title: "导入数据"
warning_title: "这是不可逆的操作"
warning_text: "这将覆盖您的所有数据。请确保选择了正确的文件,否则可能会导致数据损坏或丢失。建议先导出数据。"
proceed: "选择文件"
enable: "启用云存储"
disable: "禁用云存储"
disable_prompt:
text: "此操作将禁用设备之间的收藏同步。扩展设置仍将同步。"
action: "禁用并重新加载扩展"
about:
title: "关于"
developed_by: "由尤金·福克斯开发"
licensed_under: "许可协议"
mit_license: "MIT 许可协议"
translation_cta:
text: "发现错别字或想为您的语言提供翻译?"
button: "从这里开始"
links:
website: "我的网站"
source: "源代码"
changelog: "更新日志"
collections:
empty: "此收藏为空"
tabs_count: "$1 个标签"
actions:
open: "打开所有"
restore: "恢复所有"
new_window: "在新窗口中打开所有"
incognito:
edge: "在新 InPrivate 窗口中打开所有"
firefox: "在新隐私窗口中打开所有"
chrome: "在隐身窗口中打开所有"
incognito_check:
title: "需要权限"
message:
edge:
p1: "扩展需要权限才能在 InPrivate 窗口中打开标签"
p2: "为此,请单击“设置”,然后勾选“允许在 InPrivate 中”选项"
firefox:
p1: "扩展需要权限才能在隐私窗口中打开标签"
p2: "为此,请单击“设置”,转到“详细信息”并将“在隐私窗口中运行”设置为“允许”"
chrome:
p1: "扩展需要权限才能在隐身窗口中打开标签"
p2: "为此,请单击“设置”,然后勾选“允许在隐身中”选项"
action: "设置"
menu:
delete: "删除收藏"
add_selected: "添加选定的标签"
add_all: "添加所有标签"
add_group: "添加空组"
export_bookmarks: "导出到书签"
edit: "编辑收藏"
groups:
title: "组"
pinned: "已固定"
open: "打开所有"
empty: "此组为空"
menu:
new_window: "在新窗口中打开"
add_selected: "添加选定的标签"
add_all: "添加所有标签"
edit: "编辑组"
ungroup: "取消分组"
delete: "删除组"
tabs:
delete: "删除标签"
colors:
none: "无颜色"
any: "任何颜色"
grey: "灰色"
blue: "蓝色"
red: "红色"
yellow: "黄色"
green: "绿色"
pink: "粉色"
purple: "紫色"
cyan: "青色"
orange: "橙色"
dialogs:
edit:
title:
edit_collection: "编辑收藏"
edit_group: "编辑组"
new_group: "新组"
new_collection: "新收藏"
collection_title: "标题"
color: "颜色"
main:
header:
create_collection: "创建新收藏"
menu:
tiles_view: "平铺视图"
changelog: "更新内容?"
list:
searchbar:
title: "搜索"
filter: "筛选"
sort:
title: "排序"
options:
newest: "最新优先"
oldest: "最旧优先"
ascending: "从 A 到 Z"
descending: "从 Z 到 A"
custom: "自定义"
empty:
title: "这里还没有内容"
message: "将当前标签放到一边,或创建新收藏"
empty_search:
title: "未找到任何内容"
message: "尝试更改搜索查询"
cta_message:
title: "喜欢这个扩展吗?"
message: "考虑支持作者捐赠,或"
feedback: "留下反馈"
storage_full_message:
title: "您的云存储几乎已满($1%"
message: "您可以通过删除未使用的收藏来释放一些空间。"
parse_error_message:
title: "我们无法从您的云存储中获取收藏。"
message: "您的云存储似乎已损坏。您可以通过用本地副本替换它来修复它。"
action: "用本地副本修复"
merge_conflict_message:
title: "您的本地和云存储有冲突的更改。"
message: "要解决此问题,您可以将本地副本上传到云端,或接受云端更改。"
accept_local: "用本地替换"
accept_cloud: "接受云端更改"
+2 -1
View File
@@ -1,7 +1,7 @@
{
"name": "tabs-aside",
"private": true,
"version": "3.0.0-rc1",
"version": "3.0.0-rc4",
"type": "module",
"scripts": {
"dev": "wxt",
@@ -20,6 +20,7 @@
"@fluentui/react-components": "^9.63.0",
"@fluentui/react-icons": "^2.0.298",
"@webext-core/messaging": "^2.2.0",
"@wxt-dev/analytics": "^0.4.1",
"@wxt-dev/i18n": "^0.2.3",
"lzutf8": "^0.6.3",
"react": "^18.3.1",
+48
View File
@@ -0,0 +1,48 @@
<!-- Versión en texto plano (Chrome/Edge) -->
Basada en la funcionalidad original del navegador Microsoft Edge, esta extensión ha crecido mucho más allá de ser solo un almacenamiento temporal para pestañas.
Te permite guardar y gestionar tus pestañas de manera conveniente, proporcionando una gama de características que facilitan organizar y acceder a tus pestañas guardadas.
## Características
- Guardar pestañas: Guarda todas tus pestañas abiertas con un solo clic y restáuralas más tarde
- Organizar pestañas: Crea colecciones y subgrupos para organizar tus pestañas guardadas
- Buscar pestañas: Encuentra rápidamente las pestañas que necesitas usando la función de búsqueda
- Sincronizar entre dispositivos: Accede a tus pestañas guardadas desde cualquier dispositivo con tu cuenta
- Modo oscuro: Soporte para modo oscuro para una experiencia de navegación más cómoda
- Personalizar: Cambia la apariencia y el comportamiento de la extensión para adaptarla a tus necesidades
Consulta nuestra publicación en el blog sobre todas las nuevas características y mejoras en Pestañas a un lado 3.0 en:
https://at.xfox111.net/tabs-aside-3-0
## ¡Oye, es un software de código abierto!
Si sabes cómo mejorar esta extensión, puedes revisar su repositorio de GitHub en:
https://github.com/xfox111/TabsAsideExtension
Consulta el registro de cambios en:
https://github.com/xfox111/TabsAsideExtension/releases/latest
<!-- Versión en texto enriquecido (Firefox) -->
Basada en la funcionalidad original del navegador Microsoft Edge, esta extensión ha crecido mucho más allá de ser solo un almacenamiento temporal para pestañas.
Te permite guardar y gestionar tus pestañas de manera conveniente, proporcionando una gama de características que facilitan organizar y acceder a tus pestañas guardadas.
<b>Características</b>
<ul>
<li><b>Guardar pestañas</b>: Guarda todas tus pestañas abiertas con un solo clic y restáuralas más tarde</li>
<li><b>Organizar pestañas</b>: Crea colecciones y subgrupos para organizar tus pestañas guardadas</li>
<li><b>Buscar pestañas</b>: Encuentra rápidamente las pestañas que necesitas usando la función de búsqueda</li>
<li><b>Sincronizar entre dispositivos</b>: Accede a tus pestañas guardadas desde cualquier dispositivo con tu cuenta</li>
<li><b>Modo oscuro</b>: Soporte para modo oscuro para una experiencia de navegación más cómoda</li>
<li><b>Personalizar</b>: Cambia la apariencia y el comportamiento de la extensión para adaptarla a tus necesidades</li>
</ul>
Consulta nuestra <a href="https://at.xfox111.net/tabs-aside-3-0">publicación en el blog</a> sobre todas las nuevas características y mejoras en Pestañas a un lado 3.0
<b>¡Oye, es un software de código abierto!</b>
Si sabes cómo mejorar esta extensión, puedes revisar <a href="https://github.com/xfox111/TabsAsideExtension">su repositorio de GitHub</a>
Consulta el <a href="https://github.com/xfox111/TabsAsideExtension/releases/latest">registro de cambios</a>
+48
View File
@@ -0,0 +1,48 @@
<!-- Versione in testo semplice (Chrome/Edge) -->
Radicata nella funzionalità originale del browser Microsoft Edge, questa estensione è cresciuta molto più di un semplice spazio di archiviazione temporaneo per le schede.
Ti consente di salvare e gestire le tue schede in modo conveniente, fornendo una gamma di funzionalità che rendono facile organizzare e accedere alle tue schede salvate.
## Funzionalità
- Salva schede: Salva tutte le tue schede aperte con un solo clic e ripristinale in seguito
- Organizza schede: Crea collezioni e sottogruppi per organizzare le tue schede salvate
- Cerca schede: Trova rapidamente le schede di cui hai bisogno utilizzando la funzione di ricerca
- Sincronizza tra dispositivi: Accedi alle tue schede salvate da qualsiasi dispositivo con il tuo account
- Modalità scura: Supporto per la modalità scura per un'esperienza di navigazione più confortevole
- Personalizza: Cambia l'aspetto e il comportamento dell'estensione per soddisfare le tue esigenze
Dai un'occhiata al nostro post sul blog riguardante tutte le nuove funzionalità e miglioramenti in Schede a parte 3.0 su:
https://at.xfox111.net/tabs-aside-3-0
## Ehi, è un software open-source!
Se sai come migliorare questa estensione, puoi controllare il suo repository GitHub su:
https://github.com/xfox111/TabsAsideExtension
Consulta il registro delle modifiche alla versione su:
https://github.com/xfox111/TabsAsideExtension/releases/latest
<!-- Versione in testo ricco (Firefox) -->
Radicata nella funzionalità originale del browser Microsoft Edge, questa estensione è cresciuta molto più di un semplice spazio di archiviazione temporaneo per le schede.
Ti consente di salvare e gestire le tue schede in modo conveniente, fornendo una gamma di funzionalità che rendono facile organizzare e accedere alle tue schede salvate.
<b>Funzionalità</b>
<ul>
<li><b>Salva schede</b>: Salva tutte le tue schede aperte con un solo clic e ripristinale in seguito</li>
<li><b>Organizza schede</b>: Crea collezioni e sottogruppi per organizzare le tue schede salvate</li>
<li><b>Cerca schede</b>: Trova rapidamente le schede di cui hai bisogno utilizzando la funzione di ricerca</li>
<li><b>Sincronizza tra dispositivi</b>: Accedi alle tue schede salvate da qualsiasi dispositivo con il tuo account</li>
<li><b>Modalità scura</b>: Supporto per la modalità scura per un'esperienza di navigazione più confortevole</li>
<li><b>Personalizza</b>: Cambia l'aspetto e il comportamento dell'estensione per soddisfare le tue esigenze</li>
</ul>
Dai un'occhiata al nostro <a href="https://at.xfox111.net/tabs-aside-3-0">post sul blog</a> riguardante tutte le nuove funzionalità e miglioramenti in Schede a parte 3.0
<b>Ehi, è un software open-source!</b>
Se sai come migliorare questa estensione, puoi controllare <a href="https://github.com/xfox111/TabsAsideExtension">il suo repository GitHub</a>
Consulta il <a href="https://github.com/xfox111/TabsAsideExtension/releases/latest">registro delle modifiche</a>
+48
View File
@@ -0,0 +1,48 @@
<!-- Plaintext version (Chrome/Edge) -->
Zainspirowane funkcją z pierwszych wersji Microsoft Edge, to rozszerzenie stało się czymś więcej niż tylko tymczasowym magazynem kart.
Pozwala wygodnie zapisywać i zarządzać kartami, oferując wiele funkcji, które ułatwiają organizację i dostęp do zapisanych kart.
## Funkcje
- Zapisywanie kart: Zapisz wszystkie otwarte karty jednym kliknięciem i przywróć je później
- Organizacja kart: Twórz kolekcje i podgrupy, aby organizować zapisane karty
- Wyszukiwanie kart: Szybko znajdź potrzebne karty za pomocą funkcji wyszukiwania
- Synchronizacja między urządzeniami: Dostęp do zapisanych kart z dowolnego urządzenia za pomocą swojego konta
- Tryb ciemny: Obsługa trybu ciemnego dla bardziej komfortowego użytkowania
- Personalizacja: Dostosuj wygląd i działanie rozszerzenia do swoich potrzeb
Odwiedź naszego bloga, aby dowiedzieć się więcej o wszystkich nowych funkcjach i ulepszeniach w Odłożonych kartach 3.0 pod adresem:
https://at.xfox111.net/tabs-aside-3-0
## Przy okazji, to rozszerzenie open-source!
Jeśli wiesz, jak ulepszyć to rozszerzenie, możesz odwiedzić jego repozytorium na GitHubie:
https://github.com/xfox111/TabsAsideExtension
Lista zmian w najnowszej wersji:
https://github.com/xfox111/TabsAsideExtension/releases/latest
<!-- Rich text version (Firefox) -->
Zainspirowane funkcją z pierwszych wersji Microsoft Edge, to rozszerzenie stało się czymś więcej niż tylko tymczasowym magazynem kart.
Pozwala wygodnie zapisywać i zarządzać kartami, oferując wiele funkcji, które ułatwiają organizację i dostęp do zapisanych kart.
<b>Funkcje</b>
<ul>
<li><b>Zapisywanie kart</b>: Zapisz wszystkie otwarte karty jednym kliknięciem i przywróć je później</li>
<li><b>Organizacja kart</b>: Twórz kolekcje i podgrupy, aby organizować zapisane karty</li>
<li><b>Wyszukiwanie kart</b>: Szybko znajdź potrzebne karty za pomocą funkcji wyszukiwania</li>
<li><b>Synchronizacja między urządzeniami</b>: Dostęp do zapisanych kart z dowolnego urządzenia za pomocą swojego konta</li>
<li><b>Tryb ciemny</b>: Obsługa trybu ciemnego dla bardziej komfortowego użytkowania</li>
<li><b>Personalizacja</b>: Dostosuj wygląd i działanie rozszerzenia do swoich potrzeb</li>
</ul>
Odwiedź <a href="https://at.xfox111.net/tabs-aside-3-0">naszego bloga</a>, aby dowiedzieć się więcej o wszystkich nowych funkcjach i ulepszeniach w Odłożonych kartach 3.0
<b>Przy okazji, to rozszerzenie open-source!</b>
Jeśli wiesz, jak ulepszyć to rozszerzenie, możesz odwiedzić <a href="https://github.com/xfox111/TabsAsideExtension">jego repozytorium na GitHubie</a>
<a href="https://github.com/xfox111/TabsAsideExtension/releases/latest">Lista zmian w najnowszej wersji</a>
+49
View File
@@ -0,0 +1,49 @@
<!-- Versão em texto simples (Chrome/Edge) -->
Originando-se do recurso original do navegador Microsoft Edge, esta extensão cresceu muito além de apenas um armazenamento temporário para abas.
Ela permite que você salve e gerencie suas abas de forma conveniente, oferecendo uma variedade de recursos que facilitam a organização e o acesso às abas salvas.
## Recursos
- Salvar abas: Salve todas as suas abas abertas com um único clique e restaure-as depois
- Organizar abas: Crie coleções e subgrupos para organizar suas abas salvas
- Pesquisar abas: Encontre rapidamente as abas que você precisa usando o recurso de pesquisa
- Sincronizar entre dispositivos: Acesse suas abas salvas de qualquer dispositivo com sua conta
- Modo escuro: Suporte ao modo escuro para uma experiência de navegação mais confortável
- Personalizar: Altere a aparência e o comportamento da extensão conforme suas necessidades
Confira nossa postagem no blog sobre todos os novos recursos e melhorias do Tabs Aside 3.0 em:
https://at.xfox111.net/tabs-aside-3-0
## Ei, é um software de código aberto!
Se você sabe como melhorar esta extensão, confira seu repositório no GitHub em:
https://github.com/xfox111/TabsAsideExtension
Veja o changelog das versões em:
https://github.com/xfox111/TabsAsideExtension/releases/latest
<!-- Versão rich text (Firefox) -->
Originando-se do recurso original do navegador Microsoft Edge, esta extensão cresceu muito além de apenas um armazenamento temporário para abas.
Ela permite que você salve e gerencie suas abas de forma conveniente, oferecendo uma variedade de recursos que facilitam a organização e o acesso às abas salvas.
<b>Recursos</b>
<ul>
<li><b>Salvar abas</b>: Salve todas as suas abas abertas com um único clique e restaure-as depois</li>
<li><b>Organizar abas</b>: Crie coleções e subgrupos para organizar suas abas salvas</li>
<li><b>Pesquisar abas</b>: Encontre rapidamente as abas que você precisa usando o recurso de pesquisa</li>
<li><b>Sincronizar entre dispositivos</b>: Acesse suas abas salvas de qualquer dispositivo com sua conta</li>
<li><b>Modo escuro</b>: Suporte ao modo escuro para uma experiência de navegação mais confortável</li>
<li><b>Personalizar</b>: Altere a aparência e o comportamento da extensão conforme suas necessidades</li>
</ul>
Confira nossa <a href="https://at.xfox111.net/tabs-aside-3-0">postagem no blog</a> sobre todos os novos recursos e melhorias do Tabs Aside 3.0
<b>Ei, é um software de código aberto!</b>
Se você sabe como melhorar esta extensão, confira <a href="https://github.com/xfox111/TabsAsideExtension">seu repositório no GitHub</a>
Veja o <a href="https://github.com/xfox111/TabsAsideExtension/releases/latest">changelog das versões</a>
+48
View File
@@ -0,0 +1,48 @@
<!-- Plaintext version (Chrome/Edge) -->
源自原始 Microsoft Edge 浏览器功能,此扩展已发展为远不止是标签的临时存储。
它允许您以方便的方式保存和管理标签,提供一系列功能,使您可以轻松组织和访问已保存的标签。
## 功能
- 保存标签:一键保存所有打开的标签,并稍后恢复
- 组织标签:创建收藏和子组以组织已保存的标签
- 搜索标签:使用搜索功能快速找到所需的标签
- 跨设备同步:使用您的帐户从任何设备访问已保存的标签
- 深色模式:支持深色模式,提供更舒适的浏览体验
- 个性化:更改扩展的外观和行为以满足您的需求
查看我们关于搁置的标签页 3.0 的所有新功能和改进的博客文章:
https://at.xfox111.net/tabs-aside-3-0
## 嘿,这是一个开源软件!
如果您知道如何改进此扩展,可以查看其 GitHub 仓库:
https://github.com/xfox111/TabsAsideExtension
查看发布更新日志:
https://github.com/xfox111/TabsAsideExtension/releases/latest
<!-- Rich text version (Firefox) -->
源自原始 Microsoft Edge 浏览器功能,此扩展已发展为远不止是标签的临时存储。
它允许您以方便的方式保存和管理标签,提供一系列功能,使您可以轻松组织和访问已保存的标签。
<b>功能</b>
<ul>
<li><b>保存标签</b>:一键保存所有打开的标签,并稍后恢复</li>
<li><b>组织标签</b>:创建收藏和子组以组织已保存的标签</li>
<li><b>搜索标签</b>:使用搜索功能快速找到所需的标签</li>
<li><b>跨设备同步</b>:使用您的帐户从任何设备访问已保存的标签</li>
<li><b>深色模式</b>:支持深色模式,提供更舒适的浏览体验</li>
<li><b>个性化</b>:更改扩展的外观和行为以满足您的需求</li>
</ul>
查看我们关于 <a href="https://at.xfox111.net/tabs-aside-3-0">搁置的标签页 3.0</a> 的所有新功能和改进的<a href="https://at.xfox111.net/tabs-aside-3-0">博客文章</a>
<b>嘿,这是一个开源软件!</b>
如果您知道如何改进此扩展,可以查看<a href="https://github.com/xfox111/TabsAsideExtension">其 GitHub 仓库</a>
查看<a href="https://github.com/xfox111/TabsAsideExtension/releases/latest">发布更新日志</a>
+25 -16
View File
@@ -1,5 +1,6 @@
import { trackError } from "@/features/analytics";
import { GraphicsStorage } from "@/models/CollectionModels";
import { defineExtensionMessaging, ExtensionMessenger } from "@webext-core/messaging";
import { defineExtensionMessaging, ExtensionMessagingConfig, ExtensionMessenger } from "@webext-core/messaging";
type ProtocolMap =
{
@@ -8,19 +9,27 @@ type ProtocolMap =
refreshCollections(): void;
};
const protocol: ExtensionMessenger<ProtocolMap> = defineExtensionMessaging<ProtocolMap>();
export const onMessage = protocol.onMessage;
export const sendMessage: ExtensionMessenger<ProtocolMap>["sendMessage"] = async (...args) =>
function defineMessaging(config?: ExtensionMessagingConfig): ExtensionMessenger<ProtocolMap>
{
try
{
return await protocol.sendMessage(...args);
}
catch (ex)
{
console.error(ex);
return undefined!;
}
};
const { onMessage, sendMessage, removeAllListeners } = defineExtensionMessaging<ProtocolMap>(config);
return {
onMessage,
removeAllListeners,
sendMessage: async (type, data, args): Promise<any> =>
{
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 });
+52 -29
View File
@@ -1,7 +1,8 @@
import { track } from "@/features/analytics";
import { CollectionItem, GroupItem } from "@/models/CollectionModels";
import { Tabs } from "wxt/browser";
import { settings } from "./settings";
import sendNotification from "./sendNotification";
import { settings } from "./settings";
export default async function saveTabsToCollection(closeTabs: boolean): Promise<CollectionItem>
{
@@ -22,7 +23,15 @@ 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");
return collection;
}
@@ -36,11 +45,19 @@ async function createCollectionFromTabs(tabs: Tabs.Tab[]): Promise<[CollectionIt
tabs = tabs.filter(i =>
i.url
&& !i.url.startsWith(browser.runtime.getURL("/"))
&& 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(),
@@ -49,13 +66,6 @@ async function createCollectionFromTabs(tabs: Tabs.Tab[]): Promise<[CollectionIt
let tabIndex: number = 0;
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"
});
if (tabs[tabIndex].pinned)
{
collection.items.push({ type: "group", pinned: true, items: [] });
@@ -73,22 +83,22 @@ 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);
collection.title = group.title;
collection.color = group.color;
// TODO: Remove the check when Firefox 139 is out
if (!import.meta.env.FIREFOX)
{
const group = await browser.tabGroups!.get(tabs[0].groupId);
collection.title = group.title;
collection.color = group.color;
}
else
{
collection.color = "blue";
}
tabs.forEach(i =>
collection.items.push({ type: "tab", url: i.url!, title: i.title })
@@ -103,7 +113,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;
@@ -113,14 +123,27 @@ async function createCollectionFromTabs(tabs: Tabs.Tab[]): Promise<[CollectionIt
if (!activeGroup || activeGroup !== tab.groupId)
{
activeGroup = tab.groupId;
const group = await browser.tabGroups!.get(activeGroup);
collection.items.push({
type: "group",
color: group.color,
title: group.title,
items: []
});
// TODO: Remove the check when Firefox 139 is out
if (import.meta.env.FIREFOX)
{
collection.items.push({
type: "group",
color: "blue",
items: []
});
}
else
{
const group = await browser.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({
+3 -1
View File
@@ -1,3 +1,4 @@
import { trackError } from "@/features/analytics";
import { PublicPath } from "wxt/browser";
export default async function sendNotification(props: NotificationProps): Promise<void>
@@ -13,8 +14,9 @@ export default async function sendNotification(props: NotificationProps): Promis
}
catch (ex)
{
console.error("Error while showing cloud error notification (probably because of user restrictions)");
console.error("Error while showing notification (probably because of user restrictions)");
console.error(ex);
trackError("notification_error", ex as Error);
}
}
+8 -2
View File
@@ -2,7 +2,13 @@ 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"],
modules: ["@wxt-dev/module-react", "@wxt-dev/i18n/module", "@wxt-dev/analytics/module"],
vite: () => ({
build:
{
chunkSizeWarningLimit: 1000
}
}),
imports: {
dirsScanOptions:
{
@@ -72,7 +78,7 @@ export default defineConfig({
gecko:
{
id: "tabsaside@xfox111.net",
strict_min_version: "109.0"
strict_min_version: "138.0"
}
};
+12
View File
@@ -2031,6 +2031,13 @@
serialize-error "^11.0.0"
webextension-polyfill "^0.10.0"
"@wxt-dev/analytics@^0.4.1":
version "0.4.1"
resolved "https://registry.yarnpkg.com/@wxt-dev/analytics/-/analytics-0.4.1.tgz#23ecbff34eec04690e64f8f9850832baa207486c"
integrity sha512-BDRyfIxO7MKoXLM2jCxX+EVCDjB5jjWGM2GWlU0mYQwi+IzSwMUHnw0UMnDeQ1Zr6yiyjopgCdg4XGaV9QsLJg==
dependencies:
ua-parser-js "^1.0.38"
"@wxt-dev/i18n@^0.2.3":
version "0.2.3"
resolved "https://registry.yarnpkg.com/@wxt-dev/i18n/-/i18n-0.2.3.tgz#5688cbdf5324e86fbd65d585073121bf8d493085"
@@ -6398,6 +6405,11 @@ typescript@^5.8.3:
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.8.3.tgz#92f8a3e5e3cf497356f4178c34cd65a7f5e8440e"
integrity sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==
ua-parser-js@^1.0.38:
version "1.0.40"
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-1.0.40.tgz#ac6aff4fd8ea3e794a6aa743ec9c2fc29e75b675"
integrity sha512-z6PJ8Lml+v3ichVojCiB8toQJBuwR42ySM4ezjXIqXK3M0HczmKQ3LF4rhU55PfD99KEEXQG6yb7iOMyvYuHew==
ufo@^1.5.4:
version "1.6.1"
resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.6.1.tgz#ac2db1d54614d1b22c1d603e3aef44a85d8f146b"