aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--apps/browser-extension/src/components/TagsSelector.tsx4
-rw-r--r--apps/cli/src/commands/bookmarks.ts108
-rw-r--r--apps/cli/src/commands/lists.ts38
-rw-r--r--packages/trpc/routers/bookmarks.test.ts24
-rw-r--r--packages/trpc/routers/bookmarks.ts55
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,
};
});
}),