mirror of
https://github.com/XFox111/TabsAsideExtension.git
synced 2026-07-02 19:52:47 +03:00
Compare commits
12 Commits
v3.0.0
..
v3.0.0-rc1
| Author | SHA1 | Date | |
|---|---|---|---|
| f89d036ab8 | |||
| e59782973b | |||
| 70ed16c286 | |||
| db78314a44 | |||
| a478352ca3 | |||
| 2eba532901 | |||
| 1e60b776c4 | |||
| 0c18a3de5a | |||
| 4e40742755 | |||
| 16023ac152 | |||
| 39793a38c3 | |||
| dbc8c7fd4d |
@@ -1,2 +0,0 @@
|
||||
* @XFox111
|
||||
locales/pt_BR.yml @maisondasilva @XFox111
|
||||
@@ -12,7 +12,9 @@ updates:
|
||||
directory: "/" # Location of package manifests
|
||||
target-branch: "next"
|
||||
assignees:
|
||||
- "XFox111"
|
||||
- "xfox111"
|
||||
reviewers:
|
||||
- "xfox111"
|
||||
schedule:
|
||||
interval: monthly
|
||||
rebase-strategy: disabled
|
||||
@@ -22,7 +24,9 @@ updates:
|
||||
directory: "/"
|
||||
target-branch: "next"
|
||||
assignees:
|
||||
- "XFox111"
|
||||
- "xfox111"
|
||||
reviewers:
|
||||
- "xfox111"
|
||||
schedule:
|
||||
interval: monthly
|
||||
rebase-strategy: disabled
|
||||
@@ -32,7 +36,9 @@ updates:
|
||||
directory: "/"
|
||||
target-branch: "next"
|
||||
assignees:
|
||||
- "XFox111"
|
||||
- "xfox111"
|
||||
reviewers:
|
||||
- "xfox111"
|
||||
schedule:
|
||||
interval: monthly
|
||||
rebase-strategy: disabled
|
||||
|
||||
@@ -38,7 +38,7 @@ on:
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
container: node:24
|
||||
container: node:20
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -47,18 +47,7 @@ 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: corepack enable
|
||||
- run: yarn install
|
||||
|
||||
# Patch for firefox dnd popup (see https://github.com/clauderic/dnd-kit/issues/1043)
|
||||
- run: grep -v "this.windowListeners.add(EventName.Resize, this.handleCancel);" core.esm.js > core.esm.js.tmp && mv core.esm.js.tmp core.esm.js
|
||||
working-directory: ./node_modules/@dnd-kit/core/dist
|
||||
if: ${{ matrix.target == 'firefox' }}
|
||||
|
||||
- run: yarn zip -b ${{ matrix.target }}
|
||||
|
||||
- name: Drop build artifacts (${{ matrix.target }})
|
||||
@@ -75,7 +64,7 @@ jobs:
|
||||
extension-root: ./.output/firefox-mv3
|
||||
self-hosted: false
|
||||
|
||||
- run: yarn npm audit
|
||||
- run: yarn audit
|
||||
continue-on-error: ${{ github.event_name != 'release' && github.event.inputs.bypass_audit == 'true' }}
|
||||
|
||||
publish-github:
|
||||
|
||||
@@ -5,11 +5,9 @@ on:
|
||||
branches: [ "main", "next" ]
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- '**.txt'
|
||||
- "locales/*"
|
||||
- 'LICENSE'
|
||||
- 'PRIVACY'
|
||||
- '**/cd_pipeline.yml'
|
||||
- '**/cd_pipeline.yaml'
|
||||
- '**/dependabot.yml'
|
||||
- '**/codeql-analysis.yml'
|
||||
- '**/pr_next.yaml'
|
||||
@@ -30,7 +28,7 @@ on:
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
container: node:24
|
||||
container: node:23
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -39,18 +37,7 @@ 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: corepack enable
|
||||
- run: yarn install
|
||||
|
||||
# Patch for firefox dnd popup (see https://github.com/clauderic/dnd-kit/issues/1043)
|
||||
- run: grep -v "this.windowListeners.add(EventName.Resize, this.handleCancel);" core.esm.js > core.esm.js.tmp && mv core.esm.js.tmp core.esm.js
|
||||
working-directory: ./node_modules/@dnd-kit/core/dist
|
||||
if: ${{ matrix.target == 'firefox' }}
|
||||
|
||||
- run: yarn zip -b ${{ matrix.target }}
|
||||
|
||||
- name: Drop artifacts (${{ matrix.target }})
|
||||
@@ -67,4 +54,4 @@ jobs:
|
||||
extension-root: ./.output/firefox-mv3
|
||||
self-hosted: false
|
||||
|
||||
- run: yarn npm audit
|
||||
- run: yarn audit
|
||||
|
||||
@@ -8,7 +8,6 @@ pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
.yarn/
|
||||
.output
|
||||
stats.html
|
||||
stats-*.json
|
||||
@@ -27,5 +26,3 @@ web-ext.config.ts
|
||||
*.sw?
|
||||
|
||||
web-ext.config.js
|
||||
|
||||
.env*
|
||||
|
||||
-117
@@ -1,117 +0,0 @@
|
||||
nodeLinker: node-modules
|
||||
|
||||
packageExtensions:
|
||||
"@wxt-dev/module-react@*":
|
||||
peerDependencies:
|
||||
vite: "*"
|
||||
"@fluentui/react-accordion@*":
|
||||
peerDependencies:
|
||||
scheduler: "0.23.0"
|
||||
"@fluentui/react-avatar@*":
|
||||
peerDependencies:
|
||||
scheduler: "0.23.0"
|
||||
"@fluentui/react-carousel@*":
|
||||
peerDependencies:
|
||||
scheduler: "0.23.0"
|
||||
"@fluentui/react-color-picker@*":
|
||||
peerDependencies:
|
||||
scheduler: "0.23.0"
|
||||
"@fluentui/react-combobox@*":
|
||||
peerDependencies:
|
||||
scheduler: "0.23.0"
|
||||
"@fluentui/react-dialog@*":
|
||||
peerDependencies:
|
||||
scheduler: "0.23.0"
|
||||
"@fluentui/react-field@*":
|
||||
peerDependencies:
|
||||
scheduler: "0.23.0"
|
||||
"@fluentui/react-list@*":
|
||||
peerDependencies:
|
||||
scheduler: "0.23.0"
|
||||
"@fluentui/react-menu@*":
|
||||
peerDependencies:
|
||||
scheduler: "0.23.0"
|
||||
"@fluentui/react-nav@*":
|
||||
peerDependencies:
|
||||
scheduler: "0.23.0"
|
||||
"@fluentui/react-overflow@*":
|
||||
peerDependencies:
|
||||
scheduler: "0.23.0"
|
||||
"@fluentui/react-popover@*":
|
||||
peerDependencies:
|
||||
scheduler: "0.23.0"
|
||||
"@fluentui/react-swatch-picker@*":
|
||||
peerDependencies:
|
||||
scheduler: "0.23.0"
|
||||
"@fluentui/react-table@*":
|
||||
peerDependencies:
|
||||
scheduler: "0.23.0"
|
||||
"@fluentui/react-tabs@*":
|
||||
peerDependencies:
|
||||
scheduler: "0.23.0"
|
||||
"@fluentui/react-tag-picker@*":
|
||||
peerDependencies:
|
||||
scheduler: "0.23.0"
|
||||
"@fluentui/react-teaching-popover@*":
|
||||
peerDependencies:
|
||||
scheduler: "0.23.0"
|
||||
"@fluentui/react-toolbar@*":
|
||||
peerDependencies:
|
||||
scheduler: "0.23.0"
|
||||
"@fluentui/react-tree@*":
|
||||
peerDependencies:
|
||||
scheduler: "0.23.0"
|
||||
"@fluentui/react-alert@*":
|
||||
peerDependencies:
|
||||
scheduler: "0.23.0"
|
||||
"@fluentui/react-checkbox@*":
|
||||
peerDependencies:
|
||||
scheduler: "0.23.0"
|
||||
"@fluentui/react-components@*":
|
||||
peerDependencies:
|
||||
scheduler: "0.23.0"
|
||||
"@fluentui/react-drawer@*":
|
||||
peerDependencies:
|
||||
scheduler: "0.23.0"
|
||||
"@fluentui/react-infobutton@*":
|
||||
peerDependencies:
|
||||
scheduler: "0.23.0"
|
||||
"@fluentui/react-infolabel@*":
|
||||
peerDependencies:
|
||||
scheduler: "0.23.0"
|
||||
"@fluentui/react-input@*":
|
||||
peerDependencies:
|
||||
scheduler: "0.23.0"
|
||||
"@fluentui/react-persona@*":
|
||||
peerDependencies:
|
||||
scheduler: "0.23.0"
|
||||
"@fluentui/react-progress@*":
|
||||
peerDependencies:
|
||||
scheduler: "0.23.0"
|
||||
"@fluentui/react-radio@*":
|
||||
peerDependencies:
|
||||
scheduler: "0.23.0"
|
||||
"@fluentui/react-select@*":
|
||||
peerDependencies:
|
||||
scheduler: "0.23.0"
|
||||
"@fluentui/react-skeleton@*":
|
||||
peerDependencies:
|
||||
scheduler: "0.23.0"
|
||||
"@fluentui/react-slider@*":
|
||||
peerDependencies:
|
||||
scheduler: "0.23.0"
|
||||
"@fluentui/react-spinbutton@*":
|
||||
peerDependencies:
|
||||
scheduler: "0.23.0"
|
||||
"@fluentui/react-switch@*":
|
||||
peerDependencies:
|
||||
scheduler: "0.23.0"
|
||||
"@fluentui/react-tags@*":
|
||||
peerDependencies:
|
||||
scheduler: "0.23.0"
|
||||
"@fluentui/react-textarea@*":
|
||||
peerDependencies:
|
||||
scheduler: "0.23.0"
|
||||
"@fluentui/react-search@*":
|
||||
peerDependencies:
|
||||
scheduler: "0.23.0"
|
||||
+4
-16
@@ -1,20 +1,8 @@
|
||||
# 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 stores user data only related to its core functionality. This includes:
|
||||
2. This extension only stores user data related to its core functionality. This includes:
|
||||
- User settings
|
||||
- User saved collections of tabs
|
||||
- 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.
|
||||
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.
|
||||
|
||||
@@ -26,7 +26,7 @@ Check out our [latest blog post](https://at.xfox111.net/tabs-aside-3-0) regardin
|
||||
- English
|
||||
- Italian
|
||||
- Polish
|
||||
- Portuguese (Brazil) by [@maisondasilva](https://github.com/maisondasilva)
|
||||
- Portuguese (Brazil)
|
||||
- Russian
|
||||
- Spanish
|
||||
- Ukrainian
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
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);
|
||||
@@ -39,19 +39,16 @@ body
|
||||
/* Handle */
|
||||
::-webkit-scrollbar-thumb
|
||||
{
|
||||
/* eslint-disable-next-line css/no-invalid-properties */
|
||||
background-color: var(--colorNeutralStroke1);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover
|
||||
{
|
||||
/* eslint-disable-next-line css/no-invalid-properties */
|
||||
background-color: var(--colorNeutralStroke1Hover);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover:active
|
||||
{
|
||||
/* eslint-disable-next-line css/no-invalid-properties */
|
||||
background-color: var(--colorNeutralStroke1Pressed);
|
||||
}
|
||||
|
||||
+1
-1
@@ -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://chromewebstore.google.com/detail/tabs-aside/mgmjbodjgijnebfgohlnjkegdpbdjgin";
|
||||
"https://chrome.google.com/webstore/detail/mgmjbodjgijnebfgohlnjkegdbdjgin";
|
||||
|
||||
+23
-67
@@ -1,4 +1,3 @@
|
||||
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";
|
||||
@@ -12,7 +11,6 @@ import { settings } from "@/utils/settings";
|
||||
import watchTabSelection from "@/utils/watchTabSelection";
|
||||
import { Tabs, Windows } from "wxt/browser";
|
||||
import { Unwatch } from "wxt/storage";
|
||||
import { openCollection, openGroup } from "./sidepanel/utils/opener";
|
||||
|
||||
export default defineBackground(() =>
|
||||
{
|
||||
@@ -32,7 +30,6 @@ 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;
|
||||
|
||||
@@ -69,13 +66,6 @@ export default defineBackground(() =>
|
||||
icon: graphicsCache[data.url]?.icon
|
||||
};
|
||||
});
|
||||
onMessage("refreshCollections", () => { });
|
||||
|
||||
if (import.meta.env.FIREFOX)
|
||||
{
|
||||
onMessage("openCollection", ({ data }) => openCollection(data.collection, data.targetWindow));
|
||||
onMessage("openGroup", ({ data }) => openGroup(data.group, data.newWindow));
|
||||
}
|
||||
|
||||
setupTabCaputre();
|
||||
async function setupTabCaputre(): Promise<void>
|
||||
@@ -85,9 +75,6 @@ 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
|
||||
@@ -101,16 +88,11 @@ export default defineBackground(() =>
|
||||
preview: graphicsCache[tab.url]?.preview,
|
||||
icon: graphicsCache[tab.url]?.icon
|
||||
};
|
||||
|
||||
logger("Captured tab", tab.url);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
graphicsCache[tab.url] = {
|
||||
capture: null!,
|
||||
preview: graphicsCache[tab.url]?.preview,
|
||||
icon: graphicsCache[tab.url]?.icon
|
||||
};
|
||||
}
|
||||
catch (ex) { logger(ex); }
|
||||
};
|
||||
|
||||
setInterval(() =>
|
||||
@@ -256,35 +238,24 @@ export default defineBackground(() =>
|
||||
setupCollectionView();
|
||||
async function setupCollectionView(): Promise<void>
|
||||
{
|
||||
const enforcePinnedTab = async (): Promise<void> =>
|
||||
const enforcePinnedTab = async (info: Tabs.OnHighlightedHighlightInfoType): Promise<void> =>
|
||||
{
|
||||
logger("enforcePinnedTab");
|
||||
logger("enforcePinnedTab", info);
|
||||
|
||||
const openWindows: Windows.Window[] = await browser.windows.getAll({ populate: true });
|
||||
const activeWindow: Windows.Window = await browser.windows.getCurrent({ populate: true });
|
||||
|
||||
for (const openWindow of openWindows)
|
||||
{
|
||||
if (openWindow.incognito || openWindow.type !== "normal")
|
||||
continue;
|
||||
if (activeWindow.incognito)
|
||||
return;
|
||||
|
||||
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!));
|
||||
}
|
||||
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 updateView = async (viewLocation: SettingsValue<"listLocation">): Promise<void> =>
|
||||
@@ -293,6 +264,7 @@ 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!));
|
||||
@@ -310,7 +282,11 @@ export default defineBackground(() =>
|
||||
|
||||
if (viewLocation === "pinned")
|
||||
{
|
||||
enforcePinnedTab();
|
||||
await browser.tabs.create({
|
||||
url: browser.runtime.getURL("/sidepanel.html"),
|
||||
active: false,
|
||||
pinned: true
|
||||
});
|
||||
browser.tabs.onHighlighted.addListener(enforcePinnedTab);
|
||||
}
|
||||
};
|
||||
@@ -353,25 +329,6 @@ 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
|
||||
@@ -417,6 +374,5 @@ export default defineBackground(() =>
|
||||
catch (ex)
|
||||
{
|
||||
console.error(ex);
|
||||
trackError("background_error", ex as Error);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -27,9 +27,6 @@ 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);
|
||||
};
|
||||
|
||||
|
||||
@@ -14,8 +14,6 @@ ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||
</App>
|
||||
);
|
||||
|
||||
analytics.page("options_page");
|
||||
|
||||
function OptionsPage(): React.ReactElement
|
||||
{
|
||||
const [selection, setSelection] = useState<SelectionType>("general");
|
||||
|
||||
@@ -4,16 +4,13 @@ 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 = URL.createObjectURL(blob);
|
||||
element.href = `data:application/json;charset=utf-8,${data}`;
|
||||
element.setAttribute("download", "tabs-aside_data.json");
|
||||
|
||||
document.body.appendChild(element);
|
||||
element.click();
|
||||
|
||||
URL.revokeObjectURL(element.href);
|
||||
document.body.removeChild(element);
|
||||
};
|
||||
|
||||
@@ -37,12 +37,7 @@ export default async function importData(): Promise<boolean | null>
|
||||
await browser.storage.local.set(data.local);
|
||||
|
||||
if (data.sync)
|
||||
{
|
||||
if (import.meta.env.FIREFOX && data.sync.contextAction === "context")
|
||||
data.sync.contextAction = "open";
|
||||
|
||||
await browser.storage.sync.set(data.sync);
|
||||
}
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
|
||||
@@ -27,7 +27,7 @@ export const useStyles_CollectionView = makeStyles({
|
||||
},
|
||||
verticalRoot:
|
||||
{
|
||||
maxHeight: "560px"
|
||||
height: "560px"
|
||||
},
|
||||
empty:
|
||||
{
|
||||
@@ -74,8 +74,7 @@ export const useStyles_CollectionView = makeStyles({
|
||||
{
|
||||
gridAutoFlow: "row",
|
||||
width: "100%",
|
||||
paddingBottom: tokens.spacingVerticalS,
|
||||
gridAutoRows: import.meta.env.FIREFOX ? "min-content" : undefined
|
||||
paddingBottom: tokens.spacingVerticalS
|
||||
},
|
||||
dragOverlay:
|
||||
{
|
||||
|
||||
@@ -33,7 +33,7 @@ export default function CollectionView({ collection, index: collectionIndex, dra
|
||||
const colorCls = useGroupColors();
|
||||
|
||||
return (
|
||||
<CollectionContext.Provider value={ { collection, tabCount, hasPinnedGroup } }>
|
||||
<CollectionContext.Provider value={ { collection, collectionIndex, tabCount, hasPinnedGroup } }>
|
||||
<div
|
||||
ref={ setNodeRef } { ...nodeProps }
|
||||
className={ mergeClasses(
|
||||
@@ -50,30 +50,26 @@ export default function CollectionView({ collection, index: collectionIndex, dra
|
||||
|
||||
<CollectionHeader dragHandleProps={ activatorProps } dragHandleRef={ setActivatorNodeRef } />
|
||||
|
||||
{ (!activeItem || activeItem.item.type !== "collection") && !dragOverlay &&
|
||||
<>
|
||||
{ collection.items.length < 1 ?
|
||||
<div className={ cls.empty }>
|
||||
<CollectionsRegular fontSize={ 32 } />
|
||||
<Body1Strong>{ i18n.t("collections.empty") }</Body1Strong>
|
||||
</div>
|
||||
:
|
||||
<div className={ mergeClasses(cls.list, !tilesView && cls.verticalList) }>
|
||||
<SortableContext
|
||||
items={ collection.items.map((_, index) => [collectionIndex, index].join("/")) }
|
||||
strategy={ tilesView ? horizontalListSortingStrategy : verticalListSortingStrategy }
|
||||
>
|
||||
{ collection.items.map((i, index) =>
|
||||
i.type === "group" ?
|
||||
<GroupView
|
||||
key={ index } group={ i } indices={ [collectionIndex, index] } />
|
||||
:
|
||||
<TabView key={ index } tab={ i } indices={ [collectionIndex, index] } />
|
||||
) }
|
||||
</SortableContext>
|
||||
</div>
|
||||
}
|
||||
</>
|
||||
{ collection.items.length < 1 ?
|
||||
<div className={ cls.empty }>
|
||||
<CollectionsRegular fontSize={ 32 } />
|
||||
<Body1Strong>{ i18n.t("collections.empty") }</Body1Strong>
|
||||
</div>
|
||||
:
|
||||
<div className={ mergeClasses(cls.list, !tilesView && cls.verticalList) }>
|
||||
<SortableContext
|
||||
items={ collection.items.map((_, index) => [collectionIndex, index].join("/")) }
|
||||
strategy={ tilesView ? horizontalListSortingStrategy : verticalListSortingStrategy }
|
||||
>
|
||||
{ collection.items.map((i, index) =>
|
||||
i.type === "group" ?
|
||||
<GroupView
|
||||
key={ index } group={ i } indices={ [collectionIndex, index] } />
|
||||
:
|
||||
<TabView key={ index } tab={ i } indices={ [collectionIndex, index] } />
|
||||
) }
|
||||
</SortableContext>
|
||||
</div>
|
||||
}
|
||||
</div >
|
||||
</CollectionContext.Provider>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
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";
|
||||
@@ -24,27 +23,15 @@ export default function EditDialog(props: GroupEditDialogProps): ReactElement
|
||||
|
||||
const cls = useStyles_EditDialog();
|
||||
const colorCls = useGroupColors();
|
||||
const horizontalNavigationAttributes = fui.useArrowNavigationGroup({ axis: "horizontal" });
|
||||
|
||||
const onSubmit = (e: React.FormEvent<HTMLFormElement>) =>
|
||||
{
|
||||
e.preventDefault();
|
||||
handleSave();
|
||||
};
|
||||
|
||||
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 : undefined,
|
||||
title,
|
||||
items: props.collection?.items ?? []
|
||||
});
|
||||
else if (color === "pinned")
|
||||
@@ -58,87 +45,85 @@ export default function EditDialog(props: GroupEditDialogProps): ReactElement
|
||||
type: "group",
|
||||
pinned: false,
|
||||
color: color!,
|
||||
title: title ? title : undefined,
|
||||
title,
|
||||
items: props.group?.items ?? []
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<fui.DialogSurface className={ fui.mergeClasses(cls.surface, (color && color !== "pinned") && colorCls[color]) }>
|
||||
<form onSubmit={ onSubmit }>
|
||||
<fui.DialogBody>
|
||||
<fui.DialogTitle>
|
||||
{
|
||||
props.type === "collection" ?
|
||||
i18n.t(`dialogs.edit.title.${props.collection ? "edit" : "new"}_collection`) :
|
||||
i18n.t(`dialogs.edit.title.${props.group ? "edit" : "new"}_group`)
|
||||
}
|
||||
</fui.DialogTitle>
|
||||
<fui.DialogBody>
|
||||
<fui.DialogTitle>
|
||||
{
|
||||
props.type === "collection" ?
|
||||
i18n.t(`dialogs.edit.title.${props.collection ? "edit" : "new"}_collection`) :
|
||||
i18n.t(`dialogs.edit.title.${props.group ? "edit" : "new"}_group`)
|
||||
}
|
||||
</fui.DialogTitle>
|
||||
|
||||
<fui.DialogContent>
|
||||
<div className={ cls.content }>
|
||||
<fui.Field label={ i18n.t("dialogs.edit.collection_title") }>
|
||||
<fui.Input
|
||||
contentBefore={ <Rename20Regular /> }
|
||||
disabled={ color === "pinned" }
|
||||
placeholder={
|
||||
props.type === "collection" ? getCollectionTitle(props.collection, true) : ""
|
||||
}
|
||||
value={ color === "pinned" ? i18n.t("groups.pinned") : title }
|
||||
onChange={ (_, e) => setTitle(e.value) } />
|
||||
</fui.Field>
|
||||
<fui.Field label="Color">
|
||||
<div className={ cls.colorPicker } { ...horizontalNavigationAttributes }>
|
||||
{ (props.type === "group" && (!props.hidePinned || props.group?.pinned)) &&
|
||||
<fui.ToggleButton
|
||||
checked={ color === "pinned" }
|
||||
onClick={ () => setColor("pinned") }
|
||||
icon={ <Pin20Filled /> }
|
||||
shape="circular"
|
||||
>
|
||||
{ i18n.t("groups.pinned") }
|
||||
</fui.ToggleButton>
|
||||
}
|
||||
{ props.type === "collection" &&
|
||||
<fui.ToggleButton
|
||||
checked={ color === undefined }
|
||||
onClick={ () => setColor(undefined) }
|
||||
icon={ <CircleOff20Regular /> }
|
||||
shape="circular"
|
||||
>
|
||||
{ i18n.t("colors.none") }
|
||||
</fui.ToggleButton>
|
||||
}
|
||||
{ Object.keys(colorCls).map(i =>
|
||||
<fui.ToggleButton
|
||||
checked={ color === i }
|
||||
onClick={ () => setColor(i as chrome.tabGroups.ColorEnum) }
|
||||
className={ fui.mergeClasses(cls.colorButton, colorCls[i as chrome.tabGroups.ColorEnum]) }
|
||||
icon={ {
|
||||
className: cls.colorButton_icon,
|
||||
children: <Circle20Filled />
|
||||
} }
|
||||
key={ i }
|
||||
shape="circular"
|
||||
>
|
||||
{ i18n.t(`colors.${i as chrome.tabGroups.ColorEnum}`) }
|
||||
</fui.ToggleButton>
|
||||
) }
|
||||
</div>
|
||||
</fui.Field>
|
||||
</div>
|
||||
</fui.DialogContent>
|
||||
<fui.DialogContent>
|
||||
<form className={ cls.content }>
|
||||
<fui.Field label={ i18n.t("dialogs.edit.collection_title") }>
|
||||
<fui.Input
|
||||
contentBefore={ <Rename20Regular /> }
|
||||
disabled={ color === "pinned" }
|
||||
placeholder={
|
||||
props.type === "collection" ? getCollectionTitle(props.collection) : ""
|
||||
}
|
||||
value={ color === "pinned" ? i18n.t("groups.pinned") : title }
|
||||
onChange={ (_, e) => setTitle(e.value) } />
|
||||
</fui.Field>
|
||||
<fui.Field label="Color">
|
||||
<div className={ cls.colorPicker }>
|
||||
{ (props.type === "group" && (!props.hidePinned || props.group?.pinned)) &&
|
||||
<fui.ToggleButton
|
||||
checked={ color === "pinned" }
|
||||
onClick={ () => setColor("pinned") }
|
||||
icon={ <Pin20Filled /> }
|
||||
shape="circular"
|
||||
>
|
||||
{ i18n.t("groups.pinned") }
|
||||
</fui.ToggleButton>
|
||||
}
|
||||
{ props.type === "collection" &&
|
||||
<fui.ToggleButton
|
||||
checked={ color === undefined }
|
||||
onClick={ () => setColor(undefined) }
|
||||
icon={ <CircleOff20Regular /> }
|
||||
shape="circular"
|
||||
>
|
||||
{ i18n.t("colors.none") }
|
||||
</fui.ToggleButton>
|
||||
}
|
||||
{ Object.keys(colorCls).map(i =>
|
||||
<fui.ToggleButton
|
||||
checked={ color === i }
|
||||
onClick={ () => setColor(i as chrome.tabGroups.ColorEnum) }
|
||||
className={ fui.mergeClasses(cls.colorButton, colorCls[i as chrome.tabGroups.ColorEnum]) }
|
||||
icon={ {
|
||||
className: cls.colorButton_icon,
|
||||
children: <Circle20Filled />
|
||||
} }
|
||||
key={ i }
|
||||
shape="circular"
|
||||
>
|
||||
{ i18n.t(`colors.${i as chrome.tabGroups.ColorEnum}`) }
|
||||
</fui.ToggleButton>
|
||||
) }
|
||||
</div>
|
||||
</fui.Field>
|
||||
</form>
|
||||
</fui.DialogContent>
|
||||
|
||||
<fui.DialogActions>
|
||||
<fui.DialogTrigger disableButtonEnhancement>
|
||||
<fui.Button appearance="primary" as="button" type="submit">{ i18n.t("common.actions.save") }</fui.Button>
|
||||
</fui.DialogTrigger>
|
||||
<fui.DialogTrigger disableButtonEnhancement>
|
||||
<fui.Button appearance="subtle">{ i18n.t("common.actions.cancel") }</fui.Button>
|
||||
</fui.DialogTrigger>
|
||||
</fui.DialogActions>
|
||||
</fui.DialogBody>
|
||||
</form>
|
||||
<fui.DialogActions>
|
||||
<fui.DialogTrigger disableButtonEnhancement>
|
||||
<fui.Button appearance="primary" onClick={ handleSave }>{ i18n.t("common.actions.save") }</fui.Button>
|
||||
</fui.DialogTrigger>
|
||||
<fui.DialogTrigger disableButtonEnhancement>
|
||||
<fui.Button appearance="subtle">{ i18n.t("common.actions.cancel") }</fui.Button>
|
||||
</fui.DialogTrigger>
|
||||
</fui.DialogActions>
|
||||
</fui.DialogBody>
|
||||
</fui.DialogSurface>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -68,7 +68,12 @@ export const useStyles_GroupView = makeStyles({
|
||||
{
|
||||
display: "flex",
|
||||
gap: tokens.spacingHorizontalS,
|
||||
visibility: "hidden"
|
||||
visibility: "hidden",
|
||||
|
||||
"@media (pointer: coarse)":
|
||||
{
|
||||
visibility: "visible"
|
||||
}
|
||||
},
|
||||
showToolbar:
|
||||
{
|
||||
@@ -99,23 +104,12 @@ export const useStyles_GroupView = makeStyles({
|
||||
display: "flex",
|
||||
columnGap: tokens.spacingHorizontalS,
|
||||
rowGap: tokens.spacingHorizontalSNudge,
|
||||
height: "100%",
|
||||
position: "relative"
|
||||
height: "100%"
|
||||
},
|
||||
verticalList:
|
||||
{
|
||||
flexFlow: "column"
|
||||
},
|
||||
verticalListCollapsed:
|
||||
{
|
||||
maxHeight: "136px",
|
||||
overflow: "clip"
|
||||
},
|
||||
horizontalListCollapsed:
|
||||
{
|
||||
maxWidth: "400px",
|
||||
overflow: "clip"
|
||||
},
|
||||
listContainer:
|
||||
{
|
||||
padding: `${tokens.spacingVerticalS} ${tokens.spacingHorizontalXS}`,
|
||||
|
||||
@@ -88,13 +88,7 @@ export default function GroupView({ group, indices, dragOverlay }: GroupViewProp
|
||||
<Caption1Strong>{ i18n.t("groups.empty") }</Caption1Strong>
|
||||
</div>
|
||||
:
|
||||
<div
|
||||
className={ mergeClasses(
|
||||
cls.list,
|
||||
!tilesView && cls.verticalList,
|
||||
((active?.item.type === "group" && active?.indices[0] === indices[0]) || dragOverlay) && (tilesView ? cls.horizontalListCollapsed : cls.verticalListCollapsed)
|
||||
) }
|
||||
>
|
||||
<div className={ mergeClasses(cls.list, !tilesView && cls.verticalList) }>
|
||||
<SortableContext
|
||||
items={ group.items.map((_, index) => [...indices, index].join("/")) }
|
||||
disabled={ disableSorting }
|
||||
|
||||
@@ -8,7 +8,6 @@ export const useStyles_TabView = makeStyles({
|
||||
|
||||
width: "160px",
|
||||
height: "120px",
|
||||
flexShrink: 0,
|
||||
marginBottom: tokens.spacingVerticalSNudge,
|
||||
|
||||
border: `${tokens.strokeWidthThin} solid ${tokens.colorNeutralStroke3}`,
|
||||
@@ -59,6 +58,8 @@ export const useStyles_TabView = makeStyles({
|
||||
display: "grid",
|
||||
gridTemplateColumns: "auto 1fr auto",
|
||||
alignItems: "center",
|
||||
gap: tokens.spacingHorizontalSNudge,
|
||||
paddingLeft: tokens.spacingHorizontalS,
|
||||
|
||||
borderBottomLeftRadius: tokens.borderRadiusMedium,
|
||||
borderBottomRightRadius: tokens.borderRadiusMedium,
|
||||
@@ -71,9 +72,6 @@ export const useStyles_TabView = makeStyles({
|
||||
icon:
|
||||
{
|
||||
cursor: "grab",
|
||||
padding: `${tokens.spacingVerticalSNudge} ${tokens.spacingHorizontalSNudge}`,
|
||||
height: "32px",
|
||||
boxSizing: "border-box",
|
||||
|
||||
"&:active":
|
||||
{
|
||||
@@ -88,7 +86,12 @@ export const useStyles_TabView = makeStyles({
|
||||
},
|
||||
deleteButton:
|
||||
{
|
||||
display: "none"
|
||||
display: "none",
|
||||
|
||||
"@media (pointer: coarse)":
|
||||
{
|
||||
display: "inline-flex"
|
||||
}
|
||||
},
|
||||
showDeleteButton:
|
||||
{
|
||||
|
||||
@@ -9,12 +9,10 @@ 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
|
||||
@@ -31,18 +29,16 @@ 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(...removeIndex)
|
||||
onConfirm: () => removeItem(...indices)
|
||||
});
|
||||
else
|
||||
removeItem(...removeIndex);
|
||||
removeItem(...indices);
|
||||
};
|
||||
|
||||
const handleClick: MouseEventHandler<HTMLAnchorElement> = (args) =>
|
||||
@@ -83,6 +79,7 @@ export default function TabView({ tab, indices, dragOverlay }: TabViewProps): Re
|
||||
ref={ setActivatorNodeRef } { ...activatorProps }
|
||||
src={ graphics[tab.url]?.icon ?? faviconPlaceholder }
|
||||
onError={ e => e.currentTarget.src = faviconPlaceholder }
|
||||
height={ 20 } width={ 20 }
|
||||
className={ cls.icon } draggable={ false } />
|
||||
|
||||
<Tooltip relationship="description" content={ tab.title ?? tab.url }>
|
||||
|
||||
@@ -1,32 +1,26 @@
|
||||
import { getCollectionTitle } from "@/entrypoints/sidepanel/utils/getCollectionTitle";
|
||||
import getSelectedTabs from "@/entrypoints/sidepanel/utils/getSelectedTabs";
|
||||
import useSettings from "@/hooks/useSettings";
|
||||
import { GroupItem, TabItem } from "@/models/CollectionModels";
|
||||
import { 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 [contextOpen, setContextOpen] = useState<boolean>(false);
|
||||
const [listLocation] = useSettings("listLocation");
|
||||
const isTab: boolean = listLocation === "tab" || listLocation === "pinned";
|
||||
const { updateCollection } = useCollections();
|
||||
const { tabCount, collection } = useContext<CollectionContextType>(CollectionContext);
|
||||
const { tabCount, collection, collectionIndex } = useContext<CollectionContextType>(CollectionContext);
|
||||
const [alwaysShowToolbars] = useSettings("alwaysShowToolbars");
|
||||
|
||||
const AddIcon = bundleIcon(Add20Filled, Add20Regular);
|
||||
|
||||
const handleAddSelected = async () =>
|
||||
{
|
||||
const newTabs: (TabItem | GroupItem)[] = isTab ?
|
||||
(await saveTabsToCollection(false)).items :
|
||||
await getSelectedTabs();
|
||||
updateCollection({ ...collection, items: [...collection.items, ...newTabs] }, collection.timestamp);
|
||||
const newTabs: TabItem[] = await getSelectedTabs();
|
||||
updateCollection({ ...collection, items: [...collection.items, ...newTabs] }, collectionIndex);
|
||||
};
|
||||
|
||||
const cls = useStyles();
|
||||
@@ -54,18 +48,18 @@ export default function CollectionHeader({ dragHandleRef, dragHandleProps }: Col
|
||||
mergeClasses(
|
||||
cls.toolbar,
|
||||
"CollectionView__toolbar",
|
||||
(alwaysShowToolbars === true || contextOpen) && cls.showToolbar
|
||||
alwaysShowToolbars === true && cls.showToolbar
|
||||
) }
|
||||
>
|
||||
{ tabCount < 1 ?
|
||||
<Button icon={ <AddIcon /> } appearance="subtle" onClick={ handleAddSelected }>
|
||||
{ isTab ? i18n.t("collections.menu.add_all") : i18n.t("collections.menu.add_selected") }
|
||||
{ i18n.t("collections.menu.add_selected") }
|
||||
</Button>
|
||||
:
|
||||
<OpenCollectionButton onOpenChange={ (_, e) => setContextOpen(e.open) } />
|
||||
<OpenCollectionButton />
|
||||
}
|
||||
|
||||
<CollectionMoreButton onAddSelected={ handleAddSelected } onOpenChange={ (_, e) => setContextOpen(e.open) } />
|
||||
<CollectionMoreButton onAddSelected={ handleAddSelected } />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -101,7 +95,12 @@ const useStyles = makeStyles({
|
||||
{
|
||||
display: "none",
|
||||
gap: tokens.spacingHorizontalS,
|
||||
alignItems: "flex-start"
|
||||
alignItems: "flex-start",
|
||||
|
||||
"@media (pointer: coarse)":
|
||||
{
|
||||
display: "flex"
|
||||
}
|
||||
},
|
||||
showToolbar:
|
||||
{
|
||||
|
||||
@@ -1,19 +1,17 @@
|
||||
import { useDialog } from "@/contexts/DialogProvider";
|
||||
import { useDangerStyles } from "@/hooks/useDangerStyles";
|
||||
import useSettings from "@/hooks/useSettings";
|
||||
import { Button, Menu, MenuDivider, MenuItem, MenuList, MenuOpenChangeData, MenuOpenEvent, MenuPopover, MenuTrigger, Tooltip } from "@fluentui/react-components";
|
||||
import { Button, Menu, MenuDivider, MenuItem, MenuList, MenuPopover, MenuTrigger, Tooltip } from "@fluentui/react-components";
|
||||
import * as ic from "@fluentui/react-icons";
|
||||
import CollectionContext, { CollectionContextType } from "../../contexts/CollectionContext";
|
||||
import { useCollections } from "../../contexts/CollectionsProvider";
|
||||
import exportCollectionToBookmarks from "../../utils/exportCollectionToBookmarks";
|
||||
import EditDialog from "../EditDialog";
|
||||
|
||||
export default function CollectionMoreButton({ onAddSelected, onOpenChange }: CollectionMoreButtonProps): React.ReactElement
|
||||
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 } = useContext<CollectionContextType>(CollectionContext);
|
||||
const { tabCount, hasPinnedGroup, collection, collectionIndex } = useContext<CollectionContextType>(CollectionContext);
|
||||
const dialog = useDialog();
|
||||
const [deletePrompt] = useSettings("deletePrompt");
|
||||
|
||||
@@ -21,6 +19,7 @@ export default function CollectionMoreButton({ onAddSelected, onOpenChange }: Co
|
||||
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();
|
||||
@@ -33,10 +32,10 @@ export default function CollectionMoreButton({ onAddSelected, onOpenChange }: Co
|
||||
content: i18n.t("common.delete_prompt"),
|
||||
destructive: true,
|
||||
confirmText: i18n.t("common.actions.delete"),
|
||||
onConfirm: () => removeItem(collection.timestamp)
|
||||
onConfirm: () => removeItem(collectionIndex)
|
||||
});
|
||||
else
|
||||
removeItem(collection.timestamp);
|
||||
removeItem(collectionIndex);
|
||||
};
|
||||
|
||||
const handleEdit = () =>
|
||||
@@ -44,7 +43,7 @@ export default function CollectionMoreButton({ onAddSelected, onOpenChange }: Co
|
||||
<EditDialog
|
||||
type="collection"
|
||||
collection={ collection }
|
||||
onSave={ item => updateCollection(item, collection.timestamp) } />
|
||||
onSave={ item => updateCollection(item, collectionIndex) } />
|
||||
);
|
||||
|
||||
const handleCreateGroup = () =>
|
||||
@@ -52,11 +51,22 @@ export default function CollectionMoreButton({ onAddSelected, onOpenChange }: Co
|
||||
<EditDialog
|
||||
type="group"
|
||||
hidePinned={ hasPinnedGroup }
|
||||
onSave={ group => updateCollection({ ...collection, items: [...collection.items, group] }, collection.timestamp) } />
|
||||
onSave={ group => updateCollection({ ...collection, items: [...collection.items, group] }, collectionIndex) } />
|
||||
);
|
||||
|
||||
const handleAddPinnedGroup = () =>
|
||||
{
|
||||
updateCollection({
|
||||
...collection,
|
||||
items: [
|
||||
{ type: "group", pinned: true, items: [] },
|
||||
...collection.items
|
||||
]
|
||||
}, collectionIndex);
|
||||
};
|
||||
|
||||
return (
|
||||
<Menu onOpenChange={ onOpenChange }>
|
||||
<Menu>
|
||||
<Tooltip relationship="label" content={ i18n.t("common.tooltips.more") }>
|
||||
<MenuTrigger>
|
||||
<Button appearance="subtle" icon={ <ic.MoreVertical20Regular /> } />
|
||||
@@ -67,12 +77,19 @@ export default function CollectionMoreButton({ onAddSelected, onOpenChange }: Co
|
||||
<MenuList>
|
||||
{ tabCount > 0 &&
|
||||
<MenuItem icon={ <AddIcon /> } onClick={ () => onAddSelected?.() }>
|
||||
{ isTab ? i18n.t("collections.menu.add_all") : i18n.t("collections.menu.add_selected") }
|
||||
{ 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") }
|
||||
</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") }
|
||||
@@ -94,5 +111,4 @@ export default function CollectionMoreButton({ onAddSelected, onOpenChange }: Co
|
||||
export type CollectionMoreButtonProps =
|
||||
{
|
||||
onAddSelected?: () => void;
|
||||
onOpenChange?: (e: MenuOpenEvent, data: MenuOpenChangeData) => void;
|
||||
};
|
||||
|
||||
@@ -7,8 +7,6 @@ import getSelectedTabs from "@/entrypoints/sidepanel/utils/getSelectedTabs";
|
||||
import { useDangerStyles } from "@/hooks/useDangerStyles";
|
||||
import useSettings from "@/hooks/useSettings";
|
||||
import { TabItem } from "@/models/CollectionModels";
|
||||
import { sendMessage } from "@/utils/messaging";
|
||||
import saveTabsToCollection from "@/utils/saveTabsToCollection";
|
||||
import { Button, Menu, MenuItem, MenuList, MenuPopover, MenuTrigger, Tooltip } from "@fluentui/react-components";
|
||||
import * as ic from "@fluentui/react-icons";
|
||||
import { ReactElement } from "react";
|
||||
@@ -16,10 +14,8 @@ import { openGroup } from "../../utils/opener";
|
||||
|
||||
export default function GroupMoreMenu(): ReactElement
|
||||
{
|
||||
const [listLocation] = useSettings("listLocation");
|
||||
const isTab: boolean = listLocation === "tab" || listLocation === "pinned";
|
||||
const { group, indices } = useContext<GroupContextType>(GroupContext);
|
||||
const { hasPinnedGroup, collection } = useContext<CollectionContextType>(CollectionContext);
|
||||
const { hasPinnedGroup } = useContext<CollectionContextType>(CollectionContext);
|
||||
const [deletePrompt] = useSettings("deletePrompt");
|
||||
const dialog = useDialog();
|
||||
const { updateGroup, removeItem, ungroup } = useCollections();
|
||||
@@ -34,18 +30,16 @@ 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(...removeIndex)
|
||||
onConfirm: () => removeItem(...indices)
|
||||
});
|
||||
else
|
||||
removeItem(...removeIndex);
|
||||
removeItem(...indices);
|
||||
};
|
||||
|
||||
const handleEdit = () =>
|
||||
@@ -54,23 +48,13 @@ export default function GroupMoreMenu(): ReactElement
|
||||
type="group"
|
||||
group={ group }
|
||||
hidePinned={ hasPinnedGroup }
|
||||
onSave={ item => updateGroup(item, collection.timestamp, indices[1]) } />
|
||||
onSave={ item => updateGroup(item, indices[0], indices[1]) } />
|
||||
);
|
||||
|
||||
const openGroupInNewWindow = () =>
|
||||
{
|
||||
if (import.meta.env.FIREFOX && listLocation === "popup")
|
||||
sendMessage("openGroup", { group, newWindow: true });
|
||||
else
|
||||
openGroup(group, true);
|
||||
};
|
||||
|
||||
const handleAddSelected = async () =>
|
||||
{
|
||||
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]);
|
||||
const newTabs: TabItem[] = await getSelectedTabs();
|
||||
updateGroup({ ...group, items: [...group.items, ...newTabs] }, indices[0], indices[1]);
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -84,23 +68,25 @@ export default function GroupMoreMenu(): ReactElement
|
||||
<MenuPopover>
|
||||
<MenuList>
|
||||
{ group.items.length > 0 &&
|
||||
<MenuItem icon={ <NewWindowIcon /> } onClick={ openGroupInNewWindow }>
|
||||
<MenuItem icon={ <NewWindowIcon /> } onClick={ () => openGroup(group, true) }>
|
||||
{ i18n.t("groups.menu.new_window") }
|
||||
</MenuItem>
|
||||
}
|
||||
|
||||
<MenuItem icon={ <AddIcon /> } onClick={ handleAddSelected }>
|
||||
{ isTab ? i18n.t("groups.menu.add_all") : i18n.t("groups.menu.add_selected") }
|
||||
{ i18n.t("groups.menu.add_selected") }
|
||||
</MenuItem>
|
||||
|
||||
<MenuItem icon={ <EditIcon /> } onClick={ handleEdit }>
|
||||
{ i18n.t("groups.menu.edit") }
|
||||
</MenuItem>
|
||||
{ (!import.meta.env.FIREFOX || group.pinned !== true) &&
|
||||
<MenuItem icon={ <EditIcon /> } onClick={ handleEdit }>
|
||||
{ i18n.t("groups.menu.edit") }
|
||||
</MenuItem>
|
||||
}
|
||||
{ group.items.length > 0 &&
|
||||
<MenuItem
|
||||
className={ dangerCls.menuItem }
|
||||
icon={ <UngroupIcon /> }
|
||||
onClick={ () => ungroup(collection.timestamp, indices[1]) }
|
||||
onClick={ () => ungroup(indices[0], indices[1]) }
|
||||
>
|
||||
{ i18n.t("groups.menu.ungroup") }
|
||||
</MenuItem>
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
import { useDialog } from "@/contexts/DialogProvider";
|
||||
import useSettings from "@/hooks/useSettings";
|
||||
import browserLocaleKey from "@/utils/browserLocaleKey";
|
||||
import { sendMessage } from "@/utils/messaging";
|
||||
import { Menu, MenuButtonProps, MenuItem, MenuList, MenuOpenChangeData, MenuOpenEvent, MenuPopover, MenuTrigger, SplitButton } from "@fluentui/react-components";
|
||||
import { Menu, MenuButtonProps, MenuItem, MenuList, MenuPopover, MenuTrigger, SplitButton } from "@fluentui/react-components";
|
||||
import * as ic from "@fluentui/react-icons";
|
||||
import CollectionContext, { CollectionContextType } from "../../contexts/CollectionContext";
|
||||
import { useCollections } from "../../contexts/CollectionsProvider";
|
||||
import { openCollection } from "../../utils/opener";
|
||||
|
||||
export default function OpenCollectionButton({ onOpenChange }: OpenCollectionButtonProps): React.ReactElement
|
||||
export default function OpenCollectionButton(): React.ReactElement
|
||||
{
|
||||
const [defaultAction] = useSettings("defaultRestoreAction");
|
||||
const [listLocation] = useSettings("listLocation");
|
||||
const { removeItem } = useCollections();
|
||||
const dialog = useDialog();
|
||||
const { collection } = useContext<CollectionContextType>(CollectionContext);
|
||||
const { collection, collectionIndex } = useContext<CollectionContextType>(CollectionContext);
|
||||
|
||||
const OpenIcon = ic.bundleIcon(ic.Open20Filled, ic.Open20Regular);
|
||||
const RestoreIcon = ic.bundleIcon(ic.ArrowExportRtl20Filled, ic.ArrowExportRtl20Regular);
|
||||
@@ -24,12 +22,7 @@ export default function OpenCollectionButton({ onOpenChange }: OpenCollectionBut
|
||||
const handleIncognito = async () =>
|
||||
{
|
||||
if (await browser.extension.isAllowedIncognitoAccess())
|
||||
{
|
||||
if (import.meta.env.FIREFOX && listLocation === "popup")
|
||||
sendMessage("openCollection", { collection, targetWindow: "incognito" });
|
||||
else
|
||||
openCollection(collection, "incognito");
|
||||
}
|
||||
openCollection(collection, "incognito");
|
||||
else
|
||||
dialog.pushPrompt({
|
||||
title: i18n.t("collections.incognito_check.title"),
|
||||
@@ -52,18 +45,16 @@ export default function OpenCollectionButton({ onOpenChange }: OpenCollectionBut
|
||||
};
|
||||
|
||||
const handleOpen = (mode: "current" | "new") =>
|
||||
import.meta.env.FIREFOX && listLocation === "popup" && mode === "new" ?
|
||||
() => sendMessage("openCollection", { collection, targetWindow: "new" }) :
|
||||
() => openCollection(collection, mode);
|
||||
() => openCollection(collection, mode);
|
||||
|
||||
const handleRestore = async () =>
|
||||
{
|
||||
await openCollection(collection);
|
||||
removeItem(collection.timestamp);
|
||||
removeItem(collectionIndex);
|
||||
};
|
||||
|
||||
return (
|
||||
<Menu onOpenChange={ onOpenChange }>
|
||||
<Menu>
|
||||
<MenuTrigger disableButtonEnhancement>
|
||||
{ (triggerProps: MenuButtonProps) => defaultAction === "restore" ?
|
||||
<SplitButton
|
||||
@@ -93,7 +84,7 @@ export default function OpenCollectionButton({ onOpenChange }: OpenCollectionBut
|
||||
{ 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 }>
|
||||
@@ -104,8 +95,3 @@ export default function OpenCollectionButton({ onOpenChange }: OpenCollectionBut
|
||||
</Menu>
|
||||
);
|
||||
}
|
||||
|
||||
export type OpenCollectionButtonProps =
|
||||
{
|
||||
onOpenChange?: (e: MenuOpenEvent, data: MenuOpenChangeData) => void;
|
||||
};
|
||||
|
||||
@@ -8,6 +8,7 @@ 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, sendMessage } from "@/utils/messaging";
|
||||
import { onMessage } from "@/utils/messaging";
|
||||
import { createContext } from "react";
|
||||
import mergePinnedGroups from "../utils/mergePinnedGroups";
|
||||
|
||||
@@ -40,7 +40,6 @@ 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 =>
|
||||
@@ -50,14 +49,12 @@ 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[collectionIndex].items[indices[1]] as GroupItem).items.splice(indices[2], 1);
|
||||
(collections[indices[0]].items[indices[1]] as GroupItem).items.splice(indices[2], 1);
|
||||
else if (indices.length > 1)
|
||||
collections[collectionIndex].items.splice(indices[1], 1);
|
||||
collections[indices[0]].items.splice(indices[1], 1);
|
||||
else
|
||||
collections.splice(collectionIndex, 1);
|
||||
collections.splice(indices[0], 1);
|
||||
|
||||
updateStorage(collections);
|
||||
};
|
||||
@@ -67,23 +64,20 @@ export default function CollectionsProvider({ children }: React.PropsWithChildre
|
||||
updateStorage(collectionList);
|
||||
};
|
||||
|
||||
const updateCollection = (collection: CollectionItem, id: number): void =>
|
||||
const updateCollection = (collection: CollectionItem, index: number): void =>
|
||||
{
|
||||
const index: number = collections.findIndex(i => i.timestamp === id);
|
||||
collections[index] = collection;
|
||||
updateStorage(collections);
|
||||
};
|
||||
|
||||
const updateGroup = (group: GroupItem, collectionId: number, groupIndex: number): void =>
|
||||
const updateGroup = (group: GroupItem, collectionIndex: number, groupIndex: number): void =>
|
||||
{
|
||||
const collectionIndex: number = collections.findIndex(i => i.timestamp === collectionId);
|
||||
collections[collectionIndex].items[groupIndex] = group;
|
||||
updateStorage(collections);
|
||||
};
|
||||
|
||||
const ungroup = (collectionId: number, groupIndex: number): void =>
|
||||
const ungroup = (collectionIndex: 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);
|
||||
@@ -113,9 +107,9 @@ export type CollectionsContextType =
|
||||
addCollection: (collection: CollectionItem) => void;
|
||||
|
||||
updateCollections: (collections: CollectionItem[]) => void;
|
||||
updateCollection: (collection: CollectionItem, id: number) => void;
|
||||
updateGroup: (group: GroupItem, collectionId: number, groupIndex: number) => void;
|
||||
ungroup: (collectionId: number, groupIndex: number) => void;
|
||||
updateCollection: (collection: CollectionItem, index: number) => void;
|
||||
updateGroup: (group: GroupItem, collectionIndex: number, groupIndex: number) => void;
|
||||
ungroup: (collectionIndex: number, groupIndex: number) => void;
|
||||
|
||||
removeItem: (...indices: number[]) => void;
|
||||
};
|
||||
|
||||
@@ -46,10 +46,6 @@ export const useStyles_CollectionListView = makeStyles({
|
||||
listView:
|
||||
{
|
||||
display: "grid",
|
||||
|
||||
"@media screen and (min-width: 360px)":
|
||||
{
|
||||
gridTemplateColumns: "repeat(auto-fit, minmax(360px, 1fr))"
|
||||
}
|
||||
gridTemplateColumns: "repeat(auto-fit, minmax(360px, 1fr))"
|
||||
}
|
||||
});
|
||||
|
||||
@@ -5,7 +5,6 @@ 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";
|
||||
@@ -21,7 +20,6 @@ import { collisionDetector } from "../../utils/dnd/collisionDetector";
|
||||
import { useStyles_CollectionListView } from "./CollectionListView.styles";
|
||||
import SearchBar from "./SearchBar";
|
||||
import StorageCapacityIssueMessage from "./messages/StorageCapacityIssueMessage";
|
||||
import { snapHandleToCursor } from "../../utils/dnd/snapHandleToCursor";
|
||||
|
||||
export default function CollectionListView(): ReactElement
|
||||
{
|
||||
@@ -34,7 +32,7 @@ export default function CollectionListView(): ReactElement
|
||||
const [active, setActive] = useState<DndItem | null>(null);
|
||||
|
||||
const sensors = useSensors(
|
||||
useSensor(MouseSensor, { activationConstraint: { delay: 10, tolerance: 20 } }),
|
||||
useSensor(MouseSensor, { activationConstraint: { delay: 100, tolerance: 0 } }),
|
||||
useSensor(TouchSensor, { activationConstraint: { delay: 300, tolerance: 20 } })
|
||||
);
|
||||
|
||||
@@ -66,8 +64,6 @@ export default function CollectionListView(): ReactElement
|
||||
updateCollections(result);
|
||||
if (sortMode !== "custom")
|
||||
setSortMode("custom");
|
||||
|
||||
track("used_drag_and_drop");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -111,7 +107,6 @@ export default function CollectionListView(): ReactElement
|
||||
collisionDetection={ collisionDetector(!tilesView) }
|
||||
onDragStart={ handleDragStart }
|
||||
onDragEnd={ handleDragEnd }
|
||||
modifiers={ [snapHandleToCursor] }
|
||||
>
|
||||
<SortableContext
|
||||
items={ resultList.map((_, index) => index.toString()) }
|
||||
@@ -123,25 +118,27 @@ export default function CollectionListView(): ReactElement
|
||||
</SortableContext>
|
||||
|
||||
<DragOverlay dropAnimation={ null }>
|
||||
{ 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" ?
|
||||
{ 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
|
||||
} }
|
||||
>
|
||||
<GroupView group={ active.item } indices={ [-1] } dragOverlay />
|
||||
:
|
||||
<TabView tab={ active.item } indices={ [-1] } dragOverlay />
|
||||
}
|
||||
</CollectionContext.Provider>
|
||||
:
|
||||
<></>
|
||||
</CollectionContext.Provider>
|
||||
}
|
||||
{ active.item.type === "tab" &&
|
||||
<TabView tab={ active.item } indices={ [-1] } dragOverlay />
|
||||
}
|
||||
</>
|
||||
}
|
||||
</DragOverlay>
|
||||
</DndContext>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
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";
|
||||
@@ -28,11 +27,6 @@ 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)
|
||||
@@ -42,7 +36,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) } onClick={ () => track("feedback_clicked") }>{ i18n.t("cta_message.feedback") }</Link>
|
||||
{ i18n.t("cta_message.message") } <Link { ...extLink(storeLink) }>{ i18n.t("cta_message.feedback") }</Link>
|
||||
</MessageBarBody>
|
||||
<MessageBarActions
|
||||
containerAction={
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
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";
|
||||
@@ -42,10 +41,10 @@ export default function MoreButton(): ReactElement
|
||||
|
||||
<fui.MenuDivider />
|
||||
|
||||
<fui.MenuItemLink icon={ <BmcIcon /> } { ...extLink(buyMeACoffeeLink) } onClick={ () => track("feedback_clicked") }>
|
||||
<fui.MenuItemLink icon={ <BmcIcon /> } { ...extLink(buyMeACoffeeLink) }>
|
||||
{ i18n.t("common.cta.sponsor") }
|
||||
</fui.MenuItemLink>
|
||||
<fui.MenuItemLink icon={ <FeedbackIcon /> } { ...extLink(storeLink) } onClick={ () => track("bmc_clicked") }>
|
||||
<fui.MenuItemLink icon={ <FeedbackIcon /> } { ...extLink(storeLink) } >
|
||||
{ i18n.t("common.cta.feedback") }
|
||||
</fui.MenuItemLink>
|
||||
<fui.MenuItemLink icon={ <LearnIcon /> } { ...extLink(githubLinks.release) } >
|
||||
|
||||
@@ -15,7 +15,6 @@ ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||
);
|
||||
|
||||
document.title = i18n.t("manifest.name");
|
||||
analytics.page("collection_list");
|
||||
|
||||
function MainPage(): React.ReactElement
|
||||
{
|
||||
|
||||
@@ -33,11 +33,9 @@ export function collisionDetector(vertical?: boolean): CollisionDetection
|
||||
|
||||
if (activeItem.item.type === "collection")
|
||||
{
|
||||
// If we drag a collection, we should ignore other items, like tabs or groups
|
||||
if (droppableItem.item.type !== "collection")
|
||||
continue;
|
||||
|
||||
// Using distance between centers
|
||||
value = distanceBetween(centerOfRectangle(rect), centerRect);
|
||||
collisions.push({ id, data: { droppableContainer, value } });
|
||||
continue;
|
||||
@@ -46,20 +44,14 @@ export function collisionDetector(vertical?: boolean): CollisionDetection
|
||||
const intersectionRatio: number = getIntersectionRatio(rect, collisionRect);
|
||||
const intersectionCoefficient: number = intersectionRatio / getMaxIntersectionRatio(rect, collisionRect);
|
||||
|
||||
// Dragging a tab or a group over a collection
|
||||
if (droppableItem.item.type === "collection")
|
||||
{
|
||||
// Ignoring collection, if the tab or the group is inside that collection
|
||||
if (activeItem.indices.length === 2 && activeItem.indices[0] === droppableItem.indices[0])
|
||||
continue;
|
||||
|
||||
// Ignoring collection if we're dragging a tab or a group that doesn't belong to the collection,
|
||||
// but intersection ratio is less than 0.7
|
||||
if (intersectionCoefficient < 0.7)
|
||||
if (intersectionCoefficient < 0.7 && activeItem.item.type === "tab")
|
||||
continue;
|
||||
|
||||
// If we're dragging a tab, that's inside a group that belongs to the collection,
|
||||
// we substract the group's intersection from the collection's one
|
||||
if (activeItem.indices.length === 3 && activeItem.indices[0] === droppableItem.indices[0])
|
||||
{
|
||||
const [collectionId, groupId] = activeItem.indices;
|
||||
@@ -70,23 +62,16 @@ export function collisionDetector(vertical?: boolean): CollisionDetection
|
||||
|
||||
value = 1 / (intersectionRatio - getIntersectionRatio(groupRect, collisionRect));
|
||||
}
|
||||
// Otherwise, use intersection ratio
|
||||
// At this point we're dragging either:
|
||||
// - a group, that doesn't belong to the collection
|
||||
// - a tab, that either belongs to the collection's group, or has intersection coefficient >= .7
|
||||
else
|
||||
{
|
||||
value = 2 / intersectionRatio;
|
||||
value = 1 / intersectionRatio;
|
||||
}
|
||||
}
|
||||
// If we're dragging a tab or a group over another group's dropzone
|
||||
else if (droppableItem.item.type === "group" && (id as string).endsWith("_dropzone"))
|
||||
{
|
||||
// Ignore, if we're dragging a group
|
||||
if (activeItem.item.type === "group")
|
||||
continue;
|
||||
|
||||
// Ignore, if we're dragging a tab, that's inside the group
|
||||
if (
|
||||
activeItem.indices.length === 3 &&
|
||||
activeItem.indices[0] === droppableItem.indices[0] &&
|
||||
@@ -94,15 +79,11 @@ export function collisionDetector(vertical?: boolean): CollisionDetection
|
||||
)
|
||||
continue;
|
||||
|
||||
// Ignore, if coefficient is less than .5
|
||||
// (at this point we're dragging a tab, that's outside of the group's dropzone)
|
||||
if (intersectionCoefficient < 0.5)
|
||||
continue;
|
||||
|
||||
// Use intersection between the tab and the group's dropzone
|
||||
value = 1 / intersectionRatio;
|
||||
}
|
||||
// We're dragging a group or a tab over its sibling
|
||||
else if (activeItem.indices.length === droppableItem.indices.length)
|
||||
{
|
||||
if (activeItem.indices[0] !== droppableItem.indices[0])
|
||||
@@ -111,22 +92,9 @@ export function collisionDetector(vertical?: boolean): CollisionDetection
|
||||
if (activeItem.indices.length === 3 && activeItem.indices[1] !== droppableItem.indices[1])
|
||||
continue;
|
||||
|
||||
// Ignore pinned groups
|
||||
if (droppableItem.item.type === "group" && droppableItem.item.pinned === true)
|
||||
continue;
|
||||
|
||||
const collectionRect: ClientRect | undefined = droppableRects.get(activeItem.indices[0].toString());
|
||||
|
||||
if (!collectionRect)
|
||||
continue;
|
||||
|
||||
const collectionIntersectionRatio: number = getIntersectionRatio(collectionRect, collisionRect);
|
||||
const collectionIntersectionCoefficient: number = collectionIntersectionRatio / getMaxIntersectionRatio(collectionRect, collisionRect);
|
||||
|
||||
// Ignore if we are outside of the home collection
|
||||
if (collectionIntersectionCoefficient < 0.7)
|
||||
continue;
|
||||
|
||||
if (activeItem.item.type === "tab" && droppableItem.item.type === "tab")
|
||||
{
|
||||
value = distanceBetween(centerOfRectangle(rect), centerRect);
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
import { Modifier } from "@dnd-kit/core";
|
||||
import { Coordinates, getEventCoordinates } from "@dnd-kit/utilities";
|
||||
import { DndItem } from "../../hooks/useDndItem";
|
||||
|
||||
export const snapHandleToCursor: Modifier = ({
|
||||
activatorEvent,
|
||||
draggingNodeRect,
|
||||
transform,
|
||||
active
|
||||
}) =>
|
||||
{
|
||||
if (draggingNodeRect && activatorEvent)
|
||||
{
|
||||
const activeItem: DndItem | undefined = active?.data.current as DndItem;
|
||||
const activatorCoordinates: Coordinates | null = getEventCoordinates(activatorEvent);
|
||||
|
||||
if (!activatorCoordinates)
|
||||
return transform;
|
||||
|
||||
const initX: number = activatorCoordinates.x - draggingNodeRect.left;
|
||||
const initY: number = activatorCoordinates.y - draggingNodeRect.top;
|
||||
|
||||
const offsetX: number = activeItem?.item.type === "group" ? 24 : draggingNodeRect.height / 2;
|
||||
const offsetY: number = activeItem?.item.type === "group" ? 20 : draggingNodeRect.height / 2;
|
||||
|
||||
return {
|
||||
...transform,
|
||||
x: transform.x + initX - offsetX,
|
||||
y: transform.y + initY - offsetY
|
||||
};
|
||||
}
|
||||
|
||||
return transform;
|
||||
};
|
||||
@@ -1,10 +1,8 @@
|
||||
import { CollectionItem } from "@/models/CollectionModels";
|
||||
|
||||
export function getCollectionTitle(collection?: CollectionItem, useTimestamp?: boolean): string
|
||||
export function getCollectionTitle(collection?: CollectionItem): string
|
||||
{
|
||||
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" });
|
||||
return collection?.title
|
||||
|| new Date(collection?.timestamp ?? Date.now())
|
||||
.toLocaleDateString(browser.i18n.getUILanguage(), { year: "numeric", month: "short", day: "numeric" });
|
||||
}
|
||||
|
||||
@@ -1,24 +1,8 @@
|
||||
import { TabItem } from "@/models/CollectionModels";
|
||||
import sendNotification from "@/utils/sendNotification";
|
||||
import { Tabs } from "wxt/browser";
|
||||
|
||||
export default async function getSelectedTabs(): Promise<TabItem[]>
|
||||
{
|
||||
let tabs: Tabs.Tab[] = await browser.tabs.query({ currentWindow: true, highlighted: true });
|
||||
const tabCount: number = tabs.length;
|
||||
|
||||
tabs = tabs.filter(i =>
|
||||
i.url
|
||||
&& new URL(i.url).protocol !== "about:"
|
||||
&& new URL(i.url).hostname !== "newtab"
|
||||
);
|
||||
|
||||
if (tabs.length < tabCount)
|
||||
await sendNotification({
|
||||
title: i18n.t("notifications.partial_save.title"),
|
||||
message: i18n.t("notifications.partial_save.message"),
|
||||
icon: "/notification_icons/save_warning.png"
|
||||
});
|
||||
|
||||
return tabs.map(i => ({ type: "tab", url: i.url!, title: i.title }));
|
||||
const tabs: Tabs.Tab[] = await browser.tabs.query({ currentWindow: true, highlighted: true });
|
||||
return tabs.filter(i => i.url).map(i => ({ type: "tab", url: i.url!, title: i.title }));
|
||||
}
|
||||
|
||||
@@ -55,17 +55,19 @@ 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 tabs: Tabs.Tab[] = await Promise.all(group.items.map(async i =>
|
||||
await createTab(i.url, windowId, discard, group.pinned)
|
||||
const tabIds: number[] = await Promise.all(group.items.map(async i =>
|
||||
(await createTab(i.url, windowId, discard, group.pinned)).id!
|
||||
));
|
||||
|
||||
// "Pinned" group is technically not a group, so not much else to do here
|
||||
if (group.pinned === true)
|
||||
// and Firefox doesn't even support tab groups
|
||||
if (group.pinned === true || import.meta.env.FIREFOX)
|
||||
return;
|
||||
|
||||
const groupId: number = await chrome.tabs.group({
|
||||
tabIds: tabs.filter(i => i.windowId === windowId).map(i => i.id!),
|
||||
createProperties: { windowId }
|
||||
tabIds, createProperties: {
|
||||
windowId
|
||||
}
|
||||
});
|
||||
|
||||
await chrome.tabGroups.update(groupId, {
|
||||
@@ -77,14 +79,12 @@ async function createGroup(group: GroupItem, windowId: number, discard?: boolean
|
||||
async function manageWindow(handle: (windowId: number) => Promise<void>, windowProps?: Windows.CreateCreateDataType): Promise<void>
|
||||
{
|
||||
const currentWindow: Windows.Window = windowProps ?
|
||||
await browser.windows.create({ url: "about:blank", focused: false, ...windowProps }) :
|
||||
await browser.windows.create({ url: "about:blank", focused: true, ...windowProps }) :
|
||||
await browser.windows.getCurrent();
|
||||
const windowId: number = currentWindow.id!;
|
||||
|
||||
await handle(windowId);
|
||||
|
||||
await browser.windows.update(windowId, { focused: true });
|
||||
|
||||
if (windowProps)
|
||||
// Close "about:blank" tab
|
||||
await browser.tabs.remove(currentWindow.tabs![0].id!);
|
||||
|
||||
+1
-2
@@ -88,8 +88,7 @@ export default defineConfig([
|
||||
"@typescript-eslint/no-unused-vars": ["warn"],
|
||||
"prefer-const": ["warn"],
|
||||
"@stylistic/padded-blocks": ["warn"],
|
||||
"no-empty": ["off"],
|
||||
"@stylistic/eol-last": ["warn"]
|
||||
"no-empty": ["off"]
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
export { default as userPropertiesStorage } from "./utils/userPropertiesStorage";
|
||||
export { default as trackError } from "./utils/trackError";
|
||||
export { default as track } from "./utils/track";
|
||||
@@ -1,11 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
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,4 +1,3 @@
|
||||
import { trackError } from "@/features/analytics";
|
||||
import { CollectionItem } from "@/models/CollectionModels";
|
||||
import getLogger from "@/utils/getLogger";
|
||||
import { collectionStorage } from "./collectionStorage";
|
||||
@@ -33,7 +32,6 @@ 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"];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ export default async function getCollectionsFromCloud(): Promise<CollectionItem[
|
||||
const chunks: Record<string, string> =
|
||||
await browser.storage.sync.get(getChunkKeys(0, chunkCount)) as Record<string, string>;
|
||||
|
||||
const data: string = decompress(Object.values(chunks).join(), { inputEncoding: "Base64" });
|
||||
const data: string = decompress(Object.values(chunks).join(), { inputEncoding: "StorageBinaryString" });
|
||||
|
||||
return parseCollections(data);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { trackError } from "@/features/analytics";
|
||||
import { CollectionItem } from "@/models/CollectionModels";
|
||||
import getLogger from "@/utils/getLogger";
|
||||
import { collectionStorage } from "./collectionStorage";
|
||||
@@ -38,6 +37,5 @@ async function replaceLocalWithCloud(): Promise<void>
|
||||
{
|
||||
logger("Failed to get cloud storage");
|
||||
console.error(ex);
|
||||
trackError("conflict_resolve_with_cloud_error", ex as Error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { trackError } from "@/features/analytics";
|
||||
import { CollectionItem, GraphicsStorage } from "@/models/CollectionModels";
|
||||
import getLogger from "@/utils/getLogger";
|
||||
import sendNotification from "@/utils/sendNotification";
|
||||
@@ -27,7 +26,6 @@ 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({
|
||||
|
||||
@@ -14,7 +14,7 @@ export default async function saveCollectionsToCloud(collections: CollectionItem
|
||||
return;
|
||||
}
|
||||
|
||||
const data: string = compress(serializeCollections(collections), { outputEncoding: "Base64" });
|
||||
const data: string = compress(serializeCollections(collections), { outputEncoding: "StorageBinaryString" });
|
||||
const chunks: string[] = splitIntoChunks(data);
|
||||
|
||||
if (chunks.length > collectionStorage.maxChunkCount)
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { sendMessage } from "@/utils/messaging";
|
||||
import { collectionStorage } from "./collectionStorage";
|
||||
import saveCollectionsToCloud from "./saveCollectionsToCloud";
|
||||
|
||||
@@ -15,6 +14,6 @@ export default async function setCloudStorage(enable: boolean): Promise<void>
|
||||
{
|
||||
await collectionStorage.disableCloud.setValue(true);
|
||||
await saveCollectionsToCloud([], 0);
|
||||
await sendMessage("refreshCollections", undefined);
|
||||
browser.runtime.reload();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
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";
|
||||
|
||||
@@ -24,7 +23,9 @@ export default function WelcomeDialog(): React.ReactElement
|
||||
{ i18n.t("features.v3welcome.text2") }
|
||||
</fui.Body1>
|
||||
<ul>
|
||||
<li>{ i18n.t("features.v3welcome.list.item1") }</li>
|
||||
{ !import.meta.env.FIREFOX &&
|
||||
<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>
|
||||
@@ -38,10 +39,7 @@ export default function WelcomeDialog(): React.ReactElement
|
||||
|
||||
<fui.DialogActions>
|
||||
<fui.DialogTrigger disableButtonEnhancement>
|
||||
<fui.Button
|
||||
appearance="primary" as="a" { ...extLink(v3blogPost) }
|
||||
onClick={ () => track("visit_blog_button_click") }
|
||||
>
|
||||
<fui.Button appearance="primary" as="a" { ...extLink(v3blogPost) }>
|
||||
{ i18n.t("features.v3welcome.actions.visit_blog") }
|
||||
</fui.Button>
|
||||
</fui.DialogTrigger>
|
||||
|
||||
+1
-2
@@ -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,7 +174,6 @@ 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
@@ -1,251 +0,0 @@
|
||||
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
@@ -1,251 +0,0 @@
|
||||
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
@@ -1,251 +0,0 @@
|
||||
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: "Zapisz karty (domyślna akcja)"
|
||||
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"
|
||||
@@ -1,251 +0,0 @@
|
||||
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"
|
||||
+6
-7
@@ -84,7 +84,7 @@ options_page:
|
||||
icon_action:
|
||||
title: "При нажатии на иконку расширения:"
|
||||
options:
|
||||
action: "Сохранить вкладки (действие по умолчанию)"
|
||||
action: "Выполнить действие по умолчанию"
|
||||
context: "Показать контекстное меню"
|
||||
open: "Открыть список коллекций"
|
||||
change_shortcuts: "Изменить горячие клавиши"
|
||||
@@ -149,20 +149,20 @@ collections:
|
||||
title: "Требуется разрешение"
|
||||
message:
|
||||
edge:
|
||||
p1: "Расширению необходимо дополнительное разрешение, чтобы открыть вкладки в режиме InPrivate"
|
||||
p1: "Расширению необходимо дополнительное разрешение чтобы открыть вкладки в режиме InPrivate"
|
||||
p2: "Для этого нажмите \"Настройки\" и затем отметьте опцию \"Разрешить в режиме InPrivate\""
|
||||
firefox:
|
||||
p1: "Расширению необходимо дополнительное разрешение, чтобы открыть вкладки в приватном окне"
|
||||
p1: "Расширению необходимо дополнительное разрешение чтобы открыть вкладки в приватном окне"
|
||||
p2: "Для этого нажмите \"Настройки\", перейдите в \"Подробности\" и разрешите \"Запуск в приватных окнах\""
|
||||
chrome:
|
||||
p1: "Расширению необходимо дополнительное разрешение, чтобы открыть вкладки в режиме инкогнито"
|
||||
p1: "Расширению необходимо дополнительное разрешение чтобы открыть вкладки в режиме инкогнито"
|
||||
p2: "Для этого нажмите \"Настройки\" и отметьте опцию \"Разрешить использование в режиме инкогнито\""
|
||||
action: "Настройки"
|
||||
menu:
|
||||
delete: "Удалить коллекцию"
|
||||
add_selected: "Добавить выбранные вкладки"
|
||||
add_all: "Добавить все вкладки"
|
||||
add_group: "Добавить пустую группу"
|
||||
add_pinned: "Добавить закрепленную группу"
|
||||
export_bookmarks: "Экспортировать в закладки"
|
||||
edit: "Редактировать коллекцию"
|
||||
|
||||
@@ -174,7 +174,6 @@ groups:
|
||||
menu:
|
||||
new_window: "Открыть в новом окне"
|
||||
add_selected: "Добавить выбранные вкладки"
|
||||
add_all: "Добавить все вкладки"
|
||||
edit: "Редактировать группу"
|
||||
ungroup: "Разгруппировать"
|
||||
delete: "Удалить группу"
|
||||
@@ -246,6 +245,6 @@ parse_error_message:
|
||||
|
||||
merge_conflict_message:
|
||||
title: "В локальном и облачном хранилищах есть конфликтующие изменения."
|
||||
message: "Чтобы это исправить, вы можете сохранить локальную копию в облако либо принять изменения из облака."
|
||||
message: "Чтобы это исправить, вы можете сохранить локальную копию в облако, либо принять изменения из облака."
|
||||
accept_local: "Заменить локальной"
|
||||
accept_cloud: "Принять облачные изменения"
|
||||
|
||||
+6
-7
@@ -84,7 +84,7 @@ options_page:
|
||||
icon_action:
|
||||
title: "При натисканні на іконку розширення:"
|
||||
options:
|
||||
action: "Зберегти вкладки (дія за замовчуванням)"
|
||||
action: "Виконати дію за замовчуванням"
|
||||
context: "Показати контекстне меню"
|
||||
open: "Відкрити список колекцій"
|
||||
change_shortcuts: "Змінити гарячі клавіші"
|
||||
@@ -161,8 +161,8 @@ collections:
|
||||
menu:
|
||||
delete: "Видалити колекцію"
|
||||
add_selected: "Додати вибрані вкладки"
|
||||
add_all: "Додати всі вкладки"
|
||||
add_group: "Додати порожню групу"
|
||||
add_pinned: "Додати закріплену групу"
|
||||
export_bookmarks: "Експортувати в закладки"
|
||||
edit: "Редагувати колекцію"
|
||||
|
||||
@@ -174,7 +174,6 @@ groups:
|
||||
menu:
|
||||
new_window: "Відкрити у новому вікні"
|
||||
add_selected: "Додати вибрані вкладки"
|
||||
add_all: "Додати всі вкладки"
|
||||
edit: "Редагувати групу"
|
||||
ungroup: "Розгрупувати"
|
||||
delete: "Видалити групу"
|
||||
@@ -191,9 +190,9 @@ colors:
|
||||
yellow: "Жовтий"
|
||||
green: "Зелений"
|
||||
pink: "Рожевий"
|
||||
purple: "Пурпуровий"
|
||||
cyan: "Бірюзовий"
|
||||
orange: "Оранжевий"
|
||||
purple: "Фіолетовий"
|
||||
cyan: "Голубий"
|
||||
orange: "Помаранчевий"
|
||||
|
||||
dialogs:
|
||||
edit:
|
||||
@@ -246,6 +245,6 @@ parse_error_message:
|
||||
|
||||
merge_conflict_message:
|
||||
title: "В локальному і облачному хранилищах є конфліктуючі зміни."
|
||||
message: "Щоб це виправити, ви можете зберегти локальну копію в хмарі або прийняти зміни з хмари."
|
||||
message: "Щоб це виправити, ви можете зберегти локальну копію в хмарі, або прийняти зміни з хмари."
|
||||
accept_local: "Заменить локальною"
|
||||
accept_cloud: "Прийняти облачні зміни"
|
||||
|
||||
@@ -1,251 +0,0 @@
|
||||
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: "接受云端更改"
|
||||
+20
-21
@@ -1,47 +1,46 @@
|
||||
{
|
||||
"name": "tabs-aside",
|
||||
"private": true,
|
||||
"version": "3.0.0",
|
||||
"version": "3.0.0-rc1",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "wxt",
|
||||
"build": "yarn lint && wxt build --mv3",
|
||||
"zip": "yarn lint && wxt zip --mv3",
|
||||
"build": "wxt build --mv3",
|
||||
"zip": "wxt zip --mv3",
|
||||
"lint": "tsc --noEmit && eslint . -c eslint.config.js",
|
||||
"prepare": "wxt prepare",
|
||||
"postinstall": "yarn prepare"
|
||||
"prebuild": "yarn lint",
|
||||
"prezip": "yarn lint",
|
||||
"postinstall": "wxt prepare"
|
||||
},
|
||||
"dependencies": {
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
"@dnd-kit/modifiers": "^9.0.0",
|
||||
"@dnd-kit/sortable": "^10.0.0",
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
"@fluentui/react-components": "^9.68.1",
|
||||
"@fluentui/react-icons": "^2.0.307",
|
||||
"@webext-core/messaging": "^2.3.0",
|
||||
"@wxt-dev/analytics": "^0.4.1",
|
||||
"@wxt-dev/i18n": "^0.2.4",
|
||||
"@fluentui/react-components": "^9.63.0",
|
||||
"@fluentui/react-icons": "^2.0.298",
|
||||
"@webext-core/messaging": "^2.2.0",
|
||||
"@wxt-dev/i18n": "^0.2.3",
|
||||
"lzutf8": "^0.6.3",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1"
|
||||
"react-dom": "18.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/css": "^0.10.0",
|
||||
"@eslint/js": "^9.32.0",
|
||||
"@eslint/json": "^0.13.1",
|
||||
"@stylistic/eslint-plugin": "^5.2.2",
|
||||
"@eslint/css": "^0.7.0",
|
||||
"@eslint/js": "^9.26.0",
|
||||
"@eslint/json": "^0.12.0",
|
||||
"@stylistic/eslint-plugin": "^4.2.0",
|
||||
"@types/react": "^18.3.1",
|
||||
"@types/react-dom": "^18.3.1",
|
||||
"@types/scheduler": "0.23.0",
|
||||
"@wxt-dev/module-react": "^1.1.3",
|
||||
"eslint": "^9.32.0",
|
||||
"eslint": "^9.26.0",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"globals": "^16.3.0",
|
||||
"globals": "^16.0.0",
|
||||
"scheduler": "0.23.0",
|
||||
"typescript": "^5.8.3",
|
||||
"typescript-eslint": "^8.38.0",
|
||||
"vite": "^7.0.6",
|
||||
"typescript-eslint": "^8.31.1",
|
||||
"vite": "^6.3.4",
|
||||
"wxt": "~0.19.29"
|
||||
},
|
||||
"packageManager": "yarn@4.9.2"
|
||||
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
|
||||
}
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
<!-- 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>
|
||||
@@ -1,48 +0,0 @@
|
||||
<!-- 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>
|
||||
@@ -1,48 +0,0 @@
|
||||
<!-- 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>
|
||||
@@ -1,49 +0,0 @@
|
||||
<!-- 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>
|
||||
@@ -1,48 +0,0 @@
|
||||
<!-- 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>
|
||||
+17
-33
@@ -1,42 +1,26 @@
|
||||
import { trackError } from "@/features/analytics";
|
||||
import { CollectionItem, GraphicsStorage, GroupItem } from "@/models/CollectionModels";
|
||||
import { defineExtensionMessaging, ExtensionMessagingConfig, ExtensionMessenger, ExtensionSendMessageArgs, GetDataType, GetReturnType } from "@webext-core/messaging";
|
||||
import { GraphicsStorage } from "@/models/CollectionModels";
|
||||
import { defineExtensionMessaging, ExtensionMessenger } from "@webext-core/messaging";
|
||||
|
||||
type ProtocolMap =
|
||||
{
|
||||
addThumbnail(data: { url: string; thumbnail: string; }): void;
|
||||
getGraphicsCache(): GraphicsStorage;
|
||||
refreshCollections(): void;
|
||||
|
||||
openCollection(data: { collection: CollectionItem; targetWindow: "new" | "incognito"; }): void;
|
||||
openGroup(data: { group: GroupItem; newWindow: boolean; }): void;
|
||||
};
|
||||
|
||||
function defineMessaging(config?: ExtensionMessagingConfig): ExtensionMessenger<ProtocolMap>
|
||||
const protocol: ExtensionMessenger<ProtocolMap> = defineExtensionMessaging<ProtocolMap>();
|
||||
|
||||
export const onMessage = protocol.onMessage;
|
||||
|
||||
export const sendMessage: ExtensionMessenger<ProtocolMap>["sendMessage"] = async (...args) =>
|
||||
{
|
||||
const { onMessage, sendMessage, removeAllListeners }: ExtensionMessenger<ProtocolMap> = defineExtensionMessaging<ProtocolMap>(config);
|
||||
|
||||
return {
|
||||
onMessage,
|
||||
removeAllListeners,
|
||||
async sendMessage<TType extends keyof ProtocolMap>(
|
||||
type: TType,
|
||||
data: GetDataType<ProtocolMap[TType]>,
|
||||
...args: ExtensionSendMessageArgs
|
||||
): Promise<GetReturnType<ProtocolMap[TType]>>
|
||||
{
|
||||
try
|
||||
{
|
||||
return await sendMessage(type, data, ...args);
|
||||
}
|
||||
catch (ex)
|
||||
{
|
||||
console.error(ex);
|
||||
trackError("messaging_error", ex as Error);
|
||||
return undefined!;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const { onMessage, sendMessage } = defineMessaging({ logger: console });
|
||||
try
|
||||
{
|
||||
return await protocol.sendMessage(...args);
|
||||
}
|
||||
catch (ex)
|
||||
{
|
||||
console.error(ex);
|
||||
return undefined!;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { track } from "@/features/analytics";
|
||||
import { CollectionItem, GroupItem } from "@/models/CollectionModels";
|
||||
import { Tabs } from "wxt/browser";
|
||||
import sendNotification from "./sendNotification";
|
||||
import { settings } from "./settings";
|
||||
import sendNotification from "./sendNotification";
|
||||
|
||||
export default async function saveTabsToCollection(closeTabs: boolean): Promise<CollectionItem>
|
||||
{
|
||||
@@ -23,15 +22,7 @@ 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;
|
||||
}
|
||||
@@ -45,19 +36,11 @@ 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(),
|
||||
@@ -66,6 +49,13 @@ 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: [] });
|
||||
@@ -83,12 +73,20 @@ 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 !== -1 &&
|
||||
if (tabs[0].groupId && tabs[0].groupId !== chrome.tabGroups.TAB_GROUP_ID_NONE &&
|
||||
tabs.every(i => i.groupId === tabs[0].groupId)
|
||||
)
|
||||
{
|
||||
const group = await chrome.tabGroups.get(tabs[0].groupId);
|
||||
const group = await browser.tabGroups!.get(tabs[0].groupId);
|
||||
collection.title = group.title;
|
||||
collection.color = group.color;
|
||||
|
||||
@@ -105,7 +103,7 @@ async function createCollectionFromTabs(tabs: Tabs.Tab[]): Promise<[CollectionIt
|
||||
{
|
||||
const tab = tabs[tabIndex];
|
||||
|
||||
if (!tab.groupId || tab.groupId === -1)
|
||||
if (!tab.groupId || tab.groupId === chrome.tabGroups.TAB_GROUP_ID_NONE)
|
||||
{
|
||||
collection.items.push({ type: "tab", url: tab.url!, title: tab.title });
|
||||
activeGroup = null;
|
||||
@@ -115,7 +113,7 @@ async function createCollectionFromTabs(tabs: Tabs.Tab[]): Promise<[CollectionIt
|
||||
if (!activeGroup || activeGroup !== tab.groupId)
|
||||
{
|
||||
activeGroup = tab.groupId;
|
||||
const group = await chrome.tabGroups.get(activeGroup);
|
||||
const group = await browser.tabGroups!.get(activeGroup);
|
||||
|
||||
collection.items.push({
|
||||
type: "group",
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { trackError } from "@/features/analytics";
|
||||
import { PublicPath } from "wxt/browser";
|
||||
|
||||
export default async function sendNotification(props: NotificationProps): Promise<void>
|
||||
@@ -14,9 +13,8 @@ export default async function sendNotification(props: NotificationProps): Promis
|
||||
}
|
||||
catch (ex)
|
||||
{
|
||||
console.error("Error while showing notification (probably because of user restrictions)");
|
||||
console.error("Error while showing cloud error notification (probably because of user restrictions)");
|
||||
console.error(ex);
|
||||
trackError("notification_error", ex as Error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+8
-12
@@ -2,13 +2,7 @@ import { ConfigEnv, defineConfig, UserManifest } from "wxt";
|
||||
|
||||
// See https://wxt.dev/api/config.html
|
||||
export default defineConfig({
|
||||
modules: ["@wxt-dev/module-react", "@wxt-dev/i18n/module", "@wxt-dev/analytics/module"],
|
||||
vite: () => ({
|
||||
build:
|
||||
{
|
||||
chunkSizeWarningLimit: 1000
|
||||
}
|
||||
}),
|
||||
modules: ["@wxt-dev/module-react", "@wxt-dev/i18n/module"],
|
||||
imports: {
|
||||
dirsScanOptions:
|
||||
{
|
||||
@@ -24,8 +18,7 @@ export default defineConfig({
|
||||
description: "__MSG_manifest_description__",
|
||||
homepage_url: "https://github.com/xfox111/TabsAsideExtension/",
|
||||
|
||||
action:
|
||||
{
|
||||
action: {
|
||||
default_title: "__MSG_manifest_name__"
|
||||
},
|
||||
|
||||
@@ -37,8 +30,7 @@ export default defineConfig({
|
||||
"tabs",
|
||||
"notifications",
|
||||
"contextMenus",
|
||||
"bookmarks",
|
||||
"tabGroups"
|
||||
"bookmarks"
|
||||
],
|
||||
|
||||
commands:
|
||||
@@ -80,13 +72,17 @@ export default defineConfig({
|
||||
gecko:
|
||||
{
|
||||
id: "tabsaside@xfox111.net",
|
||||
strict_min_version: "139.0"
|
||||
strict_min_version: "109.0"
|
||||
}
|
||||
};
|
||||
|
||||
// @ts-expect-error author key in Firefox has a different format
|
||||
manifest.author = "__MSG_manifest_author__";
|
||||
}
|
||||
else
|
||||
{
|
||||
manifest.permissions!.push("tabGroups");
|
||||
}
|
||||
|
||||
return manifest;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user