Releases
v1.13.0
Compare
Sorry, something went wrong.
No results found
Features
Live collaboration — Real-time multiplayer editing, turned on with collaboration: { doc }.
Editors on the same doc see each other's edits, carets and avatars, named by collaboration.user.
offline: true survives a reload by writing document content to browser storage.
Every tool must support read-only, because a collaboration editor boots read-only.
persistence, server and ticket — The document wiring that used to be yours to write.
persistence: { load, save } loads on mount and saves as the document changes, against your endpoint.
Saves are queued, never parallel, and only the newest pending document is sent.
server fills in the uploader and preview endpoints. ticket names the endpoint minting an access pass.
Documents convert outside the browser — Blok.Server runs the editor's own serializer in-process.
AddBlokDocuments() registers it, and AddBlokServer already includes it.
IBlokDocumentConverter gives Markdown, HTML and plain text out, and Markdown in.
Markdown cannot express every block, so both directions report what changed.
Translate a document without handing a model its JSON — extractTexts and injectTexts move the prose past a model.
The model never sees the structure, so it cannot drop ids or reorder blocks.
URLs, file names and code blocks are left out of the list.
A list whose length does not match the document throws rather than misplacing a translation.
Markdown out of a saved document — blocksToMarkdown is the synchronous, DOM-free twin of markdownToBlocks.
Headings become #, lists - or 1., and tables GFM pipe grids.
blocksToMarkdownWithReport also names every construct dropped or emitted lossily.
Markup written into Markdown comes back as literal text, since Blok has no raw-HTML block.
A heading keeps the anchor its links point at — HeaderData.anchor renders as the heading's id.
It is captured from a pasted heading's own id, so in-document links survive a paste and a save.
restoreHeadingAnchors repairs documents already imported without it.
That pass reports every fragment it refused as no-match or ambiguous, rather than guessing.
An uploader can delete what it stored — delete?(url, ctx) is a third optional method on BlokUploader.
Blok sweeps the assets this session uploaded and then stopped using.
With the method absent nothing is ever deleted, which is what happened until now.
UploaderConfig.headers may now be a function, so a short-lived pass is minted per request.
Change a live document from outside the browser — POST /sync/{doc}/edit and /reset work over HTTP.
An edit inserts, updates or removes blocks, all-or-nothing, and reaches every open tab.
A reset reloads the document from your endpoint and tells every tab to pick it up.
Both require a pass with write: true naming that document.
Bug Fixes
Pasted HTML ran before anything sanitized it — A pasted <img src=x onerror=…> executed before clean() saw it.
Clipboard HTML was parsed into an element owned by the live document, which still loads resources.
Untrusted parses now use a document with no browsing context.
The URL scheme check moved inside clean(), so a pasted javascript: link is no longer clickable.
A long article could not be converted at all — An article that died on the server's engine now converts in milliseconds.
Both serializers reallocated the whole accumulated document for every block.
blocksToHtml also sent every inline field through a sanitizer that parsed twice.
Measured on the real engine: 600 KB went from failing outright to 7 ms.
A legacy document's nested content read as empty — Plain text, Markdown and HTML all dropped a legacy block's children.
Documents written before nesting moved to parent and content keep their children inside data.
Those shapes are now expanded once, in the document model, so every reader sees them.
One unreadable block is skipped and reported, rather than costing the caller the whole article.
Markdown export lost the fence language and the nesting — Code fences exported unlabelled, and Tab-nested paragraphs exported as code.
Four leading spaces after a blank line is an indented code block, so that indent survives only inside a list item.
A container rendered only its direct children, so a list inside a toggle lost every level below the first.
Lossy conversions now report what changed, instead of reading as "nothing was lost".
The caret did not survive a read-only round trip — Entering read-only remembers where the caret stood, and leaving it puts it back.
It holds on both the in-place toggle and the save, clear and render path.
The restore declines when the user focused something else, or when the block is gone.
A collaboration status blip that changes nothing no longer kills a live caret.
A link to somewhere on the same page opened a new tab — A bare #fragment reopened the whole document instead of moving inside it.
So did the absolute URL that "Copy link to block" hands out.
A fragment now scrolls, an identical URL does nothing, and a query-only difference navigates in place.
Following a link is reading, so the handler no longer unbinds in read-only.
Maintenance
The server runs its own Yjs engine — Nothing it ships carries native code any more.
YDotNet and the native build matrix are replaced by a managed C# implementation.
Each wave was verified against a shared Yjs oracle rather than against itself.
A service account with no home directory now needs no configuration.
blok-sync.v2 is on the wire, and deliberately not spoken yet — The service negotiates it, and the client still offers only v1.
The operation, acknowledgement and rejection codecs exist on both sides against pinned fixtures.
An operation id is settled and journalled before anyone can see the write.
Do not build on the v2 frames until the client negotiates them.
The documentation site was one step from being indexed as its error state — A crawl recorded the homepage's only <h1> as "Application Error".
A component drew with Math.random() during render, so React 19 discarded the server HTML.
Docs had also not deployed for three days, blocked by a script demanding impossible release assets.
A post-deploy smoke test now asserts what the host actually serves.
yarn serve starts the collaboration backend — The dev playground boots the sync service beside it.
Multiplayer is exercised locally by default.
Pass --no-server to run the playground alone.
Gates and dependencies — The .NET solution keeps three test layers plus conformance and package smoke tests.
CI requires 80% line and branch coverage on merged production code.
It audits NuGet packages, scans for secrets, scans the built image, and analyzes C# with CodeQL.
Mutation testing runs on the diff alone, carrying surviving mutants between commits.
You can’t perform that action at this time.