Browse Source

implemented d-tag searches and corrected wikilinks to use them

master
Silberengel 8 months ago
parent
commit
d396b6f292
  1. 65
      src/lib/components/EventSearch.svelte
  2. 2
      src/lib/utils/markup/MarkupInfo.md
  3. 2
      src/lib/utils/markup/basicMarkupParser.ts
  4. 92
      src/routes/events/+page.svelte

65
src/lib/components/EventSearch.svelte

@ -6,12 +6,15 @@
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import type { NDKEvent } from '$lib/utils/nostrUtils'; import type { NDKEvent } from '$lib/utils/nostrUtils';
import RelayDisplay from './RelayDisplay.svelte'; import RelayDisplay from './RelayDisplay.svelte';
import { getActiveRelays } from '$lib/ndk';
const { loading, error, searchValue, onEventFound, event } = $props<{ const { loading, error, searchValue, dTagValue, onEventFound, onSearchResults, event } = $props<{
loading: boolean; loading: boolean;
error: string | null; error: string | null;
searchValue: string | null; searchValue: string | null;
dTagValue: string | null;
onEventFound: (event: NDKEvent) => void; onEventFound: (event: NDKEvent) => void;
onSearchResults: (results: NDKEvent[]) => void;
event: NDKEvent | null; event: NDKEvent | null;
}>(); }>();
@ -27,15 +30,70 @@
} }
}); });
$effect(() => {
if (dTagValue) {
searchByDTag(dTagValue);
}
});
$effect(() => { $effect(() => {
foundEvent = event; foundEvent = event;
}); });
async function searchByDTag(dTag: string) {
localError = null;
searching = true;
try {
console.log('[Events] Searching for events with d-tag:', dTag);
const ndk = $ndkInstance;
if (!ndk) {
localError = 'NDK not initialized';
return;
}
const filter = { '#d': [dTag] };
const relaySet = getActiveRelays(ndk);
// Fetch multiple events with the same d-tag
const events = await ndk.fetchEvents(filter, { closeOnEose: true }, relaySet);
const eventArray = Array.from(events);
if (eventArray.length === 0) {
localError = `No events found with d-tag: ${dTag}`;
onSearchResults([]);
} else if (eventArray.length === 1) {
// If only one event found, treat it as a single event result
handleFoundEvent(eventArray[0]);
} else {
// Multiple events found, show as search results
console.log(`[Events] Found ${eventArray.length} events with d-tag: ${dTag}`);
onSearchResults(eventArray);
}
} catch (err) {
console.error('[Events] Error searching by d-tag:', err);
localError = 'Error searching for events with this d-tag.';
onSearchResults([]);
} finally {
searching = false;
}
}
async function searchEvent(clearInput: boolean = true, queryOverride?: string) { async function searchEvent(clearInput: boolean = true, queryOverride?: string) {
localError = null; localError = null;
const query = (queryOverride !== undefined ? queryOverride : searchQuery).trim(); const query = (queryOverride !== undefined ? queryOverride : searchQuery).trim();
if (!query) return; if (!query) return;
// Check if this is a d-tag search
if (query.startsWith('d:')) {
const dTag = query.slice(2).trim();
if (dTag) {
const encoded = encodeURIComponent(dTag);
goto(`?d=${encoded}`, { replaceState: false, keepFocus: true, noScroll: true });
return;
}
}
// Only update the URL if this is a manual search // Only update the URL if this is a manual search
if (clearInput) { if (clearInput) {
const encoded = encodeURIComponent(query); const encoded = encodeURIComponent(query);
@ -162,7 +220,7 @@
<div class="flex gap-2"> <div class="flex gap-2">
<Input <Input
bind:value={searchQuery} bind:value={searchQuery}
placeholder="Enter event ID, nevent, or naddr..." placeholder="Enter event ID, nevent, naddr, or d:tag-name..."
class="flex-grow" class="flex-grow"
on:keydown={(e: KeyboardEvent) => e.key === 'Enter' && searchEvent(true)} on:keydown={(e: KeyboardEvent) => e.key === 'Enter' && searchEvent(true)}
/> />
@ -197,8 +255,5 @@
{#if !foundEvent && Object.values(relayStatuses).some(s => s === 'pending')} {#if !foundEvent && Object.values(relayStatuses).some(s => s === 'pending')}
<div class="text-gray-700 dark:text-gray-300 mt-2">Searching relays...</div> <div class="text-gray-700 dark:text-gray-300 mt-2">Searching relays...</div>
{/if} {/if}
{#if !foundEvent && !searching && Object.values(relayStatuses).every(s => s !== 'pending')}
<div class="text-red-500 mt-2">Event not found on any relay.</div>
{/if}
</div> </div>
</div> </div>

2
src/lib/utils/markup/MarkupInfo.md

@ -30,7 +30,7 @@ The **advanced markup parser** includes all features of the basic parser, plus:
- **Tables:** Pipe-delimited tables with or without headers - **Tables:** Pipe-delimited tables with or without headers
- **Footnotes:** `[^1]` or `[^Smith]`, which should appear where the footnote shall be placed, and will be displayed as unique, consecutive numbers - **Footnotes:** `[^1]` or `[^Smith]`, which should appear where the footnote shall be placed, and will be displayed as unique, consecutive numbers
- **Footnote References:** `[^1]: footnote text` or `[^Smith]: Smith, Adam. 1984 "The Wiggle Mysteries`, which will be listed in order, at the bottom of the event, with back-reference links to the footnote, and text footnote labels appended - **Footnote References:** `[^1]: footnote text` or `[^Smith]: Smith, Adam. 1984 "The Wiggle Mysteries`, which will be listed in order, at the bottom of the event, with back-reference links to the footnote, and text footnote labels appended
- **Wikilinks:** `[[NIP-54]]` will render as a hyperlink and goes to [NIP-54](./wiki?d=nip-54) - **Wikilinks:** `[[NIP-54]]` will render as a hyperlink and goes to [NIP-54](./events?d=nip-54)
## Publications and Wikis ## Publications and Wikis

2
src/lib/utils/markup/basicMarkupParser.ts

@ -142,7 +142,7 @@ function replaceWikilinks(text: string): string {
return text.replace(/\[\[([^\]|]+)(?:\|([^\]]+))?\]\]/g, (_match, target, label) => { return text.replace(/\[\[([^\]|]+)(?:\|([^\]]+))?\]\]/g, (_match, target, label) => {
const normalized = normalizeDTag(target.trim()); const normalized = normalizeDTag(target.trim());
const display = (label || target).trim(); const display = (label || target).trim();
const url = `./wiki?d=${normalized}`; const url = `./events?d=${normalized}`;
// Output as a clickable <a> with the [[display]] format and matching link colors // Output as a clickable <a> with the [[display]] format and matching link colors
return `<a class="wikilink text-primary-600 dark:text-primary-500 hover:underline" data-dtag="${normalized}" data-url="${url}" href="${url}">${display}</a>`; return `<a class="wikilink text-primary-600 dark:text-primary-500 hover:underline" data-dtag="${normalized}" data-url="${url}" href="${url}">${display}</a>`;
}); });

92
src/routes/events/+page.svelte

@ -7,11 +7,15 @@
import EventDetails from '$lib/components/EventDetails.svelte'; import EventDetails from '$lib/components/EventDetails.svelte';
import RelayActions from '$lib/components/RelayActions.svelte'; import RelayActions from '$lib/components/RelayActions.svelte';
import CommentBox from '$lib/components/CommentBox.svelte'; import CommentBox from '$lib/components/CommentBox.svelte';
import { userBadge } from '$lib/snippets/UserSnippets.svelte';
import { getMatchingTags, toNpub } from '$lib/utils/nostrUtils';
let loading = $state(false); let loading = $state(false);
let error = $state<string | null>(null); let error = $state<string | null>(null);
let searchValue = $state<string | null>(null); let searchValue = $state<string | null>(null);
let dTagValue = $state<string | null>(null);
let event = $state<NDKEvent | null>(null); let event = $state<NDKEvent | null>(null);
let searchResults = $state<NDKEvent[]>([]);
let profile = $state<{ let profile = $state<{
name?: string; name?: string;
display_name?: string; display_name?: string;
@ -27,6 +31,7 @@
function handleEventFound(newEvent: NDKEvent) { function handleEventFound(newEvent: NDKEvent) {
event = newEvent; event = newEvent;
searchResults = [];
if (newEvent.kind === 0) { if (newEvent.kind === 0) {
try { try {
profile = JSON.parse(newEvent.content); profile = JSON.parse(newEvent.content);
@ -38,10 +43,33 @@
} }
} }
function handleSearchResults(results: NDKEvent[]) {
searchResults = results;
event = null;
profile = null;
}
function getSummary(event: NDKEvent): string | undefined {
return getMatchingTags(event, 'summary')[0]?.[1];
}
function getDeferrelNaddr(event: NDKEvent): string | undefined {
// Look for a 'deferrel' tag, e.g. ['deferrel', 'naddr1...']
return getMatchingTags(event, 'deferrel')[0]?.[1];
}
$effect(() => { $effect(() => {
const id = $page.url.searchParams.get('id'); const id = $page.url.searchParams.get('id');
const dTag = $page.url.searchParams.get('d');
if (id !== searchValue) { if (id !== searchValue) {
searchValue = id; searchValue = id;
dTagValue = null;
}
if (dTag !== dTagValue) {
dTagValue = dTag;
searchValue = null;
} }
}); });
@ -60,9 +88,19 @@
<P class="mb-3"> <P class="mb-3">
Use this page to view any event (npub, nprofile, nevent, naddr, note, pubkey, or eventID). Use this page to view any event (npub, nprofile, nevent, naddr, note, pubkey, or eventID).
You can also search for events by d-tag using the format "d:tag-name".
</P> </P>
<EventSearch {loading} {error} {searchValue} {event} onEventFound={handleEventFound} /> <EventSearch
{loading}
{error}
{searchValue}
{dTagValue}
{event}
onEventFound={handleEventFound}
onSearchResults={handleSearchResults}
/>
{#if event} {#if event}
<EventDetails {event} {profile} {searchValue} /> <EventDetails {event} {profile} {searchValue} />
<RelayActions {event} /> <RelayActions {event} />
@ -77,5 +115,57 @@
</div> </div>
{/if} {/if}
{/if} {/if}
{#if searchResults.length > 0}
<div class="mt-8">
<Heading tag="h2" class="h-leather mb-4">
Search Results for d-tag: "{dTagValue}" ({searchResults.length} events)
</Heading>
<div class="space-y-4">
{#each searchResults as result, index}
<button
class="w-full text-left border border-gray-300 dark:border-gray-600 rounded-lg p-4 bg-white dark:bg-primary-900/70 hover:bg-gray-100 dark:hover:bg-primary-800 focus:bg-gray-100 dark:focus:bg-primary-800 focus:outline-none focus:ring-2 focus:ring-primary-500 transition-colors overflow-hidden"
onclick={() => handleEventFound(result)}
>
<div class="flex flex-col gap-1">
<div class="flex items-center gap-2 mb-1">
<span class="font-medium text-gray-800 dark:text-gray-100">Event {index + 1}</span>
<span class="text-xs text-gray-600 dark:text-gray-400">Kind: {result.kind}</span>
<span class="text-xs text-gray-600 dark:text-gray-400">
{@render userBadge(toNpub(result.pubkey) as string, undefined)}
</span>
<span class="text-xs text-gray-500 dark:text-gray-400 ml-auto">
{result.created_at ? new Date(result.created_at * 1000).toLocaleDateString() : 'Unknown date'}
</span>
</div>
{#if getSummary(result)}
<div class="text-sm text-primary-900 dark:text-primary-200 mb-1 line-clamp-2">
{getSummary(result)}
</div>
{/if}
{#if getDeferrelNaddr(result)}
<div class="text-xs text-primary-800 dark:text-primary-300 mb-1">
Read
<a
class="underline text-primary-700 dark:text-primary-400 hover:text-primary-900 dark:hover:text-primary-200 break-all"
href={'/publications?d=' + encodeURIComponent(dTagValue || '')}
onclick={e => e.stopPropagation()}
tabindex="0"
>
{getDeferrelNaddr(result)}
</a>
</div>
{/if}
{#if result.content}
<div class="text-sm text-gray-800 dark:text-gray-200 mt-1 line-clamp-2 break-words">
{result.content.slice(0, 200)}{result.content.length > 200 ? '...' : ''}
</div>
{/if}
</div>
</button>
{/each}
</div>
</div>
{/if}
</main> </main>
</div> </div>

Loading…
Cancel
Save