@owomark/view 0.1.0 → 0.1.2
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/dist/chunk-3CKPBCIP.js +144 -0
- package/dist/{chunk-Z7WJR3XO.js → chunk-4V3WQMGV.js} +4 -3
- package/dist/chunk-6LWPFJCB.js +23 -0
- package/dist/chunk-F3LG7AML.js +192 -0
- package/dist/chunk-Y72HQJQI.js +45 -0
- package/dist/index.js +14 -392
- package/dist/internal/virtual/block-layout-map.d.ts +62 -0
- package/dist/internal/virtual/block-layout-map.js +6 -0
- package/dist/internal/virtual/height-cache.d.ts +17 -0
- package/dist/internal/virtual/height-cache.js +6 -0
- package/dist/internal/virtual/height-estimator.d.ts +13 -0
- package/dist/internal/virtual/height-estimator.js +6 -0
- package/dist/internal/virtual/viewport-manager.d.ts +63 -0
- package/dist/internal/virtual/viewport-manager.js +6 -0
- package/dist/static.js +1 -1
- package/package.json +35 -14
- package/src/theme/owomark.css +4 -0
- package/src/theme/preview.css +210 -0
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
// src/virtual/block-layout-map.ts
|
|
2
|
+
var BlockLayoutMap = class {
|
|
3
|
+
layouts = [];
|
|
4
|
+
indexMap = /* @__PURE__ */ new Map();
|
|
5
|
+
_totalHeight = 0;
|
|
6
|
+
get totalHeight() {
|
|
7
|
+
return this._totalHeight;
|
|
8
|
+
}
|
|
9
|
+
get size() {
|
|
10
|
+
return this.layouts.length;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Rebuild the layout table from a new block list.
|
|
14
|
+
* Blocks that already have measured heights retain them;
|
|
15
|
+
* new blocks use the provided estimated height.
|
|
16
|
+
*/
|
|
17
|
+
rebuild(blockIds, getEstimatedHeight) {
|
|
18
|
+
const oldHeights = /* @__PURE__ */ new Map();
|
|
19
|
+
for (const layout of this.layouts) {
|
|
20
|
+
oldHeights.set(layout.blockId, { height: layout.height, measured: layout.measured });
|
|
21
|
+
}
|
|
22
|
+
this.layouts = [];
|
|
23
|
+
this.indexMap.clear();
|
|
24
|
+
let offsetY = 0;
|
|
25
|
+
for (let i = 0; i < blockIds.length; i++) {
|
|
26
|
+
const blockId = blockIds[i];
|
|
27
|
+
const existing = oldHeights.get(blockId);
|
|
28
|
+
const height = existing ? existing.height : getEstimatedHeight(blockId);
|
|
29
|
+
const measured = existing ? existing.measured : false;
|
|
30
|
+
this.layouts.push({ blockId, offsetY, height, measured });
|
|
31
|
+
this.indexMap.set(blockId, i);
|
|
32
|
+
offsetY += height;
|
|
33
|
+
}
|
|
34
|
+
this._totalHeight = offsetY;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Update a single block's height. Returns true if the height actually changed.
|
|
38
|
+
* Recomputes offsetY for all subsequent blocks.
|
|
39
|
+
*/
|
|
40
|
+
updateHeight(blockId, height, measured) {
|
|
41
|
+
const idx = this.indexMap.get(blockId);
|
|
42
|
+
if (idx === void 0) return false;
|
|
43
|
+
const layout = this.layouts[idx];
|
|
44
|
+
if (layout.height === height && layout.measured === measured) return false;
|
|
45
|
+
layout.height = height;
|
|
46
|
+
layout.measured = measured;
|
|
47
|
+
this.recomputeFrom(idx);
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Batch-update multiple block heights with a single recompute pass.
|
|
52
|
+
* Avoids O(k·n) when k blocks change in the same frame (e.g. ResizeObserver).
|
|
53
|
+
*/
|
|
54
|
+
updateHeightBatch(updates) {
|
|
55
|
+
let minChangedIndex = Infinity;
|
|
56
|
+
for (const { blockId, height, measured } of updates) {
|
|
57
|
+
const idx = this.indexMap.get(blockId);
|
|
58
|
+
if (idx === void 0) continue;
|
|
59
|
+
const layout = this.layouts[idx];
|
|
60
|
+
if (layout.height === height && layout.measured === measured) continue;
|
|
61
|
+
layout.height = height;
|
|
62
|
+
layout.measured = measured;
|
|
63
|
+
if (idx < minChangedIndex) minChangedIndex = idx;
|
|
64
|
+
}
|
|
65
|
+
if (minChangedIndex === Infinity) return false;
|
|
66
|
+
this.recomputeFrom(minChangedIndex);
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Invalidate all measured heights (e.g., on container width change).
|
|
71
|
+
* Heights revert to estimated values via the provided estimator.
|
|
72
|
+
*/
|
|
73
|
+
invalidateAll(getEstimatedHeight) {
|
|
74
|
+
let offsetY = 0;
|
|
75
|
+
for (const layout of this.layouts) {
|
|
76
|
+
layout.height = getEstimatedHeight(layout.blockId);
|
|
77
|
+
layout.measured = false;
|
|
78
|
+
layout.offsetY = offsetY;
|
|
79
|
+
offsetY += layout.height;
|
|
80
|
+
}
|
|
81
|
+
this._totalHeight = offsetY;
|
|
82
|
+
}
|
|
83
|
+
getLayout(blockId) {
|
|
84
|
+
const idx = this.indexMap.get(blockId);
|
|
85
|
+
return idx !== void 0 ? this.layouts[idx] : null;
|
|
86
|
+
}
|
|
87
|
+
getLayoutAt(index) {
|
|
88
|
+
return this.layouts[index] ?? null;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Find the range of block indices visible within [scrollTop, scrollTop + viewportHeight],
|
|
92
|
+
* expanded by bufferPx on each side.
|
|
93
|
+
*/
|
|
94
|
+
getVisibleRange(scrollTop, viewportHeight, bufferPx) {
|
|
95
|
+
const top = Math.max(0, scrollTop - bufferPx);
|
|
96
|
+
const bottom = scrollTop + viewportHeight + bufferPx;
|
|
97
|
+
if (this.layouts.length === 0) {
|
|
98
|
+
return { startIndex: 0, endIndex: -1 };
|
|
99
|
+
}
|
|
100
|
+
const startIndex = this.findIndexAtOffset(top);
|
|
101
|
+
const endIndex = this.findIndexAtOffset(bottom);
|
|
102
|
+
return {
|
|
103
|
+
startIndex: Math.max(0, startIndex),
|
|
104
|
+
endIndex: Math.min(this.layouts.length - 1, endIndex)
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
getAllLayouts() {
|
|
108
|
+
return this.layouts;
|
|
109
|
+
}
|
|
110
|
+
// -------------------------------------------------------------------------
|
|
111
|
+
// Private
|
|
112
|
+
// -------------------------------------------------------------------------
|
|
113
|
+
recomputeFrom(fromIndex) {
|
|
114
|
+
let offsetY = fromIndex > 0 ? this.layouts[fromIndex - 1].offsetY + this.layouts[fromIndex - 1].height : 0;
|
|
115
|
+
for (let i = fromIndex; i < this.layouts.length; i++) {
|
|
116
|
+
this.layouts[i].offsetY = offsetY;
|
|
117
|
+
offsetY += this.layouts[i].height;
|
|
118
|
+
}
|
|
119
|
+
this._totalHeight = offsetY;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Binary search: find the index of the block that contains the given Y offset.
|
|
123
|
+
*/
|
|
124
|
+
findIndexAtOffset(y) {
|
|
125
|
+
let lo = 0;
|
|
126
|
+
let hi = this.layouts.length - 1;
|
|
127
|
+
while (lo <= hi) {
|
|
128
|
+
const mid = lo + hi >>> 1;
|
|
129
|
+
const layout = this.layouts[mid];
|
|
130
|
+
if (layout.offsetY + layout.height <= y) {
|
|
131
|
+
lo = mid + 1;
|
|
132
|
+
} else if (layout.offsetY > y) {
|
|
133
|
+
hi = mid - 1;
|
|
134
|
+
} else {
|
|
135
|
+
return mid;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return Math.min(lo, this.layouts.length - 1);
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
export {
|
|
143
|
+
BlockLayoutMap
|
|
144
|
+
};
|
|
@@ -892,9 +892,10 @@ function visit(node) {
|
|
|
892
892
|
}
|
|
893
893
|
}
|
|
894
894
|
function transform(node) {
|
|
895
|
-
const
|
|
896
|
-
const
|
|
897
|
-
const
|
|
895
|
+
const properties = node.properties ?? {};
|
|
896
|
+
const sideType = String(properties["data-side-type"] || "plain");
|
|
897
|
+
const sideText = properties["data-side-text"] ? String(properties["data-side-text"]) : null;
|
|
898
|
+
const isOrphan = properties["data-side-orphan"] != null;
|
|
898
899
|
const mainContent = el("div", { class: "side-annotation-main" }, node.children);
|
|
899
900
|
const asideChildren = [];
|
|
900
901
|
if (isOrphan) {
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// src/virtual/height-cache.ts
|
|
2
|
+
var HeightProjectionCache = class {
|
|
3
|
+
cache = /* @__PURE__ */ new Map();
|
|
4
|
+
get(renderKey) {
|
|
5
|
+
return this.cache.get(renderKey);
|
|
6
|
+
}
|
|
7
|
+
set(renderKey, height) {
|
|
8
|
+
this.cache.set(renderKey, height);
|
|
9
|
+
}
|
|
10
|
+
has(renderKey) {
|
|
11
|
+
return this.cache.has(renderKey);
|
|
12
|
+
}
|
|
13
|
+
clear() {
|
|
14
|
+
this.cache.clear();
|
|
15
|
+
}
|
|
16
|
+
get size() {
|
|
17
|
+
return this.cache.size;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export {
|
|
22
|
+
HeightProjectionCache
|
|
23
|
+
};
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
// src/virtual/viewport-manager.ts
|
|
2
|
+
var BLOCK_ID_ATTR = "data-block-id";
|
|
3
|
+
var SOURCE_START_ATTR = "data-source-line-start";
|
|
4
|
+
var SOURCE_END_ATTR = "data-source-line-end";
|
|
5
|
+
var HEIGHT_TRANSITION = "top 0.15s ease-out";
|
|
6
|
+
var TOTAL_HEIGHT_TRANSITION = "height 0.15s ease-out";
|
|
7
|
+
var SKELETON_GAP = 8;
|
|
8
|
+
function createSkeletonHtml(height) {
|
|
9
|
+
const innerHeight = Math.max(height - SKELETON_GAP, 8);
|
|
10
|
+
return `<div class="owo-skeleton" style="height:${innerHeight}px;margin-bottom:${SKELETON_GAP}px;border-radius:4px;background:linear-gradient(90deg,#e2e2e2 25%,#efefef 50%,#e2e2e2 75%);background-size:200% 100%;animation:zm-skeleton-pulse 1.5s ease-in-out infinite" aria-hidden="true"></div>`;
|
|
11
|
+
}
|
|
12
|
+
function ensureSkeletonStyles(doc) {
|
|
13
|
+
try {
|
|
14
|
+
if (doc.getElementById?.("owo-skeleton-styles")) return;
|
|
15
|
+
const style = doc.createElement("style");
|
|
16
|
+
style.id = "owo-skeleton-styles";
|
|
17
|
+
style.textContent = `@keyframes zm-skeleton-pulse{0%{background-position:200% 0}100%{background-position:-200% 0}}`;
|
|
18
|
+
const target = doc.head ?? doc.body;
|
|
19
|
+
target?.appendChild(style);
|
|
20
|
+
} catch {
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
var VirtualViewportManager = class {
|
|
24
|
+
root = null;
|
|
25
|
+
scrollContainer = null;
|
|
26
|
+
contentLayer = null;
|
|
27
|
+
/** Currently mounted block wrappers, keyed by blockId. */
|
|
28
|
+
mounted = /* @__PURE__ */ new Map();
|
|
29
|
+
/** Unmount callbacks for DOM-mounted renderers. */
|
|
30
|
+
domUnmounts = /* @__PURE__ */ new Map();
|
|
31
|
+
/** Buffer size as fraction of viewport height (each side). */
|
|
32
|
+
bufferFraction = 0.5;
|
|
33
|
+
/**
|
|
34
|
+
* Mount the viewport manager onto a root element.
|
|
35
|
+
* Creates an inner scroll container with a content layer for absolute positioning.
|
|
36
|
+
*/
|
|
37
|
+
mount(root) {
|
|
38
|
+
this.root = root;
|
|
39
|
+
this.scrollContainer = root;
|
|
40
|
+
root.style.overflow = "auto";
|
|
41
|
+
root.style.position = "relative";
|
|
42
|
+
const doc = root.ownerDocument;
|
|
43
|
+
ensureSkeletonStyles(doc);
|
|
44
|
+
this.contentLayer = doc.createElement("div");
|
|
45
|
+
this.contentLayer.style.position = "relative";
|
|
46
|
+
this.contentLayer.style.width = "100%";
|
|
47
|
+
this.contentLayer.style.transition = TOTAL_HEIGHT_TRANSITION;
|
|
48
|
+
root.appendChild(this.contentLayer);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Update which blocks are mounted based on scroll position and layout map.
|
|
52
|
+
* Returns the set of blockIds that are newly mounted (need rendering).
|
|
53
|
+
*/
|
|
54
|
+
reconcile(blocks, layoutMap) {
|
|
55
|
+
if (!this.scrollContainer || !this.contentLayer) {
|
|
56
|
+
return { mounted: /* @__PURE__ */ new Set(), unmounted: /* @__PURE__ */ new Set(), newlyMounted: /* @__PURE__ */ new Set() };
|
|
57
|
+
}
|
|
58
|
+
const scrollTop = this.scrollContainer.scrollTop;
|
|
59
|
+
const viewportHeight = this.scrollContainer.clientHeight;
|
|
60
|
+
const bufferPx = viewportHeight * this.bufferFraction;
|
|
61
|
+
this.contentLayer.style.height = `${layoutMap.totalHeight}px`;
|
|
62
|
+
const { startIndex, endIndex } = layoutMap.getVisibleRange(
|
|
63
|
+
scrollTop,
|
|
64
|
+
viewportHeight,
|
|
65
|
+
bufferPx
|
|
66
|
+
);
|
|
67
|
+
const shouldMount = /* @__PURE__ */ new Set();
|
|
68
|
+
for (let i = startIndex; i <= endIndex && i < blocks.length; i++) {
|
|
69
|
+
shouldMount.add(blocks[i].blockId);
|
|
70
|
+
}
|
|
71
|
+
const unmounted = /* @__PURE__ */ new Set();
|
|
72
|
+
for (const [blockId, wrapper] of this.mounted) {
|
|
73
|
+
if (!shouldMount.has(blockId)) {
|
|
74
|
+
this.cleanupDomMount(blockId);
|
|
75
|
+
wrapper.remove();
|
|
76
|
+
this.mounted.delete(blockId);
|
|
77
|
+
unmounted.add(blockId);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
const newlyMounted = /* @__PURE__ */ new Set();
|
|
81
|
+
const doc = this.contentLayer.ownerDocument;
|
|
82
|
+
for (let i = startIndex; i <= endIndex && i < blocks.length; i++) {
|
|
83
|
+
const block = blocks[i];
|
|
84
|
+
const layout = layoutMap.getLayoutAt(i);
|
|
85
|
+
if (!layout) continue;
|
|
86
|
+
let wrapper = this.mounted.get(block.blockId);
|
|
87
|
+
if (!wrapper) {
|
|
88
|
+
wrapper = doc.createElement("div");
|
|
89
|
+
wrapper.setAttribute(BLOCK_ID_ATTR, block.blockId);
|
|
90
|
+
wrapper.setAttribute(SOURCE_START_ATTR, String(block.startLine));
|
|
91
|
+
wrapper.setAttribute(SOURCE_END_ATTR, String(block.endLine));
|
|
92
|
+
wrapper.style.position = "absolute";
|
|
93
|
+
wrapper.style.left = "0";
|
|
94
|
+
wrapper.style.width = "100%";
|
|
95
|
+
wrapper.style.transition = HEIGHT_TRANSITION;
|
|
96
|
+
wrapper.innerHTML = createSkeletonHtml(layout.height);
|
|
97
|
+
this.contentLayer.appendChild(wrapper);
|
|
98
|
+
this.mounted.set(block.blockId, wrapper);
|
|
99
|
+
newlyMounted.add(block.blockId);
|
|
100
|
+
} else {
|
|
101
|
+
wrapper.setAttribute(SOURCE_START_ATTR, String(block.startLine));
|
|
102
|
+
wrapper.setAttribute(SOURCE_END_ATTR, String(block.endLine));
|
|
103
|
+
}
|
|
104
|
+
wrapper.style.top = `${layout.offsetY}px`;
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
mounted: shouldMount,
|
|
108
|
+
unmounted,
|
|
109
|
+
newlyMounted
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Update all mounted block positions from the layout map.
|
|
114
|
+
*/
|
|
115
|
+
updateAllPositions(layoutMap) {
|
|
116
|
+
if (this.contentLayer) {
|
|
117
|
+
this.contentLayer.style.height = `${layoutMap.totalHeight}px`;
|
|
118
|
+
}
|
|
119
|
+
for (const [blockId, wrapper] of this.mounted) {
|
|
120
|
+
const layout = layoutMap.getLayout(blockId);
|
|
121
|
+
if (layout) {
|
|
122
|
+
wrapper.style.top = `${layout.offsetY}px`;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Get the wrapper element for a mounted block.
|
|
128
|
+
*/
|
|
129
|
+
getWrapper(blockId) {
|
|
130
|
+
return this.mounted.get(blockId) ?? null;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Set HTML content for a block wrapper.
|
|
134
|
+
*/
|
|
135
|
+
setBlockHtml(blockId, html) {
|
|
136
|
+
const wrapper = this.mounted.get(blockId);
|
|
137
|
+
if (!wrapper) return;
|
|
138
|
+
this.cleanupDomMount(blockId);
|
|
139
|
+
wrapper.innerHTML = html;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Mount a DOM renderer into a block wrapper.
|
|
143
|
+
*/
|
|
144
|
+
mountDomContent(blockId, mountFn, unmountFn) {
|
|
145
|
+
const wrapper = this.mounted.get(blockId);
|
|
146
|
+
if (!wrapper) return;
|
|
147
|
+
this.cleanupDomMount(blockId);
|
|
148
|
+
wrapper.innerHTML = "";
|
|
149
|
+
mountFn(wrapper);
|
|
150
|
+
if (unmountFn) this.domUnmounts.set(blockId, unmountFn);
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Get currently mounted block IDs.
|
|
154
|
+
*/
|
|
155
|
+
getMountedBlockIds() {
|
|
156
|
+
return Array.from(this.mounted.keys());
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Destroy: cleanup all mounted blocks and DOM.
|
|
160
|
+
*/
|
|
161
|
+
destroy() {
|
|
162
|
+
for (const unmount of this.domUnmounts.values()) {
|
|
163
|
+
try {
|
|
164
|
+
unmount();
|
|
165
|
+
} catch {
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
this.domUnmounts.clear();
|
|
169
|
+
for (const wrapper of this.mounted.values()) {
|
|
170
|
+
wrapper.remove();
|
|
171
|
+
}
|
|
172
|
+
this.mounted.clear();
|
|
173
|
+
this.contentLayer?.remove();
|
|
174
|
+
this.contentLayer = null;
|
|
175
|
+
this.scrollContainer = null;
|
|
176
|
+
this.root = null;
|
|
177
|
+
}
|
|
178
|
+
cleanupDomMount(blockId) {
|
|
179
|
+
const unmount = this.domUnmounts.get(blockId);
|
|
180
|
+
if (unmount) {
|
|
181
|
+
try {
|
|
182
|
+
unmount();
|
|
183
|
+
} catch {
|
|
184
|
+
}
|
|
185
|
+
this.domUnmounts.delete(blockId);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
export {
|
|
191
|
+
VirtualViewportManager
|
|
192
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// src/virtual/height-estimator.ts
|
|
2
|
+
var DEFAULT_PARAGRAPH_HEIGHT = 40;
|
|
3
|
+
var FALLBACK_BLOCK_HEIGHT = 40;
|
|
4
|
+
var CODE_LINE_HEIGHT = 22;
|
|
5
|
+
var CODE_MIN_HEIGHT = 44;
|
|
6
|
+
var TEXT_LINE_HEIGHT = 24;
|
|
7
|
+
var THEMATIC_BREAK_HEIGHT = 24;
|
|
8
|
+
var HEAVY_SMALL_THRESHOLD = 3;
|
|
9
|
+
var HEAVY_MEDIUM_THRESHOLD = 10;
|
|
10
|
+
var HEAVY_LARGE_THRESHOLD = 50;
|
|
11
|
+
var HEAVY_SMALL_HEIGHT = 60;
|
|
12
|
+
var HEAVY_MEDIUM_HEIGHT = 200;
|
|
13
|
+
var HEAVY_LARGE_HEIGHT = 500;
|
|
14
|
+
var HEAVY_KINDS = /* @__PURE__ */ new Set(["mermaid", "katex", "math-block", "chart", "embed"]);
|
|
15
|
+
function estimateBlockHeight(block) {
|
|
16
|
+
const lineCount = block.endLine - block.startLine + 1;
|
|
17
|
+
if (HEAVY_KINDS.has(block.kind)) {
|
|
18
|
+
return estimateHeavyBlockHeight(lineCount);
|
|
19
|
+
}
|
|
20
|
+
if (block.kind === "code-fence") {
|
|
21
|
+
return Math.max(CODE_MIN_HEIGHT, lineCount * CODE_LINE_HEIGHT);
|
|
22
|
+
}
|
|
23
|
+
if (block.kind === "thematic-break") {
|
|
24
|
+
return THEMATIC_BREAK_HEIGHT;
|
|
25
|
+
}
|
|
26
|
+
if (lineCount <= 1) return DEFAULT_PARAGRAPH_HEIGHT;
|
|
27
|
+
return DEFAULT_PARAGRAPH_HEIGHT + (lineCount - 1) * TEXT_LINE_HEIGHT;
|
|
28
|
+
}
|
|
29
|
+
function estimateHeavyBlockHeight(lineCount) {
|
|
30
|
+
if (lineCount <= HEAVY_SMALL_THRESHOLD) return HEAVY_SMALL_HEIGHT;
|
|
31
|
+
if (lineCount <= HEAVY_MEDIUM_THRESHOLD) {
|
|
32
|
+
const t = (lineCount - HEAVY_SMALL_THRESHOLD) / (HEAVY_MEDIUM_THRESHOLD - HEAVY_SMALL_THRESHOLD);
|
|
33
|
+
return HEAVY_SMALL_HEIGHT + t * (HEAVY_MEDIUM_HEIGHT - HEAVY_SMALL_HEIGHT);
|
|
34
|
+
}
|
|
35
|
+
if (lineCount <= HEAVY_LARGE_THRESHOLD) {
|
|
36
|
+
const t = (lineCount - HEAVY_MEDIUM_THRESHOLD) / (HEAVY_LARGE_THRESHOLD - HEAVY_MEDIUM_THRESHOLD);
|
|
37
|
+
return HEAVY_MEDIUM_HEIGHT + t * (HEAVY_LARGE_HEIGHT - HEAVY_MEDIUM_HEIGHT);
|
|
38
|
+
}
|
|
39
|
+
return HEAVY_LARGE_HEIGHT;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export {
|
|
43
|
+
FALLBACK_BLOCK_HEIGHT,
|
|
44
|
+
estimateBlockHeight
|
|
45
|
+
};
|
package/dist/index.js
CHANGED
|
@@ -17,7 +17,20 @@ import {
|
|
|
17
17
|
rehypeSideAnnotation,
|
|
18
18
|
remarkConvertSoftBreaksToHardBreaks,
|
|
19
19
|
remarkSideAnnotation
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-4V3WQMGV.js";
|
|
21
|
+
import {
|
|
22
|
+
BlockLayoutMap
|
|
23
|
+
} from "./chunk-3CKPBCIP.js";
|
|
24
|
+
import {
|
|
25
|
+
HeightProjectionCache
|
|
26
|
+
} from "./chunk-6LWPFJCB.js";
|
|
27
|
+
import {
|
|
28
|
+
FALLBACK_BLOCK_HEIGHT,
|
|
29
|
+
estimateBlockHeight
|
|
30
|
+
} from "./chunk-Y72HQJQI.js";
|
|
31
|
+
import {
|
|
32
|
+
VirtualViewportManager
|
|
33
|
+
} from "./chunk-F3LG7AML.js";
|
|
21
34
|
|
|
22
35
|
// src/editor.ts
|
|
23
36
|
import { createDomAdapter } from "@owomark/core/internal/dom-adapter";
|
|
@@ -1175,208 +1188,6 @@ function createIncrementalEngine(options) {
|
|
|
1175
1188
|
}
|
|
1176
1189
|
}
|
|
1177
1190
|
|
|
1178
|
-
// src/virtual/block-layout-map.ts
|
|
1179
|
-
var BlockLayoutMap = class {
|
|
1180
|
-
layouts = [];
|
|
1181
|
-
indexMap = /* @__PURE__ */ new Map();
|
|
1182
|
-
_totalHeight = 0;
|
|
1183
|
-
get totalHeight() {
|
|
1184
|
-
return this._totalHeight;
|
|
1185
|
-
}
|
|
1186
|
-
get size() {
|
|
1187
|
-
return this.layouts.length;
|
|
1188
|
-
}
|
|
1189
|
-
/**
|
|
1190
|
-
* Rebuild the layout table from a new block list.
|
|
1191
|
-
* Blocks that already have measured heights retain them;
|
|
1192
|
-
* new blocks use the provided estimated height.
|
|
1193
|
-
*/
|
|
1194
|
-
rebuild(blockIds, getEstimatedHeight) {
|
|
1195
|
-
const oldHeights = /* @__PURE__ */ new Map();
|
|
1196
|
-
for (const layout of this.layouts) {
|
|
1197
|
-
oldHeights.set(layout.blockId, { height: layout.height, measured: layout.measured });
|
|
1198
|
-
}
|
|
1199
|
-
this.layouts = [];
|
|
1200
|
-
this.indexMap.clear();
|
|
1201
|
-
let offsetY = 0;
|
|
1202
|
-
for (let i = 0; i < blockIds.length; i++) {
|
|
1203
|
-
const blockId = blockIds[i];
|
|
1204
|
-
const existing = oldHeights.get(blockId);
|
|
1205
|
-
const height = existing ? existing.height : getEstimatedHeight(blockId);
|
|
1206
|
-
const measured = existing ? existing.measured : false;
|
|
1207
|
-
this.layouts.push({ blockId, offsetY, height, measured });
|
|
1208
|
-
this.indexMap.set(blockId, i);
|
|
1209
|
-
offsetY += height;
|
|
1210
|
-
}
|
|
1211
|
-
this._totalHeight = offsetY;
|
|
1212
|
-
}
|
|
1213
|
-
/**
|
|
1214
|
-
* Update a single block's height. Returns true if the height actually changed.
|
|
1215
|
-
* Recomputes offsetY for all subsequent blocks.
|
|
1216
|
-
*/
|
|
1217
|
-
updateHeight(blockId, height, measured) {
|
|
1218
|
-
const idx = this.indexMap.get(blockId);
|
|
1219
|
-
if (idx === void 0) return false;
|
|
1220
|
-
const layout = this.layouts[idx];
|
|
1221
|
-
if (layout.height === height && layout.measured === measured) return false;
|
|
1222
|
-
layout.height = height;
|
|
1223
|
-
layout.measured = measured;
|
|
1224
|
-
this.recomputeFrom(idx);
|
|
1225
|
-
return true;
|
|
1226
|
-
}
|
|
1227
|
-
/**
|
|
1228
|
-
* Batch-update multiple block heights with a single recompute pass.
|
|
1229
|
-
* Avoids O(k·n) when k blocks change in the same frame (e.g. ResizeObserver).
|
|
1230
|
-
*/
|
|
1231
|
-
updateHeightBatch(updates) {
|
|
1232
|
-
let minChangedIndex = Infinity;
|
|
1233
|
-
for (const { blockId, height, measured } of updates) {
|
|
1234
|
-
const idx = this.indexMap.get(blockId);
|
|
1235
|
-
if (idx === void 0) continue;
|
|
1236
|
-
const layout = this.layouts[idx];
|
|
1237
|
-
if (layout.height === height && layout.measured === measured) continue;
|
|
1238
|
-
layout.height = height;
|
|
1239
|
-
layout.measured = measured;
|
|
1240
|
-
if (idx < minChangedIndex) minChangedIndex = idx;
|
|
1241
|
-
}
|
|
1242
|
-
if (minChangedIndex === Infinity) return false;
|
|
1243
|
-
this.recomputeFrom(minChangedIndex);
|
|
1244
|
-
return true;
|
|
1245
|
-
}
|
|
1246
|
-
/**
|
|
1247
|
-
* Invalidate all measured heights (e.g., on container width change).
|
|
1248
|
-
* Heights revert to estimated values via the provided estimator.
|
|
1249
|
-
*/
|
|
1250
|
-
invalidateAll(getEstimatedHeight) {
|
|
1251
|
-
let offsetY = 0;
|
|
1252
|
-
for (const layout of this.layouts) {
|
|
1253
|
-
layout.height = getEstimatedHeight(layout.blockId);
|
|
1254
|
-
layout.measured = false;
|
|
1255
|
-
layout.offsetY = offsetY;
|
|
1256
|
-
offsetY += layout.height;
|
|
1257
|
-
}
|
|
1258
|
-
this._totalHeight = offsetY;
|
|
1259
|
-
}
|
|
1260
|
-
getLayout(blockId) {
|
|
1261
|
-
const idx = this.indexMap.get(blockId);
|
|
1262
|
-
return idx !== void 0 ? this.layouts[idx] : null;
|
|
1263
|
-
}
|
|
1264
|
-
getLayoutAt(index) {
|
|
1265
|
-
return this.layouts[index] ?? null;
|
|
1266
|
-
}
|
|
1267
|
-
/**
|
|
1268
|
-
* Find the range of block indices visible within [scrollTop, scrollTop + viewportHeight],
|
|
1269
|
-
* expanded by bufferPx on each side.
|
|
1270
|
-
*/
|
|
1271
|
-
getVisibleRange(scrollTop, viewportHeight, bufferPx) {
|
|
1272
|
-
const top = Math.max(0, scrollTop - bufferPx);
|
|
1273
|
-
const bottom = scrollTop + viewportHeight + bufferPx;
|
|
1274
|
-
if (this.layouts.length === 0) {
|
|
1275
|
-
return { startIndex: 0, endIndex: -1 };
|
|
1276
|
-
}
|
|
1277
|
-
const startIndex = this.findIndexAtOffset(top);
|
|
1278
|
-
const endIndex = this.findIndexAtOffset(bottom);
|
|
1279
|
-
return {
|
|
1280
|
-
startIndex: Math.max(0, startIndex),
|
|
1281
|
-
endIndex: Math.min(this.layouts.length - 1, endIndex)
|
|
1282
|
-
};
|
|
1283
|
-
}
|
|
1284
|
-
getAllLayouts() {
|
|
1285
|
-
return this.layouts;
|
|
1286
|
-
}
|
|
1287
|
-
// -------------------------------------------------------------------------
|
|
1288
|
-
// Private
|
|
1289
|
-
// -------------------------------------------------------------------------
|
|
1290
|
-
recomputeFrom(fromIndex) {
|
|
1291
|
-
let offsetY = fromIndex > 0 ? this.layouts[fromIndex - 1].offsetY + this.layouts[fromIndex - 1].height : 0;
|
|
1292
|
-
for (let i = fromIndex; i < this.layouts.length; i++) {
|
|
1293
|
-
this.layouts[i].offsetY = offsetY;
|
|
1294
|
-
offsetY += this.layouts[i].height;
|
|
1295
|
-
}
|
|
1296
|
-
this._totalHeight = offsetY;
|
|
1297
|
-
}
|
|
1298
|
-
/**
|
|
1299
|
-
* Binary search: find the index of the block that contains the given Y offset.
|
|
1300
|
-
*/
|
|
1301
|
-
findIndexAtOffset(y) {
|
|
1302
|
-
let lo = 0;
|
|
1303
|
-
let hi = this.layouts.length - 1;
|
|
1304
|
-
while (lo <= hi) {
|
|
1305
|
-
const mid = lo + hi >>> 1;
|
|
1306
|
-
const layout = this.layouts[mid];
|
|
1307
|
-
if (layout.offsetY + layout.height <= y) {
|
|
1308
|
-
lo = mid + 1;
|
|
1309
|
-
} else if (layout.offsetY > y) {
|
|
1310
|
-
hi = mid - 1;
|
|
1311
|
-
} else {
|
|
1312
|
-
return mid;
|
|
1313
|
-
}
|
|
1314
|
-
}
|
|
1315
|
-
return Math.min(lo, this.layouts.length - 1);
|
|
1316
|
-
}
|
|
1317
|
-
};
|
|
1318
|
-
|
|
1319
|
-
// src/virtual/height-cache.ts
|
|
1320
|
-
var HeightProjectionCache = class {
|
|
1321
|
-
cache = /* @__PURE__ */ new Map();
|
|
1322
|
-
get(renderKey) {
|
|
1323
|
-
return this.cache.get(renderKey);
|
|
1324
|
-
}
|
|
1325
|
-
set(renderKey, height) {
|
|
1326
|
-
this.cache.set(renderKey, height);
|
|
1327
|
-
}
|
|
1328
|
-
has(renderKey) {
|
|
1329
|
-
return this.cache.has(renderKey);
|
|
1330
|
-
}
|
|
1331
|
-
clear() {
|
|
1332
|
-
this.cache.clear();
|
|
1333
|
-
}
|
|
1334
|
-
get size() {
|
|
1335
|
-
return this.cache.size;
|
|
1336
|
-
}
|
|
1337
|
-
};
|
|
1338
|
-
|
|
1339
|
-
// src/virtual/height-estimator.ts
|
|
1340
|
-
var DEFAULT_PARAGRAPH_HEIGHT = 40;
|
|
1341
|
-
var FALLBACK_BLOCK_HEIGHT = 40;
|
|
1342
|
-
var CODE_LINE_HEIGHT = 22;
|
|
1343
|
-
var CODE_MIN_HEIGHT = 44;
|
|
1344
|
-
var TEXT_LINE_HEIGHT = 24;
|
|
1345
|
-
var THEMATIC_BREAK_HEIGHT = 24;
|
|
1346
|
-
var HEAVY_SMALL_THRESHOLD = 3;
|
|
1347
|
-
var HEAVY_MEDIUM_THRESHOLD = 10;
|
|
1348
|
-
var HEAVY_LARGE_THRESHOLD = 50;
|
|
1349
|
-
var HEAVY_SMALL_HEIGHT = 60;
|
|
1350
|
-
var HEAVY_MEDIUM_HEIGHT = 200;
|
|
1351
|
-
var HEAVY_LARGE_HEIGHT = 500;
|
|
1352
|
-
var HEAVY_KINDS = /* @__PURE__ */ new Set(["mermaid", "katex", "math-block", "chart", "embed"]);
|
|
1353
|
-
function estimateBlockHeight(block) {
|
|
1354
|
-
const lineCount = block.endLine - block.startLine + 1;
|
|
1355
|
-
if (HEAVY_KINDS.has(block.kind)) {
|
|
1356
|
-
return estimateHeavyBlockHeight(lineCount);
|
|
1357
|
-
}
|
|
1358
|
-
if (block.kind === "code-fence") {
|
|
1359
|
-
return Math.max(CODE_MIN_HEIGHT, lineCount * CODE_LINE_HEIGHT);
|
|
1360
|
-
}
|
|
1361
|
-
if (block.kind === "thematic-break") {
|
|
1362
|
-
return THEMATIC_BREAK_HEIGHT;
|
|
1363
|
-
}
|
|
1364
|
-
if (lineCount <= 1) return DEFAULT_PARAGRAPH_HEIGHT;
|
|
1365
|
-
return DEFAULT_PARAGRAPH_HEIGHT + (lineCount - 1) * TEXT_LINE_HEIGHT;
|
|
1366
|
-
}
|
|
1367
|
-
function estimateHeavyBlockHeight(lineCount) {
|
|
1368
|
-
if (lineCount <= HEAVY_SMALL_THRESHOLD) return HEAVY_SMALL_HEIGHT;
|
|
1369
|
-
if (lineCount <= HEAVY_MEDIUM_THRESHOLD) {
|
|
1370
|
-
const t = (lineCount - HEAVY_SMALL_THRESHOLD) / (HEAVY_MEDIUM_THRESHOLD - HEAVY_SMALL_THRESHOLD);
|
|
1371
|
-
return HEAVY_SMALL_HEIGHT + t * (HEAVY_MEDIUM_HEIGHT - HEAVY_SMALL_HEIGHT);
|
|
1372
|
-
}
|
|
1373
|
-
if (lineCount <= HEAVY_LARGE_THRESHOLD) {
|
|
1374
|
-
const t = (lineCount - HEAVY_MEDIUM_THRESHOLD) / (HEAVY_LARGE_THRESHOLD - HEAVY_MEDIUM_THRESHOLD);
|
|
1375
|
-
return HEAVY_MEDIUM_HEIGHT + t * (HEAVY_LARGE_HEIGHT - HEAVY_MEDIUM_HEIGHT);
|
|
1376
|
-
}
|
|
1377
|
-
return HEAVY_LARGE_HEIGHT;
|
|
1378
|
-
}
|
|
1379
|
-
|
|
1380
1191
|
// src/virtual/offscreen-measurer.ts
|
|
1381
1192
|
var OffscreenMeasurer = class {
|
|
1382
1193
|
container = null;
|
|
@@ -1434,195 +1245,6 @@ var OffscreenMeasurer = class {
|
|
|
1434
1245
|
}
|
|
1435
1246
|
};
|
|
1436
1247
|
|
|
1437
|
-
// src/virtual/viewport-manager.ts
|
|
1438
|
-
var BLOCK_ID_ATTR2 = "data-block-id";
|
|
1439
|
-
var SOURCE_START_ATTR2 = "data-source-line-start";
|
|
1440
|
-
var SOURCE_END_ATTR2 = "data-source-line-end";
|
|
1441
|
-
var HEIGHT_TRANSITION = "top 0.15s ease-out";
|
|
1442
|
-
var TOTAL_HEIGHT_TRANSITION = "height 0.15s ease-out";
|
|
1443
|
-
var SKELETON_GAP = 8;
|
|
1444
|
-
function createSkeletonHtml(height) {
|
|
1445
|
-
const innerHeight = Math.max(height - SKELETON_GAP, 8);
|
|
1446
|
-
return `<div class="owo-skeleton" style="height:${innerHeight}px;margin-bottom:${SKELETON_GAP}px;border-radius:4px;background:linear-gradient(90deg,#e2e2e2 25%,#efefef 50%,#e2e2e2 75%);background-size:200% 100%;animation:zm-skeleton-pulse 1.5s ease-in-out infinite" aria-hidden="true"></div>`;
|
|
1447
|
-
}
|
|
1448
|
-
function ensureSkeletonStyles(doc) {
|
|
1449
|
-
try {
|
|
1450
|
-
if (doc.getElementById?.("owo-skeleton-styles")) return;
|
|
1451
|
-
const style = doc.createElement("style");
|
|
1452
|
-
style.id = "owo-skeleton-styles";
|
|
1453
|
-
style.textContent = `@keyframes zm-skeleton-pulse{0%{background-position:200% 0}100%{background-position:-200% 0}}`;
|
|
1454
|
-
const target = doc.head ?? doc.body;
|
|
1455
|
-
target?.appendChild(style);
|
|
1456
|
-
} catch {
|
|
1457
|
-
}
|
|
1458
|
-
}
|
|
1459
|
-
var VirtualViewportManager = class {
|
|
1460
|
-
root = null;
|
|
1461
|
-
scrollContainer = null;
|
|
1462
|
-
contentLayer = null;
|
|
1463
|
-
/** Currently mounted block wrappers, keyed by blockId. */
|
|
1464
|
-
mounted = /* @__PURE__ */ new Map();
|
|
1465
|
-
/** Unmount callbacks for DOM-mounted renderers. */
|
|
1466
|
-
domUnmounts = /* @__PURE__ */ new Map();
|
|
1467
|
-
/** Buffer size as fraction of viewport height (each side). */
|
|
1468
|
-
bufferFraction = 0.5;
|
|
1469
|
-
/**
|
|
1470
|
-
* Mount the viewport manager onto a root element.
|
|
1471
|
-
* Creates an inner scroll container with a content layer for absolute positioning.
|
|
1472
|
-
*/
|
|
1473
|
-
mount(root) {
|
|
1474
|
-
this.root = root;
|
|
1475
|
-
this.scrollContainer = root;
|
|
1476
|
-
root.style.overflow = "auto";
|
|
1477
|
-
root.style.position = "relative";
|
|
1478
|
-
const doc = root.ownerDocument;
|
|
1479
|
-
ensureSkeletonStyles(doc);
|
|
1480
|
-
this.contentLayer = doc.createElement("div");
|
|
1481
|
-
this.contentLayer.style.position = "relative";
|
|
1482
|
-
this.contentLayer.style.width = "100%";
|
|
1483
|
-
this.contentLayer.style.transition = TOTAL_HEIGHT_TRANSITION;
|
|
1484
|
-
root.appendChild(this.contentLayer);
|
|
1485
|
-
}
|
|
1486
|
-
/**
|
|
1487
|
-
* Update which blocks are mounted based on scroll position and layout map.
|
|
1488
|
-
* Returns the set of blockIds that are newly mounted (need rendering).
|
|
1489
|
-
*/
|
|
1490
|
-
reconcile(blocks, layoutMap) {
|
|
1491
|
-
if (!this.scrollContainer || !this.contentLayer) {
|
|
1492
|
-
return { mounted: /* @__PURE__ */ new Set(), unmounted: /* @__PURE__ */ new Set(), newlyMounted: /* @__PURE__ */ new Set() };
|
|
1493
|
-
}
|
|
1494
|
-
const scrollTop = this.scrollContainer.scrollTop;
|
|
1495
|
-
const viewportHeight = this.scrollContainer.clientHeight;
|
|
1496
|
-
const bufferPx = viewportHeight * this.bufferFraction;
|
|
1497
|
-
this.contentLayer.style.height = `${layoutMap.totalHeight}px`;
|
|
1498
|
-
const { startIndex, endIndex } = layoutMap.getVisibleRange(
|
|
1499
|
-
scrollTop,
|
|
1500
|
-
viewportHeight,
|
|
1501
|
-
bufferPx
|
|
1502
|
-
);
|
|
1503
|
-
const shouldMount = /* @__PURE__ */ new Set();
|
|
1504
|
-
for (let i = startIndex; i <= endIndex && i < blocks.length; i++) {
|
|
1505
|
-
shouldMount.add(blocks[i].blockId);
|
|
1506
|
-
}
|
|
1507
|
-
const unmounted = /* @__PURE__ */ new Set();
|
|
1508
|
-
for (const [blockId, wrapper] of this.mounted) {
|
|
1509
|
-
if (!shouldMount.has(blockId)) {
|
|
1510
|
-
this.cleanupDomMount(blockId);
|
|
1511
|
-
wrapper.remove();
|
|
1512
|
-
this.mounted.delete(blockId);
|
|
1513
|
-
unmounted.add(blockId);
|
|
1514
|
-
}
|
|
1515
|
-
}
|
|
1516
|
-
const newlyMounted = /* @__PURE__ */ new Set();
|
|
1517
|
-
const doc = this.contentLayer.ownerDocument;
|
|
1518
|
-
for (let i = startIndex; i <= endIndex && i < blocks.length; i++) {
|
|
1519
|
-
const block = blocks[i];
|
|
1520
|
-
const layout = layoutMap.getLayoutAt(i);
|
|
1521
|
-
if (!layout) continue;
|
|
1522
|
-
let wrapper = this.mounted.get(block.blockId);
|
|
1523
|
-
if (!wrapper) {
|
|
1524
|
-
wrapper = doc.createElement("div");
|
|
1525
|
-
wrapper.setAttribute(BLOCK_ID_ATTR2, block.blockId);
|
|
1526
|
-
wrapper.setAttribute(SOURCE_START_ATTR2, String(block.startLine));
|
|
1527
|
-
wrapper.setAttribute(SOURCE_END_ATTR2, String(block.endLine));
|
|
1528
|
-
wrapper.style.position = "absolute";
|
|
1529
|
-
wrapper.style.left = "0";
|
|
1530
|
-
wrapper.style.width = "100%";
|
|
1531
|
-
wrapper.style.transition = HEIGHT_TRANSITION;
|
|
1532
|
-
wrapper.innerHTML = createSkeletonHtml(layout.height);
|
|
1533
|
-
this.contentLayer.appendChild(wrapper);
|
|
1534
|
-
this.mounted.set(block.blockId, wrapper);
|
|
1535
|
-
newlyMounted.add(block.blockId);
|
|
1536
|
-
} else {
|
|
1537
|
-
wrapper.setAttribute(SOURCE_START_ATTR2, String(block.startLine));
|
|
1538
|
-
wrapper.setAttribute(SOURCE_END_ATTR2, String(block.endLine));
|
|
1539
|
-
}
|
|
1540
|
-
wrapper.style.top = `${layout.offsetY}px`;
|
|
1541
|
-
}
|
|
1542
|
-
return {
|
|
1543
|
-
mounted: shouldMount,
|
|
1544
|
-
unmounted,
|
|
1545
|
-
newlyMounted
|
|
1546
|
-
};
|
|
1547
|
-
}
|
|
1548
|
-
/**
|
|
1549
|
-
* Update all mounted block positions from the layout map.
|
|
1550
|
-
*/
|
|
1551
|
-
updateAllPositions(layoutMap) {
|
|
1552
|
-
if (this.contentLayer) {
|
|
1553
|
-
this.contentLayer.style.height = `${layoutMap.totalHeight}px`;
|
|
1554
|
-
}
|
|
1555
|
-
for (const [blockId, wrapper] of this.mounted) {
|
|
1556
|
-
const layout = layoutMap.getLayout(blockId);
|
|
1557
|
-
if (layout) {
|
|
1558
|
-
wrapper.style.top = `${layout.offsetY}px`;
|
|
1559
|
-
}
|
|
1560
|
-
}
|
|
1561
|
-
}
|
|
1562
|
-
/**
|
|
1563
|
-
* Get the wrapper element for a mounted block.
|
|
1564
|
-
*/
|
|
1565
|
-
getWrapper(blockId) {
|
|
1566
|
-
return this.mounted.get(blockId) ?? null;
|
|
1567
|
-
}
|
|
1568
|
-
/**
|
|
1569
|
-
* Set HTML content for a block wrapper.
|
|
1570
|
-
*/
|
|
1571
|
-
setBlockHtml(blockId, html) {
|
|
1572
|
-
const wrapper = this.mounted.get(blockId);
|
|
1573
|
-
if (!wrapper) return;
|
|
1574
|
-
this.cleanupDomMount(blockId);
|
|
1575
|
-
wrapper.innerHTML = html;
|
|
1576
|
-
}
|
|
1577
|
-
/**
|
|
1578
|
-
* Mount a DOM renderer into a block wrapper.
|
|
1579
|
-
*/
|
|
1580
|
-
mountDomContent(blockId, mountFn, unmountFn) {
|
|
1581
|
-
const wrapper = this.mounted.get(blockId);
|
|
1582
|
-
if (!wrapper) return;
|
|
1583
|
-
this.cleanupDomMount(blockId);
|
|
1584
|
-
wrapper.innerHTML = "";
|
|
1585
|
-
mountFn(wrapper);
|
|
1586
|
-
if (unmountFn) this.domUnmounts.set(blockId, unmountFn);
|
|
1587
|
-
}
|
|
1588
|
-
/**
|
|
1589
|
-
* Get currently mounted block IDs.
|
|
1590
|
-
*/
|
|
1591
|
-
getMountedBlockIds() {
|
|
1592
|
-
return Array.from(this.mounted.keys());
|
|
1593
|
-
}
|
|
1594
|
-
/**
|
|
1595
|
-
* Destroy: cleanup all mounted blocks and DOM.
|
|
1596
|
-
*/
|
|
1597
|
-
destroy() {
|
|
1598
|
-
for (const unmount of this.domUnmounts.values()) {
|
|
1599
|
-
try {
|
|
1600
|
-
unmount();
|
|
1601
|
-
} catch {
|
|
1602
|
-
}
|
|
1603
|
-
}
|
|
1604
|
-
this.domUnmounts.clear();
|
|
1605
|
-
for (const wrapper of this.mounted.values()) {
|
|
1606
|
-
wrapper.remove();
|
|
1607
|
-
}
|
|
1608
|
-
this.mounted.clear();
|
|
1609
|
-
this.contentLayer?.remove();
|
|
1610
|
-
this.contentLayer = null;
|
|
1611
|
-
this.scrollContainer = null;
|
|
1612
|
-
this.root = null;
|
|
1613
|
-
}
|
|
1614
|
-
cleanupDomMount(blockId) {
|
|
1615
|
-
const unmount = this.domUnmounts.get(blockId);
|
|
1616
|
-
if (unmount) {
|
|
1617
|
-
try {
|
|
1618
|
-
unmount();
|
|
1619
|
-
} catch {
|
|
1620
|
-
}
|
|
1621
|
-
this.domUnmounts.delete(blockId);
|
|
1622
|
-
}
|
|
1623
|
-
}
|
|
1624
|
-
};
|
|
1625
|
-
|
|
1626
1248
|
// src/strategies/virtual.ts
|
|
1627
1249
|
function createVirtualEngine(options) {
|
|
1628
1250
|
const themeKey = options?.themeKey ?? "";
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Block Layout Map — coordinate table for virtual layout.
|
|
3
|
+
*
|
|
4
|
+
* Maintains blockId → { offsetY, height } for all blocks.
|
|
5
|
+
* When any block's height changes, all subsequent blocks' offsetY are recomputed.
|
|
6
|
+
*/
|
|
7
|
+
type BlockLayout = {
|
|
8
|
+
blockId: string;
|
|
9
|
+
offsetY: number;
|
|
10
|
+
height: number;
|
|
11
|
+
measured: boolean;
|
|
12
|
+
};
|
|
13
|
+
declare class BlockLayoutMap {
|
|
14
|
+
private layouts;
|
|
15
|
+
private indexMap;
|
|
16
|
+
private _totalHeight;
|
|
17
|
+
get totalHeight(): number;
|
|
18
|
+
get size(): number;
|
|
19
|
+
/**
|
|
20
|
+
* Rebuild the layout table from a new block list.
|
|
21
|
+
* Blocks that already have measured heights retain them;
|
|
22
|
+
* new blocks use the provided estimated height.
|
|
23
|
+
*/
|
|
24
|
+
rebuild(blockIds: string[], getEstimatedHeight: (blockId: string) => number): void;
|
|
25
|
+
/**
|
|
26
|
+
* Update a single block's height. Returns true if the height actually changed.
|
|
27
|
+
* Recomputes offsetY for all subsequent blocks.
|
|
28
|
+
*/
|
|
29
|
+
updateHeight(blockId: string, height: number, measured: boolean): boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Batch-update multiple block heights with a single recompute pass.
|
|
32
|
+
* Avoids O(k·n) when k blocks change in the same frame (e.g. ResizeObserver).
|
|
33
|
+
*/
|
|
34
|
+
updateHeightBatch(updates: Iterable<{
|
|
35
|
+
blockId: string;
|
|
36
|
+
height: number;
|
|
37
|
+
measured: boolean;
|
|
38
|
+
}>): boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Invalidate all measured heights (e.g., on container width change).
|
|
41
|
+
* Heights revert to estimated values via the provided estimator.
|
|
42
|
+
*/
|
|
43
|
+
invalidateAll(getEstimatedHeight: (blockId: string) => number): void;
|
|
44
|
+
getLayout(blockId: string): BlockLayout | null;
|
|
45
|
+
getLayoutAt(index: number): BlockLayout | null;
|
|
46
|
+
/**
|
|
47
|
+
* Find the range of block indices visible within [scrollTop, scrollTop + viewportHeight],
|
|
48
|
+
* expanded by bufferPx on each side.
|
|
49
|
+
*/
|
|
50
|
+
getVisibleRange(scrollTop: number, viewportHeight: number, bufferPx: number): {
|
|
51
|
+
startIndex: number;
|
|
52
|
+
endIndex: number;
|
|
53
|
+
};
|
|
54
|
+
getAllLayouts(): readonly BlockLayout[];
|
|
55
|
+
private recomputeFrom;
|
|
56
|
+
/**
|
|
57
|
+
* Binary search: find the index of the block that contains the given Y offset.
|
|
58
|
+
*/
|
|
59
|
+
private findIndexAtOffset;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export { BlockLayoutMap };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Height Projection Cache.
|
|
3
|
+
*
|
|
4
|
+
* Stores renderKey → measuredHeight so that blocks with the same
|
|
5
|
+
* renderKey (same content + theme + kind) can reuse a known height
|
|
6
|
+
* when they re-enter the viewport, eliminating layout shift.
|
|
7
|
+
*/
|
|
8
|
+
declare class HeightProjectionCache {
|
|
9
|
+
private cache;
|
|
10
|
+
get(renderKey: string): number | undefined;
|
|
11
|
+
set(renderKey: string, height: number): void;
|
|
12
|
+
has(renderKey: string): boolean;
|
|
13
|
+
clear(): void;
|
|
14
|
+
get size(): number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export { HeightProjectionCache };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { PreviewBlock } from '@owomark/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Heuristic height estimation for blocks before measurement.
|
|
5
|
+
*
|
|
6
|
+
* Uses block kind and source line count to produce a rough height estimate.
|
|
7
|
+
* These estimates are used as initial placeholder heights in the coordinate
|
|
8
|
+
* table until real measurement completes.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
declare function estimateBlockHeight(block: PreviewBlock): number;
|
|
12
|
+
|
|
13
|
+
export { estimateBlockHeight };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { PreviewBlock } from '@owomark/core';
|
|
2
|
+
import { BlockLayoutMap } from './block-layout-map.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Virtual Viewport Manager.
|
|
6
|
+
*
|
|
7
|
+
* Manages which blocks are mounted to the visible DOM based on scroll position.
|
|
8
|
+
* Uses absolute positioning for block wrappers and sets container total height
|
|
9
|
+
* to create a virtual scrollbar.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
declare class VirtualViewportManager {
|
|
13
|
+
private root;
|
|
14
|
+
private scrollContainer;
|
|
15
|
+
private contentLayer;
|
|
16
|
+
/** Currently mounted block wrappers, keyed by blockId. */
|
|
17
|
+
private mounted;
|
|
18
|
+
/** Unmount callbacks for DOM-mounted renderers. */
|
|
19
|
+
private domUnmounts;
|
|
20
|
+
/** Buffer size as fraction of viewport height (each side). */
|
|
21
|
+
private bufferFraction;
|
|
22
|
+
/**
|
|
23
|
+
* Mount the viewport manager onto a root element.
|
|
24
|
+
* Creates an inner scroll container with a content layer for absolute positioning.
|
|
25
|
+
*/
|
|
26
|
+
mount(root: HTMLElement): void;
|
|
27
|
+
/**
|
|
28
|
+
* Update which blocks are mounted based on scroll position and layout map.
|
|
29
|
+
* Returns the set of blockIds that are newly mounted (need rendering).
|
|
30
|
+
*/
|
|
31
|
+
reconcile(blocks: PreviewBlock[], layoutMap: BlockLayoutMap): {
|
|
32
|
+
mounted: Set<string>;
|
|
33
|
+
unmounted: Set<string>;
|
|
34
|
+
newlyMounted: Set<string>;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Update all mounted block positions from the layout map.
|
|
38
|
+
*/
|
|
39
|
+
updateAllPositions(layoutMap: BlockLayoutMap): void;
|
|
40
|
+
/**
|
|
41
|
+
* Get the wrapper element for a mounted block.
|
|
42
|
+
*/
|
|
43
|
+
getWrapper(blockId: string): HTMLElement | null;
|
|
44
|
+
/**
|
|
45
|
+
* Set HTML content for a block wrapper.
|
|
46
|
+
*/
|
|
47
|
+
setBlockHtml(blockId: string, html: string): void;
|
|
48
|
+
/**
|
|
49
|
+
* Mount a DOM renderer into a block wrapper.
|
|
50
|
+
*/
|
|
51
|
+
mountDomContent(blockId: string, mountFn: (container: HTMLElement) => void, unmountFn?: () => void): void;
|
|
52
|
+
/**
|
|
53
|
+
* Get currently mounted block IDs.
|
|
54
|
+
*/
|
|
55
|
+
getMountedBlockIds(): string[];
|
|
56
|
+
/**
|
|
57
|
+
* Destroy: cleanup all mounted blocks and DOM.
|
|
58
|
+
*/
|
|
59
|
+
destroy(): void;
|
|
60
|
+
private cleanupDomMount;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export { VirtualViewportManager };
|
package/dist/static.js
CHANGED
package/package.json
CHANGED
|
@@ -1,20 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@owomark/view",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Rendering engine, preview engine, DOM view layer, and official base theme for OwoMark.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"module": "./dist/index.js",
|
|
8
8
|
"types": "./dist/index.d.ts",
|
|
9
9
|
"license": "MIT",
|
|
10
|
-
"repository": {
|
|
11
|
-
"type": "git",
|
|
12
|
-
"url": "https://github.com/kzhoa/qblog.git",
|
|
13
|
-
"directory": "owomark/packages/owomark-view"
|
|
14
|
-
},
|
|
15
|
-
"bugs": {
|
|
16
|
-
"url": "https://github.com/kzhoa/qblog/issues"
|
|
17
|
-
},
|
|
18
10
|
"keywords": [
|
|
19
11
|
"markdown",
|
|
20
12
|
"preview",
|
|
@@ -33,14 +25,37 @@
|
|
|
33
25
|
"types": "./dist/static.d.ts",
|
|
34
26
|
"import": "./dist/static.js"
|
|
35
27
|
},
|
|
28
|
+
"./internal/virtual/block-layout-map": {
|
|
29
|
+
"types": "./dist/internal/virtual/block-layout-map.d.ts",
|
|
30
|
+
"import": "./dist/internal/virtual/block-layout-map.js"
|
|
31
|
+
},
|
|
32
|
+
"./internal/virtual/height-cache": {
|
|
33
|
+
"types": "./dist/internal/virtual/height-cache.d.ts",
|
|
34
|
+
"import": "./dist/internal/virtual/height-cache.js"
|
|
35
|
+
},
|
|
36
|
+
"./internal/virtual/height-estimator": {
|
|
37
|
+
"types": "./dist/internal/virtual/height-estimator.d.ts",
|
|
38
|
+
"import": "./dist/internal/virtual/height-estimator.js"
|
|
39
|
+
},
|
|
40
|
+
"./internal/virtual/viewport-manager": {
|
|
41
|
+
"types": "./dist/internal/virtual/viewport-manager.d.ts",
|
|
42
|
+
"import": "./dist/internal/virtual/viewport-manager.js"
|
|
43
|
+
},
|
|
36
44
|
"./style.css": "./src/style.css",
|
|
37
45
|
"./mdx-components.css": "./src/mdx-components/mdx-components.css",
|
|
38
46
|
"./owomark.css": "./src/theme/owomark.css",
|
|
47
|
+
"./preview.css": "./src/theme/preview.css",
|
|
39
48
|
"./light.css": "./src/theme/light.css",
|
|
40
49
|
"./dark.css": "./src/theme/dark.css",
|
|
50
|
+
"./slash-menu.css": "./src/theme/slash-menu.css",
|
|
41
51
|
"./side-annotation.css": "./src/theme/side-annotation.css"
|
|
42
52
|
},
|
|
43
|
-
"files": [
|
|
53
|
+
"files": [
|
|
54
|
+
"dist",
|
|
55
|
+
"src/style.css",
|
|
56
|
+
"src/mdx-components/mdx-components.css",
|
|
57
|
+
"src/theme"
|
|
58
|
+
],
|
|
44
59
|
"sideEffects": [
|
|
45
60
|
"./src/style.css",
|
|
46
61
|
"./src/mdx-components/mdx-components.css",
|
|
@@ -54,7 +69,7 @@
|
|
|
54
69
|
"access": "public"
|
|
55
70
|
},
|
|
56
71
|
"dependencies": {
|
|
57
|
-
"@owomark/core": "^0.1.
|
|
72
|
+
"@owomark/core": "^0.1.2",
|
|
58
73
|
"rehype-katex": "^6.0.3",
|
|
59
74
|
"rehype-pretty-code": "^0.14.1",
|
|
60
75
|
"rehype-slug": "^6.0.0",
|
|
@@ -74,9 +89,15 @@
|
|
|
74
89
|
"react-dom": "^18.0.0 || ^19.0.0"
|
|
75
90
|
},
|
|
76
91
|
"peerDependenciesMeta": {
|
|
77
|
-
"@mdx-js/mdx": {
|
|
78
|
-
|
|
79
|
-
|
|
92
|
+
"@mdx-js/mdx": {
|
|
93
|
+
"optional": true
|
|
94
|
+
},
|
|
95
|
+
"react": {
|
|
96
|
+
"optional": true
|
|
97
|
+
},
|
|
98
|
+
"react-dom": {
|
|
99
|
+
"optional": true
|
|
100
|
+
}
|
|
80
101
|
},
|
|
81
102
|
"devDependencies": {
|
|
82
103
|
"@types/react": "^19.0.0",
|
package/src/theme/owomark.css
CHANGED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/* OwoMark optional preview typography preset.
|
|
2
|
+
* Import this explicitly when the host wants the official reading baseline.
|
|
3
|
+
* All selectors are scoped to `.owomark-prose` and consume `--owo-*` /
|
|
4
|
+
* `--owo-preview-*` variables with fallbacks. */
|
|
5
|
+
|
|
6
|
+
.owomark-prose {
|
|
7
|
+
color: var(--owo-preview-text, var(--owo-text, #1f2937));
|
|
8
|
+
font-size: var(--owo-preview-font-size, 1rem);
|
|
9
|
+
line-height: var(--owo-preview-line-height, 1.75);
|
|
10
|
+
max-width: none;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.owomark-prose > :first-child {
|
|
14
|
+
margin-top: 0;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.owomark-prose > :last-child {
|
|
18
|
+
margin-bottom: 0;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.owomark-prose .anchor {
|
|
22
|
+
color: var(--owo-preview-anchor, var(--owo-text-muted, #64748b));
|
|
23
|
+
text-decoration: none;
|
|
24
|
+
opacity: 0;
|
|
25
|
+
transition: opacity 0.15s ease;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.owomark-prose :is(h1, h2, h3, h4, h5, h6):hover .anchor {
|
|
29
|
+
opacity: 1;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.owomark-prose h1 {
|
|
33
|
+
color: var(--owo-preview-heading-color, var(--owo-text, #111827));
|
|
34
|
+
font-size: var(--owo-preview-h1-size, 2.25rem);
|
|
35
|
+
margin-top: 0;
|
|
36
|
+
margin-bottom: var(--owo-preview-h1-margin-bottom, 2rem);
|
|
37
|
+
line-height: var(--owo-preview-h1-line-height, 1.3);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.owomark-prose h2 {
|
|
41
|
+
color: var(--owo-preview-heading-color, var(--owo-text, #111827));
|
|
42
|
+
font-size: var(--owo-preview-h2-size, 1.5rem);
|
|
43
|
+
margin-top: var(--owo-preview-h2-margin-top, 2.5rem);
|
|
44
|
+
margin-bottom: var(--owo-preview-h2-margin-bottom, 1.5rem);
|
|
45
|
+
padding-bottom: var(--owo-preview-h2-padding-bottom, 0.5rem);
|
|
46
|
+
border-bottom: 1px solid var(--owo-preview-divider, var(--owo-border, #e2e8f0));
|
|
47
|
+
line-height: var(--owo-preview-h2-line-height, 1.35);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.owomark-prose h3 {
|
|
51
|
+
color: var(--owo-preview-heading-color, var(--owo-text, #111827));
|
|
52
|
+
font-size: var(--owo-preview-h3-size, 1.25rem);
|
|
53
|
+
margin-top: var(--owo-preview-h3-margin-top, 2rem);
|
|
54
|
+
margin-bottom: var(--owo-preview-h3-margin-bottom, 1rem);
|
|
55
|
+
line-height: var(--owo-preview-h3-line-height, 1.4);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.owomark-prose p {
|
|
59
|
+
margin-bottom: var(--owo-preview-p-margin, 1rem);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.owomark-prose ul {
|
|
63
|
+
list-style-type: disc;
|
|
64
|
+
padding-left: var(--owo-preview-list-indent, 1.5rem);
|
|
65
|
+
margin-bottom: var(--owo-preview-list-margin, 1.5rem);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.owomark-prose ol {
|
|
69
|
+
list-style-type: decimal;
|
|
70
|
+
padding-left: var(--owo-preview-list-indent, 1.5rem);
|
|
71
|
+
margin-bottom: var(--owo-preview-list-margin, 1.5rem);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.owomark-prose blockquote {
|
|
75
|
+
border-left: var(--owo-preview-blockquote-border-width, 4px) solid
|
|
76
|
+
var(--owo-preview-blockquote-border, var(--owo-border, #e2e8f0));
|
|
77
|
+
padding-left: var(--owo-preview-blockquote-padding-left, 1rem);
|
|
78
|
+
margin-left: 0;
|
|
79
|
+
margin-bottom: var(--owo-preview-blockquote-gap, 1.5rem);
|
|
80
|
+
color: var(--owo-preview-blockquote-text, var(--owo-text-muted, #64748b));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.owomark-prose code {
|
|
84
|
+
font-family: var(--owo-preview-font-mono, var(--owo-font-mono, system-ui, monospace));
|
|
85
|
+
background-color: var(--owo-preview-inline-code-bg, rgba(148, 163, 184, 0.16));
|
|
86
|
+
padding: var(--owo-preview-inline-code-padding, 0.08em 0.28em);
|
|
87
|
+
border-radius: var(--owo-preview-inline-code-radius, 3px);
|
|
88
|
+
border: 1px solid var(--owo-preview-inline-code-border, rgba(226, 232, 240, 0.9));
|
|
89
|
+
color: var(--owo-preview-inline-code-text, var(--owo-preview-text, var(--owo-text, #1f2937)));
|
|
90
|
+
font-size: var(--owo-preview-inline-code-size, 0.9em);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.owomark-prose code.language-math.math-inline {
|
|
94
|
+
font-family: inherit;
|
|
95
|
+
background-color: transparent;
|
|
96
|
+
padding: 0;
|
|
97
|
+
border: none;
|
|
98
|
+
border-radius: 0;
|
|
99
|
+
color: inherit;
|
|
100
|
+
font-size: 1em;
|
|
101
|
+
vertical-align: baseline;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.owomark-prose pre {
|
|
105
|
+
background-color: var(--owo-preview-code-block-bg, var(--owo-surface-raised, #f8fafc));
|
|
106
|
+
padding: var(--owo-preview-code-block-padding, 1rem);
|
|
107
|
+
border: 1px solid var(--owo-preview-code-block-border, var(--owo-border, #e2e8f0));
|
|
108
|
+
border-radius: var(--owo-preview-code-block-radius, 8px);
|
|
109
|
+
box-shadow: var(--owo-preview-code-block-shadow, none);
|
|
110
|
+
overflow-x: auto;
|
|
111
|
+
margin-top: var(--owo-preview-code-block-margin-top, 0.5rem);
|
|
112
|
+
margin-bottom: var(--owo-preview-code-block-margin-bottom, 0.5rem);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.owomark-prose pre code {
|
|
116
|
+
background-color: transparent;
|
|
117
|
+
padding: 0;
|
|
118
|
+
border: none;
|
|
119
|
+
color: inherit;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.owomark-prose pre.shiki {
|
|
123
|
+
background-color: var(--owo-preview-code-block-bg, var(--owo-surface-raised, #f8fafc));
|
|
124
|
+
padding: var(--owo-preview-code-block-padding, 1rem);
|
|
125
|
+
border: 1px solid var(--owo-preview-code-block-border, var(--owo-border, #e2e8f0));
|
|
126
|
+
border-radius: var(--owo-preview-code-block-radius, 8px);
|
|
127
|
+
box-shadow: var(--owo-preview-code-block-shadow, none);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.owomark-prose pre.shiki code {
|
|
131
|
+
display: grid;
|
|
132
|
+
gap: 0;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.owomark-prose pre.shiki span[data-line] {
|
|
136
|
+
min-height: var(--owo-preview-code-line-min-height, 1.5rem);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.owomark-prose img {
|
|
140
|
+
max-width: 100%;
|
|
141
|
+
height: auto;
|
|
142
|
+
border-radius: var(--owo-preview-image-radius, 4px);
|
|
143
|
+
margin: var(--owo-preview-image-margin, 2rem 0);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.owomark-prose table {
|
|
147
|
+
width: 100%;
|
|
148
|
+
border-collapse: collapse;
|
|
149
|
+
margin-bottom: var(--owo-preview-table-margin, 1.5rem);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.owomark-prose th,
|
|
153
|
+
.owomark-prose td {
|
|
154
|
+
border: 1px solid var(--owo-preview-table-border, var(--owo-border, #e2e8f0));
|
|
155
|
+
padding: var(--owo-preview-table-cell-padding, 0.625rem 0.75rem);
|
|
156
|
+
text-align: left;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.owomark-prose th {
|
|
160
|
+
background-color: var(--owo-preview-table-header-bg, var(--owo-surface-raised, #f8fafc));
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.owomark-prose .katex {
|
|
164
|
+
font-size: 1em;
|
|
165
|
+
line-height: 1.2;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.owomark-prose .katex-html {
|
|
169
|
+
display: inline-block;
|
|
170
|
+
max-width: 100%;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.owomark-prose .katex-display {
|
|
174
|
+
display: block;
|
|
175
|
+
margin: var(--owo-preview-katex-display-margin, 2rem 0);
|
|
176
|
+
padding: var(--owo-preview-katex-display-padding, 0.35rem 0.25rem 0.6rem);
|
|
177
|
+
text-align: center;
|
|
178
|
+
overflow-x: auto;
|
|
179
|
+
overflow-y: hidden;
|
|
180
|
+
-webkit-overflow-scrolling: touch;
|
|
181
|
+
scrollbar-width: thin;
|
|
182
|
+
scrollbar-color: var(--owo-preview-divider, var(--owo-border, #e2e8f0)) transparent;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
.owomark-prose .katex-display::-webkit-scrollbar {
|
|
186
|
+
height: 6px;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.owomark-prose .katex-display::-webkit-scrollbar-thumb {
|
|
190
|
+
background-color: var(--owo-preview-divider, var(--owo-border, #e2e8f0));
|
|
191
|
+
border-radius: 9999px;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
.owomark-prose .katex-display::-webkit-scrollbar-track {
|
|
195
|
+
background: transparent;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.owomark-prose .katex-display > .katex {
|
|
199
|
+
display: inline-block;
|
|
200
|
+
min-width: max-content;
|
|
201
|
+
white-space: nowrap;
|
|
202
|
+
text-align: center;
|
|
203
|
+
font-size: var(--owo-preview-katex-display-size, 1.22em);
|
|
204
|
+
line-height: 1.35;
|
|
205
|
+
padding: 0 0.4rem;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
.owomark-prose .katex-display > .katex .base {
|
|
209
|
+
margin: 0.1em 0;
|
|
210
|
+
}
|