Repository navigation
Live Preview editor: inline callouts/code/tables/math/mermaid + Edit/Read modes #114
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 16 commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
7221285
fix: callout widget height (margin collapse) + arrow-key navigation
ddutchie 33efdc3
feat: inline code-block live-preview widget + shared block-widget base
ddutchie ac16833
feat: Raw editing mode in the note editor
ddutchie 14208d1
docs: changelog — inline code blocks + Raw editing mode
ddutchie 91bb7c2
fix: block widget flushSync crash when toggling Live Preview (Write/Raw)
ddutchie cd599f8
feat: live 'current block' preview for structured blocks while editing
ddutchie 86cff00
docs: changelog — live current-block preview panel
ddutchie 9bbba15
fix: clear the editor preview panel when switching notes
ddutchie 60588ed
fix: stable controlled checkbox in markdown preview (uncontrolled→con…
ddutchie a247229
feat: S/M/L size toggle on the editor preview panel
ddutchie 3ca8f09
refactor: editor modes to Edit/Read + a Live Preview toggle
ddutchie e156a52
docs: move editor-widget notes to v2.6.3; add What's New entry
ddutchie 21df71a
feat: inline table, math, and mermaid live-preview widgets + range-tr…
ddutchie 7025379
docs: changelog + What's New for inline tables/math/mermaid
ddutchie bd48149
fix: address PR #114 review — raw migration, findBlockWidgets cache, …
ddutchie a1be9a1
fix: block-widget rendering — inherited pre-wrap gap, flushSync crash…
ddutchie 48d6161
fix: address PR #114 review — inline empty-state, CommonMark fence close
ddutchie File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| ## What's new | ||
|
|
||
| ### Editor | ||
|
|
||
| - **Code blocks now render inline while editing.** Fenced code (```` ```lang ````) shows with syntax highlighting and a copy button right in the editor's Live Preview, and clicks into raw Markdown to edit — the same click-to-edit model as callouts. | ||
| - **Tables, math, and Mermaid diagrams also render inline.** GFM tables, `$$…$$` display math, and ```` ```mermaid ```` diagrams now render live in the editor too, each clicking into its raw source to edit. Combined with the follow-the-cursor preview below, you can build a table or diagram and watch it take shape — and immediately see when the Markdown breaks. | ||
| - **Editor modes simplified to Edit / Read, with a Live Preview toggle.** The note editor's mode switch is now just **Edit** and **Read**. While editing, a **Live Preview** toggle turns the inline rendering (callouts, code blocks, marker hiding) on or off — off shows the plain Markdown source, handy when an inline preview is in the way of editing the underlying syntax. Your choice is remembered. | ||
| - **The preview panel follows what you're editing.** When your cursor is inside a table, callout, code block, or math block, the panel at the bottom of the editor renders that block live as you type — so you can see it take shape and instantly notice when the Markdown is broken (a misaligned table row, an unclosed fence). Selecting text still previews the selection; plain paragraphs don't trigger it. The panel has an **S / M / L** size toggle to shrink or grow it. | ||
|
|
||
| ### Fixes | ||
|
|
||
| - **Callouts render and navigate correctly in the editor.** Fixed the callout live-preview widget mis-measuring its height (which drifted the cursor a line or two below where you clicked) and arrow keys skipping over a callout instead of stepping into it. Editing text around a callout now behaves normally. | ||
| - **Switching notes clears the preview panel.** The bottom preview no longer lingers showing the previous note's content until you click into the new one. | ||
| - **Markdown task-list checkboxes no longer trigger a console warning** when the preview updates (e.g. drag-selecting across checkboxes). They render as stable, read-only checkboxes. |
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
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| import { describe, it, expect, afterEach } from "vitest"; | ||
| import { render, screen, cleanup, fireEvent } from "@testing-library/react"; | ||
| import { MDPreviewPanel } from "./MDPreviewPanel"; | ||
|
|
||
| /** | ||
| * The preview panel has an S/M/L size toggle that shrinks/grows the panel and | ||
| * (for S) densifies the text via --preview-scale. (Persistence to storage | ||
| * mirrors the editor-mode pattern; not asserted here because the component-test | ||
| * jsdom's localStorage is a no-op.) | ||
| */ | ||
| describe("MDPreviewPanel — size toggle", () => { | ||
| afterEach(cleanup); | ||
|
|
||
| it("defaults to medium and exposes S/M/L controls", () => { | ||
| render(<MDPreviewPanel text="hello" onDismiss={() => {}} />); | ||
| expect(screen.getByLabelText("S preview")).toBeInTheDocument(); | ||
| expect(screen.getByLabelText("M preview")).toHaveAttribute("aria-pressed", "true"); | ||
| expect(screen.getByLabelText("L preview")).toBeInTheDocument(); | ||
| }); | ||
|
|
||
| it("switching to S selects it and shrinks the type scale; L is full size", () => { | ||
| const { container } = render(<MDPreviewPanel text="hello" onDismiss={() => {}} />); | ||
| const panel = () => container.querySelector("[data-md-preview-portal]") as HTMLElement; | ||
|
|
||
| fireEvent.click(screen.getByLabelText("S preview")); | ||
| expect(screen.getByLabelText("S preview")).toHaveAttribute("aria-pressed", "true"); | ||
| // S sets a sub-1 scale so text is denser. | ||
| expect(parseFloat(panel().style.getPropertyValue("--preview-scale"))).toBeLessThan(1); | ||
|
|
||
| fireEvent.click(screen.getByLabelText("L preview")); | ||
| expect(screen.getByLabelText("L preview")).toHaveAttribute("aria-pressed", "true"); | ||
| // L keeps full-size type (only the panel grows taller). | ||
| expect(parseFloat(panel().style.getPropertyValue("--preview-scale"))).toBe(1); | ||
| }); | ||
| }); |
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
62 changes: 62 additions & 0 deletions
62
src/components/notes/NoteMarkdownPreview.component.test.tsx
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| import { describe, it, expect, vi, afterEach } from "vitest"; | ||
| import { render, cleanup } from "@testing-library/react"; | ||
| import { NoteMarkdownPreview } from "./NoteMarkdownPreview"; | ||
|
|
||
| /** | ||
| * Regression: GFM task-list checkboxes rendered by NoteMarkdownPreview must be | ||
| * STABLE controlled read-only inputs. Raw GFM emits <input type="checkbox"> | ||
| * with `checked` present only when checked, so re-rendering with changed | ||
| * content (e.g. the editor's live preview updating as you drag-select across | ||
| * checkboxes) flipped React from uncontrolled → controlled and warned. The | ||
| * `input` component override fixes this. | ||
| */ | ||
| describe("NoteMarkdownPreview — task-list checkboxes", () => { | ||
| afterEach(cleanup); | ||
|
|
||
| it("renders task-list items as read-only, controlled checkboxes", () => { | ||
| const { container } = render( | ||
| <NoteMarkdownPreview content={"- [ ] todo\n- [x] done"} />, | ||
| ); | ||
| const boxes = container.querySelectorAll<HTMLInputElement>("input[type='checkbox']"); | ||
| expect(boxes.length).toBe(2); | ||
| expect(boxes[0].checked).toBe(false); | ||
| expect(boxes[1].checked).toBe(true); | ||
| // Read-only + disabled → controlled without an onChange handler is legal. | ||
| for (const b of boxes) { | ||
| expect(b.readOnly || b.disabled).toBe(true); | ||
| } | ||
| }); | ||
|
|
||
| it("does not warn about uncontrolled→controlled when content changes", () => { | ||
| const errSpy = vi.spyOn(console, "error").mockImplementation(() => {}); | ||
| const { rerender } = render(<NoteMarkdownPreview content={"- [ ] a"} />); | ||
| // Change content (mirrors the live preview updating) — an unchecked box | ||
| // becoming checked must not flip the input's controlled-ness. | ||
| rerender(<NoteMarkdownPreview content={"- [x] a"} />); | ||
| rerender(<NoteMarkdownPreview content={"- [ ] a\n- [x] b"} />); | ||
| const controlledWarning = errSpy.mock.calls.some((args) => | ||
| String(args[0]).includes("changing an uncontrolled input to be controlled"), | ||
| ); | ||
| expect(controlledWarning).toBe(false); | ||
| errSpy.mockRestore(); | ||
| }); | ||
|
|
||
| it("inline mode drops the full-pane chrome (padding / h-full / overflow)", () => { | ||
| // Embedded inside a callout body or block widget, the preview must sit | ||
| // compactly — not carry the standalone pane's px-6 py-5 h-full overflow, | ||
| // which was inflating callout/table widgets and making them look different | ||
| // from read mode. | ||
| const { container: pane } = render(<NoteMarkdownPreview content={"hi"} />); | ||
| const { container: inline } = render(<NoteMarkdownPreview content={"hi"} inline />); | ||
| const paneRoot = pane.querySelector(".prose-cairn") as HTMLElement; | ||
| const inlineRoot = inline.querySelector(".prose-cairn") as HTMLElement; | ||
| expect(paneRoot.className).toContain("h-full"); | ||
| expect(paneRoot.className).toContain("px-6"); | ||
| expect(inlineRoot.className).not.toContain("h-full"); | ||
| expect(inlineRoot.className).not.toContain("px-6"); | ||
| expect(inlineRoot.className).not.toContain("overflow-y-auto"); | ||
| // Inline zeroes the first/last child margins so the block (e.g. a table | ||
| // wrapper's my-3) hugs the top of its host — no empty space above it. | ||
| expect(inlineRoot.className).toContain("[&>*:first-child]:mt-0"); | ||
| }); | ||
| }); |
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
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
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.