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

Compare commits

...

25 Commits

Author SHA1 Message Date
xfox111 c0037cc3a7 fix(ci): firefox patch task fail 2025-07-19 02:40:53 +03:00
xfox111 db30e81765 fix(ci): set runners container to node 24 and enable corepack for yarn 4 2025-07-19 02:37:01 +03:00
xfox111 7e57488896 chore: release candidate 8 2025-07-19 02:31:12 +03:00
xfox111 61d0e54e58 fix: eslint errors 2025-07-19 02:30:27 +03:00
xfox111 a03f2ab442 chore: migration to yarn berry 2025-07-19 02:30:08 +03:00
xfox111 2f6ed6cbfa chore(deps): dependency updates / audit resolutions 2025-07-19 02:28:52 +03:00
xfox111 aac4ac9a7e chore(loc): minor typos in Russian and Ukrainian locales 2025-07-18 21:03:11 +03:00
xfox111 bc2e2489e6 fix: misplaced draggable item 2025-07-18 21:02:25 +03:00
xfox111 57e87aace0 chore: minor dnd collision detection improvements 2025-07-18 21:01:28 +03:00
xfox111 5f2d18f509 chore: large groups now collapse on drag-n-drop 2025-07-18 20:58:05 +03:00
xfox111 54a1e48672 hotfix: drag and drop doesn't work in Firefox popup 2025-07-18 20:55:58 +03:00
xfox111 e803636c35 chore: release candidate 7 2025-07-13 16:54:54 +03:00
xfox111 c8b4ef3e15 fix: tabs in collection list view overlap on overflow on Firefox 2025-07-13 16:50:02 +03:00
xfox111 eeefd1feff fix: collection context menus flickering in Firefox popup view 2025-07-13 16:20:17 +03:00
xfox111 53adbd4f75 fix: adding forbidden tabs via "Add selected tabs" 2025-07-11 13:53:27 +03:00
xfox111 3eed3b4b01 fix: list view horizontal overflow in sidebar on firefox 2025-07-09 00:49:09 +03:00
xfox111 525130b7e9 fix: collection collapses on tab/group drag 2025-07-06 12:34:02 +03:00
xfox111 1a274348e0 fix!: incorrect byte calculation for cloud storage (changed encoding) 2025-07-06 12:17:17 +03:00
xfox111 2c8cfa1583 chore: release candidate 6 2025-07-03 12:58:37 +03:00
xfox111 e844a68f49 chore(ui): remove fixed collection height for list view 2025-07-03 12:56:58 +03:00
xfox111 794f6e3af0 chore(locale): clarify action icon option for slaivc locales (ru, uk, pl) 2025-07-03 12:06:25 +03:00
xfox111 d85d10dc58 chore(ui): increase tab's dnd handle area 2025-07-03 11:46:53 +03:00
xfox111 213cc84602 chore: reduce delay and increase tolerance for mouse drag and drop 2025-07-03 11:33:05 +03:00
xfox111 9675b65e81 fix: pinned tab opens and closes infinitely if pwa window is opened 2025-07-03 11:24:53 +03:00
xfox111 6bce330a8f chore: dependabot reviewers deprecation 2025-06-03 18:55:01 +03:00
30 changed files with 10289 additions and 6992 deletions
+2
View File
@@ -0,0 +1,2 @@
* @XFox111
locales/pt_BR.yml @maisondasilva @XFox111
+3 -9
View File
@@ -12,9 +12,7 @@ updates:
directory: "/" # Location of package manifests
target-branch: "next"
assignees:
- "xfox111"
reviewers:
- "xfox111"
- "XFox111"
schedule:
interval: monthly
rebase-strategy: disabled
@@ -24,9 +22,7 @@ updates:
directory: "/"
target-branch: "next"
assignees:
- "xfox111"
reviewers:
- "xfox111"
- "XFox111"
schedule:
interval: monthly
rebase-strategy: disabled
@@ -36,9 +32,7 @@ updates:
directory: "/"
target-branch: "next"
assignees:
- "xfox111"
reviewers:
- "xfox111"
- "XFox111"
schedule:
interval: monthly
rebase-strategy: disabled
+9 -2
View File
@@ -38,7 +38,7 @@ on:
jobs:
build:
runs-on: ubuntu-latest
container: node:20
container: node:24
strategy:
fail-fast: false
matrix:
@@ -51,7 +51,14 @@ 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 }})
@@ -68,7 +75,7 @@ jobs:
extension-root: ./.output/firefox-mv3
self-hosted: false
- run: yarn audit
- run: yarn npm audit
continue-on-error: ${{ github.event_name != 'release' && github.event.inputs.bypass_audit == 'true' }}
publish-github:
+9 -2
View File
@@ -30,7 +30,7 @@ on:
jobs:
build:
runs-on: ubuntu-latest
container: node:23
container: node:24
strategy:
fail-fast: false
matrix:
@@ -43,7 +43,14 @@ 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 }})
@@ -60,4 +67,4 @@ jobs:
extension-root: ./.output/firefox-mv3
self-hosted: false
- run: yarn audit
- run: yarn npm audit
+1
View File
@@ -8,6 +8,7 @@ pnpm-debug.log*
lerna-debug.log*
node_modules
.yarn/
.output
stats.html
stats-*.json
+117
View File
@@ -0,0 +1,117 @@
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"
+3
View File
@@ -39,16 +39,19 @@ 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
View File
@@ -257,7 +257,7 @@ export default defineBackground(() =>
for (const openWindow of openWindows)
{
if (openWindow.incognito)
if (openWindow.incognito || openWindow.type !== "normal")
continue;
const activeTabs: Tabs.Tab[] = openWindow.tabs!.filter(tab =>
@@ -27,7 +27,7 @@ export const useStyles_CollectionView = makeStyles({
},
verticalRoot:
{
height: "560px"
maxHeight: "560px"
},
empty:
{
@@ -74,7 +74,8 @@ export const useStyles_CollectionView = makeStyles({
{
gridAutoFlow: "row",
width: "100%",
paddingBottom: tokens.spacingVerticalS
paddingBottom: tokens.spacingVerticalS,
gridAutoRows: import.meta.env.FIREFOX ? "min-content" : undefined
},
dragOverlay:
{
@@ -50,26 +50,30 @@ export default function CollectionView({ collection, index: collectionIndex, dra
<CollectionHeader dragHandleProps={ activatorProps } dragHandleRef={ setActivatorNodeRef } />
{ 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>
{ (!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>
}
</>
}
</div >
</CollectionContext.Provider>
@@ -99,12 +99,23 @@ export const useStyles_GroupView = makeStyles({
display: "flex",
columnGap: tokens.spacingHorizontalS,
rowGap: tokens.spacingHorizontalSNudge,
height: "100%"
height: "100%",
position: "relative"
},
verticalList:
{
flexFlow: "column"
},
verticalListCollapsed:
{
maxHeight: "136px",
overflow: "clip"
},
horizontalListCollapsed:
{
maxWidth: "400px",
overflow: "clip"
},
listContainer:
{
padding: `${tokens.spacingVerticalS} ${tokens.spacingHorizontalXS}`,
@@ -88,7 +88,13 @@ export default function GroupView({ group, indices, dragOverlay }: GroupViewProp
<Caption1Strong>{ i18n.t("groups.empty") }</Caption1Strong>
</div>
:
<div className={ mergeClasses(cls.list, !tilesView && cls.verticalList) }>
<div
className={ mergeClasses(
cls.list,
!tilesView && cls.verticalList,
((active?.item.type === "group" && active?.indices[0] === indices[0]) || dragOverlay) && (tilesView ? cls.horizontalListCollapsed : cls.verticalListCollapsed)
) }
>
<SortableContext
items={ group.items.map((_, index) => [...indices, index].join("/")) }
disabled={ disableSorting }
@@ -8,6 +8,7 @@ export const useStyles_TabView = makeStyles({
width: "160px",
height: "120px",
flexShrink: 0,
marginBottom: tokens.spacingVerticalSNudge,
border: `${tokens.strokeWidthThin} solid ${tokens.colorNeutralStroke3}`,
@@ -58,8 +59,6 @@ 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,
@@ -72,6 +71,9 @@ export const useStyles_TabView = makeStyles({
icon:
{
cursor: "grab",
padding: `${tokens.spacingVerticalSNudge} ${tokens.spacingHorizontalSNudge}`,
height: "32px",
boxSizing: "border-box",
"&:active":
{
@@ -83,7 +83,6 @@ 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,6 +12,7 @@ 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();
@@ -53,7 +54,7 @@ export default function CollectionHeader({ dragHandleRef, dragHandleProps }: Col
mergeClasses(
cls.toolbar,
"CollectionView__toolbar",
alwaysShowToolbars === true && cls.showToolbar
(alwaysShowToolbars === true || contextOpen) && cls.showToolbar
) }
>
{ tabCount < 1 ?
@@ -61,10 +62,10 @@ export default function CollectionHeader({ dragHandleRef, dragHandleProps }: Col
{ isTab ? i18n.t("collections.menu.add_all") : i18n.t("collections.menu.add_selected") }
</Button>
:
<OpenCollectionButton />
<OpenCollectionButton onOpenChange={ (_, e) => setContextOpen(e.open) } />
}
<CollectionMoreButton onAddSelected={ handleAddSelected } />
<CollectionMoreButton onAddSelected={ handleAddSelected } onOpenChange={ (_, e) => setContextOpen(e.open) } />
</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, MenuPopover, MenuTrigger, Tooltip } from "@fluentui/react-components";
import { Button, Menu, MenuDivider, MenuItem, MenuList, MenuOpenChangeData, MenuOpenEvent, 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 }: CollectionMoreButtonProps): React.ReactElement
export default function CollectionMoreButton({ onAddSelected, onOpenChange }: CollectionMoreButtonProps): React.ReactElement
{
const [listLocation] = useSettings("listLocation");
const isTab: boolean = listLocation === "tab" || listLocation === "pinned";
@@ -56,7 +56,7 @@ export default function CollectionMoreButton({ onAddSelected }: CollectionMoreBu
);
return (
<Menu>
<Menu onOpenChange={ onOpenChange }>
<Tooltip relationship="label" content={ i18n.t("common.tooltips.more") }>
<MenuTrigger>
<Button appearance="subtle" icon={ <ic.MoreVertical20Regular /> } />
@@ -94,4 +94,5 @@ export default function CollectionMoreButton({ onAddSelected }: CollectionMoreBu
export type CollectionMoreButtonProps =
{
onAddSelected?: () => void;
onOpenChange?: (e: MenuOpenEvent, data: MenuOpenChangeData) => void;
};
@@ -1,13 +1,13 @@
import { useDialog } from "@/contexts/DialogProvider";
import useSettings from "@/hooks/useSettings";
import browserLocaleKey from "@/utils/browserLocaleKey";
import { Menu, MenuButtonProps, MenuItem, MenuList, MenuPopover, MenuTrigger, SplitButton } from "@fluentui/react-components";
import { Menu, MenuButtonProps, MenuItem, MenuList, MenuOpenChangeData, MenuOpenEvent, 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(): React.ReactElement
export default function OpenCollectionButton({ onOpenChange }: OpenCollectionButtonProps): React.ReactElement
{
const [defaultAction] = useSettings("defaultRestoreAction");
const { removeItem } = useCollections();
@@ -54,7 +54,7 @@ export default function OpenCollectionButton(): React.ReactElement
};
return (
<Menu>
<Menu onOpenChange={ onOpenChange }>
<MenuTrigger disableButtonEnhancement>
{ (triggerProps: MenuButtonProps) => defaultAction === "restore" ?
<SplitButton
@@ -95,3 +95,8 @@ export default function OpenCollectionButton(): React.ReactElement
</Menu>
);
}
export type OpenCollectionButtonProps =
{
onOpenChange?: (e: MenuOpenEvent, data: MenuOpenChangeData) => void;
};
@@ -46,6 +46,10 @@ export const useStyles_CollectionListView = makeStyles({
listView:
{
display: "grid",
gridTemplateColumns: "repeat(auto-fit, minmax(360px, 1fr))"
"@media screen and (min-width: 360px)":
{
gridTemplateColumns: "repeat(auto-fit, minmax(360px, 1fr))"
}
}
});
@@ -21,6 +21,7 @@ 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
{
@@ -33,7 +34,7 @@ export default function CollectionListView(): ReactElement
const [active, setActive] = useState<DndItem | null>(null);
const sensors = useSensors(
useSensor(MouseSensor, { activationConstraint: { delay: 100, tolerance: 0 } }),
useSensor(MouseSensor, { activationConstraint: { delay: 10, tolerance: 20 } }),
useSensor(TouchSensor, { activationConstraint: { delay: 300, tolerance: 20 } })
);
@@ -110,6 +111,7 @@ export default function CollectionListView(): ReactElement
collisionDetection={ collisionDetector(!tilesView) }
onDragStart={ handleDragStart }
onDragEnd={ handleDragEnd }
modifiers={ [snapHandleToCursor] }
>
<SortableContext
items={ resultList.map((_, index) => index.toString()) }
@@ -33,9 +33,11 @@ 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;
@@ -44,14 +46,20 @@ 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;
if (intersectionCoefficient < 0.7 && activeItem.item.type === "tab")
// 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)
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;
@@ -62,16 +70,23 @@ 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 = 1 / intersectionRatio;
value = 2 / 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] &&
@@ -79,11 +94,15 @@ 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])
@@ -92,9 +111,22 @@ 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);
@@ -0,0 +1,34 @@
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;
};
+18 -2
View File
@@ -1,8 +1,24 @@
import { TabItem } from "@/models/CollectionModels";
import sendNotification from "@/utils/sendNotification";
import { Tabs } from "wxt/browser";
export default async function getSelectedTabs(): Promise<TabItem[]>
{
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 }));
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 }));
}
@@ -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: "StorageBinaryString" });
const data: string = decompress(Object.values(chunks).join(), { inputEncoding: "Base64" });
return parseCollections(data);
}
@@ -14,7 +14,7 @@ export default async function saveCollectionsToCloud(collections: CollectionItem
return;
}
const data: string = compress(serializeCollections(collections), { outputEncoding: "StorageBinaryString" });
const data: string = compress(serializeCollections(collections), { outputEncoding: "Base64" });
const chunks: string[] = splitIntoChunks(data);
if (chunks.length > collectionStorage.maxChunkCount)
+1 -1
View File
@@ -84,7 +84,7 @@ options_page:
icon_action:
title: "Po kliknięciu ikony rozszerzenia:"
options:
action: "Wykonaj domyślną akcję"
action: "Zapisz karty (domyślna akcja)"
context: "Pokaż menu kontekstowe"
open: "Otwórz listę kolekcji"
change_shortcuts: "Zmień skróty klawiszowe"
+5 -5
View File
@@ -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
View File
@@ -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: "Прийняти облачні зміни"
+21 -21
View File
@@ -1,47 +1,47 @@
{
"name": "tabs-aside",
"private": true,
"version": "3.0.0-rc5",
"version": "3.0.0-rc8",
"type": "module",
"scripts": {
"dev": "wxt",
"build": "wxt build --mv3",
"zip": "wxt zip --mv3",
"build": "yarn lint && wxt build --mv3",
"zip": "yarn lint && wxt zip --mv3",
"lint": "tsc --noEmit && eslint . -c eslint.config.js",
"prebuild": "yarn lint",
"prezip": "yarn lint",
"postinstall": "wxt prepare"
"prepare": "wxt prepare",
"postinstall": "yarn 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.63.0",
"@fluentui/react-icons": "^2.0.298",
"@webext-core/messaging": "^2.2.0",
"@wxt-dev/analytics": "^0.4.1",
"@wxt-dev/i18n": "^0.2.3",
"@fluentui/react-components": "^9.67.0",
"@fluentui/react-icons": "^2.0.306",
"@webext-core/messaging": "^2.3.0",
"@wxt-dev/analytics": "0.4.1",
"@wxt-dev/i18n": "^0.2.4",
"lzutf8": "^0.6.3",
"react": "^18.3.1",
"react": "18.3.1",
"react-dom": "18.3.1"
},
"devDependencies": {
"@eslint/css": "^0.7.0",
"@eslint/js": "^9.26.0",
"@eslint/json": "^0.12.0",
"@stylistic/eslint-plugin": "^4.2.0",
"@eslint/css": "^0.10.0",
"@eslint/js": "^9.31.0",
"@eslint/json": "^0.13.0",
"@stylistic/eslint-plugin": "^5.2.0",
"@types/react": "^18.3.1",
"@types/react-dom": "^18.3.1",
"@types/scheduler": "^0",
"@wxt-dev/module-react": "^1.1.3",
"eslint": "^9.26.0",
"eslint": "^9.31.0",
"eslint-plugin-react": "^7.37.5",
"globals": "^16.0.0",
"globals": "^16.3.0",
"scheduler": "0.23.0",
"typescript": "^5.8.3",
"typescript-eslint": "^8.31.1",
"vite": "^6.3.4",
"typescript-eslint": "^8.37.0",
"vite": "^6.3.5",
"wxt": "~0.19.29"
},
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
"packageManager": "yarn@4.9.2"
}
+8 -4
View File
@@ -1,6 +1,6 @@
import { trackError } from "@/features/analytics";
import { GraphicsStorage } from "@/models/CollectionModels";
import { defineExtensionMessaging, ExtensionMessagingConfig, ExtensionMessenger } from "@webext-core/messaging";
import { defineExtensionMessaging, ExtensionMessagingConfig, ExtensionMessenger, ExtensionSendMessageArgs, GetDataType, GetReturnType } from "@webext-core/messaging";
type ProtocolMap =
{
@@ -11,16 +11,20 @@ type ProtocolMap =
function defineMessaging(config?: ExtensionMessagingConfig): ExtensionMessenger<ProtocolMap>
{
const { onMessage, sendMessage, removeAllListeners } = defineExtensionMessaging<ProtocolMap>(config);
const { onMessage, sendMessage, removeAllListeners }: ExtensionMessenger<ProtocolMap> = defineExtensionMessaging<ProtocolMap>(config);
return {
onMessage,
removeAllListeners,
sendMessage: async (type, data, args): Promise<any> =>
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);
return await sendMessage(type, data, ...args);
}
catch (ex)
{
+9945 -6901
View File
File diff suppressed because it is too large Load Diff