Skip to content

Releases: zahidshowrav/blockleaf

@blockleaf/editor@0.0.6

Choose a tag to compare

@github-actions github-actions released this 15 Oct 19:17
366ad5f

Patch Changes

  • dfbc49d: Stop the chrome throwing when an extension it offers is not loaded.

    isAvailable asked editor.can().toggleHighlight(), but editor.can() only
    carries the commands the configured extensions registered. With
    preset="minimal" — no highlight, no tables — selecting text threw
    editor.can(...).toggleHighlight is not a function while the toolbar was
    rendering, and the cell menu threw the same way on mergeCells, so the whole
    editor failed to mount. The same held for any host passing its own
    extensions array without one of those types.

    Every default action now checks its mark or node is in the schema before
    asking whether the command applies, through a shared whenMarkExists /
    whenNodeExists guard. A type the editor does not have reads as unavailable
    and the button is hidden, which is what isAvailable was for.

  • f254f17: Fix task list items rendering stacked instead of inline, and list markers
    disappearing under a CSS reset.

    The task item rules were keyed to li[data-type='taskItem'], an attribute
    that only exists in serialised output. Tiptap builds the live item in
    addNodeView from the node's own attributes — data-checked, data-id — and
    never applies the data-type that renderHTML merges in. So the selector
    styled every rendered page and no actual editor, and the checkbox sat above
    its label rather than beside it for as long as task lists have existed. The
    rules now match through the parent list, ul[data-type='taskList'] > li,
    which is present in both.

    list-style is now stated rather than inherited from the user agent. Every
    common reset strips list markers, so bullets and numbers vanished in any app
    using Tailwind's preflight or normalize — and "works without a CSS framework"
    should not mean "breaks with one".

    Both were found by putting a task list and a bullet list in the documentation
    site's live demo.

@blockleaf/renderer@0.0.1

Choose a tag to compare

@github-actions github-actions released this 13 Oct 18:43
e341600

Patch Changes

  • 5da026d: Add @blockleaf/renderer (RD-01) — a new package that turns a stored document
    into React elements on a server, without the editor and without ProseMirror.

    import { RenderDocument } from '@blockleaf/renderer/react';
    
    <RenderDocument doc={article.body} />;

    It has no runtime dependencies and React is an optional peer, so the
    sanitisers at the root entry work in a plain Node script. RenderDocument
    measures 1.35 kB.

    BlockleafDocument satisfies the doc prop structurally — there is no import
    from @blockleaf/editor and nothing to convert.

    Every element matches what the editor writes for the same node, so one
    stylesheet fits both; a parity suite renders the same document through both
    packages and fails if they diverge. Three differences are deliberate: the task
    checkbox is disabled, since nothing here can write a change back; an unknown
    node renders its children rather than throwing, because a document written by
    a newer editor must not take down the page; and src and href are
    sanitised again at render time, because stored JSON predates any guarantee the
    editor made about it.

  • 6d2770d: Add per-node and per-mark component overrides (RD-02).

    <RenderDocument
    	doc={article.body}
    	components={{ image: MyImage, codeBlock: MyCode }}
    />

    An override is a patch over the defaults, not a replacement mapping — naming
    image does not mean re-implementing paragraphs. A name the defaults do not
    know works too, which is how a host renders a node type of its own.

    The attribute helpers the default components use are exported alongside, so an
    override routing images through next/image can call imageAttributes(node)
    and get a sanitised src rather than re-deriving the rule.

    Overrides are merged per render rather than memoised, because the component
    has to work in a server component, where hooks do not exist. Hoisting the
    override map to module scope avoids the merge.

  • a9c4af8: Add the HTML-string and plain-text exports (RD-03).

    import { renderToHTML } from '@blockleaf/renderer/html';
    import { extractHeadings, toPlainText } from '@blockleaf/renderer';
    
    const body = renderToHTML(doc); // emails, RSS, OG images
    const excerpt = toPlainText(doc).slice(0, 200);
    const toc = extractHeadings(doc);

    renderToHTML measures 1.5 kB and needs no React at all — the ./html entry
    imports nothing. It takes the same components and marks overrides the
    React target does, and writes the same markup: the parity suite now holds
    three implementations to each other, the two targets and the editor itself.

    Escaping is this target's own job rather than React's, so text and attribute
    values are escaped separately, and a document whose stored alt is
    " onerror="alert(1) renders as an attribute rather than as a handler.

    toPlainText takes a blockSeparator and leaves image alt text out unless
    asked, since alt text reads as an interruption in an excerpt and as useful
    signal in a search index. extractHeadings returns level, text and block id
    in document order.

  • b358752: Render tables as real table markup, in both targets (RD-07).

    prosemirror-tables has no concept of a table section, so the editor writes
    every row into <tbody>, header cells included. That is tolerable in a
    surface the user is looking at and not tolerable in output: <thead> is most
    of what an email client, an RSS reader or a screen reader has to go on.

    So a leading row made entirely of header cells becomes <thead>, and the rest
    <tbody>. A row with a mix of headers and cells is a header column, which
    prosemirror-tables also allows, and is left in <tbody> rather than claiming
    to label everything below it. Header cells carry scope: col in a header
    row, row for a header cell leading a body row.

    Stored column widths move into <colgroup>, where the editor also puts them.
    The colwidth attribute the editor additionally writes on each cell is
    dropped — it is not an HTML attribute, and the width is already in the
    <col>.

    This is the only node the renderer deliberately writes differently from the
    editor. Everything else is held to it by the parity suite.

@blockleaf/editor@0.0.5

Choose a tag to compare

@github-actions github-actions released this 13 Oct 13:51
201edaa

Patch Changes

  • b6f7882: Add the upload adapter interface (MC-01). Hosts implement UploadAdapter and
    the editor drives it; no storage provider is baked in. The second argument is a
    context object rather than a bare AbortSignal so adapters can report
    determinate progress, and isUploadResult validates what an adapter resolves
    with before it can reach the document.

  • 05657cb: Add drop, paste and picker entry points for uploads (MC-02). Dropping files on
    the editor, pasting them from the clipboard, or calling openUploadPicker
    screens each file for type and size before it leaves the browser, then
    hands it to the adapter and inserts an image where it landed.

    The in-flight placeholder is a widget decoration rather than a node, so
    ProseMirror maps its position through every edit made while the upload runs —
    an image dropped at the end of a paragraph still lands there after typing
    above it — and nothing has to be stripped out of the document before saving.

    upload, uploadAccept, uploadMaxBytes and onUploadError are new options
    on useBlockleafEditor and <BlockleafEditor>. Without an adapter the
    extension is not added at all, so files fall through to the browser rather
    than being swallowed.

  • 5fa4e4c: Add upload progress, cancel and retry (MC-03). The in-flight placeholder now
    shows a progress bar driven by the adapter's onProgress, with a Cancel
    button that aborts the request through the signal.

    Progress is indeterminate until an adapter reports a fraction — fetch cannot
    measure upload progress, so a bar claiming a percentage it does not have would
    be a lie. An indeterminate progressbar omits aria-valuenow rather than
    reporting zero.

    A failed upload no longer disappears. Its placeholder stays, holding the
    position it has been mapping all along, and offers Retry and Dismiss — so a
    retry puts the image where the user dropped it, even after editing above it.
    A cancelled upload is not reported as a failure.

  • a114e5d: Add the image block with alt text and alignment (MC-04). The node is
    hand-rolled rather than taken from @tiptap/extension-image, and src is
    sanitised in the schema: https:, relative paths and raster data URLs are
    allowed, while javascript:, blob:, http: and SVG data URLs are rejected.
    An unsafe <img> arriving through a paste is dropped rather than stored with
    a null source.

    Selecting an image opens a toolbar for alignment, alt text and delete. An
    empty alt is kept rather than discarded, since that is how an image is marked
    decorative.

    The bubble menu's link editor is now one of a general panel slot, so the
    toolbar's keyboard handling lives in one place. Three CSS classes were
    renamed to match: blockleaf-bubble-menu__link-editor, __link-input and
    __link-error are now __panel, __panel-input and __panel-error.
    Closing a panel now returns focus to the button that opened it, which the
    link editor had never managed.

  • c004dfe: Add the syntax-highlighted code block with a language picker (MC-05), as the
    new opt-in subpath @blockleaf/editor/code-block.

    import { createExtensions } from '@blockleaf/editor/extensions';
    import { withCodeBlock } from '@blockleaf/editor/code-block';
    
    const extensions = withCodeBlock(createExtensions('standard'));

    It is a subpath rather than part of the default build because lowlight and
    its grammars cost 16.53 kB and the default bundle had 15.37 kB left — shipping
    it by default put the editor at 161.16 kB, over the ceiling. Importing nothing
    costs nothing, and the main entry is unchanged at 144.63 kB. See F14.

    Seven grammars ship by default — bash, css, javascript, json, python,
    typescript and xml — and hosts register more through additionalLanguages.
    StarterKit's plain code block is untouched for anyone who does not import
    the subpath, and the stored shape is identical either way.

    A code block whose language this host has not registered now renders
    unhighlighted instead of throwing during editor construction, and keeps its
    language attribute rather than having it stripped on the next save.

@blockleaf/editor@0.0.3

Choose a tag to compare

@zahidshowrav zahidshowrav released this 12 Oct 15:09
530253a

Patch Changes

  • Implement editing core milestone (EC-01 through EC-20):
    • Headless node and mark set with discriminated unions
    • Stable UUID block IDs
    • Markdown input rules and keyboard shortcuts
    • Paste normalisation for HTML, Word, and Google Docs
    • Table node set, insert/structure commands, cell merge/split, and Tab navigation
    • Opt-in Zod schema validation via @blockleaf/editor/validate
    • Playwright browser smoke test harness

@blockleaf/editor@0.0.4

Choose a tag to compare

@zahidshowrav zahidshowrav released this 13 Oct 08:23
694d586

Patch Changes

  • d4ee34a: Implement slash command menu backed by command registry with full keyboard and accessibility support (CH-01)
  • e44eabb: Add the selection bubble menu with a link editor (CH-02). Six mark toggles with ARIA toolbar keyboard navigation, and an href field that mirrors the schema's sanitisation so a rejected link says why.
  • 7f1cbc8: Add the drag handle and block menu (CH-03). Hand-rolled rather than using @tiptap/extension-drag-handle, which imports yjs at module scope and costs 32.87 kB; this costs 1.83 kB and no new dependency.
  • 5f8e57e: Add the placeholder and configure the trailing node (CH-04). The placeholder is a hand-rolled decoration plugin — @tiptap/extensionsdeclares nosideEffects, which pushed the schema-only entry from 136 B to 60.77 kB. StarterKit's trailingNode` no longer appends an empty paragraph after a heading, and a saved document that needs a trailing paragraph gets one on load without reporting it as a user edit.
  • 5efe7d3: Make the chrome keyboard-complete and axe-clean (CH-05). Adds a label prop giving the editing surface an accessible name, Shift+F10 and Mod+Alt+M for the block menu, Mod+Alt+R / Mod+Alt+C for the table menus, and fixes a colour-contrast failure on selected menu rows. The link editor now wins focus reliably, including when Mod+K is pressed before the toolbar has appeared.
  • 67a6702: Add the table grid-size picker to the slash menu (CH-06). Choosing Table now opens an 8 × 8 grid driven by pointer or arrow key, calling the insertTable command from EC-13. Slash commands can carry a picker for any command that needs an argument.
  • b40815c: Add in-table row and column handles (CH-07). Each opens a menu of structure operations calling the EC-14 and EC-15 commands, and asks those commands whether they can run rather than re-deriving availability.
  • d1f6727: Add the in-table cell bubble menu (CH-08). A second bubble menu instance with its own plugin key and shouldShow, offering merge, split and structure operations for a selection spanning cells. The text bubble menu stands down for a cell selection so the two never compete.
  • 23952c5: Fix pnpm dev leaving the sandbox unable to resolve @blockleaf/editor. The stylesheet is now copied by the bundler rather than the build script, so watch mode emits it too, and clean no longer wipes dist/ when the watcher starts. The published ./styles.css subpath is unchanged.