@owomark/core 0.1.0
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 +212 -0
- package/dist/adapter/dom-adapter.d.ts +1 -0
- package/dist/adapter/dom-adapter.js +261 -0
- package/dist/chunk-RYTHPR7H.js +3259 -0
- package/dist/dom-adapter-IZEW91gZ.d.ts +463 -0
- package/dist/index.d.ts +364 -0
- package/dist/index.js +640 -0
- package/package.json +52 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,640 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BLOCK_TYPE_TO_CLASS,
|
|
3
|
+
BQ_STEP,
|
|
4
|
+
NOT_HANDLED,
|
|
5
|
+
SIDE_ANNOTATION_TAIL_RE,
|
|
6
|
+
SIDE_CONTINUATION_TAIL_RE,
|
|
7
|
+
TOKEN_TO_CLASS,
|
|
8
|
+
applyMarkdownIndent,
|
|
9
|
+
buildBlockquoteBarsBoxShadow,
|
|
10
|
+
createBlockElement,
|
|
11
|
+
createBlockIdGenerator,
|
|
12
|
+
createCommandRegistry,
|
|
13
|
+
createOwoMarkCore,
|
|
14
|
+
deleteToLineEnd,
|
|
15
|
+
deleteToLineStart,
|
|
16
|
+
deleteWordBackward,
|
|
17
|
+
deleteWordForward,
|
|
18
|
+
detectAndRenderDirty,
|
|
19
|
+
domRangeToOffset,
|
|
20
|
+
expandDirtyRange,
|
|
21
|
+
expandWithContext,
|
|
22
|
+
fullRender,
|
|
23
|
+
getBlockAtOffset,
|
|
24
|
+
getBlockById,
|
|
25
|
+
getBlockIndexById,
|
|
26
|
+
getBlockIndexForPosition,
|
|
27
|
+
getBlockStartOffset,
|
|
28
|
+
handleCharInput,
|
|
29
|
+
handleMarkdownEnter,
|
|
30
|
+
handleSmartBackspace,
|
|
31
|
+
handleSmartDelete,
|
|
32
|
+
insertCodeFence,
|
|
33
|
+
insertImage,
|
|
34
|
+
insertLink,
|
|
35
|
+
insertMathBlock,
|
|
36
|
+
insertSideAnnotation,
|
|
37
|
+
insertTable,
|
|
38
|
+
invalidateBlockCache,
|
|
39
|
+
isVirtualSelectionCollapsed,
|
|
40
|
+
linearToVirtual,
|
|
41
|
+
linearToVirtualPosition,
|
|
42
|
+
normalizeMarkdownPaste,
|
|
43
|
+
offsetToDomRange,
|
|
44
|
+
parseMarkdownToDocument,
|
|
45
|
+
patchDirtyBlocks,
|
|
46
|
+
readSelection,
|
|
47
|
+
reconcileBlocks,
|
|
48
|
+
resetBlockIdCounter,
|
|
49
|
+
resolveBlockContextType,
|
|
50
|
+
resolveIndentSize,
|
|
51
|
+
restoreSelection,
|
|
52
|
+
serializeDocument,
|
|
53
|
+
toggleBold,
|
|
54
|
+
toggleItalic,
|
|
55
|
+
tokenizeBlock,
|
|
56
|
+
tokenizeInline,
|
|
57
|
+
updateBlockElement,
|
|
58
|
+
virtualPositionToLinear,
|
|
59
|
+
virtualPositionsEqual,
|
|
60
|
+
virtualToLinear
|
|
61
|
+
} from "./chunk-RYTHPR7H.js";
|
|
62
|
+
|
|
63
|
+
// src/preview/block-id.ts
|
|
64
|
+
function deriveBlockId(startLine, endLine) {
|
|
65
|
+
return `L${startLine}-${endLine}`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// src/preview/render-key.ts
|
|
69
|
+
function djb2(input) {
|
|
70
|
+
let hash = 5381;
|
|
71
|
+
for (let i = 0; i < input.length; i++) {
|
|
72
|
+
hash = (hash << 5) + hash ^ input.charCodeAt(i);
|
|
73
|
+
}
|
|
74
|
+
return (hash >>> 0).toString(36);
|
|
75
|
+
}
|
|
76
|
+
function deriveRenderKey(raw, kind, themeKey) {
|
|
77
|
+
const src = `${kind}:${themeKey ?? ""}:${raw}`;
|
|
78
|
+
return djb2(src);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// src/preview/projection.ts
|
|
82
|
+
var BLOCK_TYPE_TO_KIND = {
|
|
83
|
+
"paragraph": "paragraph",
|
|
84
|
+
"heading": "heading",
|
|
85
|
+
"unordered-list": "unordered-list",
|
|
86
|
+
"ordered-list": "ordered-list",
|
|
87
|
+
"blockquote": "blockquote",
|
|
88
|
+
"code-fence": "code-fence",
|
|
89
|
+
"directive-container": "custom",
|
|
90
|
+
"thematic-break": "thematic-break",
|
|
91
|
+
"math-block": "math-block",
|
|
92
|
+
"table": "table"
|
|
93
|
+
};
|
|
94
|
+
function blockTypeToPreviewKind(node) {
|
|
95
|
+
return BLOCK_TYPE_TO_KIND[node.type];
|
|
96
|
+
}
|
|
97
|
+
function isGroupableType(type) {
|
|
98
|
+
return type === "unordered-list" || type === "ordered-list" || type === "blockquote";
|
|
99
|
+
}
|
|
100
|
+
function countLines(raw) {
|
|
101
|
+
let count = 1;
|
|
102
|
+
for (let i = 0; i < raw.length; i++) {
|
|
103
|
+
if (raw.charCodeAt(i) === 10) count++;
|
|
104
|
+
}
|
|
105
|
+
return count;
|
|
106
|
+
}
|
|
107
|
+
function isBlankParagraph(block) {
|
|
108
|
+
return block.type === "paragraph" && !block.raw.trim();
|
|
109
|
+
}
|
|
110
|
+
function isSideAnnotationHead(block) {
|
|
111
|
+
return block.type === "paragraph" && SIDE_ANNOTATION_TAIL_RE.test(block.raw);
|
|
112
|
+
}
|
|
113
|
+
function isSideAnnotationContinuation(block) {
|
|
114
|
+
return block.type === "paragraph" && SIDE_CONTINUATION_TAIL_RE.test(block.raw);
|
|
115
|
+
}
|
|
116
|
+
function collectSideAnnotationChain(blocks, startIndex) {
|
|
117
|
+
if (!isSideAnnotationHead(blocks[startIndex])) return null;
|
|
118
|
+
const rawParts = [blocks[startIndex].raw];
|
|
119
|
+
let endIndex = startIndex;
|
|
120
|
+
let endLine = 0;
|
|
121
|
+
let currentLine = 0;
|
|
122
|
+
let foundContinuation = false;
|
|
123
|
+
let i = startIndex + 1;
|
|
124
|
+
while (i < blocks.length) {
|
|
125
|
+
const block = blocks[i];
|
|
126
|
+
if (isBlankParagraph(block)) {
|
|
127
|
+
const next = blocks[i + 1];
|
|
128
|
+
if (!next || !isSideAnnotationContinuation(next)) break;
|
|
129
|
+
rawParts.push(block.raw, next.raw);
|
|
130
|
+
endIndex = i + 1;
|
|
131
|
+
i += 2;
|
|
132
|
+
foundContinuation = true;
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
if (isSideAnnotationContinuation(block)) {
|
|
136
|
+
rawParts.push(block.raw);
|
|
137
|
+
endIndex = i;
|
|
138
|
+
i++;
|
|
139
|
+
foundContinuation = true;
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
if (!foundContinuation) return null;
|
|
145
|
+
for (let j = startIndex; j <= endIndex; j++) {
|
|
146
|
+
currentLine += countLines(blocks[j].raw);
|
|
147
|
+
}
|
|
148
|
+
endLine = currentLine - 1;
|
|
149
|
+
return {
|
|
150
|
+
endIndex,
|
|
151
|
+
raw: rawParts.join("\n"),
|
|
152
|
+
endLine
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
function projectToPreviewBlocks(doc, themeKey, transforms) {
|
|
156
|
+
const result = [];
|
|
157
|
+
const blocks = doc.blocks;
|
|
158
|
+
let currentLine = 1;
|
|
159
|
+
let i = 0;
|
|
160
|
+
while (i < blocks.length) {
|
|
161
|
+
const block = blocks[i];
|
|
162
|
+
const blockLines = countLines(block.raw);
|
|
163
|
+
const startLine = currentLine;
|
|
164
|
+
const sideChain = collectSideAnnotationChain(blocks, i);
|
|
165
|
+
if (sideChain) {
|
|
166
|
+
const endLine2 = startLine + sideChain.endLine;
|
|
167
|
+
const blockId2 = deriveBlockId(startLine, endLine2);
|
|
168
|
+
result.push({
|
|
169
|
+
blockId: blockId2,
|
|
170
|
+
kind: "paragraph",
|
|
171
|
+
raw: sideChain.raw,
|
|
172
|
+
startLine,
|
|
173
|
+
endLine: endLine2,
|
|
174
|
+
renderKey: deriveRenderKey(sideChain.raw, "paragraph", themeKey)
|
|
175
|
+
});
|
|
176
|
+
currentLine = endLine2 + 1;
|
|
177
|
+
i = sideChain.endIndex + 1;
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
if (isGroupableType(block.type)) {
|
|
181
|
+
const groupType = block.type;
|
|
182
|
+
const groupRawParts = [block.raw];
|
|
183
|
+
let endLine2 = startLine + blockLines - 1;
|
|
184
|
+
let j = i + 1;
|
|
185
|
+
while (j < blocks.length && blocks[j].type === groupType) {
|
|
186
|
+
const nextLines = countLines(blocks[j].raw);
|
|
187
|
+
endLine2 += nextLines;
|
|
188
|
+
groupRawParts.push(blocks[j].raw);
|
|
189
|
+
j++;
|
|
190
|
+
}
|
|
191
|
+
const raw = groupRawParts.join("\n");
|
|
192
|
+
const kind2 = blockTypeToPreviewKind(block);
|
|
193
|
+
const blockId2 = deriveBlockId(startLine, endLine2);
|
|
194
|
+
result.push({
|
|
195
|
+
blockId: blockId2,
|
|
196
|
+
kind: kind2,
|
|
197
|
+
raw,
|
|
198
|
+
startLine,
|
|
199
|
+
endLine: endLine2,
|
|
200
|
+
renderKey: deriveRenderKey(raw, kind2, themeKey)
|
|
201
|
+
});
|
|
202
|
+
currentLine = endLine2 + 1;
|
|
203
|
+
i = j;
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
const endLine = startLine + blockLines - 1;
|
|
207
|
+
const kind = blockTypeToPreviewKind(block);
|
|
208
|
+
const blockId = deriveBlockId(startLine, endLine);
|
|
209
|
+
const previewBlock = {
|
|
210
|
+
blockId,
|
|
211
|
+
kind,
|
|
212
|
+
raw: block.raw,
|
|
213
|
+
startLine,
|
|
214
|
+
endLine,
|
|
215
|
+
renderKey: deriveRenderKey(block.raw, kind, themeKey)
|
|
216
|
+
};
|
|
217
|
+
if (block.type === "code-fence") {
|
|
218
|
+
previewBlock.language = block.language || null;
|
|
219
|
+
}
|
|
220
|
+
if (block.type === "heading") {
|
|
221
|
+
previewBlock.headingLevel = block.headingLevel;
|
|
222
|
+
}
|
|
223
|
+
result.push(previewBlock);
|
|
224
|
+
currentLine = endLine + 1;
|
|
225
|
+
i++;
|
|
226
|
+
}
|
|
227
|
+
if (!transforms || transforms.length === 0) return result;
|
|
228
|
+
return result.map((block) => {
|
|
229
|
+
let b = block;
|
|
230
|
+
for (const transform of transforms) {
|
|
231
|
+
b = transform(b);
|
|
232
|
+
}
|
|
233
|
+
return b;
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
function blocksMatch(a, b) {
|
|
237
|
+
return a.renderKey === b.renderKey && a.blockId === b.blockId;
|
|
238
|
+
}
|
|
239
|
+
function computePreviewDirtyRange(oldBlocks, newBlocks) {
|
|
240
|
+
const oldLen = oldBlocks.length;
|
|
241
|
+
const newLen = newBlocks.length;
|
|
242
|
+
const minLen = Math.min(oldLen, newLen);
|
|
243
|
+
let start = 0;
|
|
244
|
+
while (start < minLen && blocksMatch(oldBlocks[start], newBlocks[start])) {
|
|
245
|
+
start++;
|
|
246
|
+
}
|
|
247
|
+
let oldEnd = oldLen - 1;
|
|
248
|
+
let newEnd = newLen - 1;
|
|
249
|
+
while (oldEnd >= start && newEnd >= start && blocksMatch(oldBlocks[oldEnd], newBlocks[newEnd])) {
|
|
250
|
+
oldEnd--;
|
|
251
|
+
newEnd--;
|
|
252
|
+
}
|
|
253
|
+
const end = Math.max(oldEnd, newEnd);
|
|
254
|
+
if (start > end) return null;
|
|
255
|
+
return { startBlock: start, endBlock: Math.min(end, newLen - 1) };
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// src/state/shared-state.ts
|
|
259
|
+
function createInitialState(markdown, themeKey, transforms) {
|
|
260
|
+
const document = parseMarkdownToDocument(markdown);
|
|
261
|
+
const previewBlocks = projectToPreviewBlocks(document, themeKey, transforms);
|
|
262
|
+
return {
|
|
263
|
+
document,
|
|
264
|
+
previewBlocks,
|
|
265
|
+
dirtyRange: null,
|
|
266
|
+
dirtyReason: markdown ? "full-rebuild" : null,
|
|
267
|
+
selection: null,
|
|
268
|
+
compositionActive: false,
|
|
269
|
+
visibleBlockIds: [],
|
|
270
|
+
version: 1,
|
|
271
|
+
markdown
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
function createSharedStateStore(options) {
|
|
275
|
+
let themeKey = options?.themeKey;
|
|
276
|
+
const transforms = options?.transforms;
|
|
277
|
+
let state = createInitialState(options?.initialMarkdown ?? "", themeKey, transforms);
|
|
278
|
+
let editor = null;
|
|
279
|
+
let editorUnsubs = [];
|
|
280
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
281
|
+
function notify() {
|
|
282
|
+
for (const listener of listeners) {
|
|
283
|
+
listener(state);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
function updateFromMarkdown(markdown, reason) {
|
|
287
|
+
const oldBlocks = state.previewBlocks;
|
|
288
|
+
const document = parseMarkdownToDocument(markdown);
|
|
289
|
+
const previewBlocks = projectToPreviewBlocks(document, themeKey, transforms);
|
|
290
|
+
const dirtyRange = computePreviewDirtyRange(oldBlocks, previewBlocks);
|
|
291
|
+
let dirtyReason = null;
|
|
292
|
+
if (dirtyRange) {
|
|
293
|
+
if (oldBlocks.length !== previewBlocks.length) {
|
|
294
|
+
dirtyReason = "structure-edit";
|
|
295
|
+
} else {
|
|
296
|
+
dirtyReason = reason;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
state = {
|
|
300
|
+
document,
|
|
301
|
+
previewBlocks,
|
|
302
|
+
dirtyRange,
|
|
303
|
+
dirtyReason,
|
|
304
|
+
selection: state.selection,
|
|
305
|
+
compositionActive: state.compositionActive,
|
|
306
|
+
visibleBlockIds: state.visibleBlockIds,
|
|
307
|
+
version: state.version + 1,
|
|
308
|
+
markdown
|
|
309
|
+
};
|
|
310
|
+
notify();
|
|
311
|
+
}
|
|
312
|
+
function invalidateAll(reason) {
|
|
313
|
+
const previewBlocks = projectToPreviewBlocks(state.document, themeKey, transforms);
|
|
314
|
+
state = {
|
|
315
|
+
...state,
|
|
316
|
+
previewBlocks,
|
|
317
|
+
dirtyRange: previewBlocks.length > 0 ? { startBlock: 0, endBlock: previewBlocks.length - 1 } : null,
|
|
318
|
+
dirtyReason: reason,
|
|
319
|
+
version: state.version + 1
|
|
320
|
+
};
|
|
321
|
+
notify();
|
|
322
|
+
}
|
|
323
|
+
function connectEditor(instance) {
|
|
324
|
+
disconnectEditor();
|
|
325
|
+
editor = instance;
|
|
326
|
+
const unsubChange = instance.onChange((markdown) => {
|
|
327
|
+
updateFromMarkdown(markdown, "text-edit");
|
|
328
|
+
});
|
|
329
|
+
const unsubSelection = instance.onSelectionChange((selection) => {
|
|
330
|
+
state = { ...state, selection, version: state.version + 1 };
|
|
331
|
+
notify();
|
|
332
|
+
});
|
|
333
|
+
const unsubComposition = instance.onCompositionStateChange((active) => {
|
|
334
|
+
state = { ...state, compositionActive: active, version: state.version + 1 };
|
|
335
|
+
notify();
|
|
336
|
+
});
|
|
337
|
+
editorUnsubs = [unsubChange, unsubSelection, unsubComposition];
|
|
338
|
+
const currentMarkdown = instance.getMarkdown();
|
|
339
|
+
if (currentMarkdown !== state.markdown) {
|
|
340
|
+
updateFromMarkdown(currentMarkdown, "full-rebuild");
|
|
341
|
+
}
|
|
342
|
+
return disconnectEditor;
|
|
343
|
+
}
|
|
344
|
+
function disconnectEditor() {
|
|
345
|
+
for (const unsub of editorUnsubs) unsub();
|
|
346
|
+
editorUnsubs = [];
|
|
347
|
+
editor = null;
|
|
348
|
+
}
|
|
349
|
+
return {
|
|
350
|
+
getState() {
|
|
351
|
+
return state;
|
|
352
|
+
},
|
|
353
|
+
subscribe(listener) {
|
|
354
|
+
listeners.add(listener);
|
|
355
|
+
return () => listeners.delete(listener);
|
|
356
|
+
},
|
|
357
|
+
setMarkdown(markdown) {
|
|
358
|
+
if (editor) {
|
|
359
|
+
editor.setMarkdown(markdown);
|
|
360
|
+
} else {
|
|
361
|
+
updateFromMarkdown(markdown, "text-edit");
|
|
362
|
+
}
|
|
363
|
+
},
|
|
364
|
+
updateVisibleBlockIds(ids) {
|
|
365
|
+
const prev = state.visibleBlockIds;
|
|
366
|
+
if (prev.length === ids.length) {
|
|
367
|
+
const prevSet = new Set(prev);
|
|
368
|
+
if (ids.every((id) => prevSet.has(id))) return;
|
|
369
|
+
}
|
|
370
|
+
state = { ...state, visibleBlockIds: ids, version: state.version + 1 };
|
|
371
|
+
notify();
|
|
372
|
+
},
|
|
373
|
+
getEditorInstance() {
|
|
374
|
+
return editor;
|
|
375
|
+
},
|
|
376
|
+
connectEditor,
|
|
377
|
+
setThemeKey(newThemeKey) {
|
|
378
|
+
if (newThemeKey === themeKey) return;
|
|
379
|
+
themeKey = newThemeKey;
|
|
380
|
+
invalidateAll("theme-change");
|
|
381
|
+
},
|
|
382
|
+
notifyRendererChange() {
|
|
383
|
+
invalidateAll("renderer-change");
|
|
384
|
+
},
|
|
385
|
+
notifyCustomBlockChange() {
|
|
386
|
+
invalidateAll("custom-block-change");
|
|
387
|
+
}
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// src/transforms/image-size.ts
|
|
392
|
+
var OBSIDIAN_RE = /!\[([^\]]*?\|(\d+%?)\s*x\s*(\d+%?))\]\(([^)]+)\)/g;
|
|
393
|
+
var PANDOC_RE = /!\[([^\]]*)\]\(([^)]+)\)\{([^}]+)\}/g;
|
|
394
|
+
var MOU_RE = /!\[([^\]]*)\]\(([^\s)]+)\s+=(\d+%?)x(\d+%?)\)/g;
|
|
395
|
+
var IMG_TAG_RE = /<img\s[^>]*?src=["']([^"']+)["'][^>]*?>/gi;
|
|
396
|
+
var WIDTH_ATTR_RE = /width=["']?(\d+%?)["']?/i;
|
|
397
|
+
var HEIGHT_ATTR_RE = /height=["']?(\d+%?)["']?/i;
|
|
398
|
+
function parseDimValue(val) {
|
|
399
|
+
if (val.endsWith("%")) return val;
|
|
400
|
+
const n = parseInt(val, 10);
|
|
401
|
+
return isNaN(n) ? val : n;
|
|
402
|
+
}
|
|
403
|
+
function extractObsidian(raw) {
|
|
404
|
+
return [...raw.matchAll(OBSIDIAN_RE)].map((m) => ({
|
|
405
|
+
src: m[4],
|
|
406
|
+
width: parseDimValue(m[2]),
|
|
407
|
+
height: parseDimValue(m[3])
|
|
408
|
+
}));
|
|
409
|
+
}
|
|
410
|
+
function extractPandoc(raw) {
|
|
411
|
+
const results = [];
|
|
412
|
+
for (const m of raw.matchAll(PANDOC_RE)) {
|
|
413
|
+
const attrs = m[3];
|
|
414
|
+
const w = attrs.match(/\b(?:width|w)=(\d+%?)/i);
|
|
415
|
+
const h = attrs.match(/\b(?:height|h)=(\d+%?)/i);
|
|
416
|
+
if (w || h) {
|
|
417
|
+
results.push({
|
|
418
|
+
src: m[2],
|
|
419
|
+
width: w ? parseDimValue(w[1]) : "auto",
|
|
420
|
+
height: h ? parseDimValue(h[1]) : "auto"
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
return results;
|
|
425
|
+
}
|
|
426
|
+
function extractMou(raw) {
|
|
427
|
+
return [...raw.matchAll(MOU_RE)].map((m) => ({
|
|
428
|
+
src: m[2],
|
|
429
|
+
width: parseDimValue(m[3]),
|
|
430
|
+
height: parseDimValue(m[4])
|
|
431
|
+
}));
|
|
432
|
+
}
|
|
433
|
+
function extractHtmlImg(raw) {
|
|
434
|
+
const results = [];
|
|
435
|
+
for (const m of raw.matchAll(IMG_TAG_RE)) {
|
|
436
|
+
const tag = m[0];
|
|
437
|
+
const w = tag.match(WIDTH_ATTR_RE);
|
|
438
|
+
const h = tag.match(HEIGHT_ATTR_RE);
|
|
439
|
+
if (w || h) {
|
|
440
|
+
results.push({
|
|
441
|
+
src: m[1],
|
|
442
|
+
width: w ? parseDimValue(w[1]) : "auto",
|
|
443
|
+
height: h ? parseDimValue(h[1]) : "auto"
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
return results;
|
|
448
|
+
}
|
|
449
|
+
function createImageSizeTransform(syntax = "obsidian") {
|
|
450
|
+
return (block) => {
|
|
451
|
+
if (block.kind !== "paragraph" && block.kind !== "html-block") return block;
|
|
452
|
+
const dims = [];
|
|
453
|
+
dims.push(...extractHtmlImg(block.raw));
|
|
454
|
+
switch (syntax) {
|
|
455
|
+
case "obsidian":
|
|
456
|
+
dims.push(...extractObsidian(block.raw));
|
|
457
|
+
break;
|
|
458
|
+
case "pandoc":
|
|
459
|
+
dims.push(...extractPandoc(block.raw));
|
|
460
|
+
break;
|
|
461
|
+
case "mou":
|
|
462
|
+
dims.push(...extractMou(block.raw));
|
|
463
|
+
break;
|
|
464
|
+
case "none":
|
|
465
|
+
break;
|
|
466
|
+
}
|
|
467
|
+
if (dims.length === 0) return block;
|
|
468
|
+
return {
|
|
469
|
+
...block,
|
|
470
|
+
attributes: {
|
|
471
|
+
...block.attributes,
|
|
472
|
+
images: dims
|
|
473
|
+
}
|
|
474
|
+
};
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// src/index.ts
|
|
479
|
+
import { produce, enableMapSet } from "immer";
|
|
480
|
+
|
|
481
|
+
// src/virtual/editor-virtual-list.ts
|
|
482
|
+
var EDITOR_LINE_HEIGHT = 24;
|
|
483
|
+
var EDITOR_HEADING_SCALE = {
|
|
484
|
+
1: 2,
|
|
485
|
+
2: 1.6,
|
|
486
|
+
3: 1.4,
|
|
487
|
+
4: 1.2,
|
|
488
|
+
5: 1.1,
|
|
489
|
+
6: 1
|
|
490
|
+
};
|
|
491
|
+
var FENCED_BLOCK_PADDING = 16;
|
|
492
|
+
var TABLE_BLOCK_PADDING = 8;
|
|
493
|
+
var HEADING_EXTRA_MARGIN = 8;
|
|
494
|
+
var HR_PADDING = 16;
|
|
495
|
+
function countNewlines(s) {
|
|
496
|
+
let count = 0;
|
|
497
|
+
for (let i = 0; i < s.length; i++) {
|
|
498
|
+
if (s.charCodeAt(i) === 10) count++;
|
|
499
|
+
}
|
|
500
|
+
return count;
|
|
501
|
+
}
|
|
502
|
+
function estimateEditorBlockHeight(block, charsPerLine = 80) {
|
|
503
|
+
const lineHeight = EDITOR_LINE_HEIGHT;
|
|
504
|
+
switch (block.type) {
|
|
505
|
+
case "heading": {
|
|
506
|
+
const scale = EDITOR_HEADING_SCALE[block.headingLevel] ?? 1;
|
|
507
|
+
return Math.ceil(lineHeight * scale) + HEADING_EXTRA_MARGIN;
|
|
508
|
+
}
|
|
509
|
+
case "code-fence":
|
|
510
|
+
case "math-block": {
|
|
511
|
+
const lineCount = countNewlines(block.raw) + 1;
|
|
512
|
+
return lineCount * lineHeight + FENCED_BLOCK_PADDING;
|
|
513
|
+
}
|
|
514
|
+
case "table": {
|
|
515
|
+
const lineCount = countNewlines(block.raw) + 1;
|
|
516
|
+
return lineCount * lineHeight + TABLE_BLOCK_PADDING;
|
|
517
|
+
}
|
|
518
|
+
case "thematic-break":
|
|
519
|
+
return lineHeight + HR_PADDING;
|
|
520
|
+
default: {
|
|
521
|
+
if (!block.raw) return lineHeight;
|
|
522
|
+
const textLines = Math.max(1, Math.ceil(block.raw.length / charsPerLine));
|
|
523
|
+
return textLines * lineHeight;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
function buildVirtualRows(blocks, heightCache, charsPerLine) {
|
|
528
|
+
return blocks.map((block, index) => ({
|
|
529
|
+
blockIndex: index,
|
|
530
|
+
blockId: block.id,
|
|
531
|
+
height: heightCache.get(block.id) ?? estimateEditorBlockHeight(block, charsPerLine)
|
|
532
|
+
}));
|
|
533
|
+
}
|
|
534
|
+
function buildPrefixSums(rows) {
|
|
535
|
+
const sums = new Array(rows.length);
|
|
536
|
+
let cum = 0;
|
|
537
|
+
for (let i = 0; i < rows.length; i++) {
|
|
538
|
+
sums[i] = cum;
|
|
539
|
+
cum += rows[i].height;
|
|
540
|
+
}
|
|
541
|
+
return sums;
|
|
542
|
+
}
|
|
543
|
+
function bisectRight(sums, target, len) {
|
|
544
|
+
let lo = 0;
|
|
545
|
+
let hi = len - 1;
|
|
546
|
+
while (lo < hi) {
|
|
547
|
+
const mid = lo + hi + 1 >>> 1;
|
|
548
|
+
if (sums[mid] <= target) {
|
|
549
|
+
lo = mid;
|
|
550
|
+
} else {
|
|
551
|
+
hi = mid - 1;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
return lo;
|
|
555
|
+
}
|
|
556
|
+
function computeVisibleRange(rows, scrollTop, viewportHeight, overscan = 5) {
|
|
557
|
+
if (rows.length === 0) {
|
|
558
|
+
return { startIndex: 0, endIndex: -1, totalHeight: 0, offsetTop: 0, offsetBottom: 0 };
|
|
559
|
+
}
|
|
560
|
+
const sums = buildPrefixSums(rows);
|
|
561
|
+
const totalHeight = sums[rows.length - 1] + rows[rows.length - 1].height;
|
|
562
|
+
const rawStart = bisectRight(sums, scrollTop, rows.length);
|
|
563
|
+
const startIndex = Math.max(0, rawStart - overscan);
|
|
564
|
+
const bottomEdge = scrollTop + viewportHeight;
|
|
565
|
+
const rawEnd = bisectRight(sums, bottomEdge, rows.length);
|
|
566
|
+
const endIndex = Math.min(rows.length - 1, rawEnd + overscan);
|
|
567
|
+
const offsetTop = sums[startIndex];
|
|
568
|
+
const offsetBottom = totalHeight - sums[endIndex] - rows[endIndex].height;
|
|
569
|
+
return { startIndex, endIndex, totalHeight, offsetTop, offsetBottom };
|
|
570
|
+
}
|
|
571
|
+
export {
|
|
572
|
+
BLOCK_TYPE_TO_CLASS,
|
|
573
|
+
BQ_STEP,
|
|
574
|
+
NOT_HANDLED,
|
|
575
|
+
TOKEN_TO_CLASS,
|
|
576
|
+
applyMarkdownIndent,
|
|
577
|
+
buildBlockquoteBarsBoxShadow,
|
|
578
|
+
buildVirtualRows,
|
|
579
|
+
computePreviewDirtyRange,
|
|
580
|
+
computeVisibleRange,
|
|
581
|
+
createBlockElement,
|
|
582
|
+
createBlockIdGenerator,
|
|
583
|
+
createCommandRegistry,
|
|
584
|
+
createImageSizeTransform,
|
|
585
|
+
createOwoMarkCore,
|
|
586
|
+
createSharedStateStore,
|
|
587
|
+
deleteToLineEnd,
|
|
588
|
+
deleteToLineStart,
|
|
589
|
+
deleteWordBackward,
|
|
590
|
+
deleteWordForward,
|
|
591
|
+
deriveBlockId,
|
|
592
|
+
deriveRenderKey,
|
|
593
|
+
detectAndRenderDirty,
|
|
594
|
+
domRangeToOffset,
|
|
595
|
+
enableMapSet,
|
|
596
|
+
estimateEditorBlockHeight,
|
|
597
|
+
expandDirtyRange,
|
|
598
|
+
expandWithContext,
|
|
599
|
+
fullRender,
|
|
600
|
+
getBlockAtOffset,
|
|
601
|
+
getBlockById,
|
|
602
|
+
getBlockIndexById,
|
|
603
|
+
getBlockIndexForPosition,
|
|
604
|
+
getBlockStartOffset,
|
|
605
|
+
handleCharInput,
|
|
606
|
+
handleMarkdownEnter,
|
|
607
|
+
handleSmartBackspace,
|
|
608
|
+
handleSmartDelete,
|
|
609
|
+
insertCodeFence,
|
|
610
|
+
insertImage,
|
|
611
|
+
insertLink,
|
|
612
|
+
insertMathBlock,
|
|
613
|
+
insertSideAnnotation,
|
|
614
|
+
insertTable,
|
|
615
|
+
invalidateBlockCache,
|
|
616
|
+
isVirtualSelectionCollapsed,
|
|
617
|
+
linearToVirtual,
|
|
618
|
+
linearToVirtualPosition,
|
|
619
|
+
normalizeMarkdownPaste,
|
|
620
|
+
offsetToDomRange,
|
|
621
|
+
parseMarkdownToDocument,
|
|
622
|
+
patchDirtyBlocks,
|
|
623
|
+
produce,
|
|
624
|
+
projectToPreviewBlocks,
|
|
625
|
+
readSelection,
|
|
626
|
+
reconcileBlocks,
|
|
627
|
+
resetBlockIdCounter,
|
|
628
|
+
resolveBlockContextType,
|
|
629
|
+
resolveIndentSize,
|
|
630
|
+
restoreSelection,
|
|
631
|
+
serializeDocument,
|
|
632
|
+
toggleBold,
|
|
633
|
+
toggleItalic,
|
|
634
|
+
tokenizeBlock,
|
|
635
|
+
tokenizeInline,
|
|
636
|
+
updateBlockElement,
|
|
637
|
+
virtualPositionToLinear,
|
|
638
|
+
virtualPositionsEqual,
|
|
639
|
+
virtualToLinear
|
|
640
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@owomark/core",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Framework-agnostic core engine for the OwoMark editor.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "https://github.com/kzhoa/qblog.git",
|
|
13
|
+
"directory": "owomark/packages/owomark-core"
|
|
14
|
+
},
|
|
15
|
+
"bugs": {
|
|
16
|
+
"url": "https://github.com/kzhoa/qblog/issues"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"markdown",
|
|
20
|
+
"editor",
|
|
21
|
+
"owomark"
|
|
22
|
+
],
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=20"
|
|
25
|
+
},
|
|
26
|
+
"exports": {
|
|
27
|
+
".": {
|
|
28
|
+
"types": "./dist/index.d.ts",
|
|
29
|
+
"import": "./dist/index.js"
|
|
30
|
+
},
|
|
31
|
+
"./internal/dom-adapter": {
|
|
32
|
+
"types": "./dist/adapter/dom-adapter.d.ts",
|
|
33
|
+
"import": "./dist/adapter/dom-adapter.js"
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"files": [
|
|
37
|
+
"dist"
|
|
38
|
+
],
|
|
39
|
+
"scripts": {
|
|
40
|
+
"build": "tsup",
|
|
41
|
+
"typecheck": "tsc --noEmit -p tsconfig.json"
|
|
42
|
+
},
|
|
43
|
+
"publishConfig": {
|
|
44
|
+
"access": "public"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"tsup": "^8.5.1"
|
|
48
|
+
},
|
|
49
|
+
"dependencies": {
|
|
50
|
+
"immer": "^11.1.4"
|
|
51
|
+
}
|
|
52
|
+
}
|