You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
75 lines
2.7 KiB
75 lines
2.7 KiB
import { ExtendedKind } from '@/constants' |
|
import type { Event } from 'nostr-tools' |
|
|
|
function getDTagValue(event: Event): string | undefined { |
|
const t = event.tags.find((x) => x[0] === 'd' && x[1])?.[1] |
|
return t |
|
} |
|
|
|
const TEXT_META_TAG_NAMES = new Set(['title', 'summary', 'description', 'subject', 'name']) |
|
|
|
/** |
|
* d-tag, content, or common text metadata tags (title, summary, description, subject, name) |
|
* contain the needle (case-insensitive). |
|
* |
|
* NIP-50 full-text search can match on metadata tags not in the `d` tag or `content` field, |
|
* so we check them here to avoid incorrectly hiding those results. |
|
* |
|
* Also checks a space-separated variant of the needle so that a hyphenated d-tag slug like |
|
* "bitcoin-wallet" matches content/titles written as "Bitcoin Wallet". |
|
*/ |
|
export function eventMatchesDTagLooseQuery(needle: string, event: Event): boolean { |
|
const q = needle.trim().toLowerCase() |
|
if (!q) return true |
|
// Also try the space-separated variant (e.g. "bitcoin-wallet" → "bitcoin wallet") |
|
const qSpace = q.replace(/-/g, ' ') |
|
const checks = qSpace !== q ? [q, qSpace] : [q] |
|
|
|
const d = getDTagValue(event)?.toLowerCase() ?? '' |
|
for (const c of checks) { |
|
if (d.includes(c)) return true |
|
} |
|
const content = (event.content ?? '').toLowerCase() |
|
for (const c of checks) { |
|
if (content.includes(c)) return true |
|
} |
|
for (const tag of event.tags) { |
|
if (tag[1] && TEXT_META_TAG_NAMES.has(tag[0])) { |
|
const val = tag[1].toLowerCase() |
|
for (const c of checks) { |
|
if (val.includes(c)) return true |
|
} |
|
} |
|
} |
|
return false |
|
} |
|
|
|
/** Sort key: exact d-tag match first, then prefix, substring, then non-d / content-only. */ |
|
function dTagMatchRank(needle: string, dVal: string | undefined): number { |
|
if (!dVal) return 4 |
|
const nl = needle.trim().toLowerCase() |
|
const dl = dVal.toLowerCase() |
|
if (dl === nl) return 0 |
|
if (dl.startsWith(nl)) return 1 |
|
if (dl.includes(nl)) return 2 |
|
return 3 |
|
} |
|
|
|
/** For merged lists: better d-tag match first; tie-break newest first. Kind 30041 sinks unless `d` equals the needle. */ |
|
export function compareEventsForDTagQuery(needle: string, a: Event, b: Event): number { |
|
const nl = needle.trim().toLowerCase() |
|
const ra = dTagMatchRank(needle, getDTagValue(a)) |
|
const rb = dTagMatchRank(needle, getDTagValue(b)) |
|
|
|
if (nl.length > 0) { |
|
const kCh = ExtendedKind.PUBLICATION_CONTENT |
|
const aExact = getDTagValue(a)?.toLowerCase() === nl |
|
const bExact = getDTagValue(b)?.toLowerCase() === nl |
|
const aBottom = a.kind === kCh && !aExact |
|
const bBottom = b.kind === kCh && !bExact |
|
if (aBottom !== bBottom) return aBottom ? 1 : -1 |
|
} |
|
|
|
if (ra !== rb) return ra - rb |
|
return b.created_at - a.created_at |
|
}
|
|
|