@owomark/core 0.1.6 → 0.1.7
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 +30 -10
- package/dist/.build-manifest.json +130 -0
- package/dist/browser.d.ts +66 -0
- package/dist/browser.js +396 -0
- package/dist/chunk-3KTK7CSS.js +82 -0
- package/dist/chunk-5JNL3LHV.js +215 -0
- package/dist/chunk-ASRCHEFF.js +0 -0
- package/dist/chunk-BKJCBEI7.js +397 -0
- package/dist/chunk-CJSBFWKS.js +549 -0
- package/dist/chunk-GA5EFGSZ.js +5820 -0
- package/dist/chunk-OOH46GIF.js +95 -0
- package/dist/chunk-ROJILHRQ.js +192 -0
- package/dist/chunk-WFPUIPWU.js +34 -0
- package/dist/chunk-WXVKSKP3.js +191 -0
- package/dist/chunk-YZYJIXGO.js +0 -0
- package/dist/editor-core-DbPhn6aI.d.ts +249 -0
- package/dist/index.d.ts +77 -87
- package/dist/index.js +127 -248
- package/dist/internal/dom-adapter.d.ts +37 -1
- package/dist/internal/dom-adapter.js +9 -2
- package/dist/public-zMo7BR9l.d.ts +469 -0
- package/dist/registry-C849sxCo.d.ts +74 -0
- package/dist/semantic/components/index.d.ts +9 -0
- package/dist/semantic/components/index.js +11 -0
- package/dist/semantic/editor/index.d.ts +9 -0
- package/dist/semantic/editor/index.js +13 -0
- package/dist/semantic/index.d.ts +7 -0
- package/dist/semantic/index.js +106 -0
- package/dist/semantic/runtime/index.d.ts +9 -0
- package/dist/semantic/runtime/index.js +13 -0
- package/dist/semantic/shared/index.d.ts +10 -0
- package/dist/semantic/shared/index.js +17 -0
- package/dist/semantic/syntax/index.d.ts +151 -0
- package/dist/semantic/syntax/index.js +63 -0
- package/dist/types-DMqYF6Zn.d.ts +83 -0
- package/package.json +29 -1
- package/dist/chunk-6CBUAORJ.js +0 -3337
- package/dist/dom-adapter-C8wuoffZ.d.ts +0 -471
package/README.md
CHANGED
|
@@ -48,16 +48,7 @@ view.destroy();
|
|
|
48
48
|
core.destroy();
|
|
49
49
|
```
|
|
50
50
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
```ts
|
|
54
|
-
import { createOwoMarkVanillaEditor } from '@owomark/view';
|
|
55
|
-
|
|
56
|
-
const editor = createOwoMarkVanillaEditor();
|
|
57
|
-
editor.setMarkdown('# Hello');
|
|
58
|
-
editor.mount(document.getElementById('editor')!);
|
|
59
|
-
editor.destroy();
|
|
60
|
-
```
|
|
51
|
+
Browser hosts should compose `createOwoMarkCore()` with `createOwoMarkView()`. The public API no longer includes a standalone DOM editor entry.
|
|
61
52
|
|
|
62
53
|
## Core API
|
|
63
54
|
|
|
@@ -126,6 +117,29 @@ import {
|
|
|
126
117
|
import { tokenizeBlock, tokenizeInline } from '@owomark/core';
|
|
127
118
|
```
|
|
128
119
|
|
|
120
|
+
Parser extension contracts are also exposed for industrial custom syntax integration:
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
import type {
|
|
124
|
+
InlineSyntaxExtension,
|
|
125
|
+
BlockSyntaxExtension,
|
|
126
|
+
ContainerBlockSyntaxExtension,
|
|
127
|
+
CustomBlockRuntimeBinding,
|
|
128
|
+
} from '@owomark/core';
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Current execution support is live for `inline.extensions`, `block.extensions`,
|
|
132
|
+
and `block.containerExtensions`. Block/container extensions now execute through
|
|
133
|
+
the parser, emit formal `custom-block` nodes, and project runtime metadata into
|
|
134
|
+
preview `custom` blocks under the shared `'custom-block-change'` invalidation
|
|
135
|
+
contract.
|
|
136
|
+
|
|
137
|
+
For the formal syntax-layer contracts that now anchor this extension surface, see:
|
|
138
|
+
|
|
139
|
+
- [docs/specs/owomark-math.md](/home/qq/proj/qblog/docs/specs/owomark-math.md)
|
|
140
|
+
- [docs/specs/owomark-side-annotation.md](/home/qq/proj/qblog/docs/specs/owomark-side-annotation.md)
|
|
141
|
+
- [docs/specs/owomark-components-extension.md](/home/qq/proj/qblog/docs/specs/owomark-components-extension.md)
|
|
142
|
+
|
|
129
143
|
### Model
|
|
130
144
|
|
|
131
145
|
```ts
|
|
@@ -153,6 +167,12 @@ import { normalizeMarkdownPaste } from '@owomark/core';
|
|
|
153
167
|
- Fenced code blocks (`` ``` ``)
|
|
154
168
|
- Thematic breaks (`---`)
|
|
155
169
|
|
|
170
|
+
Formal extension specs:
|
|
171
|
+
|
|
172
|
+
- Math: [docs/specs/owomark-math.md](/home/qq/proj/qblog/docs/specs/owomark-math.md)
|
|
173
|
+
- Side Annotation: [docs/specs/owomark-side-annotation.md](/home/qq/proj/qblog/docs/specs/owomark-side-annotation.md)
|
|
174
|
+
- Components Extension: [docs/specs/owomark-components-extension.md](/home/qq/proj/qblog/docs/specs/owomark-components-extension.md)
|
|
175
|
+
|
|
156
176
|
## Preview Projection
|
|
157
177
|
|
|
158
178
|
Convert a parsed document into preview-ready blocks for incremental rendering:
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"packageName": "@owomark/core",
|
|
4
|
+
"packagePath": "owomark/packages/owomark-core",
|
|
5
|
+
"packageFingerprint": "32024bd874bc287d8de92d6142487720dd3e31af4477e6f91a54b8e8db3aec4e",
|
|
6
|
+
"workspaceFingerprint": "29c91b05781cfd51984ade38b2b582f8dab1013dfaaf87ed5b2b6e9723653157",
|
|
7
|
+
"builtAt": "2026-04-23T03:06:11.526Z",
|
|
8
|
+
"builderVersion": "2026-04-22-industrialization-v1",
|
|
9
|
+
"inputFiles": [
|
|
10
|
+
"owomark/package.json",
|
|
11
|
+
"owomark/packages/owomark-core/package.json",
|
|
12
|
+
"owomark/packages/owomark-core/src/browser-types.ts",
|
|
13
|
+
"owomark/packages/owomark-core/src/browser.ts",
|
|
14
|
+
"owomark/packages/owomark-core/src/clipboard/paste.ts",
|
|
15
|
+
"owomark/packages/owomark-core/src/commands/backspace.ts",
|
|
16
|
+
"owomark/packages/owomark-core/src/commands/char-input.ts",
|
|
17
|
+
"owomark/packages/owomark-core/src/commands/enter.ts",
|
|
18
|
+
"owomark/packages/owomark-core/src/commands/indent.ts",
|
|
19
|
+
"owomark/packages/owomark-core/src/commands/inline-format.ts",
|
|
20
|
+
"owomark/packages/owomark-core/src/commands/slash/builtin-commands.ts",
|
|
21
|
+
"owomark/packages/owomark-core/src/commands/slash/index.ts",
|
|
22
|
+
"owomark/packages/owomark-core/src/commands/slash/registry.ts",
|
|
23
|
+
"owomark/packages/owomark-core/src/commands/slash/slash-manager.ts",
|
|
24
|
+
"owomark/packages/owomark-core/src/commands/slash/types.ts",
|
|
25
|
+
"owomark/packages/owomark-core/src/commands/word-boundary.ts",
|
|
26
|
+
"owomark/packages/owomark-core/src/conformance/document.ts",
|
|
27
|
+
"owomark/packages/owomark-core/src/core/block-context.ts",
|
|
28
|
+
"owomark/packages/owomark-core/src/core/core-events.ts",
|
|
29
|
+
"owomark/packages/owomark-core/src/core/core-selection.ts",
|
|
30
|
+
"owomark/packages/owomark-core/src/core/core-types.ts",
|
|
31
|
+
"owomark/packages/owomark-core/src/core/editor-core.ts",
|
|
32
|
+
"owomark/packages/owomark-core/src/core/index.ts",
|
|
33
|
+
"owomark/packages/owomark-core/src/index.ts",
|
|
34
|
+
"owomark/packages/owomark-core/src/internal/clipboard/paste.ts",
|
|
35
|
+
"owomark/packages/owomark-core/src/internal/commands/word-boundary.ts",
|
|
36
|
+
"owomark/packages/owomark-core/src/internal/dom-adapter.ts",
|
|
37
|
+
"owomark/packages/owomark-core/src/model/dirty-range.ts",
|
|
38
|
+
"owomark/packages/owomark-core/src/model/document/block-factory.ts",
|
|
39
|
+
"owomark/packages/owomark-core/src/model/document/block-id.ts",
|
|
40
|
+
"owomark/packages/owomark-core/src/model/document/container-parser.ts",
|
|
41
|
+
"owomark/packages/owomark-core/src/model/document/container-rules.ts",
|
|
42
|
+
"owomark/packages/owomark-core/src/model/document/decorators.ts",
|
|
43
|
+
"owomark/packages/owomark-core/src/model/document/index.ts",
|
|
44
|
+
"owomark/packages/owomark-core/src/model/document/offsets.ts",
|
|
45
|
+
"owomark/packages/owomark-core/src/model/document/parse-markdown.ts",
|
|
46
|
+
"owomark/packages/owomark-core/src/model/document/reconcile.ts",
|
|
47
|
+
"owomark/packages/owomark-core/src/model/document/serialize.ts",
|
|
48
|
+
"owomark/packages/owomark-core/src/model/document/side-annotations.ts",
|
|
49
|
+
"owomark/packages/owomark-core/src/model/selection/block-cache.ts",
|
|
50
|
+
"owomark/packages/owomark-core/src/model/selection/dom-range.ts",
|
|
51
|
+
"owomark/packages/owomark-core/src/model/selection/index.ts",
|
|
52
|
+
"owomark/packages/owomark-core/src/model/selection/linear-dom.ts",
|
|
53
|
+
"owomark/packages/owomark-core/src/model/virtual-selection.ts",
|
|
54
|
+
"owomark/packages/owomark-core/src/parser/blocks.ts",
|
|
55
|
+
"owomark/packages/owomark-core/src/parser/inline-tokens.ts",
|
|
56
|
+
"owomark/packages/owomark-core/src/patterns/side-annotation.ts",
|
|
57
|
+
"owomark/packages/owomark-core/src/preview/block-id.ts",
|
|
58
|
+
"owomark/packages/owomark-core/src/preview/projection.ts",
|
|
59
|
+
"owomark/packages/owomark-core/src/preview/render-key.ts",
|
|
60
|
+
"owomark/packages/owomark-core/src/render/block-render.ts",
|
|
61
|
+
"owomark/packages/owomark-core/src/render/blockquote-bars.ts",
|
|
62
|
+
"owomark/packages/owomark-core/src/render/dom-patch.ts",
|
|
63
|
+
"owomark/packages/owomark-core/src/scroll/index.ts",
|
|
64
|
+
"owomark/packages/owomark-core/src/scroll/progress-engine.ts",
|
|
65
|
+
"owomark/packages/owomark-core/src/scroll/projection.ts",
|
|
66
|
+
"owomark/packages/owomark-core/src/scroll/scroll-controller.ts",
|
|
67
|
+
"owomark/packages/owomark-core/src/scroll/surface-geometry.ts",
|
|
68
|
+
"owomark/packages/owomark-core/src/scroll/types.ts",
|
|
69
|
+
"owomark/packages/owomark-core/src/semantic/components/index.ts",
|
|
70
|
+
"owomark/packages/owomark-core/src/semantic/editor/index.ts",
|
|
71
|
+
"owomark/packages/owomark-core/src/semantic/index.ts",
|
|
72
|
+
"owomark/packages/owomark-core/src/semantic/runtime/index.ts",
|
|
73
|
+
"owomark/packages/owomark-core/src/semantic/shared/index.ts",
|
|
74
|
+
"owomark/packages/owomark-core/src/semantic/shared/registry.ts",
|
|
75
|
+
"owomark/packages/owomark-core/src/semantic/shared/types.ts",
|
|
76
|
+
"owomark/packages/owomark-core/src/semantic/shared/validation.ts",
|
|
77
|
+
"owomark/packages/owomark-core/src/semantic/syntax/builtin/math.ts",
|
|
78
|
+
"owomark/packages/owomark-core/src/semantic/syntax/builtin/side-annotation.ts",
|
|
79
|
+
"owomark/packages/owomark-core/src/semantic/syntax/index.ts",
|
|
80
|
+
"owomark/packages/owomark-core/src/state/shared-state.ts",
|
|
81
|
+
"owomark/packages/owomark-core/src/transforms/image-size.ts",
|
|
82
|
+
"owomark/packages/owomark-core/src/types/public.ts",
|
|
83
|
+
"owomark/packages/owomark-core/src/virtual/editor-virtual-list.ts",
|
|
84
|
+
"owomark/packages/owomark-core/tsconfig.json",
|
|
85
|
+
"owomark/packages/owomark-core/tsup.config.ts",
|
|
86
|
+
"package-lock.json"
|
|
87
|
+
],
|
|
88
|
+
"artifactFiles": [
|
|
89
|
+
"dist/browser.d.ts",
|
|
90
|
+
"dist/browser.js",
|
|
91
|
+
"dist/chunk-3KTK7CSS.js",
|
|
92
|
+
"dist/chunk-5JNL3LHV.js",
|
|
93
|
+
"dist/chunk-ASRCHEFF.js",
|
|
94
|
+
"dist/chunk-BGXCXQZP.js",
|
|
95
|
+
"dist/chunk-BKJCBEI7.js",
|
|
96
|
+
"dist/chunk-CJSBFWKS.js",
|
|
97
|
+
"dist/chunk-MPIWZLI3.js",
|
|
98
|
+
"dist/chunk-OOH46GIF.js",
|
|
99
|
+
"dist/chunk-ROJILHRQ.js",
|
|
100
|
+
"dist/chunk-SHVM2TPY.js",
|
|
101
|
+
"dist/chunk-WFPUIPWU.js",
|
|
102
|
+
"dist/chunk-WXVKSKP3.js",
|
|
103
|
+
"dist/chunk-YZYJIXGO.js",
|
|
104
|
+
"dist/editor-core-DtPL3N3I.d.ts",
|
|
105
|
+
"dist/index.d.ts",
|
|
106
|
+
"dist/index.js",
|
|
107
|
+
"dist/internal/clipboard/paste.d.ts",
|
|
108
|
+
"dist/internal/clipboard/paste.js",
|
|
109
|
+
"dist/internal/commands/word-boundary.d.ts",
|
|
110
|
+
"dist/internal/commands/word-boundary.js",
|
|
111
|
+
"dist/internal/dom-adapter.d.ts",
|
|
112
|
+
"dist/internal/dom-adapter.js",
|
|
113
|
+
"dist/public-Dbsa3XRM.d.ts",
|
|
114
|
+
"dist/registry-D4LeNPxw.d.ts",
|
|
115
|
+
"dist/semantic/components/index.d.ts",
|
|
116
|
+
"dist/semantic/components/index.js",
|
|
117
|
+
"dist/semantic/editor/index.d.ts",
|
|
118
|
+
"dist/semantic/editor/index.js",
|
|
119
|
+
"dist/semantic/index.d.ts",
|
|
120
|
+
"dist/semantic/index.js",
|
|
121
|
+
"dist/semantic/runtime/index.d.ts",
|
|
122
|
+
"dist/semantic/runtime/index.js",
|
|
123
|
+
"dist/semantic/shared/index.d.ts",
|
|
124
|
+
"dist/semantic/shared/index.js",
|
|
125
|
+
"dist/semantic/syntax/index.d.ts",
|
|
126
|
+
"dist/semantic/syntax/index.js",
|
|
127
|
+
"dist/types-UF6oV7Xu.d.ts"
|
|
128
|
+
],
|
|
129
|
+
"localDependencyFingerprints": {}
|
|
130
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { O as OwoMarkSelection, a as OwoMarkDocument, D as DirtyRange, I as InlineTokenType, e as InlineToken, f as PreviewBlock } from './public-zMo7BR9l.js';
|
|
2
|
+
import { O as OwoMarkScrollProjectionSnapshot, a as OwoMarkSurfaceGeometrySnapshot, b as OwoMarkScrollController } from './types-DMqYF6Zn.js';
|
|
3
|
+
|
|
4
|
+
declare function invalidateBlockCache(root: HTMLElement): void;
|
|
5
|
+
|
|
6
|
+
declare function domRangeToOffset(root: HTMLElement, range: {
|
|
7
|
+
startContainer: Node;
|
|
8
|
+
startOffset: number;
|
|
9
|
+
endContainer: Node;
|
|
10
|
+
endOffset: number;
|
|
11
|
+
}): OwoMarkSelection | null;
|
|
12
|
+
declare function offsetToDomRange(root: HTMLElement, selection: OwoMarkSelection): Range | null;
|
|
13
|
+
|
|
14
|
+
declare function restoreSelection(root: HTMLElement, selection: OwoMarkSelection): void;
|
|
15
|
+
declare function readSelection(root: HTMLElement): OwoMarkSelection | null;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Incremental DOM patch: only re-render dirty blocks.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Full render: build entire editor DOM from document model.
|
|
23
|
+
*/
|
|
24
|
+
declare function fullRender(root: HTMLElement, doc: OwoMarkDocument): void;
|
|
25
|
+
/**
|
|
26
|
+
* Incremental patch: only re-render blocks in dirty range.
|
|
27
|
+
*/
|
|
28
|
+
declare function patchDirtyBlocks(root: HTMLElement, doc: OwoMarkDocument, dirty: DirtyRange): void;
|
|
29
|
+
/**
|
|
30
|
+
* Detect changed blocks between old and new document, then render minimally.
|
|
31
|
+
* Falls back to full render when block count changes or no dirty blocks found
|
|
32
|
+
* despite text difference (structural shift).
|
|
33
|
+
*/
|
|
34
|
+
declare function detectAndRenderDirty(root: HTMLElement, oldDoc: OwoMarkDocument, newDoc: OwoMarkDocument, fullFallback?: boolean): void;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Render a block's inline tokens into DOM nodes within a block container.
|
|
38
|
+
* Each block is a <div data-owo-block="index"> containing text/span nodes.
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
declare const TOKEN_CLASS_EXEMPTIONS: Set<"text" | "strong-marker" | "strong" | "emphasis-marker" | "emphasis" | "custom-inline-marker" | "custom-inline" | "code-marker" | "code" | "link-bracket" | "link-text" | "link-url" | "reference-bracket" | "reference-label" | "image-marker" | "image-alt" | "image-url" | "heading-marker" | "list-marker" | "task-marker" | "task-marker-checked" | "table-separator" | "blockquote-marker" | "fence-marker" | "fence-lang" | "code-block-text" | "strikethrough-marker" | "strikethrough" | "math-marker" | "math-text" | "hr" | "html">;
|
|
42
|
+
declare const TOKEN_TO_CLASS: Record<InlineTokenType, string>;
|
|
43
|
+
declare const BLOCK_TYPE_TO_CLASS: Record<string, string>;
|
|
44
|
+
declare function createBlockElement(doc: Document, tokens: InlineToken[], blockIndex: number, blockType: string, headingLevel?: number, depth?: number): HTMLDivElement;
|
|
45
|
+
/**
|
|
46
|
+
* Update an existing block element's content with new tokens.
|
|
47
|
+
* Returns true if the block was modified.
|
|
48
|
+
*/
|
|
49
|
+
declare function updateBlockElement(doc: Document, existing: HTMLDivElement, tokens: InlineToken[], blockType: string, headingLevel?: number, depth?: number): boolean;
|
|
50
|
+
|
|
51
|
+
declare const BQ_STEP: number;
|
|
52
|
+
/**
|
|
53
|
+
* Build the CSS box-shadow value for nested blockquote bars.
|
|
54
|
+
* Returns null when depth <= 1 (no extra bars needed — the first bar is
|
|
55
|
+
* rendered by the block's own border/class styling).
|
|
56
|
+
*/
|
|
57
|
+
declare function buildBlockquoteBarsBoxShadow(depth: number): string | null;
|
|
58
|
+
|
|
59
|
+
declare function measureEditorSurfaceGeometry(projection: OwoMarkScrollProjectionSnapshot, editorRoot: HTMLElement, _body?: string): OwoMarkSurfaceGeometrySnapshot;
|
|
60
|
+
declare function measurePreviewSurfaceGeometry(projection: OwoMarkScrollProjectionSnapshot, previewRoot: HTMLElement): OwoMarkSurfaceGeometrySnapshot;
|
|
61
|
+
|
|
62
|
+
declare function createScrollController(): OwoMarkScrollController & {
|
|
63
|
+
updateProjection(previewBlocks: readonly PreviewBlock[], documentRevision: number): void;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export { BLOCK_TYPE_TO_CLASS, BQ_STEP, TOKEN_CLASS_EXEMPTIONS, TOKEN_TO_CLASS, buildBlockquoteBarsBoxShadow, createBlockElement, createScrollController, detectAndRenderDirty, domRangeToOffset, fullRender, invalidateBlockCache, measureEditorSurfaceGeometry, measurePreviewSurfaceGeometry, offsetToDomRange, patchDirtyBlocks, readSelection, restoreSelection, updateBlockElement };
|
package/dist/browser.js
ADDED
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_VIEWPORT_ANCHOR_RATIO,
|
|
3
|
+
buildScrollProjectionSnapshot,
|
|
4
|
+
resolveScrollPosition,
|
|
5
|
+
resolveScrollTopFromPosition
|
|
6
|
+
} from "./chunk-5JNL3LHV.js";
|
|
7
|
+
import {
|
|
8
|
+
BLOCK_TYPE_TO_CLASS,
|
|
9
|
+
BQ_STEP,
|
|
10
|
+
TOKEN_CLASS_EXEMPTIONS,
|
|
11
|
+
TOKEN_TO_CLASS,
|
|
12
|
+
buildBlockquoteBarsBoxShadow,
|
|
13
|
+
createBlockElement,
|
|
14
|
+
detectAndRenderDirty,
|
|
15
|
+
domRangeToOffset,
|
|
16
|
+
fullRender,
|
|
17
|
+
invalidateBlockCache,
|
|
18
|
+
offsetToDomRange,
|
|
19
|
+
patchDirtyBlocks,
|
|
20
|
+
readSelection,
|
|
21
|
+
restoreSelection,
|
|
22
|
+
updateBlockElement
|
|
23
|
+
} from "./chunk-CJSBFWKS.js";
|
|
24
|
+
import {
|
|
25
|
+
deriveSourceKey
|
|
26
|
+
} from "./chunk-WFPUIPWU.js";
|
|
27
|
+
|
|
28
|
+
// src/scroll/surface-geometry.ts
|
|
29
|
+
var geometryRevisionCounter = 0;
|
|
30
|
+
function getOffsetInContainer(el, container) {
|
|
31
|
+
const elRect = el.getBoundingClientRect();
|
|
32
|
+
const containerRect = container.getBoundingClientRect();
|
|
33
|
+
return elRect.top - containerRect.top + container.scrollTop;
|
|
34
|
+
}
|
|
35
|
+
function collectMeasuredSegments(root, selector) {
|
|
36
|
+
const elements = root.querySelectorAll(selector);
|
|
37
|
+
const measured = /* @__PURE__ */ new Map();
|
|
38
|
+
for (const el of elements) {
|
|
39
|
+
const dataset = el.dataset ?? {};
|
|
40
|
+
const sourceStart = Number(dataset.sourceLineStart);
|
|
41
|
+
const sourceEnd = Number(dataset.sourceLineEnd) || sourceStart;
|
|
42
|
+
const sourceKey = dataset.sourceKey ?? (Number.isFinite(sourceStart) ? deriveSourceKey(sourceStart, sourceEnd) : null);
|
|
43
|
+
if (!sourceKey) {
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
const top = getOffsetInContainer(el, root);
|
|
47
|
+
const bottom = top + el.offsetHeight;
|
|
48
|
+
const existing = measured.get(sourceKey);
|
|
49
|
+
if (existing) {
|
|
50
|
+
existing.top = Math.min(existing.top, top);
|
|
51
|
+
existing.bottom = Math.max(existing.bottom, bottom);
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
measured.set(sourceKey, { sourceKey, top, bottom });
|
|
55
|
+
}
|
|
56
|
+
return measured;
|
|
57
|
+
}
|
|
58
|
+
function measureEditorSurfaceGeometry(projection, editorRoot, _body) {
|
|
59
|
+
const measured = collectMeasuredSegments(editorRoot, "[data-source-key], [data-source-line-start]");
|
|
60
|
+
if (measured.size === 0) {
|
|
61
|
+
const legacyBlocks = editorRoot.querySelectorAll("[data-owo-block]");
|
|
62
|
+
legacyBlocks.forEach((el, index) => {
|
|
63
|
+
const projectionSegment = projection.segments[index];
|
|
64
|
+
if (!projectionSegment) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const top = getOffsetInContainer(el, editorRoot);
|
|
68
|
+
measured.set(projectionSegment.sourceKey, {
|
|
69
|
+
sourceKey: projectionSegment.sourceKey,
|
|
70
|
+
top,
|
|
71
|
+
bottom: top + el.offsetHeight
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
const segments = [];
|
|
76
|
+
let unmeasuredCount = 0;
|
|
77
|
+
for (const seg of projection.segments) {
|
|
78
|
+
const geo = measured.get(seg.sourceKey);
|
|
79
|
+
if (geo) {
|
|
80
|
+
segments.push({
|
|
81
|
+
sourceKey: seg.sourceKey,
|
|
82
|
+
top: geo.top,
|
|
83
|
+
bottom: geo.bottom,
|
|
84
|
+
height: geo.bottom - geo.top,
|
|
85
|
+
measured: true
|
|
86
|
+
});
|
|
87
|
+
} else {
|
|
88
|
+
unmeasuredCount++;
|
|
89
|
+
segments.push({
|
|
90
|
+
sourceKey: seg.sourceKey,
|
|
91
|
+
top: 0,
|
|
92
|
+
bottom: 0,
|
|
93
|
+
height: 0,
|
|
94
|
+
measured: false,
|
|
95
|
+
degradedReason: "segment-not-mounted-yet"
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
const status = unmeasuredCount === 0 ? "ready" : unmeasuredCount === segments.length ? "pending" : "partial";
|
|
100
|
+
geometryRevisionCounter += 1;
|
|
101
|
+
return {
|
|
102
|
+
documentRevision: projection.documentRevision,
|
|
103
|
+
projectionRevision: projection.projectionRevision,
|
|
104
|
+
geometryRevision: geometryRevisionCounter,
|
|
105
|
+
surface: "editor",
|
|
106
|
+
status,
|
|
107
|
+
segments
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
function measurePreviewSurfaceGeometry(projection, previewRoot) {
|
|
111
|
+
const measured = collectMeasuredSegments(previewRoot, "[data-source-line-start], [data-source-key]");
|
|
112
|
+
const segments = [];
|
|
113
|
+
let unmeasuredCount = 0;
|
|
114
|
+
for (const seg of projection.segments) {
|
|
115
|
+
const geo = measured.get(seg.sourceKey);
|
|
116
|
+
if (geo) {
|
|
117
|
+
segments.push({
|
|
118
|
+
sourceKey: seg.sourceKey,
|
|
119
|
+
top: geo.top,
|
|
120
|
+
bottom: geo.bottom,
|
|
121
|
+
height: geo.bottom - geo.top,
|
|
122
|
+
measured: true
|
|
123
|
+
});
|
|
124
|
+
} else {
|
|
125
|
+
unmeasuredCount++;
|
|
126
|
+
segments.push({
|
|
127
|
+
sourceKey: seg.sourceKey,
|
|
128
|
+
top: 0,
|
|
129
|
+
bottom: 0,
|
|
130
|
+
height: 0,
|
|
131
|
+
measured: false,
|
|
132
|
+
degradedReason: "segment-not-mounted-yet"
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
const status = unmeasuredCount === 0 ? "ready" : unmeasuredCount === segments.length ? "pending" : "partial";
|
|
137
|
+
geometryRevisionCounter += 1;
|
|
138
|
+
return {
|
|
139
|
+
documentRevision: projection.documentRevision,
|
|
140
|
+
projectionRevision: projection.projectionRevision,
|
|
141
|
+
geometryRevision: geometryRevisionCounter,
|
|
142
|
+
surface: "preview",
|
|
143
|
+
status,
|
|
144
|
+
segments
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// src/scroll/scroll-controller.ts
|
|
149
|
+
function getMeasuredSegment(snapshot, sourceKey) {
|
|
150
|
+
return snapshot.segments.find((segment) => segment.sourceKey === sourceKey && segment.measured) ?? null;
|
|
151
|
+
}
|
|
152
|
+
function applyScrollTop(root, scrollTop, behavior) {
|
|
153
|
+
if (typeof root.scrollTo === "function") {
|
|
154
|
+
root.scrollTo({ top: scrollTop, behavior });
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
root.scrollTop = scrollTop;
|
|
158
|
+
}
|
|
159
|
+
function createScrollController() {
|
|
160
|
+
let projectionSnapshot = {
|
|
161
|
+
documentRevision: 0,
|
|
162
|
+
projectionRevision: 0,
|
|
163
|
+
segments: []
|
|
164
|
+
};
|
|
165
|
+
let lastResolvedPosition = null;
|
|
166
|
+
const geometrySnapshots = {
|
|
167
|
+
editor: null,
|
|
168
|
+
preview: null
|
|
169
|
+
};
|
|
170
|
+
const boundRoots = {
|
|
171
|
+
editor: null,
|
|
172
|
+
preview: null
|
|
173
|
+
};
|
|
174
|
+
const projectionListeners = /* @__PURE__ */ new Set();
|
|
175
|
+
const geometryListeners = {
|
|
176
|
+
editor: /* @__PURE__ */ new Set(),
|
|
177
|
+
preview: /* @__PURE__ */ new Set()
|
|
178
|
+
};
|
|
179
|
+
function notifyProjectionListeners() {
|
|
180
|
+
for (const listener of projectionListeners) {
|
|
181
|
+
listener(projectionSnapshot);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
function notifyGeometryListeners(surface) {
|
|
185
|
+
const snapshot = geometrySnapshots[surface];
|
|
186
|
+
if (!snapshot) return;
|
|
187
|
+
for (const listener of geometryListeners[surface]) {
|
|
188
|
+
listener(snapshot);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
function getViewport(root) {
|
|
192
|
+
return {
|
|
193
|
+
scrollTop: root.scrollTop,
|
|
194
|
+
clientHeight: root.clientHeight,
|
|
195
|
+
scrollHeight: root.scrollHeight
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
function remeasureSurface(surface) {
|
|
199
|
+
const root = boundRoots[surface];
|
|
200
|
+
if (!root || projectionSnapshot.segments.length === 0) {
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
const snapshot = surface === "editor" ? measureEditorSurfaceGeometry(projectionSnapshot, root) : measurePreviewSurfaceGeometry(projectionSnapshot, root);
|
|
204
|
+
geometrySnapshots[surface] = snapshot;
|
|
205
|
+
notifyGeometryListeners(surface);
|
|
206
|
+
return snapshot;
|
|
207
|
+
}
|
|
208
|
+
function resolveTarget(position, surface, viewportHeight, scrollHeight, viewportAnchorRatio = DEFAULT_VIEWPORT_ANCHOR_RATIO) {
|
|
209
|
+
const geometry = geometrySnapshots[surface];
|
|
210
|
+
if (!geometry) {
|
|
211
|
+
return { ok: false, reason: "surface-not-bound" };
|
|
212
|
+
}
|
|
213
|
+
if (position.documentRevision !== projectionSnapshot.documentRevision) {
|
|
214
|
+
return { ok: false, reason: "document-revision-mismatch" };
|
|
215
|
+
}
|
|
216
|
+
if (position.projectionRevision !== projectionSnapshot.projectionRevision) {
|
|
217
|
+
return { ok: false, reason: "projection-revision-mismatch" };
|
|
218
|
+
}
|
|
219
|
+
if (!projectionSnapshot.segments.some((segment) => segment.sourceKey === position.sourceKey)) {
|
|
220
|
+
return { ok: false, reason: "source-key-not-found" };
|
|
221
|
+
}
|
|
222
|
+
if (geometry.status === "pending" || geometry.status === "stale") {
|
|
223
|
+
return { ok: false, reason: "target-geometry-pending" };
|
|
224
|
+
}
|
|
225
|
+
const scrollTop = resolveScrollTopFromPosition(
|
|
226
|
+
position,
|
|
227
|
+
projectionSnapshot,
|
|
228
|
+
geometry,
|
|
229
|
+
viewportHeight,
|
|
230
|
+
scrollHeight,
|
|
231
|
+
viewportAnchorRatio
|
|
232
|
+
);
|
|
233
|
+
if (scrollTop == null) {
|
|
234
|
+
return { ok: false, reason: "target-geometry-pending" };
|
|
235
|
+
}
|
|
236
|
+
return {
|
|
237
|
+
ok: true,
|
|
238
|
+
scrollTop,
|
|
239
|
+
precision: geometry.status === "ready" ? "exact" : "provisional"
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
function maybeCompensate(surface) {
|
|
243
|
+
const root = boundRoots[surface];
|
|
244
|
+
if (!root || !lastResolvedPosition) {
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
const result = resolveTarget(
|
|
248
|
+
lastResolvedPosition,
|
|
249
|
+
surface,
|
|
250
|
+
root.clientHeight,
|
|
251
|
+
root.scrollHeight
|
|
252
|
+
);
|
|
253
|
+
if (!result.ok) {
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
if (Math.abs(root.scrollTop - result.scrollTop) < 1) {
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
applyScrollTop(root, result.scrollTop, "auto");
|
|
260
|
+
}
|
|
261
|
+
return {
|
|
262
|
+
getScrollProjectionSnapshot() {
|
|
263
|
+
return projectionSnapshot;
|
|
264
|
+
},
|
|
265
|
+
subscribeScrollProjectionSnapshot(listener) {
|
|
266
|
+
projectionListeners.add(listener);
|
|
267
|
+
return () => {
|
|
268
|
+
projectionListeners.delete(listener);
|
|
269
|
+
};
|
|
270
|
+
},
|
|
271
|
+
updateProjection(previewBlocks, documentRevision) {
|
|
272
|
+
projectionSnapshot = buildScrollProjectionSnapshot(previewBlocks, documentRevision);
|
|
273
|
+
geometrySnapshots.editor = null;
|
|
274
|
+
geometrySnapshots.preview = null;
|
|
275
|
+
notifyProjectionListeners();
|
|
276
|
+
if (boundRoots.editor) remeasureSurface("editor");
|
|
277
|
+
if (boundRoots.preview) remeasureSurface("preview");
|
|
278
|
+
},
|
|
279
|
+
getSurfaceGeometrySnapshot(surface) {
|
|
280
|
+
return geometrySnapshots[surface];
|
|
281
|
+
},
|
|
282
|
+
subscribeSurfaceGeometrySnapshot(surface, listener) {
|
|
283
|
+
geometryListeners[surface].add(listener);
|
|
284
|
+
return () => {
|
|
285
|
+
geometryListeners[surface].delete(listener);
|
|
286
|
+
};
|
|
287
|
+
},
|
|
288
|
+
bindSurface(surface, root) {
|
|
289
|
+
boundRoots[surface] = root;
|
|
290
|
+
remeasureSurface(surface);
|
|
291
|
+
},
|
|
292
|
+
unbindSurface(surface, root) {
|
|
293
|
+
if (root && boundRoots[surface] && boundRoots[surface] !== root) {
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
boundRoots[surface] = null;
|
|
297
|
+
geometrySnapshots[surface] = null;
|
|
298
|
+
},
|
|
299
|
+
measureSurface(surface) {
|
|
300
|
+
const snapshot = remeasureSurface(surface);
|
|
301
|
+
maybeCompensate(surface);
|
|
302
|
+
return snapshot;
|
|
303
|
+
},
|
|
304
|
+
invalidateGeometry(surface) {
|
|
305
|
+
const current = geometrySnapshots[surface];
|
|
306
|
+
if (!current) {
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
geometrySnapshots[surface] = {
|
|
310
|
+
...current,
|
|
311
|
+
status: "stale"
|
|
312
|
+
};
|
|
313
|
+
notifyGeometryListeners(surface);
|
|
314
|
+
},
|
|
315
|
+
resolveScrollPosition(surface, viewport, viewportAnchorRatio = DEFAULT_VIEWPORT_ANCHOR_RATIO) {
|
|
316
|
+
const geometry = geometrySnapshots[surface];
|
|
317
|
+
if (!geometry || geometry.projectionRevision !== projectionSnapshot.projectionRevision) {
|
|
318
|
+
return null;
|
|
319
|
+
}
|
|
320
|
+
const position = resolveScrollPosition(
|
|
321
|
+
projectionSnapshot,
|
|
322
|
+
geometry,
|
|
323
|
+
viewport,
|
|
324
|
+
viewportAnchorRatio
|
|
325
|
+
);
|
|
326
|
+
if (position) {
|
|
327
|
+
lastResolvedPosition = position;
|
|
328
|
+
}
|
|
329
|
+
return position;
|
|
330
|
+
},
|
|
331
|
+
resolveScrollTopFromPosition(position, surface, viewportHeight, scrollHeight, viewportAnchorRatio = DEFAULT_VIEWPORT_ANCHOR_RATIO) {
|
|
332
|
+
return resolveTarget(position, surface, viewportHeight, scrollHeight, viewportAnchorRatio);
|
|
333
|
+
},
|
|
334
|
+
scrollToScrollPosition(surface, position, options) {
|
|
335
|
+
const root = boundRoots[surface];
|
|
336
|
+
if (!root) {
|
|
337
|
+
return { ok: false, reason: "surface-not-bound" };
|
|
338
|
+
}
|
|
339
|
+
const baseResult = resolveTarget(
|
|
340
|
+
position,
|
|
341
|
+
surface,
|
|
342
|
+
root.clientHeight,
|
|
343
|
+
root.scrollHeight
|
|
344
|
+
);
|
|
345
|
+
if (!baseResult.ok) {
|
|
346
|
+
return baseResult;
|
|
347
|
+
}
|
|
348
|
+
let targetScrollTop = baseResult.scrollTop;
|
|
349
|
+
const measuredSeg = geometrySnapshots[surface] ? getMeasuredSegment(geometrySnapshots[surface], position.sourceKey) : null;
|
|
350
|
+
if (measuredSeg) {
|
|
351
|
+
const offsetTop = options?.offsetTop ?? 0;
|
|
352
|
+
switch (options?.blockAlign) {
|
|
353
|
+
case "start":
|
|
354
|
+
targetScrollTop = measuredSeg.top - offsetTop;
|
|
355
|
+
break;
|
|
356
|
+
case "center":
|
|
357
|
+
targetScrollTop = measuredSeg.top - (root.clientHeight - measuredSeg.height) / 2 - offsetTop;
|
|
358
|
+
break;
|
|
359
|
+
case "end":
|
|
360
|
+
targetScrollTop = measuredSeg.bottom - root.clientHeight - offsetTop;
|
|
361
|
+
break;
|
|
362
|
+
default:
|
|
363
|
+
break;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
const maxScroll = Math.max(0, root.scrollHeight - root.clientHeight);
|
|
367
|
+
const clamped = Math.max(0, Math.min(maxScroll, targetScrollTop));
|
|
368
|
+
lastResolvedPosition = position;
|
|
369
|
+
applyScrollTop(root, clamped, options?.behavior ?? "auto");
|
|
370
|
+
return {
|
|
371
|
+
...baseResult,
|
|
372
|
+
scrollTop: clamped
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
export {
|
|
378
|
+
BLOCK_TYPE_TO_CLASS,
|
|
379
|
+
BQ_STEP,
|
|
380
|
+
TOKEN_CLASS_EXEMPTIONS,
|
|
381
|
+
TOKEN_TO_CLASS,
|
|
382
|
+
buildBlockquoteBarsBoxShadow,
|
|
383
|
+
createBlockElement,
|
|
384
|
+
createScrollController,
|
|
385
|
+
detectAndRenderDirty,
|
|
386
|
+
domRangeToOffset,
|
|
387
|
+
fullRender,
|
|
388
|
+
invalidateBlockCache,
|
|
389
|
+
measureEditorSurfaceGeometry,
|
|
390
|
+
measurePreviewSurfaceGeometry,
|
|
391
|
+
offsetToDomRange,
|
|
392
|
+
patchDirtyBlocks,
|
|
393
|
+
readSelection,
|
|
394
|
+
restoreSelection,
|
|
395
|
+
updateBlockElement
|
|
396
|
+
};
|