diff options
| author | Mohamed Bassem <me@mbassem.com> | 2025-09-28 11:03:48 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-09-28 11:03:48 +0100 |
| commit | 62f7d900c52784ff05d933b52379e5455ea6bd00 (patch) | |
| tree | 2702d74c96576447974af84850f3ba6b66beeeb4 /packages/shared | |
| parent | 9fe09bfa9021c8d85d2d9aef591936101cab19f6 (diff) | |
| download | karakeep-62f7d900c52784ff05d933b52379e5455ea6bd00.tar.zst | |
feat: Add tag search and pagination (#1987)
* feat: Add tag search and use in the homepage
* use paginated query in the all tags view
* wire the load more buttons
* add skeleton to all tags page
* fix attachedby aggregation
* fix loading states
* fix hasNextPage
* use action buttons for load more buttons
* migrate the tags auto complete to the search api
* Migrate the tags editor to the new search API
* Replace tag merging dialog with tag auto completion
* Merge both search and list APIs
* fix tags.list
* add some tests for the endpoint
* add relevance based sorting
* change cursor
* update the REST API
* fix review comments
* more fixes
* fix lockfile
* i18n
* fix visible tags
Diffstat (limited to 'packages/shared')
| -rw-r--r-- | packages/shared/types/tags.ts | 58 | ||||
| -rw-r--r-- | packages/shared/utils/switch.ts | 6 |
2 files changed, 64 insertions, 0 deletions
diff --git a/packages/shared/types/tags.ts b/packages/shared/types/tags.ts index efb26bfa..91ad1d96 100644 --- a/packages/shared/types/tags.ts +++ b/packages/shared/types/tags.ts @@ -2,6 +2,8 @@ import { z } from "zod"; import { normalizeTagName } from "../utils/tag"; +export const MAX_NUM_TAGS_PER_PAGE = 1000; + const zTagNameSchemaWithValidation = z .string() .transform((s) => normalizeTagName(s).trim()) @@ -38,3 +40,59 @@ export const zTagBasicSchema = z.object({ name: z.string(), }); export type ZTagBasic = z.infer<typeof zTagBasicSchema>; + +export const zTagCursorSchema = z.object({ + page: z.number().int().min(0), +}); + +export const zTagListRequestSchema = z.object({ + nameContains: z.string().optional(), + attachedBy: z.enum([...zAttachedByEnumSchema.options, "none"]).optional(), + sortBy: z.enum(["name", "usage", "relevance"]).optional().default("usage"), + cursor: zTagCursorSchema.nullish().default({ page: 0 }), + // TODO: Remove the optional to enforce a limit after the next release + limit: z.number().int().min(1).max(MAX_NUM_TAGS_PER_PAGE).optional(), +}); + +export const zTagListValidatedRequestSchema = zTagListRequestSchema.refine( + (val) => val.sortBy != "relevance" || val.nameContains !== undefined, + { + message: "Relevance sorting requires a nameContains filter", + path: ["sortBy"], + }, +); + +export const zTagListResponseSchema = z.object({ + tags: z.array(zGetTagResponseSchema), + nextCursor: zTagCursorSchema.nullish(), +}); +export type ZTagListResponse = z.infer<typeof zTagListResponseSchema>; + +// API Types + +export const zTagListQueryParamsSchema = z.object({ + nameContains: zTagListRequestSchema.shape.nameContains, + sort: zTagListRequestSchema.shape.sortBy, + attachedBy: zTagListRequestSchema.shape.attachedBy, + cursor: z + .string() + .transform((val, ctx) => { + try { + return JSON.parse(Buffer.from(val, "base64url").toString("utf8")); + } catch { + ctx.addIssue({ + code: "custom", + message: "Invalid cursor", + }); + return z.NEVER; + } + }) + .optional() + .pipe(zTagListRequestSchema.shape.cursor), + limit: z.coerce.number().optional(), +}); + +export const zTagListApiResultSchema = z.object({ + tags: zTagListResponseSchema.shape.tags, + nextCursor: z.string().nullish(), +}); diff --git a/packages/shared/utils/switch.ts b/packages/shared/utils/switch.ts new file mode 100644 index 00000000..9123c060 --- /dev/null +++ b/packages/shared/utils/switch.ts @@ -0,0 +1,6 @@ +export function switchCase<T extends string | number, R>( + value: T, + cases: Record<T, R>, +) { + return cases[value]; +} |
