Browse Source

Use snippets for basic markup parsing in components

master
buttercat1791 7 months ago
parent
commit
c5bca7c5e2
  1. 25
      src/lib/components/CommentBox.svelte
  2. 12
      src/lib/components/CommentViewer.svelte
  3. 84
      src/lib/components/EventDetails.svelte
  4. 27
      src/lib/components/Notifications.svelte
  5. 23
      src/lib/components/cards/ProfileHeader.svelte
  6. 2
      src/lib/components/embedded_events/EmbeddedEvent.svelte
  7. 0
      src/lib/snippets/EmbeddedSnippets.svelte
  8. 14
      src/lib/snippets/MarkupSnippets.svelte
  9. 6
      src/lib/utils/markup/advancedMarkupParser.ts
  10. 10
      src/lib/utils/markup/basicMarkupParser.ts
  11. 29
      src/routes/events/+page.svelte

25
src/lib/components/CommentBox.svelte

@ -1,16 +1,10 @@ @@ -1,16 +1,10 @@
<script lang="ts">
import { Button, Textarea, Alert, Modal, Input } from "flowbite-svelte";
import { UserOutline } from "flowbite-svelte-icons";
import { parseBasicmarkup } from "$lib/utils/markup/basicMarkupParser";
import { nip19 } from "nostr-tools";
import { toNpub, getUserMetadata } from "$lib/utils/nostrUtils";
import { toNpub } from "$lib/utils/nostrUtils";
import { searchProfiles } from "$lib/utils/search_utility";
import type {
NostrProfile,
ProfileSearchResult,
} from "$lib/utils/search_utility";
import type { NostrProfile } from "$lib/utils/search_types";
import { userStore } from "$lib/stores/userStore";
import type { NDKEvent } from "$lib/utils/nostrUtils";
import {
@ -19,11 +13,11 @@ @@ -19,11 +13,11 @@
buildReplyTags,
createSignedEvent,
publishEvent,
navigateToEvent,
} from "$lib/utils/nostrEventService";
import { tick } from "svelte";
import { goto } from "$app/navigation";
import { activeInboxRelays, activeOutboxRelays, getNdkContext } from "$lib/ndk";
import { basicMarkup } from "$lib/snippets/MarkupSnippets.svelte";
const props = $props<{
event: NDKEvent;
@ -33,7 +27,6 @@ @@ -33,7 +27,6 @@
const ndk = getNdkContext();
let content = $state("");
let preview = $state("");
let isSubmitting = $state(false);
let success = $state<{ relay: string; eventId: string } | null>(null);
let error = $state<string | null>(null);
@ -87,7 +80,6 @@ @@ -87,7 +80,6 @@
if (!success) return;
content = "";
preview = "";
});
// Markup buttons
@ -131,7 +123,6 @@ @@ -131,7 +123,6 @@
selectedText +
suffix +
content.substring(end);
updatePreview();
// Set cursor position after the inserted markup
setTimeout(() => {
@ -141,13 +132,9 @@ @@ -141,13 +132,9 @@
}, 0);
}
async function updatePreview() {
preview = await parseBasicmarkup(content);
}
function clearForm() {
content = "";
preview = "";
error = null;
showOtherRelays = false;
showSecondaryRelays = false;
@ -164,7 +151,6 @@ @@ -164,7 +151,6 @@
.replace(/^[-*]\s*/gm, "")
.replace(/^\d+\.\s*/gm, "")
.replace(/#(\w+)/g, "$1");
updatePreview();
}
async function handleSubmit(
@ -227,7 +213,6 @@ @@ -227,7 +213,6 @@
// Clear form after successful submission
content = "";
preview = "";
showOtherRelays = false;
showSecondaryRelays = false;
} catch (e) {
@ -251,7 +236,6 @@ @@ -251,7 +236,6 @@
const end = textarea.selectionEnd;
content = content.substring(0, start) + text + content.substring(end);
updatePreview();
// Wait for DOM updates to complete
await tick();
@ -578,7 +562,6 @@ @@ -578,7 +562,6 @@
<div>
<Textarea
bind:value={content}
on:input={updatePreview}
placeholder="Write your comment..."
rows={10}
class="w-full"
@ -587,7 +570,7 @@ @@ -587,7 +570,7 @@
<div
class="prose dark:prose-invert max-w-none p-4 border border-gray-300 dark:border-gray-700 rounded-lg"
>
{@html preview}
{@render basicMarkup(content, ndk)}
</div>
</div>

12
src/lib/components/CommentViewer.svelte

@ -6,8 +6,7 @@ @@ -6,8 +6,7 @@
import { goto } from "$app/navigation";
import { onMount } from "svelte";
import type { NDKEvent } from "@nostr-dev-kit/ndk";
import { parseBasicmarkup } from "$lib/utils/markup/basicMarkupParser";
import { basicMarkup } from "$lib/snippets/MarkupSnippets.svelte";
const { event } = $props<{ event: NDKEvent }>();
@ -689,6 +688,7 @@ @@ -689,6 +688,7 @@
<!-- Recursive Comment Item Component -->
{#snippet CommentItem(node: CommentNode)}
{@const comment = node.event.getMatchingTags("comment")[0]?.[1] || "No comment content"}
<div class="mb-4">
<div
class="bg-white dark:bg-gray-800 rounded-lg p-4 border border-gray-200 dark:border-gray-700 break-words"
@ -774,9 +774,7 @@ @@ -774,9 +774,7 @@
<span class="font-medium">Comment:</span>
</div>
<div class="text-sm text-gray-700 dark:text-gray-300">
{#await parseBasicmarkup(node.event.getMatchingTags("comment")[0]?.[1] || "No comment content") then parsed}
{@html parsed}
{/await}
{@render basicMarkup(comment, ndk)}
</div>
</div>
{:else}
@ -818,9 +816,7 @@ @@ -818,9 +816,7 @@
{:else}
<!-- Regular comment content -->
<div class="text-sm text-gray-700 dark:text-gray-300">
{#await parseBasicmarkup(node.event.content || "No content") then parsed}
{@html parsed}
{/await}
{@render basicMarkup(node.event.content || "No content", ndk)}
</div>
{/if}
</div>

84
src/lib/components/EventDetails.svelte

@ -14,14 +14,14 @@ @@ -14,14 +14,14 @@
import { navigateToEvent } from "$lib/utils/nostrEventService";
import ContainingIndexes from "$lib/components/util/ContainingIndexes.svelte";
import Notifications from "$lib/components/Notifications.svelte";
import { parseBasicmarkup } from "$lib/utils/markup/basicMarkupParser";
import {
repostContent,
quotedContent,
} from "$lib/components/embedded_events/EmbeddedSnippets.svelte";
} from "$lib/snippets/EmbeddedSnippets.svelte";
import { repostKinds } from "$lib/consts";
import { getNdkContext } from "$lib/ndk";
import type { UserProfile } from "$lib/models/user_profile";
import { basicMarkup } from "$lib/snippets/MarkupSnippets.svelte";
const {
event,
@ -31,10 +31,11 @@ @@ -31,10 +31,11 @@
profile?: UserProfile | null;
}>();
const ndk = getNdkContext();
let authorDisplayName = $state<string | undefined>(undefined);
let showFullContent = $state(false);
let shouldTruncate = $derived(event.content.length > 250 && !showFullContent);
let parsedContent = $state<string>("");
let isRepost = $derived(repostKinds.includes(event.kind) || (event.kind === 1 && event.getMatchingTags("q").length > 0));
function getEventTitle(event: NDKEvent): string {
@ -73,43 +74,12 @@ @@ -73,43 +74,12 @@
return "Untitled";
}
let parsedSummary = $state<string>("");
let parsedTitle = $state<string>("");
function getEventSummary(event: NDKEvent): string {
return getMatchingTags(event, "summary")[0]?.[1] || "";
}
$effect(() => {
const summary = getEventSummary(event);
if (summary) {
parseBasicmarkup(summary).then((processed) => {
parsedSummary = processed;
}).catch((error) => {
console.error("Error parsing summary:", error);
parsedSummary = summary;
});
} else {
parsedSummary = "";
}
});
$effect(() => {
const title = getEventTitle(event);
if (title && title !== "Untitled") {
parseBasicmarkup(title).then((processed) => {
parsedTitle = processed;
}).catch((error) => {
console.error("Error parsing title:", error);
parsedTitle = title;
});
} else {
parsedTitle = title || "";
}
});
function getEventTypeDisplay(event: NDKEvent): string {
const [mTag, MTag] = getMimeTags(event.kind || 0);
const [_, MTag] = getMimeTags(event.kind || 0);
return MTag[1].split("/")[1] || `Event Kind ${event.kind}`;
}
@ -281,26 +251,6 @@ @@ -281,26 +251,6 @@
return ids;
}
$effect(() => {
if (event.content) {
// For kind 6 and 16 reposts, we don't need to parse the content as basic markup since it's JSON
// For quote reposts (kind 1 with q tags), we still need to parse the content for nostr identifiers
if (repostKinds.includes(event.kind)) {
parsedContent = event.content;
} else {
// For all other events (including quote reposts), parse the content using basic markup parser
parseBasicmarkup(event.content, getNdkContext()).then((processed) => {
parsedContent = processed;
}).catch((error) => {
console.error("Error parsing content:", error);
parsedContent = event.content;
});
}
} else {
parsedContent = "";
}
});
onMount(() => {
function handleInternalLinkClick(event: MouseEvent) {
const target = event.target as HTMLElement;
@ -318,9 +268,9 @@ @@ -318,9 +268,9 @@
</script>
<div class="flex flex-col space-y-4 min-w-0">
{#if event.kind !== 0 && parsedTitle}
{#if event.kind !== 0}
<h2 class="text-2xl font-bold text-gray-900 dark:text-gray-100 break-words">
{@html parsedTitle}
{@render basicMarkup(getEventTitle(event), ndk)}
</h2>
{/if}
@ -352,20 +302,20 @@ @@ -352,20 +302,20 @@
>
</div>
{#if parsedSummary}
<div class="flex flex-col space-y-1 min-w-0">
<span class="text-gray-700 dark:text-gray-300">Summary:</span>
<div class="prose dark:prose-invert max-w-none text-gray-900 dark:text-gray-100 break-words overflow-wrap-anywhere min-w-0">
{@html parsedSummary}
{@render basicMarkup(getEventSummary(event), ndk)}
</div>
</div>
{/if}
<!-- Containing Publications -->
<ContainingIndexes {event} />
<!-- Content -->
{#if event.kind !== 0}
{@const kind = event.kind}
{@const content = event.content.trim()}
<div class="card-leather bg-highlight dark:bg-primary-800 p-4 mb-4 rounded-lg border max-w-full overflow-hidden">
<div class="flex flex-col space-y-1 min-w-0">
<span class="text-gray-700 dark:text-gray-300 font-semibold">Content:</span>
@ -387,12 +337,16 @@ @@ -387,12 +337,16 @@
Quote repost:
</div>
{@render quotedContent(event, [], getNdkContext())}
{#if event.content && event.content.trim()}
{#if content}
<div class="mt-3 pt-3 border-t border-gray-200 dark:border-gray-700">
<div class="text-xs text-gray-500 dark:text-gray-400 mb-1">
Added comment:
</div>
{@html parsedContent}
{#if repostKinds.includes(kind)}
{@html content}
{:else}
{@render basicMarkup(content, ndk)}
{/if}
</div>
{/if}
</div>
@ -400,7 +354,11 @@ @@ -400,7 +354,11 @@
{:else}
<!-- Regular content -->
<div class={shouldTruncate ? 'max-h-32 overflow-hidden' : ''}>
{@html parsedContent}
{#if repostKinds.includes(kind)}
{@html content}
{:else}
{@render basicMarkup(content, ndk)}
{/if}
</div>
{#if shouldTruncate}
<button

27
src/lib/components/Notifications.svelte

@ -17,15 +17,14 @@ @@ -17,15 +17,14 @@
getNotificationType,
fetchAuthorProfiles,
quotedContent,
} from "$lib/components/embedded_events/EmbeddedSnippets.svelte";
} from "$lib/snippets/EmbeddedSnippets.svelte";
import { buildCompleteRelaySet } from "$lib/utils/relay_management";
import { formatDate, neventEncode } from "$lib/utils";
import { NDKRelaySetFromNDK } from "$lib/utils/nostrUtils";
import { parseBasicmarkup } from "$lib/utils/markup/basicMarkupParser";
import { repostContent } from "$lib/components/embedded_events/EmbeddedSnippets.svelte";
import { repostContent } from "$lib/snippets/EmbeddedSnippets.svelte";
import { repostKinds } from "$lib/consts";
import { getNdkContext } from "$lib/ndk";
import { basicMarkup } from "$lib/snippets/MarkupSnippets.svelte";
const { event } = $props<{ event: NDKEvent }>();
@ -841,17 +840,13 @@ @@ -841,17 +840,13 @@
<div class="text-xs text-gray-500 dark:text-gray-400 mb-1">
Comment:
</div>
{#await parseBasicmarkup(message.content, ndk) then parsed}
{@html parsed}
{/await}
{@render basicMarkup(message.content, ndk)}
</div>
{/if}
</div>
{:else}
<!-- Regular content -->
{#await parseBasicmarkup(message.content || "No content", ndk) then parsed}
{@html parsed}
{/await}
{@render basicMarkup(message.content || "No content", ndk)}
{/if}
</div>
</div>
@ -951,17 +946,13 @@ @@ -951,17 +946,13 @@
<div class="text-xs text-gray-500 dark:text-gray-400 mb-1">
Comment:
</div>
{#await parseBasicmarkup(notification.content, ndk) then parsed}
{@html parsed}
{/await}
{@render basicMarkup(notification.content, ndk)}
</div>
{/if}
</div>
{:else}
<!-- Regular content -->
{#await parseBasicmarkup(notification.content || "No content", ndk) then parsed}
{@html parsed}
{/await}
{@render basicMarkup(notification.content || "No content", ndk)}
{/if}
</div>
</div>
@ -999,9 +990,7 @@ @@ -999,9 +990,7 @@
<div class="text-sm text-gray-600 dark:text-gray-400 mb-1">Replying to:</div>
<div class="text-sm text-gray-800 dark:text-gray-200">
<div class="text-sm text-gray-700 dark:text-gray-300">
{#await parseBasicmarkup(replyToMessage.content || "No content", ndk) then parsed}
{@html parsed}
{/await}
{@render basicMarkup(replyToMessage.content || "No content", ndk)}
</div>
</div>
</div>

23
src/lib/components/cards/ProfileHeader.svelte

@ -17,7 +17,8 @@ @@ -17,7 +17,8 @@
import { goto } from "$app/navigation";
import { isPubkeyInUserLists, fetchCurrentUserLists } from "$lib/utils/user_lists";
import { UserOutline } from "flowbite-svelte-icons";
import { parseBasicmarkup } from "$lib/utils/markup/basicMarkupParser";
import { basicMarkup } from "$lib/snippets/MarkupSnippets.svelte";
import { getNdkContext } from "$lib/ndk";
const {
event,
@ -31,11 +32,12 @@ @@ -31,11 +32,12 @@
communityStatusMap?: Record<string, boolean>;
}>();
const ndk = getNdkContext();
let lnModalOpen = $state(false);
let lnurl = $state<string | null>(null);
let communityStatus = $state<boolean | null>(null);
let isInUserLists = $state<boolean | null>(null);
let parsedAbout = $state<string>("");
onMount(async () => {
if (profile?.lud16) {
@ -92,19 +94,6 @@ @@ -92,19 +94,6 @@
}
});
$effect(() => {
if (profile?.about) {
parseBasicmarkup(profile.about).then((processed) => {
parsedAbout = processed;
}).catch((error) => {
console.error("Error parsing about:", error);
parsedAbout = profile.about;
});
} else {
parsedAbout = "";
}
});
function navigateToIdentifier(link: string) {
goto(link);
}
@ -211,12 +200,12 @@ @@ -211,12 +200,12 @@
<dd class="min-w-0 break-words">{profile.displayName}</dd>
</div>
{/if}
{#if parsedAbout}
{#if profile.about}
<div class="flex gap-2 min-w-0">
<dt class="font-semibold min-w-[120px] flex-shrink-0">About:</dt>
<dd class="min-w-0 break-words">
<div class="prose dark:prose-invert max-w-none text-gray-900 dark:text-gray-100 break-words overflow-wrap-anywhere min-w-0">
{@html parsedAbout}
{@render basicMarkup(profile.about, ndk)}
</div>
</dd>
</div>

2
src/lib/components/embedded_events/EmbeddedEvent.svelte

@ -2,7 +2,7 @@ @@ -2,7 +2,7 @@
import type { NDKEvent } from "@nostr-dev-kit/ndk";
import { fetchEventWithFallback, getUserMetadata, toNpub } from "$lib/utils/nostrUtils";
import { userBadge } from "$lib/snippets/UserSnippets.svelte";
import { parsedContent, repostContent, quotedContent } from "$lib/components/embedded_events/EmbeddedSnippets.svelte";
import { parsedContent, repostContent, quotedContent } from "$lib/snippets/EmbeddedSnippets.svelte";
import { naddrEncode } from "$lib/utils";
import { activeInboxRelays, getNdkContext } from "$lib/ndk";
import { goto } from "$app/navigation";

0
src/lib/components/embedded_events/EmbeddedSnippets.svelte → src/lib/snippets/EmbeddedSnippets.svelte

14
src/lib/snippets/MarkupSnippets.svelte

@ -0,0 +1,14 @@ @@ -0,0 +1,14 @@
<script module lang="ts">
import { parseBasicMarkup } from "$lib/utils/markup/basicMarkupParser";
import NDK from "@nostr-dev-kit/ndk";
export { basicMarkup };
</script>
{#snippet basicMarkup(text: string, ndk?: NDK)}
{#await parseBasicMarkup(text, ndk) then parsed}
{@html parsed}
{:catch error: Error}
<div class="text-red-500">Error processing markup: {error.message}</div>
{/await}
{/snippet}

6
src/lib/utils/markup/advancedMarkupParser.ts

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
import { parseBasicmarkup } from "./basicMarkupParser.ts";
import { parseBasicMarkup } from "./basicMarkupParser.ts";
import hljs from "highlight.js";
import "highlight.js/lib/common"; // Import common languages
import "highlight.js/styles/github-dark.css"; // Dark theme only
@ -444,7 +444,7 @@ export async function parseAdvancedmarkup(text: string): Promise<string> { @@ -444,7 +444,7 @@ export async function parseAdvancedmarkup(text: string): Promise<string> {
// Step 4: Process block-level elements (tables, headings, horizontal rules)
// AI-NOTE: 2025-01-24 - Removed duplicate processBlockquotes call to fix image rendering issues
// Blockquotes are now processed only by parseBasicmarkup to avoid double-processing conflicts
// Blockquotes are now processed only by parseBasicMarkup to avoid double-processing conflicts
processedText = processTables(processedText);
processedText = processHeadings(processedText);
processedText = processHorizontalRules(processedText);
@ -454,7 +454,7 @@ export async function parseAdvancedmarkup(text: string): Promise<string> { @@ -454,7 +454,7 @@ export async function parseAdvancedmarkup(text: string): Promise<string> {
// Step 6: Process basic markup (which will also handle Nostr identifiers)
// This includes paragraphs, inline code, links, lists, etc.
processedText = await parseBasicmarkup(processedText);
processedText = await parseBasicMarkup(processedText);
// Step 7: Restore code blocks
processedText = restoreCodeBlocks(processedText, blocks);

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

@ -8,7 +8,6 @@ import { @@ -8,7 +8,6 @@ import {
processImageWithReveal,
processMediaUrl,
processNostrIdentifiersInText,
processAllNostrIdentifiers,
processWebSocketUrls,
processWikilinks,
stripTrackingParams,
@ -217,7 +216,7 @@ function processBasicFormatting(content: string): string { @@ -217,7 +216,7 @@ function processBasicFormatting(content: string): string {
return processedText;
}
export async function parseBasicmarkup(text: string, ndk?: NDK): Promise<string> {
export async function parseBasicMarkup(text: string, ndk?: NDK): Promise<string> {
if (!text) return "";
try {
@ -258,10 +257,7 @@ export async function parseBasicmarkup(text: string, ndk?: NDK): Promise<string> @@ -258,10 +257,7 @@ export async function parseBasicmarkup(text: string, ndk?: NDK): Promise<string>
processedText = processWikilinks(processedText);
return processedText;
} catch (e: unknown) {
console.error("Error in parseBasicmarkup:", e);
return `<div class="text-red-500">Error processing markup: ${
(e as Error)?.message ?? "Unknown error"
}</div>`;
} catch (e) {
throw new Error(`Error in parseBasicMarkup: ${e}`);
}
}

29
src/routes/events/+page.svelte

@ -21,10 +21,8 @@ @@ -21,10 +21,8 @@
import { getEventType } from "$lib/utils/mime";
import ViewPublicationLink from "$lib/components/util/ViewPublicationLink.svelte";
import { checkCommunity } from "$lib/utils/search_utility";
import { parseBasicmarkup } from "$lib/utils/markup/basicMarkupParser";
import { repostContent, quotedContent } from "$lib/components/embedded_events/EmbeddedSnippets.svelte";
import { repostContent, quotedContent } from "$lib/snippets/EmbeddedSnippets.svelte";
import { repostKinds } from "$lib/consts";
import { userStore } from "$lib/stores/userStore";
import {
fetchCurrentUserLists,
@ -34,6 +32,7 @@ @@ -34,6 +32,7 @@
import type { UserProfile } from "$lib/models/user_profile";
import type { SearchType } from "$lib/models/search_type";
import { clearAllCaches } from "$lib/utils/cache_manager";
import { basicMarkup } from "$lib/snippets/MarkupSnippets.svelte";
// AI-NOTE: 2025-01-24 - Add cache clearing function for testing second-order search
// This can be called from browser console: window.clearCache()
@ -796,17 +795,13 @@ @@ -796,17 +795,13 @@
<div class="text-xs text-gray-500 dark:text-gray-400 mb-1">
Comment:
</div>
{#await parseBasicmarkup(result.content.slice(0, 100) + (result.content.length > 100 ? "..." : "")) then parsed}
{@html parsed}
{/await}
{@render basicMarkup(result.content.slice(0, 100) + (result.content.length > 100 ? "..." : ""), ndk)}
</div>
{/if}
</div>
{:else}
<!-- Regular content -->
{#await parseBasicmarkup(result.content.slice(0, 200) + (result.content.length > 200 ? "..." : "")) then parsed}
{@html parsed}
{/await}
{@render basicMarkup(result.content.slice(0, 200) + (result.content.length > 200 ? "..." : ""), ndk)}
{/if}
</div>
{/if}
@ -1011,17 +1006,13 @@ @@ -1011,17 +1006,13 @@
<div class="text-xs text-gray-500 dark:text-gray-400 mb-1">
Comment:
</div>
{#await parseBasicmarkup(result.content.slice(0, 100) + (result.content.length > 100 ? "..." : "")) then parsed}
{@html parsed}
{/await}
{@render basicMarkup(result.content.slice(0, 100) + (result.content.length > 100 ? "..." : ""), ndk)}
</div>
{/if}
</div>
{:else}
<!-- Regular content -->
{#await parseBasicmarkup(result.content.slice(0, 200) + (result.content.length > 200 ? "..." : "")) then parsed}
{@html parsed}
{/await}
{@render basicMarkup(result.content.slice(0, 200) + (result.content.length > 200 ? "..." : ""), ndk)}
{/if}
</div>
{/if}
@ -1212,17 +1203,13 @@ @@ -1212,17 +1203,13 @@
<div class="text-xs text-gray-500 dark:text-gray-400 mb-1">
Comment:
</div>
{#await parseBasicmarkup(result.content.slice(0, 100) + (result.content.length > 100 ? "..." : "")) then parsed}
{@html parsed}
{/await}
{@render basicMarkup(result.content.slice(0, 100) + (result.content.length > 100 ? "..." : ""), ndk)}
</div>
{/if}
</div>
{:else}
<!-- Regular content -->
{#await parseBasicmarkup(result.content.slice(0, 200) + (result.content.length > 200 ? "..." : "")) then parsed}
{@html parsed}
{/await}
{@render basicMarkup(result.content.slice(0, 200) + (result.content.length > 200 ? "..." : ""), ndk)}
{/if}
</div>
{/if}

Loading…
Cancel
Save