diff options
| author | omnitrix <gaobowei.ovo@gmail.com> | 2025-04-16 16:18:04 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-04-16 15:18:04 +0100 |
| commit | 71a474268e6381f8630f6d906471c830342dc50f (patch) | |
| tree | e7dc62795d4b7aad1daeff78c94c12e63c38f3ef /packages | |
| parent | dd990b51f3f85f70298a9b437b16ee7bbb406d24 (diff) | |
| download | karakeep-71a474268e6381f8630f6d906471c830342dc50f.tar.zst | |
feat(web): Add support for merging lists (#1231)
* feat: Support list merging
* some fixes
* move @hoarder to @karakeep
---------
Co-authored-by: Mohamed Bassem <me@mbassem.com>
Diffstat (limited to 'packages')
| -rw-r--r-- | packages/shared-react/hooks/lists.ts | 15 | ||||
| -rw-r--r-- | packages/shared/types/lists.ts | 13 | ||||
| -rw-r--r-- | packages/trpc/models/lists.ts | 46 | ||||
| -rw-r--r-- | packages/trpc/routers/lists.ts | 13 |
4 files changed, 87 insertions, 0 deletions
diff --git a/packages/shared-react/hooks/lists.ts b/packages/shared-react/hooks/lists.ts index 4dd9bc2b..1a98cac5 100644 --- a/packages/shared-react/hooks/lists.ts +++ b/packages/shared-react/hooks/lists.ts @@ -36,6 +36,21 @@ export function useEditBookmarkList( }); } +export function useMergeLists( + ...opts: Parameters<typeof api.lists.merge.useMutation> +) { + const apiUtils = api.useUtils(); + return api.lists.merge.useMutation({ + ...opts[0], + onSuccess: (res, req, meta) => { + apiUtils.lists.list.invalidate(); + apiUtils.bookmarks.getBookmarks.invalidate({ listId: req.targetId }); + apiUtils.lists.stats.invalidate(); + return opts[0]?.onSuccess?.(res, req, meta); + }, + }); +} + export function useAddBookmarkToList( ...opts: Parameters<typeof api.lists.addToList.useMutation> ) { diff --git a/packages/shared/types/lists.ts b/packages/shared/types/lists.ts index 474405ee..7ef5687c 100644 --- a/packages/shared/types/lists.ts +++ b/packages/shared/types/lists.ts @@ -85,3 +85,16 @@ export const zEditBookmarkListSchemaWithValidation = zEditBookmarkListSchema "Smart lists cannot have unqualified terms (aka full text search terms) in the query", path: ["query"], }); + +export const zMergeListSchema = z + .object({ + sourceId: z.string(), + targetId: z.string(), + deleteSourceAfterMerge: z.boolean(), + }) + .refine((val) => val.sourceId !== val.targetId, { + message: "Cannot merge a list into itself", + path: ["targetId"], + }); + +export type ZMergeList = z.infer<typeof zMergeListSchema>; diff --git a/packages/trpc/models/lists.ts b/packages/trpc/models/lists.ts index a6b1720c..8072060f 100644 --- a/packages/trpc/models/lists.ts +++ b/packages/trpc/models/lists.ts @@ -145,6 +145,10 @@ export abstract class List implements PrivacyAware { abstract getSize(ctx: AuthedContext): Promise<number>; abstract addBookmark(bookmarkId: string): Promise<void>; abstract removeBookmark(bookmarkId: string): Promise<void>; + abstract mergeInto( + targetList: List, + deleteSourceAfterMerge: boolean, + ): Promise<void>; } export class SmartList extends List { @@ -200,6 +204,16 @@ export class SmartList extends List { message: "Smart lists cannot be removed from", }); } + + mergeInto( + _targetList: List, + _deleteSourceAfterMerge: boolean, + ): Promise<void> { + throw new TRPCError({ + code: "BAD_REQUEST", + message: "Smart lists cannot be merged", + }); + } } export class ManualList extends List { @@ -276,4 +290,36 @@ export class ManualList extends List { } return super.update(input); } + + async mergeInto( + targetList: List, + deleteSourceAfterMerge: boolean, + ): Promise<void> { + if (targetList.type !== "manual") { + throw new TRPCError({ + code: "BAD_REQUEST", + message: "You can only merge into a manual list", + }); + } + + const bookmarkIds = await this.getBookmarkIds(); + + await this.ctx.db.transaction(async (tx) => { + await tx + .insert(bookmarksInLists) + .values( + bookmarkIds.map((id) => ({ + bookmarkId: id, + listId: targetList.list.id, + })), + ) + .onConflictDoNothing(); + + if (deleteSourceAfterMerge) { + await tx + .delete(bookmarkLists) + .where(eq(bookmarkLists.id, this.list.id)); + } + }); + } } diff --git a/packages/trpc/routers/lists.ts b/packages/trpc/routers/lists.ts index f988eb8b..12960316 100644 --- a/packages/trpc/routers/lists.ts +++ b/packages/trpc/routers/lists.ts @@ -4,6 +4,7 @@ import { z } from "zod"; import { zBookmarkListSchema, zEditBookmarkListSchemaWithValidation, + zMergeListSchema, zNewBookmarkListSchema, } from "@karakeep/shared/types/lists"; @@ -39,6 +40,18 @@ export const listsAppRouter = router({ .mutation(async ({ input, ctx }) => { return await ctx.list.update(input); }), + merge: authedProcedure + .input(zMergeListSchema) + .mutation(async ({ input, ctx }) => { + const [sourceList, targetList] = await Promise.all([ + List.fromId(ctx, input.sourceId), + List.fromId(ctx, input.targetId), + ]); + return await sourceList.mergeInto( + targetList, + input.deleteSourceAfterMerge, + ); + }), delete: authedProcedure .input( z.object({ |
