diff options
Diffstat (limited to 'packages')
| -rw-r--r-- | packages/shared/searchQueryParser.test.ts | 36 | ||||
| -rw-r--r-- | packages/shared/searchQueryParser.ts | 24 | ||||
| -rw-r--r-- | packages/shared/types/search.ts | 10 | ||||
| -rw-r--r-- | packages/trpc/lib/search.ts | 16 |
4 files changed, 83 insertions, 3 deletions
diff --git a/packages/shared/searchQueryParser.test.ts b/packages/shared/searchQueryParser.test.ts index 3954e871..37275284 100644 --- a/packages/shared/searchQueryParser.test.ts +++ b/packages/shared/searchQueryParser.test.ts @@ -332,6 +332,42 @@ describe("Search Query Parser", () => { inverse: true, }, }); + expect(parseSearchQuery("source:rss")).toEqual({ + result: "full", + text: "", + matcher: { + type: "source", + source: "rss", + inverse: false, + }, + }); + expect(parseSearchQuery("-source:rss")).toEqual({ + result: "full", + text: "", + matcher: { + type: "source", + source: "rss", + inverse: true, + }, + }); + expect(parseSearchQuery("source:web")).toEqual({ + result: "full", + text: "", + matcher: { + type: "source", + source: "web", + inverse: false, + }, + }); + expect(parseSearchQuery("-source:web")).toEqual({ + result: "full", + text: "", + matcher: { + type: "source", + source: "web", + inverse: true, + }, + }); }); test("! negation alias for -", () => { // ! should work exactly like - for negation diff --git a/packages/shared/searchQueryParser.ts b/packages/shared/searchQueryParser.ts index 027a662f..7eb3b185 100644 --- a/packages/shared/searchQueryParser.ts +++ b/packages/shared/searchQueryParser.ts @@ -16,7 +16,7 @@ import { } from "typescript-parsec"; import { z } from "zod"; -import { BookmarkTypes } from "./types/bookmarks"; +import { BookmarkTypes, zBookmarkSourceSchema } from "./types/bookmarks"; import { Matcher } from "./types/search"; import { parseRelativeDate } from "./utils/relativeDateUtils"; @@ -42,7 +42,10 @@ const lexerRules: [RegExp, TokenType][] = [ [/^\s+or/i, TokenType.Or], [/^#/, TokenType.Hash], - [/^(is|url|list|after|before|age|feed|title|tag):/, TokenType.Qualifier], + [ + /^(is|url|list|after|before|age|feed|title|tag|source):/, + TokenType.Qualifier, + ], [/^"([^"]+)"/, TokenType.StringLiteral], @@ -230,6 +233,23 @@ MATCHER.setPattern( inverse: !!minus, }, }; + case "source:": { + const parsed = zBookmarkSourceSchema.safeParse(ident); + if (!parsed.success) { + return { + text: (minus?.text ?? "") + qualifier.text + ident, + matcher: undefined, + }; + } + return { + text: "", + matcher: { + type: "source", + source: parsed.data, + inverse: !!minus, + }, + }; + } case "after:": try { return { diff --git a/packages/shared/types/search.ts b/packages/shared/types/search.ts index c29270b8..b653d883 100644 --- a/packages/shared/types/search.ts +++ b/packages/shared/types/search.ts @@ -1,6 +1,6 @@ import { z } from "zod"; -import { BookmarkTypes } from "./bookmarks"; +import { BookmarkTypes, zBookmarkSourceSchema } from "./bookmarks"; const zTagNameMatcher = z.object({ type: z.literal("tagName"), @@ -88,6 +88,12 @@ const zBrokenLinksMatcher = z.object({ brokenLinks: z.boolean(), }); +const zSourceMatcher = z.object({ + type: z.literal("source"), + source: zBookmarkSourceSchema, + inverse: z.boolean(), +}); + const zNonRecursiveMatcher = z.union([ zTagNameMatcher, zListNameMatcher, @@ -103,6 +109,7 @@ const zNonRecursiveMatcher = z.union([ zTypeMatcher, zRssFeedNameMatcher, zBrokenLinksMatcher, + zSourceMatcher, ]); type NonRecursiveMatcher = z.infer<typeof zNonRecursiveMatcher>; @@ -127,6 +134,7 @@ export const zMatcherSchema: z.ZodType<Matcher> = z.lazy(() => { zTypeMatcher, zRssFeedNameMatcher, zBrokenLinksMatcher, + zSourceMatcher, z.object({ type: z.literal("and"), matchers: z.array(zMatcherSchema), diff --git a/packages/trpc/lib/search.ts b/packages/trpc/lib/search.ts index 88f10f22..51e51d1c 100644 --- a/packages/trpc/lib/search.ts +++ b/packages/trpc/lib/search.ts @@ -373,6 +373,22 @@ async function getIds( ), ); } + case "source": { + return db + .select({ id: bookmarks.id }) + .from(bookmarks) + .where( + and( + eq(bookmarks.userId, userId), + matcher.inverse + ? or( + ne(bookmarks.source, matcher.source), + isNull(bookmarks.source), + ) + : eq(bookmarks.source, matcher.source), + ), + ); + } case "and": { const vals = await Promise.all( matcher.matchers.map((m) => getIds(db, userId, m)), |
