diff --git a/templates/annotations_banner.templ b/templates/annotations_banner.templ index 647da894..a0436ad5 100644 --- a/templates/annotations_banner.templ +++ b/templates/annotations_banner.templ @@ -61,11 +61,11 @@ templ AnnotationsBanner(book handlers.MediaDetail) {
diff --git a/templates/annotations_banner_templ.go b/templates/annotations_banner_templ.go index 13e4d50c..5b039980 100644 --- a/templates/annotations_banner_templ.go +++ b/templates/annotations_banner_templ.go @@ -50,7 +50,7 @@ func AnnotationsBanner(book handlers.MediaDetail) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "

Loading…

Markdown supported

Export annotations

Notes and highlights, as markdown files.

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "

Loading…

Markdown supported

Export annotations

Notes and highlights, as markdown files.

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/web/src/annotations-banner.ts b/web/src/annotations-banner.ts index f6880781..cb211fac 100644 --- a/web/src/annotations-banner.ts +++ b/web/src/annotations-banner.ts @@ -19,8 +19,11 @@ interface AnnotationRow { label: string; /** compact preview line: stripped markdown (note) or capped selection */ preview: string; - /** rich body for the expanded view (pre-sanitized) */ - rich: string; + /** expanded view, split so the highlight→note boundary is explicit: + * the escaped selection text, then the note's markdown-rendered HTML + * (empty when the row has no note body). */ + richSelection: string; + richNote: string; /** the ?loc= handoff value for the jump */ loc: string; /** row accent color (swatch) */ @@ -96,7 +99,8 @@ function buildAnnotationRows( kind: "note", label, preview: stripNote(content) || "(empty note)", - rich: renderNoteHtml(content), + richSelection: "", + richNote: renderNoteHtml(content), loc, color: "var(--accent)", hasNote: true, @@ -135,17 +139,19 @@ function buildAnnotationRows( } const capped = selection.length > 80 ? selection.slice(0, 80) + "…" : selection; - // Expanded body: the full highlight text followed by its rich note. - const rich = note - ? `

${escapeHtmlForRich(selection)}

` + renderNoteHtml(note) - : `

${escapeHtmlForRich(selection) || "…"}

`; + // Expanded body, split at the highlight→note boundary: the escaped + // selection, then the note's rendered markdown under a labeled divider + // (rendered by the banner template). + const richSelection = escapeHtmlForRich(selection); + const richNote = note ? renderNoteHtml(note) : ""; const row: AnnotationRow = { key: `hl-${h.id}`, kind: "highlight", label, preview: capped || "(empty highlight)", - rich, + richSelection, + richNote, loc, color, hasNote: !!note, diff --git a/web/src/book-detail.ts b/web/src/book-detail.ts index 3dce2aeb..bb35c84f 100644 --- a/web/src/book-detail.ts +++ b/web/src/book-detail.ts @@ -225,13 +225,14 @@ Alpine.data("bookDetail", () => { }, // ----- note edit/delete at the banner (objective 8a) ----- startBannerEdit(row: AnnotationRow): void { - // Highlight rows edit the note ATTACHED to the highlight (never the - // highlight itself): rich = "\n\n". + // Both kinds carry the raw note body on their source: highlights + // hold the ATTACHED note (never the highlight itself), location + // notes hold their content. The old code parsed row.rich (rendered + // HTML — no \n\n separators in it) and came up empty every time. this.bannerEditRow = row; - this.bannerEditText = - row.kind === "highlight" - ? String((row.rich.split("\n\n").slice(1).join("\n\n") || "")).trim() - : String(this.bannerSources.find(s => s.row.key === row.key)?.source ?? ""); + this.bannerEditText = String( + this.bannerSources.find(s => s.row.key === row.key)?.source ?? "", + ); this.bannerEditOpen = true; }, async saveBannerNote() {