Skip to content

Releases: Samyssmile/notectl

v2.2.5

Choose a tag to compare

@Samyssmile Samyssmile released this 01 Jul 21:55

Fixed

  • The documented table, table-row, and table-cell CSS Shadow Parts are now actually exposed (#201). The styling guide promises notectl-editor::part(table-cell) { padding: 12px; }, but no table part ever reached the live DOM, so ::part() customization silently did nothing for tables. The part attributes were added (in #123) to the table NodeSpecs' toDOM(), yet the table plugin also registers NodeViews for table, table_row, and table_cell, and the renderer always prefers a registered NodeView over NodeSpec.toDOM(), so the annotated elements were never created. The NodeViews in TableNodeViews.ts now set the parts themselves: part="table" on the scrollable .notectl-table-wrapper (matching the documented element), part="table-row" on each <tr>, and part="table-cell" on each <td>. Blockquote and code-block parts were unaffected (blockquote has no NodeView, and the code-block NodeView already set its parts). The docs' Part Inventory also gains the previously undocumented toolbar-group part.

Full changelog: https://github.com/Samyssmile/notectl/blob/main/CHANGELOG.md

v2.2.4

Choose a tag to compare

@Samyssmile Samyssmile released this 22 Jun 21:15

Fixed

  • The Angular wrapper's getContentHTML() now forwards serialization options to core (#185). Core's NotectlEditor.getContentHTML() accepts a ContentHTMLOptions argument (includeBlockIds, pretty, cssMode), most importantly { includeBlockIds: false } to produce clean export HTML with no data-block-id attributes for database storage or handoff to another system. The @notectl/angular NotectlEditorComponent.getContentHTML() was declared with no parameters and forwarded none, so an Angular consumer calling editor.getContentHTML({ includeBlockIds: false }) got a compile error and, if forced through, still received block ids. The component method now mirrors core's three overloads exactly and forwards the options to the underlying editor, so includeBlockIds, pretty, and cssMode are all reachable from the wrapper. The option types ContentHTMLOptions and ContentCSSResult are re-exported from @notectl/angular, and the NotectlTestHarness.getContentHTML() testing helper mirrors the same overloads. The signal-forms [formField], classic [formControl], and value-sync paths are intentionally left on the default (block ids kept), because those round-trips rely on data-block-id to preserve block identity and the caret across external sync.

Full changelog: https://github.com/Samyssmile/notectl/blob/main/CHANGELOG.md

v2.2.3

Choose a tag to compare

@Samyssmile Samyssmile released this 17 Jun 21:50

Fixed

  • Selecting the whole document and deleting it now clears the editor back to the placeholder, even when the first block is a title or heading (#183). With a centered title as the first block, pressing Ctrl+A then Backspace (or Delete, or Cut) emptied the text but left the cursor inside an empty, still-centered title block, so the document was never considered empty and the "Start typing..." placeholder never returned. deleteSelectionCommand now detects a full-document selection (the same range selectAll produces) and replaces the whole document with one fresh empty paragraph. Partial cross-block deletes still keep the first block's type (standard rich-text behavior), typing/paste over a full selection is unaffected, and the operation is a single undo step that restores the original blocks, types, alignment, and text.

Full changelog: https://github.com/Samyssmile/notectl/blob/main/CHANGELOG.md

v2.2.2

Choose a tag to compare

@Samyssmile Samyssmile released this 17 Jun 20:19

Added

  • The formula plugin's structural palette now covers far more notation (#169). The on-screen math keyboard previously exposed only a thin slice of the bundled converter's capabilities (fractions, scripts, roots, a few operators, partial Greek, a handful of relations, arrows, and matrices). It now offers six new groups and several extended ones, all backed by snippets the converter already understood, so no engine change was needed. New groups: Accents (\vec, \hat, \overline, \dot, \tilde), Functions (\sin, \cos, \tan, \log, \ln, \exp), Sets (\in, \notin, \subset, \subseteq, \cup, \cap, \emptyset, and the number sets ℝ ℕ ℤ ℚ ℂ via \mathbb), Logic (\forall, \exists, \neg, \land, \lor, \implies, \iff), Brackets (auto-sized \left … \right parentheses, brackets, braces, absolute value, norm, floor, and ceiling), and Dots (\cdots, \ldots, \vdots, \ddots). Extended groups: Operators gains double and contour integrals (\iint, \oint) and the derivative symbols \partial and \nabla; Greek now spans the full lowercase and uppercase alphabet (adding ε, ζ, η, κ, ν, ξ, ρ, τ, χ, ψ and the capitals Γ Θ Λ Π Φ Ψ); Relations adds \sim, \cong, and \propto. Every new button carries an English accessible name, the palette keeps its ARIA toolbar and roving-tabindex keyboard model, and all six new group labels are translated across the nine bundled locales. Covered by a new MathPaletteData test that converts every palette snippet through the bundled LaTeX-to-MathML converter and asserts zero parse errors, plus the existing locale-completeness test extended to the new keys.

Fixed

  • Keyboard shortcuts now work on non-Latin keyboard layouts (#176). With a Russian (JCUKEN) layout, command shortcuts such as Ctrl+A, Ctrl+Z, and Ctrl+B did nothing, and Delete appeared broken too because nothing had been selected. The whole keyboard path keyed off event.key, which is layout-dependent: on a Russian layout the physical A key reports event.key = 'ф', the Z key reports 'я', so normalizeKeyDescriptor produced Mod-Ф instead of Mod-A and matched no keymap, and the separate hardcoded built-in fallback for undo, redo, and select-all compared 'я' against 'z' and never fired. The fix adds a physical-key fallback modeled on ProseMirror and CodeMirror: the layout-aware event.key descriptor is still tried first and always wins when it matches, then, only when a command modifier (Ctrl or Cmd) is held and the primary descriptor matched nothing, a second descriptor derived from the layout-independent event.code (the physical US-QWERTY position, for example KeyA to A) is tried. Normal typing, IME composition, and dead keys are never touched, and AltGr combinations (Ctrl+Alt) are explicitly excluded so layouts that compose characters with AltGr are not hijacked. Descriptor logic moved into a new DOM-free, fully unit-tested input/KeyDescriptor.ts module, and the two duplicated keymap dispatch loops in KeyboardHandler were unified behind a single helper that applies the fallback in both normal and read-only mode. This is fundamentally an internationalization and accessibility fix for keyboard operability (WCAG 2.1.1). The physical fallback also covers shifted bindings: when Shift is held it tries both the US-QWERTY base glyph and its shifted glyph, so digit shortcuts such as the heading bindings Mod-Shift-1 to Mod-Shift-6 (which a real browser reports as key = '!', not '1', and which therefore never matched on any layout before) and punctuation shortcuts such as Mod-Shift-> (blockquote) and Mod-Shift-+ / Mod-Shift-_ (font size) now resolve on every layout without changing a single binding descriptor, so tooltips stay correct. Covered by new KeyDescriptor unit tests (Cyrillic mapping, the no-modifier, AltGr, and named-key gates, and the base-plus-shifted digit and punctuation expansion), new KeyboardHandler tests (plugin keymap and built-in resolution on Cyrillic input, the heading Mod-Shift-1 and blockquote Mod-Shift-> and font-size Mod-Shift-+ shifted-glyph repairs, with Latin precedence preserved), and an e2e spec that simulates a Russian layout by dispatching synthetic events with Cyrillic key plus US-QWERTY code and confirms Ctrl+A then Delete clears the document and Ctrl+Z restores it, Ctrl+B bolds, and Ctrl+Shift+. toggles blockquote.

  • Buttons inside custom toolbar popups can now be activated with the keyboard (#171). A keyboard user could focus a button inside a custom popup, such as the formula plugin's structural math palette or its Insert and Cancel actions, by using Tab and the arrow keys, but pressing Enter or Space did nothing, so the on-screen math keyboard was unusable without a pointer (a WCAG 2.1.1 Keyboard gap). The root cause was a split of responsibility: the structural palette is a self-contained roving-tabindex toolbar, like the color grid, but unlike the color grid it did not handle Enter/Space itself and let its arrow keys bubble to the host popup, which then advanced focus a second time. The palette now fully owns its keyboard, matching the color grid: it activates the focused button on Enter/Space and stops every navigation and activation key it handles from bubbling, which also fixes a latent bug where Arrow Down/Up moved the roving focus by two instead of one. For the popup's standalone Insert and Cancel buttons, which run their action on click (with mousedown only guarding text-field focus), the toolbar's shared key handler now replays a complete pointer press (mousedown, then mouseup, then click) instead of dispatching mousedown alone, so it activates both the click convention and the mousedown convention used by the link and image popups, exactly once. Covered by new MathPalette unit tests (Enter/Space activation, single-step navigation, and that handled keys do not bubble while Tab and Escape still do), new ToolbarPopupController unit tests for the click-based and mousedown-based conventions, and a formula e2e test that walks the palette to the integral symbol using Tab and the arrow keys and inserts it with Enter, without a mouse.

  • A checklist item's checked state is now exposed to screen readers (#168) — The checked state was written as aria-checked on the <li>, which carries role="listitem", a role that does not support aria-checked, so assistive technology ignored it and never announced whether an item was checked (a WCAG 4.1.2 Name/Role/Value gap). The visible checkbox was drawn purely as a CSS ::before pseudo-element, so no real element carried the state either. The marker is now a real role="checkbox" element with aria-checked and an accessible label, rendered as the first child of the list item, so the state lives in the accessibility tree and is announced when the caret enters the item. The invalid aria-checked is removed from the <li> (the data-checked attribute that drives the CSS glyph stays). To keep the marker out of the editable text flow it is contenteditable="false" and flagged data-widget; the selection layer now treats data-widget elements as zero width, so the marker never shifts a caret offset or becomes a caret position, and an empty checklist item still places the caret on its editable line rather than before the checkbox. Because the marker is a real element, the toggle click handler now matches it directly and the previous bounding-box geometry and RTL math are gone. Toggling by mouse or by Mod+Enter also announces the new state through the editor's live region, so the action is spoken even though the marker is not focused (WCAG 4.1.3), complementing the keyboard-operability fix in #167. Covered by new unit tests for the marker markup and the announcement, by SelectionSync tests proving a data-widget marker is zero width in offset space (caret placement at text start, mid-text, and in an empty item), and by checklist and cursor e2e: a new live-region assertion confirms a keyboard toggle narrates the checked state (and is not clobbered by the block-type announcer), while the existing cursor suite confirms click, drag, double-click, and arrow navigation offsets are unchanged in a real browser.

  • Checklist items can now be toggled with the keyboard (#167) — A keyboard-only user could create a checklist item but had no way to toggle its checked state: the only runtime toggle was a mousedown handler on the marker click region, and the toggleChecklistItem command was registered but bound to no key, so the checked state was unreachable without a pointer (a WCAG 2.1.1 Keyboard gap). The command is now bound to Mod+Enter (Ctrl+Enter on Windows/Linux, Cmd+Enter on macOS) while the caret is inside a checklist item. The binding declines on non-checklist blocks, so it falls through cleanly to other Mod+Enter handlers (such as the code-block insert-after shortcut). It is registered at navigation priority so it stays reachable in read-only mode when interactiveCheckboxes is enabled, giving keyboard users the same toggle access as a mouse click. The screen-reader announcement of the checked state (aria-checked currently sits on the role="listitem" element, which does not expose it) is a separate concern, tracked apart from this keyboard-operability fix. Covered by new unit tests that drive the toggle through the registered key binding (not the command directly), assert the navigation-priority registration, confirm the read-only interactive path, and verify the binding is absent when the checklist type is disabled.

  • Pasting a table into a table cell no longer creates a nested table (#166) — Pasting table HTML (for example a table copied from a spreadsheet or a web page) while the caret sat inside a table cell inserted a full table into that cell, producing a schema-invalid document, because table_cell.content.allow does not list table. The table-...

Read more

v2.2.1

Choose a tag to compare

@Samyssmile Samyssmile released this 07 Jun 00:05

Patch release with three bug fixes on top of 2.2.0.

Fixed

  • Copying an inline and a display formula together no longer loses their LaTeX source (#154) — A copied or cut selection containing both an inline and a display formula lost both formulas' LaTeX on paste, leaving them rendered but uneditable. The paste pre-sanitization pass now preserves the schema-registered MathML <semantics> / <annotation> markup, and the formula node specs strip the editor-internal data-block-id from stored MathML.
  • Typing immediately after a link no longer extends the link onto the new text (#153) — Inline marks were inclusive at their right edge, so typing at the end of a linked span absorbed the new text into the link. MarkSpec gains an inclusive flag (default true); the link mark opts out, and a single resolveCursorMarks helper makes every collapsed-caret mark derivation respect right-boundary inclusivity. Bold, italic, and other marks are unchanged.
  • Inserting a block object on an empty line no longer leaves a stray empty paragraph above it (#152) — Inserting a table, horizontal rule, image, or display formula on an empty line left a blank paragraph above the object. All four insert commands now route through a shared, inline-aware insertBlockObjectOnOwnLine primitive that consumes the empty anchor line.

Full Changelog: v2.2.0...v2.2.1

v2.1.3

Choose a tag to compare

@Samyssmile Samyssmile released this 27 May 11:12

[2.1.3] - 2026-05-18

Added

  • Toolbar group wrapper exposed as part="toolbar-group" with opt-in accessible labels (#125) — Each visual cluster of toolbar buttons is now wrapped in a real <div part="toolbar-group" class="notectl-toolbar-group"> flex container in both render paths (renderItemsByLayout and renderItemsByGroup). Consumers can style groups via ::part(toolbar-group) directly with padding / background / border — no display: contents workaround required, since the wrapper participates in layout (display: flex; align-items: center; gap: 2px). ToolbarLayoutConfig.groups and the editor-level ToolbarConfig.groups now accept a backwards-compatible union per entry: either a ReadonlyArray<Plugin> / ReadonlyArray<string> (existing tuple form, unchanged) or an object { plugins, label? }. When label is set, the wrapper additionally receives role="group" and aria-label="<label>" so assistive technology announces the cluster by name; without a label the wrapper stays role-less, matching the W3C ARIA APG toolbar example for unlabeled sub-groups. Separators continue to be rendered between groups, never inside, and empty / fully-hidden groups produce no wrapper at all. Stale wrappers are removed on every re-render, so runtime configurePlugin toggles cannot accumulate orphan DOM. New ToolbarGroupConfig type exported from @notectl/core (packages/core/src/plugins/toolbar/index.ts) for downstream typing.

  • Structured theming contract: component-scoped tokens + CSS Shadow Parts (#122, resolves #120)<notectl-editor> now exposes a three-tier theming cascade across every component instead of the previous mix of one global token plus an ad-hoc per-component opt-in. Every CSS rule that previously read var(--notectl-<global>) directly is now layered as var(--notectl-<component>-<prop>, var(--notectl-<global>, <hard-coded-fallback>)), so consumers can override at exactly the granularity they need: existing global tokens continue to cascade to every component unchanged, new component-scoped tokens (--notectl-table-border, --notectl-table-cell-bg, --notectl-table-header-bg, --notectl-blockquote-border, --notectl-blockquote-bg, --notectl-toolbar-button-bg / -fg / -hover-bg / -active-bg / -active-fg) override a single component without touching the rest, and a hard-coded fallback keeps each component usable when neither is set. The per-table inline --ntbl-border-color set by the toolbar's "Border color" action retains highest precedence so user customizations survive theme switches. Documented public tokens (--notectl-bg, --notectl-fg, --notectl-fg-muted, --notectl-border, --notectl-border-focus, --notectl-primary, --notectl-primary-fg, --notectl-focus-ring) are declared with @property so DevTools surfaces them as first-class CSS variables and invalid values fall back to a typed initial-value instead of silently breaking downstream rules. The editor additionally exposes a stable CSS Shadow Parts surface — editor, content, plugin-container (+ plugin-container-top / plugin-container-bottom modifiers), toolbar, toolbar-button (+ toolbar-button-active / toolbar-overflow-button modifiers), toolbar-divider, table, table-row, table-cell, code-block, code-block-header, code-block-content, blockquote — so consumers can target structural elements via ::part() without piercing the shadow DOM or forking the editor. Modifier parts (e.g. toolbar-button-active) are kept in sync with their ARIA counterparts (aria-pressed) and never drift. A new @media (forced-colors: active) block in editor/styles/base.ts maps editor borders, focus rings, and node-selection outlines onto Windows High Contrast / system-color palette so the editor stays legible under OS-level accessibility settings. The --ntbl-border-color mechanism remains supported as the highest-priority fallback, so the table's "Border color" toolbar action is unaffected. No exports removed, no global tokens renamed, no DOM structure changes outside additive part attributes — fully backwards compatible.

  • VS Code–style editing inside code blocks: auto-indent, multi-line indent, and bracket pairingCodeBlockPlugin gains three coordinated editing behaviors that bring it in line with what users expect from a modern IDE. Auto-indent on Enter inherits the current line's leading whitespace, adds one extra indent unit after an open bracket / brace / paren, and expands the cursor-between-pair pattern {|} into the three-line block form (open brace, indented empty line with the cursor, dedented close brace) — typing a close character on a whitespace-only line dedents by one step, or collapses the gap to consume an auto-paired close if one is in flight. Multi-line indent rebinds Tab / Shift-Tab so a selection spanning ≥ 2 lines indents every covered line (not just the column where the caret happens to sit) while preserving the selection direction; a range within a single line replaces the selection with one indent unit, matching VS Code's behaviour exactly. Bracket pairing introduces auto-pair on open, overtype on tracked auto-paired close characters (so the user's ) is consumed by an existing close instead of producing ))), wrap-selection ("foo"("foo") when typing ( over a selection), and pair-delete on Backspace. Quote handling follows VS Code's heuristics: a stray apostrophe inside an English contraction is not paired, and quotes inside an existing string or comment token are suppressed via the new SyntaxHighlighterService.getTokenAt(blockId, offset) lookup so the highlighter and the editor share a single source of truth about token boundaries. Behaviour is configurable per-plugin via indent.{mode, useSpaces, spaceCount} and pairing.{brackets, quotes, overtype, deletePair, surround}; the legacy top-level useSpaces / spaceCount options are kept as a fallback. Three plugin-API additions land alongside the feature: TextInputInterceptor in model/ (parallel to PasteInterceptor, wired through PluginContext, MiddlewareChain, and InputHandler) lets plugins intercept and rewrite text input before it becomes a transaction; PluginContext.getCompositionState() exposes the existing CompositionState so plugins can guard against IME suppression; and pluginHarness gains a notifyStateChange option so onStateChange-driven flows (the pair-stack migration in particular) can be tested in isolation. The internal pair-stack uses Transaction.mapping with assoc = +1 to migrate tracked open/close positions through every StepMap variant introduced in #118, and a deferred-op queue ensures fresh push/take operations apply in post-transaction position space; the interceptor flushes the queue on entry to drop ops left behind by middleware-suppressed transactions. Accessibility: all new key bindings respect the existing roving-tabindex and screenreader announcement surface; no new focus traps.

  • Generic position-mapping primitive (StepMap + Mapping) — Steps now carry their own position-space mapping via a getMap(docBefore): StepMap entry in the StepHandler registry, covering all 14 step types as a single discriminated union of five categories: identity (mark / attribute / schema / block-type / block-insertion), shift ([from, to) → newLen within a block — unifies insertText / deleteText / insertInlineNode / removeInlineNode as one primitive), split, merge, and blockRemoval (walks descendants). Transaction.mapping: Mapping is now a required field, populated automatically as steps are appended in TransactionBuilder.advanceDoc, and composes per-step maps with map(pos, assoc?), mapResult(pos, assoc?), mapRange(range, assocFrom?, assocTo?), appendMap, and appendMapping. The assoc argument (-1 sticky-left, +1 sticky-right) mirrors ProseMirror semantics. mapSelection(sel, mapping) exists for all three EditorSelection variants (text / node / gap) and collapses-aware (a cursor at a split boundary stays collapsed rather than tearing across two blocks). HistoryManager tracks an interveningMapping per group — extended on every new-group push and via a new recordIntervening(mapping) API for out-of-band transactions — so undo folds the restored selection through interleaved edits instead of relying on validateSelection's blind numeric clamp. New public exports from @notectl/core: Mapping, StepMap, IdentityMap / ShiftMap / SplitMap / MergeMap / BlockRemovalMap, Assoc, MapResult, PositionRange, IDENTITY_MAP, mapPositionThroughStep, collectRemovedBlockIds, mapSelection, mapTextSelection, getStepMap. Unblocks comment / annotation anchors, suggestion-mode / track-changes, remote cursors, and selection restoration under collaborative editing; lays groundwork for OT / CRDT rebasing (Phase 3, separate effort).

  • TypeScript / TSX language support (#114)CodeBlockPlugin now ships with built-in TypeScript syntax highlighting and SmartPastePlugin with TypeScript content detection, alongside the existing Java / JSON / XML support. The new TYPESCRIPT_LANGUAGE definition covers modern TS — comments, escape-aware strings and template literals, decorators, the full keyword set incl. satisfies / using / infer / keyof / asserts / out / override, numeric literals with _ separators and BigInt n suffix (hex/octal/binary/decimal/scientific), and modern operators (=>, ??, ??=, ?., ..., **=, >>>=, &&=, ||=). Word boundaries are enforced so identifiers like interfaceName are not mis-tokenized as keywords. The new TypeScriptDetector scores ES imports/exports, type / interface / enum / namespace / declare, const / let bindings, arrow functions, optional chaining, nullish coalescing, template literals, type annotations, `as...

Read more

v2.1.2

Choose a tag to compare

@Samyssmile Samyssmile released this 02 May 13:29

Fixed

  • CI on main red — Bootstrap-modal-like dropdown-position spec failed (#68 regression) — The vanillajs playground commit 98239d1 added notectl-editor { height: 100%; min-height: 0; --notectl-content-min-height: 0 } to make the editor fill a new 700px grid row. Those rules apply globally to the host element, including in dropdown-position.spec.ts which reparents #editor-container into a Bootstrap-modal-like dialog with no explicit height. There, height: 100% resolved to auto, the editor shrank to its content and the heading dropdown popup ended up mispositioned (gap: -148px) inside the transformed containing block. The grid-template-rows: 700px auto row alone keeps the actions bar pinned and the inspector height-bounded; the editor sizes to its intrinsic height (--notectl-content-min-height: 460px) inside the row, identical to v2.0.x behaviour.

Full Changelog: v2.1.1...v2.1.2

v2.1.1

Choose a tag to compare

@Samyssmile Samyssmile released this 02 May 11:08

Fixed

  • Code-block input rule loses the language attribute (#113) — Typing ```<lang> + space converts a paragraph to a code_block via a single transaction containing both deleteText and setBlockType. The TextDirectionAutoPlugin's auto-detect middleware appended its own setNodeAttr step built from a pre-transaction block.attrs snapshot; applySetNodeAttr has full-replace semantics, so by application time the snapshot was stale and clobbered the { language, backgroundColor } attrs written by setBlockType. The result was a code block with language: '', no language label and no syntax highlighting. Auto-detect now pre-scans the transaction for blocks already targeted by setBlockType or setNodeAttr and skips its emission for those blocks — symmetrical to the guards already present in the preserve-dir and inherit-dir middlewares. The next text-only transaction re-detects direction normally. Generally fixes the same data-loss pattern for any future input rule or command that combines text edits with a block-type or attr change in a single atomic transaction.

Full Changelog: v2.1.0...v2.1.1

v2.1.0

Choose a tag to compare

@Samyssmile Samyssmile released this 02 May 10:13

Added

  • BidiIsolationPlugin (@notectl/core/plugins/bidi-isolation) — Inline <bdi> mark, toggleBidi* / removeBidi / toggleBidiIsolation commands, inline toolbar dropdown, Mod-Shift-B. Works standalone; optionally consumes TextDirectionService so toggleBidiIsolation picks the direction opposite to the surrounding block.
  • TextDirectionAutoPlugin (@notectl/core/plugins/text-direction-auto) — Headless plugin that registers the three transaction middlewares (preserve / auto-detect / inherit) previously fused into the heavy TextDirectionPlugin. Hard-depends on TextDirectionPlugin via dependencies = ['text-direction']. Each middleware can be disabled individually via config.
  • TextDirectionService + TEXT_DIRECTION_SERVICE_KEY — Typed service exposing directableTypes and getBlockDir, registered by TextDirectionPlugin for cross-plugin consumption.
  • makeBlockState test helper in test/TestUtils.ts — Convenience wrapper around stateBuilder() for plugin tests that only need a static document.

Changed

  • TextDirectionPlugin is now lean (@notectl/core/plugins/text-direction) — Owns only the block-level dir attribute, toolbar dropdown, Mod-Shift-D, and ShiftDirectionHandler. Inline bidi and middleware behaviour moved to the two new plugins above. FullPreset registers all three so behaviour for preset users is unchanged.
  • Locale splitTextDirectionLocale keeps the 8 block-direction strings; BidiIsolationLocale (new) owns the 6 inline-bidi strings. All 9 language files split mechanically.

Removed (BREAKING)

  • TextDirectionAdvancedPlugin and the @notectl/core/plugins/text-direction-advanced subpath export — replace by registering TextDirectionPlugin + BidiIsolationPlugin + TextDirectionAutoPlugin explicitly (or use createFullPreset).
  • TextDirectionCorePluginTextDirectionPlugin is now the lean class; importing TextDirectionCorePlugin no longer resolves.
  • Inline bidi capabilities (<bdi> mark, toggleBidi* commands, Mod-Shift-B, auto-detect / inherit / preserve middleware) when only importing TextDirectionPlugin — those moved to the two new plugin entries. Previously the docs claimed the lean and heavy classes were "the same" — they were not, and consumers calling executeCommand('toggleBidiLTR') on the lean variant silently received false. The new split makes the contract honest.

Migration

// before
import { TextDirectionAdvancedPlugin } from '@notectl/core/plugins/text-direction-advanced';
new TextDirectionAdvancedPlugin();

// after
import { TextDirectionPlugin } from '@notectl/core/plugins/text-direction';
import { BidiIsolationPlugin } from '@notectl/core/plugins/bidi-isolation';
import { TextDirectionAutoPlugin } from '@notectl/core/plugins/text-direction-auto';
[new TextDirectionPlugin(), new BidiIsolationPlugin(), new TextDirectionAutoPlugin()];

If you build via createFullPreset(), no migration is needed — the preset now registers all three plugins, and you actually gain the inline-bidi toolbar and auto-detect middleware that the previous lean-only registration silently lacked.

Performance

  • TextDirectionAutoPlugin.handleInsertText skips the getBlockText string allocation in the early-return path that protects explicit non-auto block direction. Switches the guard to getBlockLength, which is O(n) over inline children with no string allocation. Side benefit: blocks whose only inline content is an InlineNode are now correctly protected from re-detection.

Full Changelog: v2.0.10...v2.1.0

v2.0.9

Choose a tag to compare

@Samyssmile Samyssmile released this 17 Apr 10:14

Fixed

  • Signal-form cursor reset (#103) — The caret no longer jumps to the start of the document when setJSON(getJSON()) round-trips unchanged content. This was breaking Angular signal-form bindings, where every keystroke triggers such a round-trip. setContentHTML() benefits from the same preservation since it routes through the same code path.

Changed

  • EditorView.replaceState() preserves the caret. The prior selection is carried over (validated against the new document) instead of adopting the selection from the incoming state. Undo/redo history is still cleared. This is a behavior change on the public EditorView API — any selection carried on the state passed to replaceState() is now ignored.

Added

  • EditorState.withSelection(selection) — Returns a new state with the given selection validated and clamped against the current document. Useful for callers that need to adopt an external selection safely.

Full Changelog: v2.0.8...v2.0.9