mirror of
https://github.com/XFox111/TabsAsideExtension.git
synced 2026-07-02 19:52:47 +03:00
Compare commits
47 Commits
v3.0.0
..
v3.0.0-rc4
| Author | SHA1 | Date | |
|---|---|---|---|
| e498e25c57 | |||
| d07c99e3a1 | |||
| 0ff1d63cde | |||
| dfcafae2b1 | |||
| 8f4cd4198a | |||
| 1b64f65e9f | |||
| d249e07eca | |||
| 4070907240 | |||
| 9d250dc01d | |||
| 24bf0e88ca | |||
| ef94842066 | |||
| 40490aec2d | |||
| 06aca3d3ca | |||
| a6a5c236c6 | |||
| a144221e33 | |||
| e6a69980c2 | |||
| 59b0547ec6 | |||
| eed5159a56 | |||
| 7effe309dd | |||
| 6728a50056 | |||
| 0cb036c69a | |||
| 4ef336da5b | |||
| 00492ad710 | |||
| a706c3bc89 | |||
| 4ef9e2651c | |||
| bfb849fbdf | |||
| 297a6aa95c | |||
| aa2ee02c79 | |||
| 8b77159abe | |||
| f5bf0db039 | |||
| 5d4a59153a | |||
| b6be86aac9 | |||
| d872515b8b | |||
| 8693e8d563 | |||
| 9c4121ea79 | |||
| 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:
|
||||
@@ -51,14 +51,7 @@ jobs:
|
||||
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 +68,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:
|
||||
|
||||
@@ -30,7 +30,7 @@ on:
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
container: node:24
|
||||
container: node:23
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -43,14 +43,7 @@ jobs:
|
||||
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 +60,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
|
||||
|
||||
-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"
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,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(() =>
|
||||
{
|
||||
@@ -69,13 +68,7 @@ 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));
|
||||
}
|
||||
onMessage("refreshCollections", () => {});
|
||||
|
||||
setupTabCaputre();
|
||||
async function setupTabCaputre(): Promise<void>
|
||||
@@ -264,7 +257,7 @@ export default defineBackground(() =>
|
||||
|
||||
for (const openWindow of openWindows)
|
||||
{
|
||||
if (openWindow.incognito || openWindow.type !== "normal")
|
||||
if (openWindow.incognito)
|
||||
continue;
|
||||
|
||||
const activeTabs: Tabs.Tab[] = openWindow.tabs!.filter(tab =>
|
||||
|
||||
@@ -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:
|
||||
{
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -24,13 +24,6 @@ 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 = () =>
|
||||
{
|
||||
@@ -65,80 +58,78 @@ export default function EditDialog(props: GroupEditDialogProps): ReactElement
|
||||
|
||||
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, true) : ""
|
||||
}
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -99,23 +99,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":
|
||||
{
|
||||
|
||||
@@ -83,6 +83,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 }>
|
||||
|
||||
@@ -12,7 +12,6 @@ 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();
|
||||
@@ -54,7 +53,7 @@ export default function CollectionHeader({ dragHandleRef, dragHandleProps }: Col
|
||||
mergeClasses(
|
||||
cls.toolbar,
|
||||
"CollectionView__toolbar",
|
||||
(alwaysShowToolbars === true || contextOpen) && cls.showToolbar
|
||||
alwaysShowToolbars === true && cls.showToolbar
|
||||
) }
|
||||
>
|
||||
{ tabCount < 1 ?
|
||||
@@ -62,10 +61,10 @@ export default function CollectionHeader({ dragHandleRef, dragHandleProps }: Col
|
||||
{ isTab ? i18n.t("collections.menu.add_all") : 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>
|
||||
);
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
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";
|
||||
@@ -56,7 +56,7 @@ export default function CollectionMoreButton({ onAddSelected, onOpenChange }: Co
|
||||
);
|
||||
|
||||
return (
|
||||
<Menu onOpenChange={ onOpenChange }>
|
||||
<Menu>
|
||||
<Tooltip relationship="label" content={ i18n.t("common.tooltips.more") }>
|
||||
<MenuTrigger>
|
||||
<Button appearance="subtle" icon={ <ic.MoreVertical20Regular /> } />
|
||||
@@ -94,5 +94,4 @@ export default function CollectionMoreButton({ onAddSelected, onOpenChange }: Co
|
||||
export type CollectionMoreButtonProps =
|
||||
{
|
||||
onAddSelected?: () => void;
|
||||
onOpenChange?: (e: MenuOpenEvent, data: MenuOpenChangeData) => void;
|
||||
};
|
||||
|
||||
@@ -7,12 +7,11 @@ 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";
|
||||
import { openGroup } from "../../utils/opener";
|
||||
import saveTabsToCollection from "@/utils/saveTabsToCollection";
|
||||
|
||||
export default function GroupMoreMenu(): ReactElement
|
||||
{
|
||||
@@ -57,14 +56,6 @@ export default function GroupMoreMenu(): ReactElement
|
||||
onSave={ item => updateGroup(item, collection.timestamp, 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 ?
|
||||
@@ -84,7 +75,7 @@ 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>
|
||||
}
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
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);
|
||||
@@ -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,9 +45,7 @@ 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 () =>
|
||||
{
|
||||
@@ -63,7 +54,7 @@ export default function OpenCollectionButton({ onOpenChange }: OpenCollectionBut
|
||||
};
|
||||
|
||||
return (
|
||||
<Menu onOpenChange={ onOpenChange }>
|
||||
<Menu>
|
||||
<MenuTrigger disableButtonEnhancement>
|
||||
{ (triggerProps: MenuButtonProps) => defaultAction === "restore" ?
|
||||
<SplitButton
|
||||
@@ -104,8 +95,3 @@ export default function OpenCollectionButton({ onOpenChange }: OpenCollectionBut
|
||||
</Menu>
|
||||
);
|
||||
}
|
||||
|
||||
export type OpenCollectionButtonProps =
|
||||
{
|
||||
onOpenChange?: (e: MenuOpenEvent, data: MenuOpenChangeData) => 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))"
|
||||
}
|
||||
});
|
||||
|
||||
@@ -21,7 +21,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 +33,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 } })
|
||||
);
|
||||
|
||||
@@ -111,7 +110,6 @@ export default function CollectionListView(): ReactElement
|
||||
collisionDetection={ collisionDetector(!tilesView) }
|
||||
onDragStart={ handleDragStart }
|
||||
onDragEnd={ handleDragEnd }
|
||||
modifiers={ [snapHandleToCursor] }
|
||||
>
|
||||
<SortableContext
|
||||
items={ resultList.map((_, index) => index.toString()) }
|
||||
|
||||
@@ -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,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 }));
|
||||
}
|
||||
|
||||
@@ -68,10 +68,13 @@ async function createGroup(group: GroupItem, windowId: number, discard?: boolean
|
||||
createProperties: { windowId }
|
||||
});
|
||||
|
||||
await chrome.tabGroups.update(groupId, {
|
||||
title: group.title,
|
||||
color: group.color
|
||||
});
|
||||
// Grouping support came in 138, tabGroups is expected to be in 139
|
||||
// TODO: Remove this check once the API is available
|
||||
if (!import.meta.env.FIREFOX)
|
||||
await chrome.tabGroups.update(groupId, {
|
||||
title: group.title,
|
||||
color: group.color
|
||||
});
|
||||
}
|
||||
|
||||
async function manageWindow(handle: (windowId: number) => Promise<void>, windowProps?: Windows.CreateCreateDataType): Promise<void>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
-1
@@ -84,7 +84,7 @@ options_page:
|
||||
icon_action:
|
||||
title: "Po kliknięciu ikony rozszerzenia:"
|
||||
options:
|
||||
action: "Zapisz karty (domyślna akcja)"
|
||||
action: "Wykonaj domyślną akcję"
|
||||
context: "Pokaż menu kontekstowe"
|
||||
open: "Otwórz listę kolekcji"
|
||||
change_shortcuts: "Zmień skróty klawiszowe"
|
||||
|
||||
+5
-5
@@ -84,7 +84,7 @@ options_page:
|
||||
icon_action:
|
||||
title: "При нажатии на иконку расширения:"
|
||||
options:
|
||||
action: "Сохранить вкладки (действие по умолчанию)"
|
||||
action: "Выполнить действие по умолчанию"
|
||||
context: "Показать контекстное меню"
|
||||
open: "Открыть список коллекций"
|
||||
change_shortcuts: "Изменить горячие клавиши"
|
||||
@@ -149,13 +149,13 @@ collections:
|
||||
title: "Требуется разрешение"
|
||||
message:
|
||||
edge:
|
||||
p1: "Расширению необходимо дополнительное разрешение, чтобы открыть вкладки в режиме InPrivate"
|
||||
p1: "Расширению необходимо дополнительное разрешение чтобы открыть вкладки в режиме InPrivate"
|
||||
p2: "Для этого нажмите \"Настройки\" и затем отметьте опцию \"Разрешить в режиме InPrivate\""
|
||||
firefox:
|
||||
p1: "Расширению необходимо дополнительное разрешение, чтобы открыть вкладки в приватном окне"
|
||||
p1: "Расширению необходимо дополнительное разрешение чтобы открыть вкладки в приватном окне"
|
||||
p2: "Для этого нажмите \"Настройки\", перейдите в \"Подробности\" и разрешите \"Запуск в приватных окнах\""
|
||||
chrome:
|
||||
p1: "Расширению необходимо дополнительное разрешение, чтобы открыть вкладки в режиме инкогнито"
|
||||
p1: "Расширению необходимо дополнительное разрешение чтобы открыть вкладки в режиме инкогнито"
|
||||
p2: "Для этого нажмите \"Настройки\" и отметьте опцию \"Разрешить использование в режиме инкогнито\""
|
||||
action: "Настройки"
|
||||
menu:
|
||||
@@ -246,6 +246,6 @@ parse_error_message:
|
||||
|
||||
merge_conflict_message:
|
||||
title: "В локальном и облачном хранилищах есть конфликтующие изменения."
|
||||
message: "Чтобы это исправить, вы можете сохранить локальную копию в облако либо принять изменения из облака."
|
||||
message: "Чтобы это исправить, вы можете сохранить локальную копию в облако, либо принять изменения из облака."
|
||||
accept_local: "Заменить локальной"
|
||||
accept_cloud: "Принять облачные изменения"
|
||||
|
||||
+2
-2
@@ -84,7 +84,7 @@ options_page:
|
||||
icon_action:
|
||||
title: "При натисканні на іконку розширення:"
|
||||
options:
|
||||
action: "Зберегти вкладки (дія за замовчуванням)"
|
||||
action: "Виконати дію за замовчуванням"
|
||||
context: "Показати контекстне меню"
|
||||
open: "Відкрити список колекцій"
|
||||
change_shortcuts: "Змінити гарячі клавіші"
|
||||
@@ -246,6 +246,6 @@ parse_error_message:
|
||||
|
||||
merge_conflict_message:
|
||||
title: "В локальному і облачному хранилищах є конфліктуючі зміни."
|
||||
message: "Щоб це виправити, ви можете зберегти локальну копію в хмарі або прийняти зміни з хмари."
|
||||
message: "Щоб це виправити, ви можете зберегти локальну копію в хмарі, або прийняти зміни з хмари."
|
||||
accept_local: "Заменить локальною"
|
||||
accept_cloud: "Прийняти облачні зміни"
|
||||
|
||||
+20
-20
@@ -1,47 +1,47 @@
|
||||
{
|
||||
"name": "tabs-aside",
|
||||
"private": true,
|
||||
"version": "3.0.0",
|
||||
"version": "3.0.0-rc4",
|
||||
"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",
|
||||
"@fluentui/react-components": "^9.63.0",
|
||||
"@fluentui/react-icons": "^2.0.298",
|
||||
"@webext-core/messaging": "^2.2.0",
|
||||
"@wxt-dev/analytics": "^0.4.1",
|
||||
"@wxt-dev/i18n": "^0.2.4",
|
||||
"@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"
|
||||
}
|
||||
|
||||
+5
-12
@@ -1,33 +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, ExtensionMessagingConfig, 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 { onMessage, sendMessage, removeAllListeners }: ExtensionMessenger<ProtocolMap> = defineExtensionMessaging<ProtocolMap>(config);
|
||||
const { onMessage, sendMessage, removeAllListeners } = defineExtensionMessaging<ProtocolMap>(config);
|
||||
|
||||
return {
|
||||
onMessage,
|
||||
removeAllListeners,
|
||||
async sendMessage<TType extends keyof ProtocolMap>(
|
||||
type: TType,
|
||||
data: GetDataType<ProtocolMap[TType]>,
|
||||
...args: ExtensionSendMessageArgs
|
||||
): Promise<GetReturnType<ProtocolMap[TType]>>
|
||||
sendMessage: async (type, data, args): Promise<any> =>
|
||||
{
|
||||
try
|
||||
{
|
||||
return await sendMessage(type, data, ...args);
|
||||
return await sendMessage(type, data, args);
|
||||
}
|
||||
catch (ex)
|
||||
{
|
||||
|
||||
@@ -88,9 +88,17 @@ async function createCollectionFromTabs(tabs: Tabs.Tab[]): Promise<[CollectionIt
|
||||
tabs.every(i => i.groupId === tabs[0].groupId)
|
||||
)
|
||||
{
|
||||
const group = await chrome.tabGroups.get(tabs[0].groupId);
|
||||
collection.title = group.title;
|
||||
collection.color = group.color;
|
||||
// TODO: Remove the check when Firefox 139 is out
|
||||
if (!import.meta.env.FIREFOX)
|
||||
{
|
||||
const group = await browser.tabGroups!.get(tabs[0].groupId);
|
||||
collection.title = group.title;
|
||||
collection.color = group.color;
|
||||
}
|
||||
else
|
||||
{
|
||||
collection.color = "blue";
|
||||
}
|
||||
|
||||
tabs.forEach(i =>
|
||||
collection.items.push({ type: "tab", url: i.url!, title: i.title })
|
||||
@@ -115,14 +123,27 @@ async function createCollectionFromTabs(tabs: Tabs.Tab[]): Promise<[CollectionIt
|
||||
if (!activeGroup || activeGroup !== tab.groupId)
|
||||
{
|
||||
activeGroup = tab.groupId;
|
||||
const group = await chrome.tabGroups.get(activeGroup);
|
||||
|
||||
collection.items.push({
|
||||
type: "group",
|
||||
color: group.color,
|
||||
title: group.title,
|
||||
items: []
|
||||
});
|
||||
// TODO: Remove the check when Firefox 139 is out
|
||||
if (import.meta.env.FIREFOX)
|
||||
{
|
||||
collection.items.push({
|
||||
type: "group",
|
||||
color: "blue",
|
||||
items: []
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
const group = await browser.tabGroups!.get(activeGroup);
|
||||
|
||||
collection.items.push({
|
||||
type: "group",
|
||||
color: group.color,
|
||||
title: group.title,
|
||||
items: []
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
(collection.items[collection.items.length - 1] as GroupItem).items.push({
|
||||
|
||||
+7
-5
@@ -24,8 +24,7 @@ export default defineConfig({
|
||||
description: "__MSG_manifest_description__",
|
||||
homepage_url: "https://github.com/xfox111/TabsAsideExtension/",
|
||||
|
||||
action:
|
||||
{
|
||||
action: {
|
||||
default_title: "__MSG_manifest_name__"
|
||||
},
|
||||
|
||||
@@ -37,8 +36,7 @@ export default defineConfig({
|
||||
"tabs",
|
||||
"notifications",
|
||||
"contextMenus",
|
||||
"bookmarks",
|
||||
"tabGroups"
|
||||
"bookmarks"
|
||||
],
|
||||
|
||||
commands:
|
||||
@@ -80,13 +78,17 @@ export default defineConfig({
|
||||
gecko:
|
||||
{
|
||||
id: "tabsaside@xfox111.net",
|
||||
strict_min_version: "139.0"
|
||||
strict_min_version: "138.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