diff options
| author | kamtschatka <simon.schatka@gmx.at> | 2024-07-27 22:32:37 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-07-27 13:32:37 -0700 |
| commit | 5e4decbe967ec28f9263bcb1d9907ee86262b91e (patch) | |
| tree | 0a8f9764af4ef3bc2db7c5e993fef354782a2285 | |
| parent | eb0a28ee6b1dd15fb459e0900f5e885ac5c92fec (diff) | |
| download | karakeep-5e4decbe967ec28f9263bcb1d9907ee86262b91e.tar.zst | |
feature(cli): Allow updating tags/lists from CLI (#211)
* Improve the CLI #209
added the possibility to assign tags to bookmarks while creating
added the possibility to assign a newly created to a list right away
added the possibility to add and remove tags from bookmarks
* minor tweaks
---------
Co-authored-by: MohamedBassem <me@mbassem.com>
Diffstat (limited to '')
| -rw-r--r-- | apps/browser-extension/src/components/TagsSelector.tsx | 4 | ||||
| -rw-r--r-- | apps/cli/src/commands/bookmarks.ts | 108 | ||||
| -rw-r--r-- | apps/cli/src/commands/lists.ts | 38 | ||||
| -rw-r--r-- | packages/trpc/routers/bookmarks.test.ts | 24 | ||||
| -rw-r--r-- | packages/trpc/routers/bookmarks.ts | 55 |
5 files changed, 182 insertions, 47 deletions
diff --git a/apps/browser-extension/src/components/TagsSelector.tsx b/apps/browser-extension/src/components/TagsSelector.tsx index 45cf11d5..5ca3b2d0 100644 --- a/apps/browser-extension/src/components/TagsSelector.tsx +++ b/apps/browser-extension/src/components/TagsSelector.tsx @@ -32,14 +32,14 @@ export function TagsSelector({ bookmarkId }: { bookmarkId: string }) { const { mutate } = useUpdateBookmarkTags({ onMutate: (req) => { req.attach.forEach((t) => currentlyUpdating.add(t.tagId ?? "")); - req.detach.forEach((t) => currentlyUpdating.add(t.tagId)); + req.detach.forEach((t) => currentlyUpdating.add(t.tagId ?? "")); }, onSettled: (_resp, _err, req) => { if (!req) { return; } req.attach.forEach((t) => currentlyUpdating.delete(t.tagId ?? "")); - req.detach.forEach((t) => currentlyUpdating.delete(t.tagId)); + req.detach.forEach((t) => currentlyUpdating.delete(t.tagId ?? "")); }, }); diff --git a/apps/cli/src/commands/bookmarks.ts b/apps/cli/src/commands/bookmarks.ts index 6efb41d9..51dd037b 100644 --- a/apps/cli/src/commands/bookmarks.ts +++ b/apps/cli/src/commands/bookmarks.ts @@ -1,4 +1,5 @@ import * as fs from "node:fs"; +import { addToList } from "@/commands/lists"; import { printError, printObject, @@ -23,7 +24,11 @@ function collect<T>(val: T, acc: T[]) { return acc; } -function normalizeBookmark(bookmark: ZBookmark) { +type Bookmark = Omit<ZBookmark, "tags"> & { + tags: string[]; +}; + +function normalizeBookmark(bookmark: ZBookmark): Bookmark { const ret = { ...bookmark, tags: bookmark.tags.map((t) => t.name), @@ -58,10 +63,20 @@ bookmarkCmd [], ) .option("--stdin", "reads the data from stdin and store it as a note") + .option( + "--list-id <id>", + "if set, the bookmark(s) will be added to this list", + ) + .option( + "--tag-name <tag>", + "if set, this tag will be added to the bookmark(s). Specify multiple times to add multiple tags", + collect<string>, + [], + ) .action(async (opts) => { const api = getAPIClient(); - const results: object[] = []; + const results: Bookmark[] = []; const promises = [ ...opts.link.map((url) => @@ -104,6 +119,13 @@ bookmarkCmd await Promise.allSettled(promises); printObject(results); + + await Promise.allSettled( + results.flatMap((r) => [ + updateTags(opts.tagName, [], r.id), + opts.listId ? addToList(opts.listId, r.id) : Promise.resolve(), + ]), + ); }); bookmarkCmd @@ -118,6 +140,48 @@ bookmarkCmd .catch(printError(`Failed to get the bookmark with id "${id}"`)); }); +function printTagMessage( + tags: { tagName: string }[], + bookmarkId: string, + action: "Added" | "Removed", +) { + tags.forEach((tag) => { + printStatusMessage( + true, + `${action} the tag ${tag.tagName} ${action === "Added" ? "to" : "from"} the bookmark with id ${bookmarkId}`, + ); + }); +} + +async function updateTags(addTags: string[], removeTags: string[], id: string) { + const tagsToAdd = addTags.map((addTag) => { + return { tagName: addTag }; + }); + + const tagsToRemove = removeTags.map((removeTag) => { + return { tagName: removeTag }; + }); + + if (tagsToAdd.length > 0 || tagsToRemove.length > 0) { + const api = getAPIClient(); + await api.bookmarks.updateTags + .mutate({ + bookmarkId: id, + attach: tagsToAdd, + detach: tagsToRemove, + }) + .then(() => { + printTagMessage(tagsToAdd, id, "Added"); + printTagMessage(tagsToRemove, id, "Removed"); + }) + .catch( + printError( + `Failed to add/remove tags to/from bookmark with id "${id}"`, + ), + ); + } +} + bookmarkCmd .command("update") .description("update a bookmark") @@ -127,18 +191,40 @@ bookmarkCmd .option("--no-archive", "if set, the bookmark will be unarchived") .option("--favourite", "if set, the bookmark will be favourited") .option("--no-favourite", "if set, the bookmark will be unfavourited") + .option( + "--add-tag <tag>", + "if set, this tag will be added to the bookmark. Specify multiple times to add multiple tags", + collect<string>, + [], + ) + .option( + "--remove-tag <tag>", + "if set, this tag will be removed from the bookmark. Specify multiple times to remove multiple tags", + collect<string>, + [], + ) .argument("<id>", "the id of the bookmark to get") .action(async (id, opts) => { const api = getAPIClient(); - await api.bookmarks.updateBookmark - .mutate({ - bookmarkId: id, - archived: opts.archive, - favourited: opts.favourite, - title: opts.title, - }) - .then(printObject) - .catch(printError(`Failed to update bookmark with id "${id}"`)); + await updateTags(opts.addTag, opts.removeTag, id); + + if ( + "archive" in opts || + "favourite" in opts || + "title" in opts || + "note" in opts + ) { + await api.bookmarks.updateBookmark + .mutate({ + bookmarkId: id, + archived: opts.archive, + favourited: opts.favourite, + title: opts.title, + note: opts.note, + }) + .then(printObject) + .catch(printError(`Failed to update bookmark with id "${id}"`)); + } }); bookmarkCmd diff --git a/apps/cli/src/commands/lists.ts b/apps/cli/src/commands/lists.ts index 2f85ae7b..855624d6 100644 --- a/apps/cli/src/commands/lists.ts +++ b/apps/cli/src/commands/lists.ts @@ -62,29 +62,33 @@ listsCmd .catch(printError(`Failed to delete list with id "${id}"`)); }); +export async function addToList(listId: string, bookmarkId: string) { + const api = getAPIClient(); + + await api.lists.addToList + .mutate({ + listId, + bookmarkId, + }) + .then( + printSuccess( + `Successfully added bookmark "${bookmarkId}" to list with id "${listId}"`, + ), + ) + .catch( + printError( + `Failed to add bookmark "${bookmarkId}" to list with id "${listId}"`, + ), + ); +} + listsCmd .command("add-bookmark") .description("add a bookmark to list") .requiredOption("--list <id>", "the id of the list") .requiredOption("--bookmark <bookmark>", "the id of the bookmark") .action(async (opts) => { - const api = getAPIClient(); - - await api.lists.addToList - .mutate({ - listId: opts.list, - bookmarkId: opts.bookmark, - }) - .then( - printSuccess( - `Successfully added bookmark "${opts.bookmark}" to list with id "${opts.list}"`, - ), - ) - .catch( - printError( - `Failed to add bookmark "${opts.bookmark}" to list with id "${opts.list}"`, - ), - ); + await addToList(opts.list, opts.bookmark); }); listsCmd diff --git a/packages/trpc/routers/bookmarks.test.ts b/packages/trpc/routers/bookmarks.test.ts index 67e73ad1..802bd992 100644 --- a/packages/trpc/routers/bookmarks.test.ts +++ b/packages/trpc/routers/bookmarks.test.ts @@ -126,23 +126,37 @@ describe("Bookmark Routes", () => { await api.updateTags({ bookmarkId: createdBookmark.id, - attach: [{ tagName: "tag1" }, { tagName: "tag2" }], + attach: [ + { tagName: "tag1" }, + { tagName: "tag2" }, + { tagName: "tag3" }, + { tagName: "tag4" }, + ], detach: [], }); let bookmark = await api.getBookmark({ bookmarkId: createdBookmark.id }); - expect(bookmark.tags.map((t) => t.name).sort()).toEqual(["tag1", "tag2"]); + expect(bookmark.tags.map((t) => t.name).sort()).toEqual([ + "tag1", + "tag2", + "tag3", + "tag4", + ]); const tag1Id = bookmark.tags.filter((t) => t.name == "tag1")[0].id; await api.updateTags({ bookmarkId: bookmark.id, - attach: [{ tagName: "tag3" }], - detach: [{ tagId: tag1Id }], + attach: [{ tagName: "tag5" }], + detach: [{ tagId: tag1Id }, { tagName: "tag4" }], }); bookmark = await api.getBookmark({ bookmarkId: bookmark.id }); - expect(bookmark.tags.map((t) => t.name).sort()).toEqual(["tag2", "tag3"]); + expect(bookmark.tags.map((t) => t.name).sort()).toEqual([ + "tag2", + "tag3", + "tag5", + ]); await api.updateTags({ bookmarkId: bookmark.id, diff --git a/packages/trpc/routers/bookmarks.ts b/packages/trpc/routers/bookmarks.ts index d2aa36bb..e685d5c2 100644 --- a/packages/trpc/routers/bookmarks.ts +++ b/packages/trpc/routers/bookmarks.ts @@ -665,8 +665,13 @@ export const bookmarksAppRouter = router({ tagName: z.string().optional(), }), ), - // Detach by tag ids - detach: z.array(z.object({ tagId: z.string() })), + detach: z.array( + z.object({ + // At least one of the two must be set + tagId: z.string().optional(), + tagName: z.string().optional(), // Also allow removing by tagName, to make CLI usage easier + }), + ), }), ) .output( @@ -679,23 +684,49 @@ export const bookmarksAppRouter = router({ .mutation(async ({ input, ctx }) => { return ctx.db.transaction(async (tx) => { // Detaches + const idsToRemove: string[] = []; if (input.detach.length > 0) { - await tx.delete(tagsOnBookmarks).where( - and( - eq(tagsOnBookmarks.bookmarkId, input.bookmarkId), - inArray( - tagsOnBookmarks.tagId, - input.detach.map((t) => t.tagId), + const namesToRemove: string[] = []; + input.detach.forEach((detachInfo) => { + if (detachInfo.tagId) { + idsToRemove.push(detachInfo.tagId); + } + if (detachInfo.tagName) { + namesToRemove.push(detachInfo.tagName); + } + }); + + if (namesToRemove.length > 0) { + ( + await tx.query.bookmarkTags.findMany({ + where: and( + eq(bookmarkTags.userId, ctx.user.id), + inArray(bookmarkTags.name, namesToRemove), + ), + columns: { + id: true, + }, + }) + ).forEach((tag) => { + idsToRemove.push(tag.id); + }); + } + + await tx + .delete(tagsOnBookmarks) + .where( + and( + eq(tagsOnBookmarks.bookmarkId, input.bookmarkId), + inArray(tagsOnBookmarks.tagId, idsToRemove), ), - ), - ); + ); } if (input.attach.length == 0) { return { bookmarkId: input.bookmarkId, attached: [], - detached: input.detach.map((t) => t.tagId), + detached: idsToRemove, }; } @@ -751,7 +782,7 @@ export const bookmarksAppRouter = router({ return { bookmarkId: input.bookmarkId, attached: allIds, - detached: input.detach.map((t) => t.tagId), + detached: idsToRemove, }; }); }), |
