@owomark/core 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Framework-agnostic Markdown editor engine. Provides document model, incremental rendering, input handling, commands, and selection management for a single-layer `contenteditable` editing experience.
4
4
 
5
- Official package: `@owomark/core`. The flat name `owomark-core` is reserved only as a compatibility redirect and should not be used for new installs.
5
+ Install the official package: `@owomark/core`.
6
6
 
7
7
  ## Install
8
8
 
@@ -48,7 +48,7 @@ view.destroy();
48
48
  core.destroy();
49
49
  ```
50
50
 
51
- For backward compatibility, a legacy standalone editor API is also available:
51
+ The standalone DOM editor API is also available through `@owomark/view`:
52
52
 
53
53
  ```ts
54
54
  import { createOwoMarkVanillaEditor } from '@owomark/view';
@@ -193,7 +193,7 @@ const dirtyRange = computePreviewDirtyRange(previousBlocks, blocks);
193
193
  ```
194
194
 
195
195
  - Adjacent same-type container blocks (lists, blockquotes) are grouped into a single `PreviewBlock`
196
- - Each block has a stable `blockId` (`L{startLine}-{endLine}`) for DOM reconciliation
196
+ - Each block has a content-based `blockId` (`{renderKey}#{occurrenceIndex}`) for stable DOM reconciliation
197
197
  - `renderKey` (djb2 hash of `kind:theme:raw`) drives cache invalidation
198
198
 
199
199
  ### Preview Types
@@ -194,6 +194,7 @@ function tokenizeInline(raw, baseOffset = 0) {
194
194
 
195
195
  // src/parser/blocks.ts
196
196
  var MATH_FENCE_RE = /^\$\$\s*$/;
197
+ var SINGLE_LINE_MATH_BLOCK_RE = /^ {0,3}\$\$(?!\s*$)([^\n]*?)(?<!\\)\$\$\s*$/;
197
198
  function tokenizeBlock(raw, blockType, baseOffset = 0) {
198
199
  if (blockType === "code-fence") {
199
200
  return tokenizeCodeFence(raw, baseOffset);
@@ -207,6 +208,18 @@ function tokenizeBlock(raw, blockType, baseOffset = 0) {
207
208
  });
208
209
  }
209
210
  if (blockType === "math-block") {
211
+ const singleLineMatch = raw.match(SINGLE_LINE_MATH_BLOCK_RE);
212
+ if (singleLineMatch) {
213
+ const leadingWhitespace = raw.match(/^ {0,3}/)?.[0] ?? "";
214
+ const trimmed = raw.slice(leadingWhitespace.length).replace(/\s+$/, "");
215
+ const inner = trimmed.slice(2, -2);
216
+ const start = baseOffset + leadingWhitespace.length;
217
+ return [
218
+ { type: "fence-marker", text: "$$", start, end: start + 2 },
219
+ { type: "math-text", text: inner, start: start + 2, end: start + 2 + inner.length },
220
+ { type: "fence-marker", text: "$$", start: start + 2 + inner.length, end: start + 4 + inner.length }
221
+ ];
222
+ }
210
223
  return tokenizeMultiLineBlock(raw, baseOffset, (line, _i, isFirst, isLast) => {
211
224
  if ((isFirst || isLast) && MATH_FENCE_RE.test(line)) {
212
225
  return [{ type: "fence-marker", text: line, start: -1, end: -1 }];
@@ -409,6 +422,19 @@ function parseMarkdownToDocument(markdown, genId) {
409
422
  continue;
410
423
  }
411
424
  }
425
+ if (SINGLE_LINE_MATH_BLOCK_RE.test(line)) {
426
+ const tokens2 = tokenizeBlock(line, "math-block", offset);
427
+ blocks.push({
428
+ type: "math-block",
429
+ raw: line,
430
+ id: genBlockId(),
431
+ depth: 0,
432
+ decorators: tokensToDecorators(tokens2, line, offset)
433
+ });
434
+ offset += line.length + 1;
435
+ i++;
436
+ continue;
437
+ }
412
438
  if (MATH_FENCE_RE.test(line)) {
413
439
  let closeIndex = -1;
414
440
  for (let j = i + 1; j < lines.length; j++) {
@@ -130,6 +130,12 @@ type JsonValue = JsonPrimitive | JsonValue[] | {
130
130
  [key: string]: JsonValue;
131
131
  };
132
132
  type PreviewBlock = {
133
+ /**
134
+ * Content-based identity: `{renderKey}#{occurrenceIndex}`.
135
+ * Stable across blank line edits — only changes when block content changes.
136
+ * Used as the DOM wrapper lifecycle key by the virtual preview engine.
137
+ * For line-range positional mapping (scroll sync), use startLine/endLine.
138
+ */
133
139
  blockId: string;
134
140
  kind: PreviewBlockKind;
135
141
  raw: string;
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { B as BlockNode, a as BlockContextType, O as OwoMarkSelection, C as CoreStateSnapshot, S as SlashState, b as CommandRegistry, c as OwoMarkDocument, D as DirtyRange, P as PreviewBlockKind, d as PreviewBlock, e as BlockTransform, f as OwoMarkSharedStateController, I as InlineToken, g as InlineTokenType } from './dom-adapter-BwEilZtY.js';
2
- export { h as BeforeInputIntent, i as BlockContext, j as BlockContextListener, k as BlockInsertType, l as BlockType, m as CommandContext, n as CommandDefinition, o as CompositionState, p as CoreApplyAction, q as CoreApplyResult, r as CoreSelectionSnapshot, s as CreateOwoMarkCoreOptions, t as Decorator, u as DecoratorType, v as DocumentChangeCallback, w as DomAdapterInstance, H as HeadingLevel, x as HistoryEntry, y as IndentMode, z as IndentResult, J as JsonPrimitive, A as JsonValue, K as KeyDownIntent, L as Leaf, E as OwoMarkCommands, F as OwoMarkCore, G as OwoMarkEditorInstance, M as OwoMarkEditorLike, N as OwoMarkSharedState, Q as OwoMarkSharedStateStore, R as PasteIntent, T as PreviewDirtyReason, U as SlashStateListener, V as SlashTriggerInfo, W as VirtualPosition, X as VirtualSelection, Y as applyMarkdownIndent, Z as createOwoMarkCore, _ as getBlockIndexForPosition, $ as isVirtualSelectionCollapsed, a0 as linearToVirtual, a1 as linearToVirtualPosition, a2 as resolveIndentSize, a3 as virtualPositionToLinear, a4 as virtualPositionsEqual, a5 as virtualToLinear } from './dom-adapter-BwEilZtY.js';
1
+ import { B as BlockNode, a as BlockContextType, O as OwoMarkSelection, C as CoreStateSnapshot, S as SlashState, b as CommandRegistry, c as OwoMarkDocument, D as DirtyRange, P as PreviewBlockKind, d as PreviewBlock, e as BlockTransform, f as OwoMarkSharedStateController, I as InlineToken, g as InlineTokenType } from './dom-adapter-CTSJe5Uo.js';
2
+ export { h as BeforeInputIntent, i as BlockContext, j as BlockContextListener, k as BlockInsertType, l as BlockType, m as CommandContext, n as CommandDefinition, o as CompositionState, p as CoreApplyAction, q as CoreApplyResult, r as CoreSelectionSnapshot, s as CreateOwoMarkCoreOptions, t as Decorator, u as DecoratorType, v as DocumentChangeCallback, w as DomAdapterInstance, H as HeadingLevel, x as HistoryEntry, y as IndentMode, z as IndentResult, J as JsonPrimitive, A as JsonValue, K as KeyDownIntent, L as Leaf, E as OwoMarkCommands, F as OwoMarkCore, G as OwoMarkEditorInstance, M as OwoMarkEditorLike, N as OwoMarkSharedState, Q as OwoMarkSharedStateStore, R as PasteIntent, T as PreviewDirtyReason, U as SlashStateListener, V as SlashTriggerInfo, W as VirtualPosition, X as VirtualSelection, Y as applyMarkdownIndent, Z as createOwoMarkCore, _ as getBlockIndexForPosition, $ as isVirtualSelectionCollapsed, a0 as linearToVirtual, a1 as linearToVirtualPosition, a2 as resolveIndentSize, a3 as virtualPositionToLinear, a4 as virtualPositionsEqual, a5 as virtualToLinear } from './dom-adapter-CTSJe5Uo.js';
3
3
  export { WordBoundaryResult, deleteToLineEnd, deleteToLineStart, deleteWordBackward, deleteWordForward } from './internal/commands/word-boundary.js';
4
4
  export { normalizeMarkdownPaste } from './internal/clipboard/paste.js';
5
5
  export { enableMapSet, produce } from 'immer';
@@ -95,12 +95,22 @@ declare function expandDirtyRange(current: DirtyRange | null, blockIndex: number
95
95
  declare function expandWithContext(range: DirtyRange, totalBlocks: number, context?: number): DirtyRange | null;
96
96
 
97
97
  /**
98
- * Stable block identity derivation.
98
+ * Block identity derivation.
99
99
  *
100
- * Both editor and preview sides derive blockId from the same source line
101
- * range, ensuring they share a common identity without runtime DOM coupling.
100
+ * blockId is a content-based identity: stable across blank line edits.
101
+ * sourceKey is a line-range positional tag: used by scroll sync for
102
+ * editor↔preview coordinate mapping.
102
103
  */
103
- declare function deriveBlockId(startLine: number, endLine: number): string;
104
+ /**
105
+ * Derive a content-based block identity from its render key and
106
+ * occurrence index. Two blocks with identical content but different
107
+ * positions get different blockIds via their occurrence index.
108
+ */
109
+ declare function deriveBlockId(renderKey: string, occurrenceIndex: number): string;
110
+ /**
111
+ * Derive a line-range positional key for scroll sync mapping.
112
+ */
113
+ declare function deriveSourceKey(startLine: number, endLine: number): string;
104
114
 
105
115
  /**
106
116
  * Derive a stable render key for a preview block.
@@ -352,4 +362,4 @@ declare function buildVirtualRows(blocks: readonly BlockNode[], heightCache: Map
352
362
  /** Compute the visible range given scroll state. */
353
363
  declare function computeVisibleRange(rows: VirtualRow[], scrollTop: number, viewportHeight: number, overscan?: number): VisibleRange;
354
364
 
355
- export { BLOCK_TYPE_TO_CLASS, BQ_STEP, BlockContextType, type BlockIdGenerator, BlockNode, BlockTransform, CommandRegistry, type CommandResult, type CoreEventHub, type CoreSlashStateListener, CoreStateSnapshot, type CreateSharedStateOptions, DirtyRange, type ImageSizeSyntax, InlineToken, InlineTokenType, NOT_HANDLED, OwoMarkDocument, OwoMarkSelection, OwoMarkSharedStateController, PreviewBlock, PreviewBlockKind, SlashState, TOKEN_TO_CLASS, type VirtualRow, type VisibleRange, buildBlockquoteBarsBoxShadow, buildVirtualRows, computePreviewDirtyRange, computeVisibleRange, createBlockElement, createBlockIdGenerator, createCommandRegistry, createImageSizeTransform, createSharedStateStore, deriveBlockId, deriveRenderKey, detectAndRenderDirty, domRangeToOffset, estimateEditorBlockHeight, expandDirtyRange, expandWithContext, fullRender, getBlockAtOffset, getBlockById, getBlockIndexById, getBlockStartOffset, handleCharInput, handleMarkdownEnter, handleSmartBackspace, handleSmartDelete, insertCodeFence, insertImage, insertLink, insertMathBlock, insertSideAnnotation, insertTable, invalidateBlockCache, offsetToDomRange, parseMarkdownToDocument, patchDirtyBlocks, projectToPreviewBlocks, readSelection, reconcileBlocks, resetBlockIdCounter, resolveBlockContextType, restoreSelection, serializeDocument, toggleBold, toggleItalic, tokenizeBlock, tokenizeInline, updateBlockElement };
365
+ export { BLOCK_TYPE_TO_CLASS, BQ_STEP, BlockContextType, type BlockIdGenerator, BlockNode, BlockTransform, CommandRegistry, type CommandResult, type CoreEventHub, type CoreSlashStateListener, CoreStateSnapshot, type CreateSharedStateOptions, DirtyRange, type ImageSizeSyntax, InlineToken, InlineTokenType, NOT_HANDLED, OwoMarkDocument, OwoMarkSelection, OwoMarkSharedStateController, PreviewBlock, PreviewBlockKind, SlashState, TOKEN_TO_CLASS, type VirtualRow, type VisibleRange, buildBlockquoteBarsBoxShadow, buildVirtualRows, computePreviewDirtyRange, computeVisibleRange, createBlockElement, createBlockIdGenerator, createCommandRegistry, createImageSizeTransform, createSharedStateStore, deriveBlockId, deriveRenderKey, deriveSourceKey, detectAndRenderDirty, domRangeToOffset, estimateEditorBlockHeight, expandDirtyRange, expandWithContext, fullRender, getBlockAtOffset, getBlockById, getBlockIndexById, getBlockStartOffset, handleCharInput, handleMarkdownEnter, handleSmartBackspace, handleSmartDelete, insertCodeFence, insertImage, insertLink, insertMathBlock, insertSideAnnotation, insertTable, invalidateBlockCache, offsetToDomRange, parseMarkdownToDocument, patchDirtyBlocks, projectToPreviewBlocks, readSelection, reconcileBlocks, resetBlockIdCounter, resolveBlockContextType, restoreSelection, serializeDocument, toggleBold, toggleItalic, tokenizeBlock, tokenizeInline, updateBlockElement };
package/dist/index.js CHANGED
@@ -53,7 +53,7 @@ import {
53
53
  virtualPositionToLinear,
54
54
  virtualPositionsEqual,
55
55
  virtualToLinear
56
- } from "./chunk-RINTEGPG.js";
56
+ } from "./chunk-TRLKIMRD.js";
57
57
  import {
58
58
  normalizeMarkdownPaste
59
59
  } from "./chunk-BGXCXQZP.js";
@@ -65,7 +65,10 @@ import {
65
65
  } from "./chunk-MPIWZLI3.js";
66
66
 
67
67
  // src/preview/block-id.ts
68
- function deriveBlockId(startLine, endLine) {
68
+ function deriveBlockId(renderKey, occurrenceIndex) {
69
+ return `${renderKey}#${occurrenceIndex}`;
70
+ }
71
+ function deriveSourceKey(startLine, endLine) {
69
72
  return `L${startLine}-${endLine}`;
70
73
  }
71
74
 
@@ -159,6 +162,7 @@ function collectSideAnnotationChain(blocks, startIndex) {
159
162
  function projectToPreviewBlocks(doc, themeKey, transforms) {
160
163
  const result = [];
161
164
  const blocks = doc.blocks;
165
+ const occurrenceCounter = /* @__PURE__ */ new Map();
162
166
  let currentLine = 1;
163
167
  let i = 0;
164
168
  while (i < blocks.length) {
@@ -168,14 +172,16 @@ function projectToPreviewBlocks(doc, themeKey, transforms) {
168
172
  const sideChain = collectSideAnnotationChain(blocks, i);
169
173
  if (sideChain) {
170
174
  const endLine2 = startLine + sideChain.endLine;
171
- const blockId2 = deriveBlockId(startLine, endLine2);
175
+ const renderKey2 = deriveRenderKey(sideChain.raw, "paragraph", themeKey);
176
+ const occurrence2 = occurrenceCounter.get(renderKey2) ?? 0;
177
+ occurrenceCounter.set(renderKey2, occurrence2 + 1);
172
178
  result.push({
173
- blockId: blockId2,
179
+ blockId: deriveBlockId(renderKey2, occurrence2),
174
180
  kind: "paragraph",
175
181
  raw: sideChain.raw,
176
182
  startLine,
177
183
  endLine: endLine2,
178
- renderKey: deriveRenderKey(sideChain.raw, "paragraph", themeKey)
184
+ renderKey: renderKey2
179
185
  });
180
186
  currentLine = endLine2 + 1;
181
187
  i = sideChain.endIndex + 1;
@@ -199,14 +205,16 @@ function projectToPreviewBlocks(doc, themeKey, transforms) {
199
205
  }
200
206
  const raw = groupRawParts.join("\n");
201
207
  const kind2 = blockTypeToPreviewKind(block);
202
- const blockId2 = deriveBlockId(startLine, endLine2);
208
+ const renderKey2 = deriveRenderKey(raw, kind2, themeKey);
209
+ const occurrence2 = occurrenceCounter.get(renderKey2) ?? 0;
210
+ occurrenceCounter.set(renderKey2, occurrence2 + 1);
203
211
  result.push({
204
- blockId: blockId2,
212
+ blockId: deriveBlockId(renderKey2, occurrence2),
205
213
  kind: kind2,
206
214
  raw,
207
215
  startLine,
208
216
  endLine: endLine2,
209
- renderKey: deriveRenderKey(raw, kind2, themeKey)
217
+ renderKey: renderKey2
210
218
  });
211
219
  currentLine = endLine2 + 1;
212
220
  i = j;
@@ -214,14 +222,16 @@ function projectToPreviewBlocks(doc, themeKey, transforms) {
214
222
  }
215
223
  const endLine = startLine + blockLines - 1;
216
224
  const kind = blockTypeToPreviewKind(block);
217
- const blockId = deriveBlockId(startLine, endLine);
225
+ const renderKey = deriveRenderKey(block.raw, kind, themeKey);
226
+ const occurrence = occurrenceCounter.get(renderKey) ?? 0;
227
+ occurrenceCounter.set(renderKey, occurrence + 1);
218
228
  const previewBlock = {
219
- blockId,
229
+ blockId: deriveBlockId(renderKey, occurrence),
220
230
  kind,
221
231
  raw: block.raw,
222
232
  startLine,
223
233
  endLine,
224
- renderKey: deriveRenderKey(block.raw, kind, themeKey)
234
+ renderKey
225
235
  };
226
236
  if (block.type === "code-fence") {
227
237
  previewBlock.language = block.language || null;
@@ -243,7 +253,7 @@ function projectToPreviewBlocks(doc, themeKey, transforms) {
243
253
  });
244
254
  }
245
255
  function blocksMatch(a, b) {
246
- return a.renderKey === b.renderKey && a.blockId === b.blockId;
256
+ return a.blockId === b.blockId;
247
257
  }
248
258
  function computePreviewDirtyRange(oldBlocks, newBlocks) {
249
259
  const oldLen = oldBlocks.length;
@@ -599,6 +609,7 @@ export {
599
609
  deleteWordForward,
600
610
  deriveBlockId,
601
611
  deriveRenderKey,
612
+ deriveSourceKey,
602
613
  detectAndRenderDirty,
603
614
  domRangeToOffset,
604
615
  enableMapSet,
@@ -1 +1 @@
1
- export { v as DocumentChangeCallback, w as DomAdapterInstance, a6 as createDomAdapter } from '../dom-adapter-BwEilZtY.js';
1
+ export { v as DocumentChangeCallback, w as DomAdapterInstance, a6 as createDomAdapter } from '../dom-adapter-CTSJe5Uo.js';
@@ -4,7 +4,7 @@ import {
4
4
  fullRender,
5
5
  readSelection,
6
6
  restoreSelection
7
- } from "../chunk-RINTEGPG.js";
7
+ } from "../chunk-TRLKIMRD.js";
8
8
  import "../chunk-BGXCXQZP.js";
9
9
  import "../chunk-MPIWZLI3.js";
10
10
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@owomark/core",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Framework-agnostic core engine for the OwoMark editor.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",