diff --git a/app/services/search_service.ts b/app/services/search_service.ts
index c5e26c9..f0d9955 100644
--- a/app/services/search_service.ts
+++ b/app/services/search_service.ts
@@ -85,6 +85,19 @@ function contains(word: string): string {
return `%${word.replace(/[\\%_]/g, (c) => `\\${c}`)}%`
}
+/**
+ * A search word's condition on release_texts: anywhere in a text (ILIKE), or with a leading "^" at a word's start, the
+ * text's or after a character that's no letter or digit ("^zed" finds "Zed.Dead", "The_Zed", not "Facialized"). Both
+ * use the trigram index.
+ */
+function wordMatch(word: string): { sql: string; binding: string } {
+ if (word.startsWith('^') && word.length > 1) {
+ const rest = word.slice(1).replace(/[\\^$.|?*+()[\]{}]/g, (c) => `\\${c}`)
+ return { sql: 't.text ~* ?', binding: `(^|[^[:alnum:]])${rest}` }
+ }
+ return { sql: 't.text ILIKE ?', binding: contains(word) }
+}
+
/** Searching and browsing the catalog, for the web interface and Newznab. */
class SearchService {
async search(params: SearchParams) {
@@ -93,10 +106,12 @@ class SearchService {
if (!params.judged) where.push('NOT EXISTS (SELECT 1 FROM release_verdicts v WHERE v.release_id = r.id)')
const bindings: unknown[] = []
// Every word in one of the release's texts (its name, the names the prober found inside; release_texts, by its
- // trigram index, which yields ids: never an OR across the two tables); "The.Block.AU" and "the block au" alike.
+ // trigram index, which yields ids: never an OR across the two tables); "The.Block.AU" and "the block au" alike;
+ // "^zed" only at a word's start.
for (const word of (params.q ?? '').split(/[\s._-]+/).filter(Boolean).slice(0, 8)) {
- where.push('r.id IN (SELECT t.release_id FROM release_texts t WHERE t.text ILIKE ?)')
- bindings.push(contains(word))
+ const match = wordMatch(word)
+ where.push(`r.id IN (SELECT t.release_id FROM release_texts t WHERE ${match.sql})`)
+ bindings.push(match.binding)
}
if (params.readable) where.push('(r.readable OR r.inner_name IS NOT NULL)')
if (params.complete) where.push('r.complete')
diff --git a/public/index.html b/public/index.html
index 6b73f85..80291ea 100644
--- a/public/index.html
+++ b/public/index.html
@@ -22,7 +22,7 @@