aboutsummaryrefslogtreecommitdiffstats
path: root/packages/shared
diff options
context:
space:
mode:
authorMohamed Bassem <me@mbassem.com>2025-01-02 13:00:58 +0200
committerGitHub <noreply@github.com>2025-01-02 13:00:58 +0200
commit5ecdc36b7d60aa66b49e01e9fec8ba61ad537376 (patch)
tree57577822bb104b95900ba577a265fb4f8cf70b78 /packages/shared
parent5df0258b2cd884347eabfa866d7e7fbc7225cdb3 (diff)
downloadkarakeep-5ecdc36b7d60aa66b49e01e9fec8ba61ad537376.tar.zst
feat: Add support for smart lists (#802)
* feat: Add support for smart lists * i18n * Fix update list endpoint * Add a test for smart lists * Add header to the query explainer * Hide remove from lists in the smart context list * Add proper validation to list form --------- Co-authored-by: Deepak Kapoor <41769111+orthdron@users.noreply.github.com>
Diffstat (limited to '')
-rw-r--r--packages/shared-react/hooks/bookmark-list-context.tsx27
-rw-r--r--packages/shared-react/hooks/lists.ts3
-rw-r--r--packages/shared/types/lists.ts82
3 files changed, 95 insertions, 17 deletions
diff --git a/packages/shared-react/hooks/bookmark-list-context.tsx b/packages/shared-react/hooks/bookmark-list-context.tsx
new file mode 100644
index 00000000..d00e0567
--- /dev/null
+++ b/packages/shared-react/hooks/bookmark-list-context.tsx
@@ -0,0 +1,27 @@
+"use client";
+
+import { createContext, useContext } from "react";
+
+import { ZBookmarkList } from "@hoarder/shared/types/lists";
+
+export const BookmarkListContext = createContext<ZBookmarkList | undefined>(
+ undefined,
+);
+
+export function BookmarkListContextProvider({
+ list,
+ children,
+}: {
+ list: ZBookmarkList;
+ children: React.ReactNode;
+}) {
+ return (
+ <BookmarkListContext.Provider value={list}>
+ {children}
+ </BookmarkListContext.Provider>
+ );
+}
+
+export function useBookmarkListContext() {
+ return useContext(BookmarkListContext);
+}
diff --git a/packages/shared-react/hooks/lists.ts b/packages/shared-react/hooks/lists.ts
index 10633a08..46477228 100644
--- a/packages/shared-react/hooks/lists.ts
+++ b/packages/shared-react/hooks/lists.ts
@@ -28,6 +28,9 @@ export function useEditBookmarkList(
onSuccess: (res, req, meta) => {
apiUtils.lists.list.invalidate();
apiUtils.lists.get.invalidate({ listId: req.listId });
+ if (res.type === "smart") {
+ apiUtils.bookmarks.getBookmarks.invalidate({ listId: req.listId });
+ }
return opts[0]?.onSuccess?.(res, req, meta);
},
});
diff --git a/packages/shared/types/lists.ts b/packages/shared/types/lists.ts
index d2041907..bd6786b0 100644
--- a/packages/shared/types/lists.ts
+++ b/packages/shared/types/lists.ts
@@ -1,28 +1,76 @@
import { z } from "zod";
-export const zNewBookmarkListSchema = z.object({
- name: z
- .string()
- .min(1, "List name can't be empty")
- .max(40, "List name is at most 40 chars"),
- icon: z.string(),
- parentId: z.string().nullish(),
-});
+import { parseSearchQuery } from "../searchQueryParser";
+
+export const zNewBookmarkListSchema = z
+ .object({
+ name: z
+ .string()
+ .min(1, "List name can't be empty")
+ .max(40, "List name is at most 40 chars"),
+ icon: z.string(),
+ type: z.enum(["manual", "smart"]).optional().default("manual"),
+ query: z.string().min(1).optional(),
+ parentId: z.string().nullish(),
+ })
+ .refine((val) => val.type === "smart" || !val.query, {
+ message: "Manual lists cannot have a query",
+ path: ["query"],
+ })
+ .refine((val) => val.type === "manual" || val.query, {
+ message: "Smart lists must have a query",
+ path: ["query"],
+ })
+ .refine(
+ (val) => !val.query || parseSearchQuery(val.query).result === "full",
+ {
+ message: "Smart search query is not valid",
+ path: ["query"],
+ },
+ )
+ .refine((val) => !val.query || parseSearchQuery(val.query).text.length == 0, {
+ message:
+ "Smart lists cannot have unqualified terms (aka full text search terms) in the query",
+ path: ["query"],
+ });
export const zBookmarkListSchema = z.object({
id: z.string(),
name: z.string(),
icon: z.string(),
parentId: z.string().nullable(),
+ type: z.enum(["manual", "smart"]).default("manual"),
+ query: z.string().nullish(),
});
-export const zBookmarkListWithBookmarksSchema = zBookmarkListSchema.merge(
- z.object({
- bookmarks: z.array(z.string()),
- }),
-);
-
export type ZBookmarkList = z.infer<typeof zBookmarkListSchema>;
-export type ZBookmarkListWithBookmarks = z.infer<
- typeof zBookmarkListWithBookmarksSchema
->;
+
+export const zEditBookmarkListSchema = z.object({
+ listId: z.string(),
+ name: z
+ .string()
+ .min(1, "List name can't be empty")
+ .max(40, "List name is at most 40 chars")
+ .optional(),
+ icon: z.string().optional(),
+ parentId: z.string().nullish(),
+ query: z.string().min(1).optional(),
+});
+
+export const zEditBookmarkListSchemaWithValidation = zEditBookmarkListSchema
+ .refine((val) => val.parentId != val.listId, {
+ message: "List can't be its own parent",
+ path: ["parentId"],
+ })
+ .refine(
+ (val) => !val.query || parseSearchQuery(val.query).result === "full",
+ {
+ message: "Smart search query is not valid",
+ path: ["query"],
+ },
+ )
+ .refine((val) => !val.query || parseSearchQuery(val.query).text.length == 0, {
+ message:
+ "Smart lists cannot have unqualified terms (aka full text search terms) in the query",
+ path: ["query"],
+ });