M6: lightweight markdown rendering on note cards
Render a small Markdown subset in the read view — headings (#..###), bullet/ordered lists, blockquote, fenced code, and inline **bold** / *italic* / `code` — alongside the existing [[wiki-links]] (task 1905). Capture stays plain text; only the card render is formatted. Hand-rolled dependency-free parser (notes/markdown.ts) rendered as Vue vnodes (MarkdownText/MarkdownInline), never v-html, so there is no HTML-injection surface — matches the no-heavy-dep ethos. Headings require '# ' (space), so a #tag (no space) stays plain text and is never mistaken for a heading; the two coexist. Replaces LinkedText (links-only) on the card; LinkedText deleted. Pure frontend — no backend/DB/migration. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FRgehjoz7Yv8LkUfADxACm
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
<script setup lang="ts">
|
||||
import { useRouter } from "vue-router";
|
||||
import { useNotesStore } from "../stores/notes";
|
||||
import { useTitlesStore } from "../stores/titles";
|
||||
import type { InlineToken } from "../notes/markdown";
|
||||
|
||||
defineProps<{ tokens: InlineToken[] }>();
|
||||
|
||||
const router = useRouter();
|
||||
const notes = useNotesStore();
|
||||
const titles = useTitlesStore();
|
||||
|
||||
// A [[wiki-link]] on the card: resolve the title and open the target note (creating
|
||||
// it first if it doesn't exist), via the board's ?open=<id> mechanism.
|
||||
async function follow(title: string) {
|
||||
await titles.load();
|
||||
let hit = titles.resolve(title);
|
||||
if (!hit) {
|
||||
const created = await notes.createTitled(title);
|
||||
await titles.reload();
|
||||
hit = { id: created.id, title: created.title ?? title };
|
||||
}
|
||||
void router.push({ path: "/", query: { open: hit.id } });
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Rendered tightly (no whitespace between tokens) so a token's own leading/trailing
|
||||
spaces are preserved and no extra spaces are introduced. -->
|
||||
<template
|
||||
><template v-for="(t, i) in tokens" :key="i"
|
||||
><span
|
||||
v-if="t.type === 'link'"
|
||||
role="link"
|
||||
tabindex="0"
|
||||
class="cursor-pointer font-medium text-brand-700 underline-offset-2 hover:underline dark:text-brand"
|
||||
@click.stop="follow(t.value)"
|
||||
@keydown.enter.stop.prevent="follow(t.value)"
|
||||
>{{ t.value }}</span
|
||||
><strong v-else-if="t.type === 'bold'" class="font-semibold">{{ t.value }}</strong
|
||||
><em v-else-if="t.type === 'italic'">{{ t.value }}</em
|
||||
><code
|
||||
v-else-if="t.type === 'code'"
|
||||
class="rounded bg-black/5 px-1 py-0.5 font-mono text-[0.85em] dark:bg-white/10"
|
||||
>{{ t.value }}</code
|
||||
><template v-else>{{ t.value }}</template></template
|
||||
></template
|
||||
>
|
||||
Reference in New Issue
Block a user