Browse Source

Fix wiki link rendering and styling

Fixes:
- Wiki links now render correctly in preview (use placeholder approach to avoid Asciidoctor anchor syntax collision)
- Extract all tags (not just t-tags) for display
- Update colors to use theme leather tones (primary-700/800)
- Lighten bubble backgrounds for better readability
- Fix {@const} placement for Svelte 5 compliance

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
master
limina1 4 months ago
parent
commit
9220e13255
  1. 131
      src/lib/components/ZettelEditor.svelte

131
src/lib/components/ZettelEditor.svelte

@ -23,7 +23,7 @@
} from "$lib/utils/asciidoc_publication_parser"; } from "$lib/utils/asciidoc_publication_parser";
import { getNdkContext } from "$lib/ndk"; import { getNdkContext } from "$lib/ndk";
import Asciidoctor from "asciidoctor"; import Asciidoctor from "asciidoctor";
import { extractWikiLinks } from "$lib/utils/wiki_links"; import { extractWikiLinks, renderWikiLinksToHtml } from "$lib/utils/wiki_links";
// Initialize Asciidoctor processor // Initialize Asciidoctor processor
const asciidoctor = Asciidoctor(); const asciidoctor = Asciidoctor();
@ -218,7 +218,8 @@
event = findEventByDTag(publicationResult.contentEvents, node.dTag, node.eventKind); event = findEventByDTag(publicationResult.contentEvents, node.dTag, node.eventKind);
} }
const tags = event?.tags.filter((t: string[]) => t[0] === "t") || []; // Extract all tags (t for hashtags, w for wiki links)
const tags = event?.tags || [];
// Extract the title from the title tag // Extract the title from the title tag
const titleTag = event?.tags.find((t: string[]) => t[0] === "title"); const titleTag = event?.tags.find((t: string[]) => t[0] === "title");
@ -722,18 +723,18 @@
fontWeight: "500", fontWeight: "500",
fontStyle: "italic", fontStyle: "italic",
}, },
// Wiki links // Wiki links - using theme primary colors (leather tones)
".cm-wiki-link-auto": { ".cm-wiki-link-auto": {
color: "#8B5CF6", // violet-500 for [[term]] (auto) color: "var(--color-primary-700)", // [[term]] (auto) - medium leather
fontWeight: "500", fontWeight: "500",
backgroundColor: "rgba(139, 92, 246, 0.1)", backgroundColor: "color-mix(in srgb, var(--color-primary-700) 10%, transparent)",
padding: "2px 4px", padding: "2px 4px",
borderRadius: "3px", borderRadius: "3px",
}, },
".cm-wiki-link-ref": { ".cm-wiki-link-ref": {
color: "#06B6D4", // cyan-500 for [[w:term]] (reference) color: "var(--color-primary-800)", // [[w:term]] (reference) - darker leather
fontWeight: "500", fontWeight: "500",
backgroundColor: "rgba(6, 182, 212, 0.1)", backgroundColor: "color-mix(in srgb, var(--color-primary-800) 10%, transparent)",
padding: "2px 4px", padding: "2px 4px",
borderRadius: "3px", borderRadius: "3px",
}, },
@ -1026,10 +1027,11 @@
</h2> </h2>
<!-- Tags and wiki links --> <!-- Tags and wiki links -->
{@const tTags = section.tags?.filter((tag) => tag[0] === 't') || []} {#if section.tags && section.tags.length > 0}
{@const wTags = section.tags?.filter((tag) => tag[0] === 'w') || []} {@const tTags = section.tags.filter((tag) => tag[0] === 't')}
{@const wTags = section.tags.filter((tag) => tag[0] === 'w')}
{#if tTags.length > 0 || wTags.length > 0} {#if tTags.length > 0 || wTags.length > 0}
<div class="space-y-2"> <div class="space-y-2">
<!-- Hashtags (t-tags) --> <!-- Hashtags (t-tags) -->
{#if tTags.length > 0} {#if tTags.length > 0}
@ -1049,7 +1051,7 @@
<div class="flex flex-wrap gap-2"> <div class="flex flex-wrap gap-2">
{#each wTags as tag} {#each wTags as tag}
<span <span
class="bg-cyan-100 text-cyan-800 dark:bg-cyan-900 dark:text-cyan-200 px-2 py-1 rounded text-xs font-medium border border-cyan-300 dark:border-cyan-700" class="bg-primary-50 text-primary-800 dark:bg-primary-950/40 dark:text-primary-200 px-2 py-1 rounded-full text-xs font-medium"
title="Wiki reference: {tag[1]}" title="Wiki reference: {tag[1]}"
> >
🔗 {tag[2] || tag[1]} 🔗 {tag[2] || tag[1]}
@ -1058,6 +1060,7 @@
</div> </div>
{/if} {/if}
</div> </div>
{/if}
{/if} {/if}
</div> </div>
{:else} {:else}
@ -1087,10 +1090,11 @@
</div> </div>
<!-- Tags and wiki links (green for content events) --> <!-- Tags and wiki links (green for content events) -->
{@const tTags = section.tags?.filter((tag) => tag[0] === 't') || []} {#if section.tags && section.tags.length > 0}
{@const wTags = section.tags?.filter((tag) => tag[0] === 'w') || []} {@const tTags = section.tags.filter((tag) => tag[0] === 't')}
{@const wTags = section.tags.filter((tag) => tag[0] === 'w')}
{#if tTags.length > 0 || wTags.length > 0} {#if tTags.length > 0 || wTags.length > 0}
<div class="space-y-2"> <div class="space-y-2">
<!-- Hashtags (t-tags) --> <!-- Hashtags (t-tags) -->
{#if tTags.length > 0} {#if tTags.length > 0}
@ -1110,15 +1114,16 @@
<div class="flex flex-wrap gap-2"> <div class="flex flex-wrap gap-2">
{#each wTags as tag} {#each wTags as tag}
<span <span
class="bg-cyan-100 text-cyan-800 dark:bg-cyan-900 dark:text-cyan-200 px-2 py-1 rounded text-xs font-medium border border-cyan-300 dark:border-cyan-700" class="bg-primary-50 text-primary-800 dark:bg-primary-950/40 dark:text-primary-200 px-2 py-1 rounded-full text-xs font-medium"
title="Wiki reference: {tag[1]}" title="Wiki reference: {tag[1]}"
> >
🔗 {tag[2] || tag[1]} 🔗 {tag[2] || tag[1]}
</span> </span>
{/each} {/each}
</div} </div>
{/if} {/if}
</div> </div>
{/if}
{/if} {/if}
<!-- Content rendered as AsciiDoc --> <!-- Content rendered as AsciiDoc -->
@ -1127,17 +1132,30 @@
class="prose prose-sm dark:prose-invert max-w-none mt-4" class="prose prose-sm dark:prose-invert max-w-none mt-4"
> >
{@html (() => { {@html (() => {
// Extract wiki links and replace with placeholders BEFORE Asciidoctor
const wikiLinks = extractWikiLinks(section.content);
let contentWithPlaceholders = section.content;
const placeholders = new Map();
wikiLinks.forEach((link, index) => {
// Use a placeholder inside a passthrough macro - Asciidoctor will strip pass:[...] and leave the inner text
const innerPlaceholder = `WIKILINK${index}PLACEHOLDER`;
const placeholder = `pass:[${innerPlaceholder}]`;
placeholders.set(innerPlaceholder, link); // Store by inner placeholder (what will remain after Asciidoctor)
contentWithPlaceholders = contentWithPlaceholders.replace(link.fullMatch, placeholder);
});
// Check if content contains nested headers // Check if content contains nested headers
const hasNestedHeaders = section.content.includes('\n===') || section.content.includes('\n===='); const hasNestedHeaders = contentWithPlaceholders.includes('\n===') || contentWithPlaceholders.includes('\n====');
let rendered;
if (hasNestedHeaders) { if (hasNestedHeaders) {
// For proper nested header parsing, we need full document context // For proper nested header parsing, we need full document context
// Create a complete AsciiDoc document structure // Create a complete AsciiDoc document structure
// Important: Ensure proper level sequence for nested headers // Important: Ensure proper level sequence for nested headers
const fullDoc = `= Temporary Document\n\n${"=".repeat(section.level)} ${section.title}\n\n${section.content}`; const fullDoc = `= Temporary Document\n\n${"=".repeat(section.level)} ${section.title}\n\n${contentWithPlaceholders}`;
rendered = asciidoctor.convert(fullDoc, {
const rendered = asciidoctor.convert(fullDoc, {
standalone: false, standalone: false,
attributes: { attributes: {
showtitle: false, showtitle: false,
@ -1145,7 +1163,6 @@
}, },
}); });
// Extract just the content we want (remove the temporary structure) // Extract just the content we want (remove the temporary structure)
// Find the section we care about // Find the section we care about
const sectionStart = rendered.indexOf(`<h${section.level}`); const sectionStart = rendered.indexOf(`<h${section.level}`);
@ -1157,15 +1174,13 @@
// Find where the section ends (at the closing div) // Find where the section ends (at the closing div)
const sectionEnd = afterHeader.lastIndexOf('</div>'); const sectionEnd = afterHeader.lastIndexOf('</div>');
if (sectionEnd !== -1) { if (sectionEnd !== -1) {
const extracted = afterHeader.substring(0, sectionEnd); rendered = afterHeader.substring(0, sectionEnd);
return extracted;
} }
} }
} }
return rendered;
} else { } else {
// Simple content without nested headers // Simple content without nested headers
return asciidoctor.convert(section.content, { rendered = asciidoctor.convert(contentWithPlaceholders, {
standalone: false, standalone: false,
attributes: { attributes: {
showtitle: false, showtitle: false,
@ -1173,6 +1188,32 @@
}, },
}); });
} }
// Replace placeholders with actual wiki link HTML
// Use a global regex to catch all occurrences (Asciidoctor might have duplicated them)
placeholders.forEach((link, placeholder) => {
const className =
link.type === 'auto'
? 'wiki-link wiki-link-auto'
: link.type === 'w'
? 'wiki-link wiki-link-ref'
: 'wiki-link wiki-link-def';
const title =
link.type === 'w'
? 'Wiki reference (mentions this concept)'
: link.type === 'd'
? 'Wiki definition (defines this concept)'
: 'Wiki link (searches both references and definitions)';
const html = `<a class="${className}" href="#wiki/${link.type}/${encodeURIComponent(link.term)}" title="${title}" data-wiki-type="${link.type}" data-wiki-term="${link.term}">${link.displayText}</a>`;
// Use global replace to handle all occurrences
const regex = new RegExp(placeholder.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g');
rendered = rendered.replace(regex, html);
});
return rendered;
})()} })()}
</div> </div>
{/if} {/if}
@ -1567,3 +1608,43 @@ Understanding the nature of knowledge...
{/if} {/if}
</div> </div>
</div> </div>
<style>
/* Wiki link styling in preview */
:global(.prose .wiki-link) {
text-decoration: none;
border-bottom: 1px dotted currentColor;
transition: all 0.2s;
}
:global(.prose .wiki-link:hover) {
border-bottom-style: solid;
}
/* Use theme primary colors (leather tones) */
:global(.prose .wiki-link-auto) {
color: var(--color-primary-700); /* medium leather */
}
:global(.prose .wiki-link-ref) {
color: var(--color-primary-800); /* darker leather */
}
:global(.prose .wiki-link-def) {
color: var(--color-warning-600); /* amber/yellow for definitions */
font-weight: 500;
}
/* Dark mode - use lighter shades for contrast */
:global(.dark .prose .wiki-link-auto) {
color: var(--color-primary-300); /* lighter leather for dark mode */
}
:global(.dark .prose .wiki-link-ref) {
color: var(--color-primary-400); /* medium-light leather for dark mode */
}
:global(.dark .prose .wiki-link-def) {
color: var(--color-warning-400); /* brighter amber for dark mode */
}
</style>

Loading…
Cancel
Save