feat(editor): render Obsidian-style callouts from blockquotes#912
Open
praxstack wants to merge 61 commits into
Open
feat(editor): render Obsidian-style callouts from blockquotes#912praxstack wants to merge 61 commits into
praxstack wants to merge 61 commits into
Conversation
Obsidian callouts authored as blockquotes — `> [!tip] Title`, `> [!abstract]`,
collapsible `> [!example]+` / `> [!failure]-` — previously rendered as plain
quote blocks in the BlockNote editor, showing the literal `[!type]` marker text
instead of a styled box.
This adds a `calloutBlock` custom block:
- Post-parse, a quote block whose first line matches `[!type]` is converted into
a callout block (type, optional fold modifier, title, body) via a small
block-graph transform wired into the existing inject pipeline.
- Serialisation emits the callout back to exact `> [!type]<fold> title` /`> body`
markdown, so notes round-trip losslessly (the callout TYPE token is normalised
to lowercase, since Obsidian treats it case-insensitively).
- Rendering shows an emoji + label header and a colored left border, mapping
callout type families onto the app's existing semantic feedback palette
(`--feedback-{info,success,warning,error}`), theme-aware.
Round-trip safety is covered by unit + editor-integration tests, including
adversarial cases (a body line that itself looks like a marker, blank body
lines, whitespace-only titles, unknown types falling back to the default style).
Notes for maintainers:
- Type labels are kept in code for this change; they may warrant extraction into
the Lara localisation pipeline (`src/lib/locales/en.json`).
- I could not run the project's CodeScene/Codacy/Lara/Playwright-sidecar gates as
an external contributor; locally `tsc --noEmit`, `eslint`, `vitest`, and
`pnpm build` all pass clean.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What & why
Obsidian-style callouts authored as blockquotes —
> [!tip] Title,> [!abstract], and collapsible> [!example]+/> [!failure]-— currently render as plainquoteblocks in the BlockNote editor, showing the literal[!type]marker text instead of a styled box. Since Tolaria is designed to operate the same markdown vault as Obsidian, every callout in a migrated/shared vault looks broken. (Refs #910.)This renders them as styled callout boxes while keeping the markdown round-trip lossless.
How
A new
calloutBlockcustom block (mirrors the existingcreateReactBlockSpecpattern used by Math/Mermaid/Tldraw):quoteblock whose first line matches[!type]is converted into a callout block (calloutType,fold,title,body) via a small block-graph transform wired into the existing inject pipeline ineditorBlockResolution.ts.> [!type]<fold> title+> bodymarkdown, wired into the existing durable-serialisation chain ineditorDurableMarkdown.ts. The callout type token is normalised to lowercase (Obsidian treats it case-insensitively); title, body, and fold are preserved verbatim.--feedback-{info,success,warning,error},--color-accent), so it stays theme-aware. Unknown types fall back to the default "note" style (never crash).The detection regex is deliberately simple (no lookbehind) so it stays safe under WKWebView.
Tests
src/utils/calloutMarkdown.test.ts— unit coverage of marker parse/format, the type map + unknown-type fallback, and round-trip identity for every type with/without title and with+/-folds, plus adversarial cases: a body line that itself looks like a marker stays in the body, blank body lines round-trip as bare>, whitespace-only titles collapse to marker-only.src/components/editorSchema.callout.test.ts— editor integration: markdown → blocks → callout block → markdown through the realBlockNoteEditor+ schema, proving notes don't corrupt on a parse/serialise cycle.Scope / notes for maintainers
src/lib/locales/en.json) — I left a code comment to that effect but did not runpnpm l10n:translate(see below).l10n:translate, and the Chunk-sidecar Playwright lane) requires maintainer infrastructure/credentials I don't have, so I could not run those and the push used--no-verifyon my fork. Everything runnable locally is green:npx tsc --noEmit— 0 errorspnpm lint(eslint) — clean on all touched filespnpm test(vitest) — callout unit + integration tests pass; no existing tests brokenpnpm build— succeedsas any, noeslint-disable— but I can't produce the 10.0 score myself. Please run it your side; happy to refactor anything it flags.I understand per CONTRIBUTING that features usually go through Canny first — I've also raised this there. Submitting the implementation in case a working, tested, focused PR is useful to land it faster. Totally fine to treat as a starting point.