@seamlet/astro 0.11.3 → 0.12.1
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 +19 -7
- package/components/RichLine.astro +2 -5
- package/components/RichText.astro +2 -4
- package/dist/bridge.d.ts +2 -3
- package/dist/bridge.js +557 -393
- package/dist/chunk-QCVWHHJC.js +14 -0
- package/dist/{chunk-P2OQOZBC.js → chunk-QL4K4HR6.js} +8 -0
- package/dist/{chunk-2E7VDSUK.js → chunk-SSECNMAD.js} +34 -43
- package/dist/{chunk-3BS5CE26.js → chunk-W2LIOR2C.js} +6 -2
- package/dist/{chunk-DRDPKFHZ.js → chunk-ZS32CW5F.js} +396 -207
- package/dist/cli.js +96 -37
- package/dist/client.js +4 -1
- package/dist/index.js +52 -19
- package/dist/{messages-Dx0jNy3N.d.ts → messages-CvZhLRSE.d.ts} +29 -52
- package/dist/{content-6k-E7NWf.d.ts → parse-content-Eg8yRq_t.d.ts} +69 -1
- package/dist/preview-requests.d.ts +6 -1
- package/dist/preview-requests.js +3 -1
- package/dist/protocol.d.ts +31 -5
- package/dist/protocol.js +4 -3
- package/dist/routes/media.js +26 -5
- package/dist/routes/own-photos.js +2 -1
- package/dist/schema.d.ts +13 -8
- package/dist/schema.js +13 -1
- package/dist/site-description-Cg5KNhhG.d.ts +112 -0
- package/dist/site.d.ts +2 -2
- package/dist/site.js +83 -9
- package/package.json +1 -1
- package/routes/preview.astro +8 -11
- package/dist/parse-content-C9ldnmvj.d.ts +0 -69
- package/dist/site-description-4Jx6Nq6X.d.ts +0 -36
package/dist/bridge.js
CHANGED
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
shortcutOf,
|
|
19
19
|
toggleList,
|
|
20
20
|
toggleMark
|
|
21
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-SSECNMAD.js";
|
|
22
22
|
import {
|
|
23
23
|
FIELD_PATH_ATTRIBUTE,
|
|
24
24
|
REVEALED_ATTRIBUTE,
|
|
@@ -26,8 +26,9 @@ import {
|
|
|
26
26
|
REVEAL_EVENT,
|
|
27
27
|
asOneParagraph,
|
|
28
28
|
readRichText,
|
|
29
|
-
resolveFieldPath
|
|
30
|
-
|
|
29
|
+
resolveFieldPath,
|
|
30
|
+
safeHref
|
|
31
|
+
} from "./chunk-ZS32CW5F.js";
|
|
31
32
|
|
|
32
33
|
// src/bridge/typed-fields.ts
|
|
33
34
|
function typedKindAt(schema, path) {
|
|
@@ -55,6 +56,51 @@ function typedKindAt(schema, path) {
|
|
|
55
56
|
}
|
|
56
57
|
}
|
|
57
58
|
|
|
59
|
+
// src/bridge/event-listening.ts
|
|
60
|
+
function onElement(element, type, listener) {
|
|
61
|
+
return {
|
|
62
|
+
target: element,
|
|
63
|
+
type,
|
|
64
|
+
listener,
|
|
65
|
+
isCapturing: false
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function onDocument(document2, type, listener) {
|
|
69
|
+
return {
|
|
70
|
+
target: document2,
|
|
71
|
+
type,
|
|
72
|
+
listener,
|
|
73
|
+
isCapturing: true
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
function onWindow(view, type, listener) {
|
|
77
|
+
if (view === null) return [];
|
|
78
|
+
return [
|
|
79
|
+
{
|
|
80
|
+
target: view,
|
|
81
|
+
type,
|
|
82
|
+
listener,
|
|
83
|
+
isCapturing: false
|
|
84
|
+
}
|
|
85
|
+
];
|
|
86
|
+
}
|
|
87
|
+
function listenToAll(listenings) {
|
|
88
|
+
for (const { target, type, listener, isCapturing } of listenings) {
|
|
89
|
+
target.addEventListener(type, listener, isCapturing);
|
|
90
|
+
}
|
|
91
|
+
return () => {
|
|
92
|
+
for (const { target, type, listener, isCapturing } of listenings) {
|
|
93
|
+
target.removeEventListener(type, listener, isCapturing);
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// src/bridge/dom-nodes.ts
|
|
99
|
+
function asElement(value) {
|
|
100
|
+
const isElement = typeof value === "object" && value !== null && "nodeType" in value && value.nodeType === Node.ELEMENT_NODE;
|
|
101
|
+
return isElement ? value : void 0;
|
|
102
|
+
}
|
|
103
|
+
|
|
58
104
|
// src/bridge/page-markers.ts
|
|
59
105
|
var MARKER_SELECTOR = `[${FIELD_PATH_ATTRIBUTE}]`;
|
|
60
106
|
var HTML_NAMESPACE = "http://www.w3.org/1999/xhtml";
|
|
@@ -100,15 +146,13 @@ function findClosestMarkedElement(document2, path) {
|
|
|
100
146
|
function isHtmlElement(value) {
|
|
101
147
|
return asElement(value)?.namespaceURI === HTML_NAMESPACE;
|
|
102
148
|
}
|
|
103
|
-
function asElement(value) {
|
|
104
|
-
const isElement2 = typeof value === "object" && value !== null && "nodeType" in value && value.nodeType === Node.ELEMENT_NODE;
|
|
105
|
-
return isElement2 ? value : void 0;
|
|
106
|
-
}
|
|
107
149
|
|
|
108
150
|
// src/bridge/page-reveals.ts
|
|
109
151
|
var DETAILS_SELECTOR = "details";
|
|
110
152
|
var REVEAL_SELECTOR = `[${REVEAL_ATTRIBUTE}]`;
|
|
111
153
|
var SWITCH_SELECTOR = 'input[type="radio"], input[type="checkbox"]';
|
|
154
|
+
var MARKER_SELECTOR2 = `[${FIELD_PATH_ATTRIBUTE}]`;
|
|
155
|
+
var KEY_SEPARATOR = "\n";
|
|
112
156
|
function revealAround(element) {
|
|
113
157
|
for (let part = element; part !== null; ) {
|
|
114
158
|
if (isDetails(part)) part.open = true;
|
|
@@ -116,70 +160,77 @@ function revealAround(element) {
|
|
|
116
160
|
part = part.parentElement;
|
|
117
161
|
}
|
|
118
162
|
}
|
|
119
|
-
function
|
|
163
|
+
function revealPartAt(target) {
|
|
120
164
|
const details = detailsOfSummaryAround(target);
|
|
121
|
-
if (details
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
}
|
|
126
|
-
if (!isOnField) details.open = false;
|
|
165
|
+
if (details !== void 0) details.open = true;
|
|
166
|
+
const control = switchAround(target);
|
|
167
|
+
const isUncheckedRadio = control?.type === "radio" && !control.checked;
|
|
168
|
+
if (control !== void 0 && isUncheckedRadio) checkSwitch(control, true);
|
|
127
169
|
}
|
|
128
|
-
function
|
|
170
|
+
function togglePartAt(target) {
|
|
171
|
+
const details = detailsOfSummaryAround(target);
|
|
172
|
+
if (details !== void 0) details.open = !details.open;
|
|
129
173
|
const control = switchAround(target);
|
|
130
174
|
if (control === void 0) return;
|
|
131
175
|
const isRadio = control.type === "radio";
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
control.checked = isRadio || !control.checked;
|
|
135
|
-
announceSwitch(control);
|
|
176
|
+
if (isRadio && control.checked) return;
|
|
177
|
+
checkSwitch(control, isRadio || !control.checked);
|
|
136
178
|
}
|
|
137
179
|
function revealedPartsOf(document2) {
|
|
138
180
|
const details = [...document2.querySelectorAll(DETAILS_SELECTOR)];
|
|
139
181
|
const parts = [...document2.querySelectorAll(REVEAL_SELECTOR)];
|
|
140
182
|
const switches = [...document2.querySelectorAll(SWITCH_SELECTOR)];
|
|
141
183
|
return {
|
|
142
|
-
openDetails:
|
|
143
|
-
revealed:
|
|
144
|
-
|
|
145
|
-
(part) => part.hasAttribute(REVEALED_ATTRIBUTE)
|
|
146
|
-
),
|
|
147
|
-
flippedSwitches: indexesWhere(
|
|
184
|
+
openDetails: keysWhere(details, (part) => isDetails(part) && part.open),
|
|
185
|
+
revealed: keysWhere(parts, (part) => part.hasAttribute(REVEALED_ATTRIBUTE)),
|
|
186
|
+
flippedSwitches: keysWhere(
|
|
148
187
|
switches,
|
|
149
188
|
(control) => isSwitch(control) && control.checked !== control.defaultChecked
|
|
150
189
|
)
|
|
151
190
|
};
|
|
152
191
|
}
|
|
153
192
|
function restoreRevealedParts(document2, revealed) {
|
|
154
|
-
const details =
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
193
|
+
const details = partsWithKeys(
|
|
194
|
+
document2,
|
|
195
|
+
DETAILS_SELECTOR,
|
|
196
|
+
revealed.openDetails
|
|
197
|
+
);
|
|
198
|
+
const parts = partsWithKeys(document2, REVEAL_SELECTOR, revealed.revealed);
|
|
199
|
+
for (const part of details) {
|
|
200
|
+
if (isDetails(part)) part.open = true;
|
|
159
201
|
}
|
|
160
|
-
for (const
|
|
161
|
-
|
|
162
|
-
|
|
202
|
+
for (const part of parts) reveal(part);
|
|
203
|
+
const switches = partsWithKeys(
|
|
204
|
+
document2,
|
|
205
|
+
SWITCH_SELECTOR,
|
|
206
|
+
revealed.flippedSwitches
|
|
207
|
+
);
|
|
208
|
+
for (const control of switches) {
|
|
209
|
+
if (isSwitch(control)) flipFromDefault(control);
|
|
163
210
|
}
|
|
164
|
-
restoreSwitches(document2, revealed.flippedSwitches);
|
|
165
211
|
}
|
|
166
|
-
function
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
212
|
+
function flipFromDefault(control) {
|
|
213
|
+
checkSwitch(control, !control.defaultChecked);
|
|
214
|
+
}
|
|
215
|
+
function checkSwitch(control, isChecked) {
|
|
216
|
+
control.checked = isChecked;
|
|
217
|
+
announceSwitch(control);
|
|
218
|
+
}
|
|
219
|
+
function partsWithKeys(document2, selector, keys) {
|
|
220
|
+
const wanted = new Set(keys);
|
|
221
|
+
const parts = [...document2.querySelectorAll(selector)];
|
|
222
|
+
const partKeys = partKeysOf(parts);
|
|
223
|
+
return parts.filter((_, index) => wanted.has(partKeys[index] ?? ""));
|
|
174
224
|
}
|
|
175
225
|
function announceSwitch(control) {
|
|
176
226
|
control.dispatchEvent(new Event("input", { bubbles: true }));
|
|
177
227
|
control.dispatchEvent(new Event("change", { bubbles: true }));
|
|
178
228
|
}
|
|
179
229
|
function switchAround(target) {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
const
|
|
230
|
+
const element = asElement(target);
|
|
231
|
+
if (element === void 0) return void 0;
|
|
232
|
+
const label = element.closest("label");
|
|
233
|
+
const control = isLabel(label) ? label.control : element;
|
|
183
234
|
return control !== null && isSwitch(control) ? control : void 0;
|
|
184
235
|
}
|
|
185
236
|
function reveal(part) {
|
|
@@ -188,16 +239,29 @@ function reveal(part) {
|
|
|
188
239
|
part.dispatchEvent(new CustomEvent(REVEAL_EVENT, { bubbles: true }));
|
|
189
240
|
}
|
|
190
241
|
function detailsOfSummaryAround(target) {
|
|
191
|
-
const summary =
|
|
242
|
+
const summary = asElement(target)?.closest("summary");
|
|
192
243
|
const details = summary?.parentElement;
|
|
193
244
|
const isSummaryOfDetails = details !== null && details !== void 0 && isDetails(details);
|
|
194
245
|
return isSummaryOfDetails ? details : void 0;
|
|
195
246
|
}
|
|
196
|
-
function
|
|
247
|
+
function keysWhere(elements, isPicked) {
|
|
248
|
+
const keys = partKeysOf(elements);
|
|
197
249
|
return elements.flatMap(
|
|
198
|
-
(element, index) => isPicked(element) ? [index] : []
|
|
250
|
+
(element, index) => isPicked(element) ? [keys[index] ?? ""] : []
|
|
199
251
|
);
|
|
200
252
|
}
|
|
253
|
+
function partKeysOf(parts) {
|
|
254
|
+
const names = parts.map(partNameOf);
|
|
255
|
+
return names.map((name, index) => {
|
|
256
|
+
const place = names.slice(0, index).filter((each) => each === name).length;
|
|
257
|
+
return `${name}${KEY_SEPARATOR}${String(place)}`;
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
function partNameOf(part) {
|
|
261
|
+
if (part.id !== "") return `#${part.id}`;
|
|
262
|
+
const marked = part.closest(MARKER_SELECTOR2) ?? part.querySelector(MARKER_SELECTOR2);
|
|
263
|
+
return marked?.getAttribute(FIELD_PATH_ATTRIBUTE) ?? "";
|
|
264
|
+
}
|
|
201
265
|
function isDetails(element) {
|
|
202
266
|
return element.localName === "details";
|
|
203
267
|
}
|
|
@@ -207,43 +271,70 @@ function isLabel(element) {
|
|
|
207
271
|
function isSwitch(element) {
|
|
208
272
|
return element.matches(SWITCH_SELECTOR);
|
|
209
273
|
}
|
|
210
|
-
function isElement(value) {
|
|
211
|
-
return value !== null && "nodeType" in value && value.nodeType === Node.ELEMENT_NODE;
|
|
212
|
-
}
|
|
213
274
|
|
|
214
275
|
// src/bridge/page-events.ts
|
|
215
276
|
var DRAG_GONE_AFTER_MS = 150;
|
|
216
277
|
function listenToPage(document2, listeners) {
|
|
217
|
-
const
|
|
218
|
-
const
|
|
219
|
-
const link = linkToFollow(event.target);
|
|
220
|
-
listeners.onHover(
|
|
221
|
-
link === void 0 ? markedElementAround(event.target)?.path : void 0
|
|
222
|
-
);
|
|
223
|
-
listeners.onLinkHover(link);
|
|
224
|
-
};
|
|
278
|
+
const fileDrags = followFileDrags(listeners);
|
|
279
|
+
const { onLayoutChange } = listeners;
|
|
225
280
|
const leave = () => {
|
|
226
281
|
listeners.onHover(void 0);
|
|
227
282
|
listeners.onLinkHover(void 0);
|
|
228
283
|
};
|
|
229
|
-
const
|
|
230
|
-
event
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
284
|
+
const stopListening = listenToAll([
|
|
285
|
+
onDocument(document2, "mouseover", (event) => {
|
|
286
|
+
hover(event, listeners);
|
|
287
|
+
}),
|
|
288
|
+
onDocument(document2, "click", (event) => {
|
|
289
|
+
click(event, listeners);
|
|
290
|
+
}),
|
|
291
|
+
onDocument(document2, "auxclick", preventDefault),
|
|
292
|
+
onDocument(document2, "submit", preventDefault),
|
|
293
|
+
onDocument(document2, "dragstart", preventDefault),
|
|
294
|
+
onDocument(document2, "keydown", listeners.onKeyDown),
|
|
295
|
+
...fileDrags.listenings(document2),
|
|
296
|
+
onDocument(document2, "scroll", onLayoutChange),
|
|
297
|
+
// An opened or closed `<details>` moves what is below it; toggle does not bubble.
|
|
298
|
+
onDocument(document2, "toggle", onLayoutChange),
|
|
299
|
+
// A flipped switch shows and hides parts of the page.
|
|
300
|
+
onDocument(document2, "change", onLayoutChange),
|
|
301
|
+
// Images load after the page is shown and move what is below them; load does not bubble.
|
|
302
|
+
onDocument(document2, "load", onLayoutChange),
|
|
303
|
+
onElement(document2.documentElement, "mouseleave", leave),
|
|
304
|
+
...onWindow(document2.defaultView, "resize", onLayoutChange)
|
|
305
|
+
]);
|
|
306
|
+
return () => {
|
|
307
|
+
fileDrags.stop();
|
|
308
|
+
stopListening();
|
|
246
309
|
};
|
|
310
|
+
}
|
|
311
|
+
function hover(event, listeners) {
|
|
312
|
+
const link = linkToFollow(event.target);
|
|
313
|
+
const path = link === void 0 ? markedElementAround(event.target)?.path : void 0;
|
|
314
|
+
listeners.onHover(path);
|
|
315
|
+
listeners.onLinkHover(link);
|
|
316
|
+
}
|
|
317
|
+
function click(event, listeners) {
|
|
318
|
+
event.preventDefault();
|
|
319
|
+
const link = linkToFollow(event.target);
|
|
320
|
+
if (link !== void 0) {
|
|
321
|
+
listeners.onLinkClick(link);
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
const marked = markedElementAround(event.target);
|
|
325
|
+
if (marked === void 0) {
|
|
326
|
+
togglePartAt(event.target);
|
|
327
|
+
listeners.onClick(void 0);
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
revealPartAt(event.target);
|
|
331
|
+
const point = { x: event.clientX, y: event.clientY };
|
|
332
|
+
listeners.onClick({ ...marked, point });
|
|
333
|
+
}
|
|
334
|
+
function preventDefault(event) {
|
|
335
|
+
event.preventDefault();
|
|
336
|
+
}
|
|
337
|
+
function followFileDrags(listeners) {
|
|
247
338
|
let dragGoneTimer;
|
|
248
339
|
const dragFile = (event) => {
|
|
249
340
|
if (!carriesFiles(event)) return;
|
|
@@ -270,36 +361,15 @@ function listenToPage(document2, listeners) {
|
|
|
270
361
|
clearTimeout(dragGoneTimer);
|
|
271
362
|
dragGoneTimer = setTimeout(listeners.onFileDragEnd, DRAG_GONE_AFTER_MS);
|
|
272
363
|
};
|
|
273
|
-
|
|
274
|
-
[
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
["drop", dropFile],
|
|
282
|
-
["dragleave", leaveWithFile],
|
|
283
|
-
["scroll", listeners.onLayoutChange],
|
|
284
|
-
// An opened or closed `<details>` moves what is below it; toggle does not bubble.
|
|
285
|
-
["toggle", listeners.onLayoutChange],
|
|
286
|
-
// A flipped switch shows and hides parts of the page.
|
|
287
|
-
["change", listeners.onLayoutChange],
|
|
288
|
-
// Images load after the page is shown and move what is below them; load does not bubble.
|
|
289
|
-
["load", listeners.onLayoutChange]
|
|
290
|
-
];
|
|
291
|
-
for (const [type, listener] of documentListeners) {
|
|
292
|
-
document2.addEventListener(type, listener, true);
|
|
293
|
-
}
|
|
294
|
-
document2.documentElement.addEventListener("mouseleave", leave);
|
|
295
|
-
view?.addEventListener("resize", listeners.onLayoutChange);
|
|
296
|
-
return () => {
|
|
297
|
-
clearTimeout(dragGoneTimer);
|
|
298
|
-
for (const [type, listener] of documentListeners) {
|
|
299
|
-
document2.removeEventListener(type, listener, true);
|
|
364
|
+
return {
|
|
365
|
+
listenings: (document2) => [
|
|
366
|
+
onDocument(document2, "dragover", dragFile),
|
|
367
|
+
onDocument(document2, "drop", dropFile),
|
|
368
|
+
onDocument(document2, "dragleave", leaveWithFile)
|
|
369
|
+
],
|
|
370
|
+
stop: () => {
|
|
371
|
+
clearTimeout(dragGoneTimer);
|
|
300
372
|
}
|
|
301
|
-
document2.documentElement.removeEventListener("mouseleave", leave);
|
|
302
|
-
view?.removeEventListener("resize", listeners.onLayoutChange);
|
|
303
373
|
};
|
|
304
374
|
}
|
|
305
375
|
function linkToFollow(target) {
|
|
@@ -337,13 +407,16 @@ function measureLayout(document2, link) {
|
|
|
337
407
|
}
|
|
338
408
|
);
|
|
339
409
|
const root = document2.documentElement;
|
|
340
|
-
const linkBox = link === void 0 ? void 0 : boxOf(link.element);
|
|
341
410
|
return {
|
|
342
411
|
markers,
|
|
343
412
|
viewport: { width: root.clientWidth, height: root.clientHeight },
|
|
344
|
-
link: link === void 0
|
|
413
|
+
link: link === void 0 ? void 0 : linkBoxOf(link)
|
|
345
414
|
};
|
|
346
415
|
}
|
|
416
|
+
function linkBoxOf(link) {
|
|
417
|
+
const box = boxOf(link.element);
|
|
418
|
+
return box === void 0 ? void 0 : { href: link.href, box };
|
|
419
|
+
}
|
|
347
420
|
function createLayoutReports(document2, report) {
|
|
348
421
|
const view = document2.defaultView;
|
|
349
422
|
let link;
|
|
@@ -389,24 +462,18 @@ function followPresses(document2, onRelease) {
|
|
|
389
462
|
const releaseOnMove = (event) => {
|
|
390
463
|
if (event.buttons === 0) release();
|
|
391
464
|
};
|
|
392
|
-
const
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
document2.addEventListener(type, listener, true);
|
|
400
|
-
}
|
|
401
|
-
view?.addEventListener("blur", release);
|
|
465
|
+
const stopListening = listenToAll([
|
|
466
|
+
onDocument(document2, "pointerdown", press),
|
|
467
|
+
onDocument(document2, "pointerup", release),
|
|
468
|
+
onDocument(document2, "pointercancel", release),
|
|
469
|
+
onDocument(document2, "pointermove", releaseOnMove),
|
|
470
|
+
...onWindow(view, "blur", release)
|
|
471
|
+
]);
|
|
402
472
|
return {
|
|
403
473
|
isPressed: () => isPressed,
|
|
404
474
|
stop: () => {
|
|
405
475
|
clearTimeout(releaseTimer);
|
|
406
|
-
|
|
407
|
-
document2.removeEventListener(type, listener, true);
|
|
408
|
-
}
|
|
409
|
-
view?.removeEventListener("blur", release);
|
|
476
|
+
stopListening();
|
|
410
477
|
}
|
|
411
478
|
};
|
|
412
479
|
}
|
|
@@ -416,11 +483,15 @@ function createPageRenderer(options) {
|
|
|
416
483
|
let request;
|
|
417
484
|
let heldPage;
|
|
418
485
|
const show = (html) => {
|
|
419
|
-
|
|
486
|
+
const isPageBusy = options.isPagePressed() || !options.canReplacePage();
|
|
487
|
+
if (isPageBusy) {
|
|
488
|
+
heldPage = html;
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
420
491
|
replacePageBody(options.document, html);
|
|
421
492
|
options.onRendered();
|
|
422
493
|
};
|
|
423
|
-
const
|
|
494
|
+
const render = (content) => {
|
|
424
495
|
request?.abort();
|
|
425
496
|
heldPage = void 0;
|
|
426
497
|
const currentRequest = new AbortController();
|
|
@@ -429,13 +500,7 @@ function createPageRenderer(options) {
|
|
|
429
500
|
pageAddress(options.document),
|
|
430
501
|
content,
|
|
431
502
|
currentRequest.signal
|
|
432
|
-
).then((
|
|
433
|
-
if (options.isPagePressed()) {
|
|
434
|
-
heldPage = html;
|
|
435
|
-
return;
|
|
436
|
-
}
|
|
437
|
-
show(html);
|
|
438
|
-
}).catch((error) => {
|
|
503
|
+
).then(show).catch((error) => {
|
|
439
504
|
if (currentRequest.signal.aborted) return;
|
|
440
505
|
options.onFailed(error);
|
|
441
506
|
});
|
|
@@ -446,7 +511,7 @@ function createPageRenderer(options) {
|
|
|
446
511
|
if (html !== void 0) show(html);
|
|
447
512
|
};
|
|
448
513
|
return {
|
|
449
|
-
render
|
|
514
|
+
render,
|
|
450
515
|
showHeldPage,
|
|
451
516
|
stop: () => {
|
|
452
517
|
request?.abort();
|
|
@@ -482,12 +547,14 @@ function replacePageBody(document2, html) {
|
|
|
482
547
|
}
|
|
483
548
|
|
|
484
549
|
// src/bridge/editable-elements.ts
|
|
550
|
+
var EDITABLE_AS_PLAIN_TEXT = "plaintext-only";
|
|
551
|
+
var EDITABLE_AS_RICH_TEXT = "true";
|
|
485
552
|
var OUTLINE = "outline";
|
|
486
553
|
var NO_OUTLINE = "none";
|
|
487
554
|
var IMPORTANT = "important";
|
|
488
555
|
var keptOutlines = /* @__PURE__ */ new WeakMap();
|
|
489
|
-
function makeEditable(element,
|
|
490
|
-
element.setAttribute("contenteditable",
|
|
556
|
+
function makeEditable(element, editableAs) {
|
|
557
|
+
element.setAttribute("contenteditable", editableAs);
|
|
491
558
|
if (keptOutlines.has(element)) return;
|
|
492
559
|
const { style } = element;
|
|
493
560
|
keptOutlines.set(element, {
|
|
@@ -569,45 +636,56 @@ function whiteSpaceOf(element) {
|
|
|
569
636
|
return view === null ? "normal" : view.getComputedStyle(element).whiteSpace;
|
|
570
637
|
}
|
|
571
638
|
|
|
572
|
-
// src/bridge/
|
|
573
|
-
|
|
574
|
-
function startInlineTextEditing(element, options) {
|
|
575
|
-
const reportInput = () => {
|
|
576
|
-
options.onInput(readElementText(element));
|
|
577
|
-
};
|
|
639
|
+
// src/bridge/typing-sessions.ts
|
|
640
|
+
function startTypingSession(element, options) {
|
|
578
641
|
const endOnKey = (event) => {
|
|
579
|
-
const
|
|
580
|
-
if (
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
} else if (isEnd) {
|
|
584
|
-
event.preventDefault();
|
|
585
|
-
end("kept");
|
|
586
|
-
}
|
|
587
|
-
};
|
|
588
|
-
const endOnBlur = () => {
|
|
589
|
-
end("kept");
|
|
590
|
-
};
|
|
591
|
-
const stop = () => {
|
|
592
|
-
element.removeEventListener("input", reportInput);
|
|
593
|
-
element.removeEventListener("keydown", endOnKey);
|
|
594
|
-
element.removeEventListener("blur", endOnBlur);
|
|
595
|
-
makeUneditable(element);
|
|
642
|
+
const outcome = outcomeOfKey(event, options.isEndingKey);
|
|
643
|
+
if (outcome === void 0) return;
|
|
644
|
+
event.preventDefault();
|
|
645
|
+
end(outcome);
|
|
596
646
|
};
|
|
597
647
|
let hasEnded = false;
|
|
598
648
|
const end = (outcome) => {
|
|
599
649
|
if (hasEnded) return;
|
|
600
650
|
hasEnded = true;
|
|
601
|
-
|
|
651
|
+
stopListening();
|
|
652
|
+
options.onStop();
|
|
653
|
+
makeUneditable(element);
|
|
602
654
|
options.onEnd(outcome);
|
|
603
655
|
};
|
|
604
|
-
makeEditable(element,
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
656
|
+
makeEditable(element, options.editableAs);
|
|
657
|
+
const stopListening = listenToAll([
|
|
658
|
+
onElement(element, "keydown", endOnKey),
|
|
659
|
+
...options.listeners
|
|
660
|
+
]);
|
|
608
661
|
element.focus();
|
|
662
|
+
return { end };
|
|
663
|
+
}
|
|
664
|
+
function outcomeOfKey(event, isEndingKey) {
|
|
665
|
+
if (event.key === "Escape") return "reverted";
|
|
666
|
+
return isEndingKey(event) ? "kept" : void 0;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// src/bridge/inline-text-editing.ts
|
|
670
|
+
function startInlineTextEditing(element, options) {
|
|
671
|
+
const reportInput = () => {
|
|
672
|
+
options.onInput(readElementText(element));
|
|
673
|
+
};
|
|
674
|
+
const endOnBlur = () => {
|
|
675
|
+
session.end("kept");
|
|
676
|
+
};
|
|
677
|
+
const session = startTypingSession(element, {
|
|
678
|
+
editableAs: EDITABLE_AS_PLAIN_TEXT,
|
|
679
|
+
isEndingKey: (event) => event.key === "Enter" && options.isSingleLine,
|
|
680
|
+
listeners: [
|
|
681
|
+
onElement(element, "input", reportInput),
|
|
682
|
+
onElement(element, "blur", endOnBlur)
|
|
683
|
+
],
|
|
684
|
+
onStop: () => void 0,
|
|
685
|
+
onEnd: options.onEnd
|
|
686
|
+
});
|
|
609
687
|
return () => {
|
|
610
|
-
end("kept");
|
|
688
|
+
session.end("kept");
|
|
611
689
|
};
|
|
612
690
|
}
|
|
613
691
|
function placeCaretAt(element, point) {
|
|
@@ -637,145 +715,47 @@ function caretRangeAt(document2, point) {
|
|
|
637
715
|
return lookup.caretRangeFromPoint?.(point.x, point.y) ?? void 0;
|
|
638
716
|
}
|
|
639
717
|
|
|
640
|
-
// src/bridge/
|
|
641
|
-
|
|
642
|
-
var PASTED_LINE_BREAKS = /\s*\n\s*/gu;
|
|
643
|
-
var SPACE = " ";
|
|
644
|
-
function canEditRichTextInPlace(element, value, isInline) {
|
|
645
|
-
if (hasMarkedDescendants(element)) return false;
|
|
646
|
-
const normalized = readRichText(value) ?? [];
|
|
647
|
-
const expected = isInline ? asOneParagraph(normalized) : normalized;
|
|
648
|
-
const shown = readTextOf(element, isInline);
|
|
649
|
-
return JSON.stringify(shown) === JSON.stringify(expected);
|
|
650
|
-
}
|
|
651
|
-
function startRichTextEditing(element, options) {
|
|
718
|
+
// src/bridge/typed-selections.ts
|
|
719
|
+
function createSelectionReports(element, readText, onSelect) {
|
|
652
720
|
const document2 = element.ownerDocument;
|
|
653
721
|
const view = document2.defaultView;
|
|
654
722
|
let lastSelection;
|
|
655
723
|
let reportFrame;
|
|
656
|
-
let blurTimer;
|
|
657
|
-
const readText = () => readTextOf(element, options.isInline);
|
|
658
724
|
const liveSelection = () => isSelectionIn(element) ? readSelectionFrom(element) : void 0;
|
|
659
|
-
const
|
|
725
|
+
const report = () => {
|
|
660
726
|
reportFrame = void 0;
|
|
661
|
-
|
|
727
|
+
onSelect(measureSelection(element, readText(), lastSelection));
|
|
662
728
|
};
|
|
663
|
-
const
|
|
729
|
+
const schedule = () => {
|
|
664
730
|
if (reportFrame !== void 0) return;
|
|
665
|
-
reportFrame = view?.requestAnimationFrame(
|
|
731
|
+
reportFrame = view?.requestAnimationFrame(report);
|
|
666
732
|
};
|
|
667
|
-
const
|
|
733
|
+
const follow = () => {
|
|
668
734
|
const selection = document2.hasFocus() ? liveSelection() : void 0;
|
|
669
735
|
if (selection === void 0) return;
|
|
670
736
|
lastSelection = selection;
|
|
671
|
-
|
|
672
|
-
};
|
|
673
|
-
const reportInput = () => {
|
|
674
|
-
options.onInput(readText());
|
|
675
|
-
followSelection();
|
|
737
|
+
schedule();
|
|
676
738
|
};
|
|
677
|
-
const
|
|
678
|
-
render(element, changed, options.isInline);
|
|
739
|
+
const keep = (selection) => {
|
|
679
740
|
lastSelection = selection;
|
|
680
741
|
if (document2.hasFocus()) selectIn(element, selection);
|
|
681
|
-
|
|
682
|
-
scheduleReport();
|
|
742
|
+
schedule();
|
|
683
743
|
};
|
|
684
|
-
const format = (requested) => {
|
|
685
|
-
const selection = liveSelection() ?? lastSelection;
|
|
686
|
-
const isListInLine = requested === "list" && options.isInline;
|
|
687
|
-
if (selection === void 0 || isListInLine) return;
|
|
688
|
-
const current = readText();
|
|
689
|
-
const changed = requested === "list" ? toggleList(current, selection) : toggleMark(current, selection, requested);
|
|
690
|
-
change(changed, selection);
|
|
691
|
-
};
|
|
692
|
-
const linkTo = (href) => {
|
|
693
|
-
const selection = lastSelection;
|
|
694
|
-
if (selection === void 0) return;
|
|
695
|
-
const current = readText();
|
|
696
|
-
const target = isCollapsed(selection) ? linkSelectionAt(current, selection.start) : selection;
|
|
697
|
-
if (target === void 0) return;
|
|
698
|
-
change(setLink(current, target, href), selection);
|
|
699
|
-
};
|
|
700
|
-
const pressKey = (event) => {
|
|
701
|
-
const isEnd = event.key === "Enter" && options.isInline && !event.shiftKey;
|
|
702
|
-
const shortcut = formatShortcutOf(event);
|
|
703
|
-
if (event.key === "Escape") {
|
|
704
|
-
event.preventDefault();
|
|
705
|
-
end("reverted");
|
|
706
|
-
} else if (isEnd) {
|
|
707
|
-
event.preventDefault();
|
|
708
|
-
end("kept");
|
|
709
|
-
} else if (shortcut !== void 0) {
|
|
710
|
-
event.preventDefault();
|
|
711
|
-
if (shortcut !== "none") format(shortcut);
|
|
712
|
-
}
|
|
713
|
-
};
|
|
714
|
-
const pastePlainText = (event) => {
|
|
715
|
-
event.preventDefault();
|
|
716
|
-
const pasted = event.clipboardData?.getData("text/plain") ?? "";
|
|
717
|
-
const words = options.isInline ? pasted.replace(PASTED_LINE_BREAKS, SPACE) : pasted;
|
|
718
|
-
insertPlainText(element, words);
|
|
719
|
-
reportInput();
|
|
720
|
-
};
|
|
721
|
-
const refuseDrop = (event) => {
|
|
722
|
-
event.preventDefault();
|
|
723
|
-
};
|
|
724
|
-
const endOnBlur = () => {
|
|
725
|
-
clearTimeout(blurTimer);
|
|
726
|
-
blurTimer = setTimeout(() => {
|
|
727
|
-
const isFocusOnPage = document2.hasFocus();
|
|
728
|
-
const isFocusInText = document2.activeElement === element;
|
|
729
|
-
if (isFocusOnPage && !isFocusInText) end("kept");
|
|
730
|
-
});
|
|
731
|
-
};
|
|
732
|
-
const stopListening = () => {
|
|
733
|
-
clearTimeout(blurTimer);
|
|
734
|
-
if (reportFrame !== void 0) view?.cancelAnimationFrame(reportFrame);
|
|
735
|
-
element.removeEventListener("input", reportInput);
|
|
736
|
-
element.removeEventListener("keydown", pressKey);
|
|
737
|
-
element.removeEventListener("paste", pastePlainText);
|
|
738
|
-
element.removeEventListener("drop", refuseDrop);
|
|
739
|
-
element.removeEventListener("blur", endOnBlur);
|
|
740
|
-
document2.removeEventListener("selectionchange", followSelection);
|
|
741
|
-
document2.removeEventListener("scroll", scheduleReport, true);
|
|
742
|
-
view?.removeEventListener("resize", scheduleReport);
|
|
743
|
-
makeUneditable(element);
|
|
744
|
-
};
|
|
745
|
-
let hasEnded = false;
|
|
746
|
-
const end = (outcome) => {
|
|
747
|
-
if (hasEnded) return;
|
|
748
|
-
hasEnded = true;
|
|
749
|
-
stopListening();
|
|
750
|
-
options.onSelect(void 0);
|
|
751
|
-
options.onEnd(outcome);
|
|
752
|
-
};
|
|
753
|
-
makeEditable(element, EDITABLE_AS_RICH_TEXT);
|
|
754
|
-
element.addEventListener("input", reportInput);
|
|
755
|
-
element.addEventListener("keydown", pressKey);
|
|
756
|
-
element.addEventListener("paste", pastePlainText);
|
|
757
|
-
element.addEventListener("drop", refuseDrop);
|
|
758
|
-
element.addEventListener("blur", endOnBlur);
|
|
759
|
-
document2.addEventListener("selectionchange", followSelection);
|
|
760
|
-
document2.addEventListener("scroll", scheduleReport, true);
|
|
761
|
-
view?.addEventListener("resize", scheduleReport);
|
|
762
|
-
element.focus();
|
|
763
744
|
return {
|
|
764
|
-
|
|
765
|
-
|
|
745
|
+
listeners: [
|
|
746
|
+
onDocument(document2, "selectionchange", follow),
|
|
747
|
+
onDocument(document2, "scroll", schedule),
|
|
748
|
+
...onWindow(view, "resize", schedule)
|
|
749
|
+
],
|
|
750
|
+
current: () => liveSelection() ?? lastSelection,
|
|
751
|
+
last: () => lastSelection,
|
|
752
|
+
follow,
|
|
753
|
+
keep,
|
|
766
754
|
stop: () => {
|
|
767
|
-
|
|
755
|
+
if (reportFrame !== void 0) view?.cancelAnimationFrame(reportFrame);
|
|
768
756
|
}
|
|
769
757
|
};
|
|
770
758
|
}
|
|
771
|
-
function readTextOf(element, isInline) {
|
|
772
|
-
const read = readRichTextFrom(element);
|
|
773
|
-
return isInline ? asOneParagraph(read) : read;
|
|
774
|
-
}
|
|
775
|
-
function render(element, text, isInline) {
|
|
776
|
-
if (isInline) renderInlineRichText(element, text);
|
|
777
|
-
else renderRichText(element, text);
|
|
778
|
-
}
|
|
779
759
|
function measureSelection(element, text, selection) {
|
|
780
760
|
if (selection === void 0) return void 0;
|
|
781
761
|
const range = isSelectionIn(element) ? element.ownerDocument.getSelection()?.getRangeAt(0) : rangeOf(element, selection);
|
|
@@ -789,7 +769,8 @@ function measureSelection(element, text, selection) {
|
|
|
789
769
|
}
|
|
790
770
|
function boxOfRange(range) {
|
|
791
771
|
const whole = range.getBoundingClientRect();
|
|
792
|
-
const
|
|
772
|
+
const hasOwnBox = whole.width > 0 || whole.height > 0;
|
|
773
|
+
const rectangle = hasOwnBox ? whole : range.getClientRects()[0];
|
|
793
774
|
if (rectangle === void 0) return void 0;
|
|
794
775
|
const isShown = rectangle.width > 0 || rectangle.height > 0;
|
|
795
776
|
if (!isShown) return void 0;
|
|
@@ -806,6 +787,118 @@ function isSelectionIn(element) {
|
|
|
806
787
|
const hasRange = (selection?.rangeCount ?? 0) > 0;
|
|
807
788
|
return hasRange && anchor !== null && element.contains(anchor);
|
|
808
789
|
}
|
|
790
|
+
|
|
791
|
+
// src/bridge/rich-text-editing.ts
|
|
792
|
+
function canEditRichTextInPlace(element, value, layout) {
|
|
793
|
+
if (hasMarkedDescendants(element)) return false;
|
|
794
|
+
const normalized = readRichText(value) ?? [];
|
|
795
|
+
const expected = layout.shapeOf(normalized);
|
|
796
|
+
const shown = layout.read(element);
|
|
797
|
+
return JSON.stringify(shown) === JSON.stringify(expected);
|
|
798
|
+
}
|
|
799
|
+
function startRichTextEditing(element, options) {
|
|
800
|
+
const { layout } = options;
|
|
801
|
+
const readText = () => layout.read(element);
|
|
802
|
+
const selections = createSelectionReports(
|
|
803
|
+
element,
|
|
804
|
+
readText,
|
|
805
|
+
options.onSelect
|
|
806
|
+
);
|
|
807
|
+
const reportInput = () => {
|
|
808
|
+
options.onInput(readText());
|
|
809
|
+
selections.follow();
|
|
810
|
+
};
|
|
811
|
+
const change = (changed, selection) => {
|
|
812
|
+
layout.render(element, changed);
|
|
813
|
+
selections.keep(selection);
|
|
814
|
+
options.onInput(changed);
|
|
815
|
+
};
|
|
816
|
+
const format = (requested) => {
|
|
817
|
+
const selection = selections.current();
|
|
818
|
+
const isRefusedList = requested === "list" && !layout.canHoldLists;
|
|
819
|
+
if (selection === void 0 || isRefusedList) return;
|
|
820
|
+
change(formatted(readText(), selection, requested), selection);
|
|
821
|
+
};
|
|
822
|
+
const linkTo = (href) => {
|
|
823
|
+
const selection = selections.last();
|
|
824
|
+
const isUnsafe = href !== void 0 && safeHref(href) === void 0;
|
|
825
|
+
if (selection === void 0 || isUnsafe) return;
|
|
826
|
+
const current = readText();
|
|
827
|
+
const target = isCollapsed(selection) ? linkSelectionAt(current, selection.start) : selection;
|
|
828
|
+
if (target === void 0) return;
|
|
829
|
+
change(setLink(current, target, href), selection);
|
|
830
|
+
};
|
|
831
|
+
const blurs = endWhenFocusMovesOnPage(element, () => {
|
|
832
|
+
session.end("kept");
|
|
833
|
+
});
|
|
834
|
+
const session = startTypingSession(element, {
|
|
835
|
+
editableAs: EDITABLE_AS_RICH_TEXT,
|
|
836
|
+
isEndingKey: layout.isEndingKey,
|
|
837
|
+
listeners: [
|
|
838
|
+
onElement(element, "input", reportInput),
|
|
839
|
+
onElement(element, "keydown", (event) => {
|
|
840
|
+
formatOnShortcut(event, format);
|
|
841
|
+
}),
|
|
842
|
+
onElement(element, "paste", (event) => {
|
|
843
|
+
pastePlainText(event, element, layout);
|
|
844
|
+
reportInput();
|
|
845
|
+
}),
|
|
846
|
+
onElement(element, "drop", refuseDrop),
|
|
847
|
+
onElement(element, "blur", blurs.listener),
|
|
848
|
+
...selections.listeners
|
|
849
|
+
],
|
|
850
|
+
onStop: () => {
|
|
851
|
+
blurs.stop();
|
|
852
|
+
selections.stop();
|
|
853
|
+
},
|
|
854
|
+
onEnd: (outcome) => {
|
|
855
|
+
options.onSelect(void 0);
|
|
856
|
+
options.onEnd(outcome);
|
|
857
|
+
}
|
|
858
|
+
});
|
|
859
|
+
return {
|
|
860
|
+
format,
|
|
861
|
+
setLink: linkTo,
|
|
862
|
+
stop: () => {
|
|
863
|
+
session.end("kept");
|
|
864
|
+
}
|
|
865
|
+
};
|
|
866
|
+
}
|
|
867
|
+
function formatted(text, selection, format) {
|
|
868
|
+
return format === "list" ? toggleList(text, selection) : toggleMark(text, selection, format);
|
|
869
|
+
}
|
|
870
|
+
function formatOnShortcut(event, format) {
|
|
871
|
+
const shortcut = formatShortcutOf(event);
|
|
872
|
+
if (shortcut === void 0) return;
|
|
873
|
+
event.preventDefault();
|
|
874
|
+
if (shortcut !== "refused") format(shortcut);
|
|
875
|
+
}
|
|
876
|
+
function pastePlainText(event, element, layout) {
|
|
877
|
+
event.preventDefault();
|
|
878
|
+
const pasted = event.clipboardData?.getData("text/plain") ?? "";
|
|
879
|
+
insertPlainText(element, layout.pastedWords(pasted));
|
|
880
|
+
}
|
|
881
|
+
function refuseDrop(event) {
|
|
882
|
+
event.preventDefault();
|
|
883
|
+
}
|
|
884
|
+
function endWhenFocusMovesOnPage(element, end) {
|
|
885
|
+
let blurTimer;
|
|
886
|
+
const listener = () => {
|
|
887
|
+
clearTimeout(blurTimer);
|
|
888
|
+
blurTimer = setTimeout(() => {
|
|
889
|
+
const { ownerDocument } = element;
|
|
890
|
+
const isFocusOnPage = ownerDocument.hasFocus();
|
|
891
|
+
const isFocusInText = ownerDocument.activeElement === element;
|
|
892
|
+
if (isFocusOnPage && !isFocusInText) end();
|
|
893
|
+
});
|
|
894
|
+
};
|
|
895
|
+
return {
|
|
896
|
+
listener,
|
|
897
|
+
stop: () => {
|
|
898
|
+
clearTimeout(blurTimer);
|
|
899
|
+
}
|
|
900
|
+
};
|
|
901
|
+
}
|
|
809
902
|
function formatShortcutOf(event) {
|
|
810
903
|
const hasCommandKey = event.metaKey || event.ctrlKey;
|
|
811
904
|
if (!hasCommandKey || event.altKey || event.shiftKey) return void 0;
|
|
@@ -815,12 +908,35 @@ function formatShortcutOf(event) {
|
|
|
815
908
|
case "KeyI":
|
|
816
909
|
return "italic";
|
|
817
910
|
case "KeyU":
|
|
818
|
-
return "
|
|
911
|
+
return "refused";
|
|
819
912
|
default:
|
|
820
913
|
return void 0;
|
|
821
914
|
}
|
|
822
915
|
}
|
|
823
916
|
|
|
917
|
+
// src/bridge/rich-text-layouts.ts
|
|
918
|
+
var PASTED_LINE_BREAKS = /\s*\n\s*/gu;
|
|
919
|
+
var SPACE = " ";
|
|
920
|
+
var BLOCKS_LAYOUT = {
|
|
921
|
+
shapeOf: (text) => text,
|
|
922
|
+
read: readRichTextFrom,
|
|
923
|
+
render: renderRichText,
|
|
924
|
+
canHoldLists: true,
|
|
925
|
+
isEndingKey: () => false,
|
|
926
|
+
pastedWords: (pasted) => pasted
|
|
927
|
+
};
|
|
928
|
+
var LINE_LAYOUT = {
|
|
929
|
+
shapeOf: asOneParagraph,
|
|
930
|
+
read: (element) => asOneParagraph(readRichTextFrom(element)),
|
|
931
|
+
render: renderInlineRichText,
|
|
932
|
+
canHoldLists: false,
|
|
933
|
+
isEndingKey: (event) => event.key === "Enter" && !event.shiftKey,
|
|
934
|
+
pastedWords: (pasted) => pasted.replace(PASTED_LINE_BREAKS, SPACE)
|
|
935
|
+
};
|
|
936
|
+
function richTextLayoutOf(isInline) {
|
|
937
|
+
return isInline ? LINE_LAYOUT : BLOCKS_LAYOUT;
|
|
938
|
+
}
|
|
939
|
+
|
|
824
940
|
// src/bridge/page-typing.ts
|
|
825
941
|
var PREPARED_TYPING_TIMEOUT_MS = 3e3;
|
|
826
942
|
var EDITABLE_VALUES = {
|
|
@@ -828,93 +944,111 @@ var EDITABLE_VALUES = {
|
|
|
828
944
|
rich: EDITABLE_AS_RICH_TEXT
|
|
829
945
|
};
|
|
830
946
|
function createPageTyping(options) {
|
|
831
|
-
|
|
832
|
-
let
|
|
947
|
+
const { send } = options;
|
|
948
|
+
let typing;
|
|
833
949
|
let prepared;
|
|
834
|
-
const
|
|
950
|
+
const isTyping = () => typing !== void 0 || prepared !== void 0;
|
|
951
|
+
const reportIfOver = () => {
|
|
952
|
+
if (!isTyping()) options.onTypingOver();
|
|
953
|
+
};
|
|
954
|
+
const releasePrepared = () => {
|
|
835
955
|
if (prepared === void 0) return;
|
|
836
956
|
clearTimeout(prepared.timer);
|
|
837
|
-
if (
|
|
957
|
+
if (typing === void 0) {
|
|
838
958
|
prepared.element.blur();
|
|
839
959
|
makeUneditable(prepared.element);
|
|
840
960
|
}
|
|
841
961
|
prepared = void 0;
|
|
842
962
|
};
|
|
963
|
+
const handOverPrepared = () => {
|
|
964
|
+
if (prepared !== void 0) clearTimeout(prepared.timer);
|
|
965
|
+
prepared = void 0;
|
|
966
|
+
};
|
|
843
967
|
const prepare = (element, kind) => {
|
|
844
968
|
const isTypedAlready = element.hasAttribute("contenteditable");
|
|
845
969
|
if (isTypedAlready) return;
|
|
846
|
-
|
|
970
|
+
releasePrepared();
|
|
847
971
|
makeEditable(element, EDITABLE_VALUES[kind]);
|
|
848
972
|
element.focus({ preventScroll: true });
|
|
849
973
|
const timer = setTimeout(() => {
|
|
850
|
-
|
|
974
|
+
releasePrepared();
|
|
975
|
+
reportIfOver();
|
|
851
976
|
}, PREPARED_TYPING_TIMEOUT_MS);
|
|
852
977
|
prepared = { element, timer };
|
|
853
978
|
};
|
|
854
979
|
const stop = () => {
|
|
855
|
-
|
|
980
|
+
typing?.stop();
|
|
856
981
|
};
|
|
857
|
-
const
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
982
|
+
const endedAt = (path) => (outcome) => {
|
|
983
|
+
typing = void 0;
|
|
984
|
+
send({ type: "typing-ended", path, outcome });
|
|
985
|
+
reportIfOver();
|
|
861
986
|
};
|
|
862
|
-
const
|
|
987
|
+
const begin = (start) => {
|
|
863
988
|
stop();
|
|
864
|
-
const { path } =
|
|
989
|
+
const { path } = start;
|
|
865
990
|
const element = findMarkedElement(options.document, path);
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
991
|
+
if (element === void 0 || !start.canTypeIn(element)) {
|
|
992
|
+
releasePrepared();
|
|
993
|
+
send({ type: "typing-refused", path });
|
|
994
|
+
reportIfOver();
|
|
870
995
|
return;
|
|
871
996
|
}
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
997
|
+
handOverPrepared();
|
|
998
|
+
send({ type: "typing-started", path });
|
|
999
|
+
typing = start.typeIn(element, endedAt(path));
|
|
1000
|
+
placeCaretAt(element, start.point);
|
|
1001
|
+
};
|
|
1002
|
+
const reportTyped = (message) => {
|
|
1003
|
+
send(message);
|
|
1004
|
+
options.onInput();
|
|
1005
|
+
};
|
|
1006
|
+
const typePlainText = (request) => {
|
|
1007
|
+
const { path, text } = request;
|
|
1008
|
+
begin({
|
|
1009
|
+
path,
|
|
1010
|
+
point: request.point,
|
|
1011
|
+
canTypeIn: (element) => canEditTextInPlace(element, text),
|
|
1012
|
+
typeIn: (element, onEnd) => ({
|
|
1013
|
+
stop: startInlineTextEditing(element, {
|
|
1014
|
+
isSingleLine: request.isSingleLine,
|
|
1015
|
+
onInput: (typed) => {
|
|
1016
|
+
reportTyped({ type: "typed", path, text: typed });
|
|
1017
|
+
},
|
|
1018
|
+
onEnd
|
|
1019
|
+
})
|
|
1020
|
+
})
|
|
881
1021
|
});
|
|
882
|
-
placeCaretAt(element, request.point);
|
|
883
1022
|
};
|
|
884
|
-
const
|
|
885
|
-
|
|
886
|
-
const
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
},
|
|
905
|
-
onEnd: ended(path)
|
|
1023
|
+
const typeRichText = (request) => {
|
|
1024
|
+
const { path, text } = request;
|
|
1025
|
+
const layout = richTextLayoutOf(request.isInline);
|
|
1026
|
+
begin({
|
|
1027
|
+
path,
|
|
1028
|
+
point: request.point,
|
|
1029
|
+
canTypeIn: (element) => canEditRichTextInPlace(element, text, layout),
|
|
1030
|
+
typeIn: (element, onEnd) => {
|
|
1031
|
+
const editing = startRichTextEditing(element, {
|
|
1032
|
+
layout,
|
|
1033
|
+
onInput: (typed) => {
|
|
1034
|
+
reportTyped({ type: "rich-typed", path, text: typed });
|
|
1035
|
+
},
|
|
1036
|
+
onSelect: (selection) => {
|
|
1037
|
+
send({ type: "typed-selection", path, selection });
|
|
1038
|
+
},
|
|
1039
|
+
onEnd
|
|
1040
|
+
});
|
|
1041
|
+
return { stop: editing.stop, richText: editing };
|
|
1042
|
+
}
|
|
906
1043
|
});
|
|
907
|
-
richEditing = editing;
|
|
908
|
-
stopEditing = editing.stop;
|
|
909
|
-
placeCaretAt(element, request.point);
|
|
910
1044
|
};
|
|
911
1045
|
return {
|
|
912
1046
|
prepare,
|
|
913
|
-
isTyping
|
|
914
|
-
start,
|
|
915
|
-
startRich,
|
|
916
|
-
format: (format) =>
|
|
917
|
-
setLink: (href) =>
|
|
1047
|
+
isTyping,
|
|
1048
|
+
start: typePlainText,
|
|
1049
|
+
startRich: typeRichText,
|
|
1050
|
+
format: (format) => typing?.richText?.format(format),
|
|
1051
|
+
setLink: (href) => typing?.richText?.setLink(href),
|
|
918
1052
|
stop
|
|
919
1053
|
};
|
|
920
1054
|
}
|
|
@@ -950,10 +1084,16 @@ function connectPage(document2, schema, send) {
|
|
|
950
1084
|
const layout = createLayoutReports(document2, (measured) => {
|
|
951
1085
|
send({ type: "layout", layout: measured });
|
|
952
1086
|
});
|
|
953
|
-
const
|
|
954
|
-
const presses = followPresses(document2, () => {
|
|
1087
|
+
const showHeldPage = () => {
|
|
955
1088
|
renderer.showHeldPage();
|
|
1089
|
+
};
|
|
1090
|
+
const typing = createPageTyping({
|
|
1091
|
+
document: document2,
|
|
1092
|
+
send,
|
|
1093
|
+
onInput: layout.schedule,
|
|
1094
|
+
onTypingOver: showHeldPage
|
|
956
1095
|
});
|
|
1096
|
+
const presses = followPresses(document2, showHeldPage);
|
|
957
1097
|
const renderer = createPageRenderer({
|
|
958
1098
|
document: document2,
|
|
959
1099
|
// Replacing the page under a text being typed would throw the typing away.
|
|
@@ -968,31 +1108,45 @@ function connectPage(document2, schema, send) {
|
|
|
968
1108
|
send({ type: "render-failed" });
|
|
969
1109
|
}
|
|
970
1110
|
});
|
|
971
|
-
const
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
typing
|
|
977
|
-
|
|
1111
|
+
const page = {
|
|
1112
|
+
document: document2,
|
|
1113
|
+
schema,
|
|
1114
|
+
send,
|
|
1115
|
+
layout,
|
|
1116
|
+
typing,
|
|
1117
|
+
renderer
|
|
978
1118
|
};
|
|
979
|
-
const stopListening = listenToPage(document2,
|
|
1119
|
+
const stopListening = listenToPage(document2, pageListenersOf(page));
|
|
1120
|
+
layout.reportNow();
|
|
1121
|
+
return {
|
|
1122
|
+
receive: (message) => {
|
|
1123
|
+
receiveEditorMessage(page, message);
|
|
1124
|
+
},
|
|
1125
|
+
stop: () => {
|
|
1126
|
+
typing.stop();
|
|
1127
|
+
renderer.stop();
|
|
1128
|
+
presses.stop();
|
|
1129
|
+
layout.stop();
|
|
1130
|
+
stopListening();
|
|
1131
|
+
}
|
|
1132
|
+
};
|
|
1133
|
+
}
|
|
1134
|
+
function pageListenersOf(page) {
|
|
1135
|
+
const { schema, send, layout } = page;
|
|
1136
|
+
return {
|
|
980
1137
|
onHover: (path) => {
|
|
981
1138
|
send({ type: "hover", path });
|
|
982
1139
|
},
|
|
983
1140
|
onClick: (pointer) => {
|
|
984
|
-
|
|
985
|
-
if (pointer !== void 0 && kind !== void 0) {
|
|
986
|
-
typing.prepare(pointer.element, kind);
|
|
987
|
-
}
|
|
988
|
-
const target = pointer === void 0 ? void 0 : { path: pointer.path, point: pointer.point };
|
|
989
|
-
send({ type: "click", target });
|
|
1141
|
+
clickOn(page, pointer);
|
|
990
1142
|
},
|
|
991
1143
|
onLinkHover: layout.hoverLink,
|
|
992
1144
|
onLinkClick: (link) => {
|
|
993
1145
|
send({ type: "link-click", href: link.href });
|
|
994
1146
|
},
|
|
995
|
-
onKeyDown:
|
|
1147
|
+
onKeyDown: (event) => {
|
|
1148
|
+
pressKey(page, event);
|
|
1149
|
+
},
|
|
996
1150
|
onFileDragOver: (path) => {
|
|
997
1151
|
const isTaken = imageFieldAt(schema, path) !== void 0;
|
|
998
1152
|
send({ type: "file-drag", path: isTaken ? path : void 0 });
|
|
@@ -1006,45 +1160,55 @@ function connectPage(document2, schema, send) {
|
|
|
1006
1160
|
if (isTaken) send({ type: "file-drop", path, file });
|
|
1007
1161
|
},
|
|
1008
1162
|
onLayoutChange: layout.schedule
|
|
1009
|
-
});
|
|
1010
|
-
layout.reportNow();
|
|
1011
|
-
const receive = (message) => {
|
|
1012
|
-
switch (message.type) {
|
|
1013
|
-
case "connect":
|
|
1014
|
-
break;
|
|
1015
|
-
case "render":
|
|
1016
|
-
renderer.render(message.content);
|
|
1017
|
-
break;
|
|
1018
|
-
case "start-typing":
|
|
1019
|
-
typing.start(message);
|
|
1020
|
-
break;
|
|
1021
|
-
case "start-rich-typing":
|
|
1022
|
-
typing.startRich(message);
|
|
1023
|
-
break;
|
|
1024
|
-
case "format-typed":
|
|
1025
|
-
typing.format(message.format);
|
|
1026
|
-
break;
|
|
1027
|
-
case "link-typed":
|
|
1028
|
-
typing.setLink(message.href);
|
|
1029
|
-
break;
|
|
1030
|
-
case "stop-typing":
|
|
1031
|
-
typing.stop();
|
|
1032
|
-
break;
|
|
1033
|
-
case "scroll-to":
|
|
1034
|
-
scrollToField(document2, message.path);
|
|
1035
|
-
break;
|
|
1036
|
-
}
|
|
1037
1163
|
};
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1164
|
+
}
|
|
1165
|
+
function clickOn(page, pointer) {
|
|
1166
|
+
if (pointer === void 0) {
|
|
1167
|
+
page.send({ type: "click", target: void 0 });
|
|
1168
|
+
return;
|
|
1169
|
+
}
|
|
1170
|
+
const kind = typedKindAt(page.schema, pointer.path);
|
|
1171
|
+
if (kind !== void 0) page.typing.prepare(pointer.element, kind);
|
|
1172
|
+
page.send({
|
|
1173
|
+
type: "click",
|
|
1174
|
+
target: { path: pointer.path, point: pointer.point }
|
|
1175
|
+
});
|
|
1176
|
+
}
|
|
1177
|
+
function pressKey(page, event) {
|
|
1178
|
+
const key = editorKeyOf(event);
|
|
1179
|
+
const isTypingEscape = key === "escape" && page.typing.isTyping();
|
|
1180
|
+
if (key === void 0 || isTypingEscape) return;
|
|
1181
|
+
event.preventDefault();
|
|
1182
|
+
page.typing.stop();
|
|
1183
|
+
page.send({ type: "key", key });
|
|
1184
|
+
}
|
|
1185
|
+
function receiveEditorMessage(page, message) {
|
|
1186
|
+
const { typing } = page;
|
|
1187
|
+
switch (message.type) {
|
|
1188
|
+
case "connect":
|
|
1189
|
+
break;
|
|
1190
|
+
case "render":
|
|
1191
|
+
page.renderer.render(message.content);
|
|
1192
|
+
break;
|
|
1193
|
+
case "start-typing":
|
|
1194
|
+
typing.start(message);
|
|
1195
|
+
break;
|
|
1196
|
+
case "start-rich-typing":
|
|
1197
|
+
typing.startRich(message);
|
|
1198
|
+
break;
|
|
1199
|
+
case "format-typed":
|
|
1200
|
+
typing.format(message.format);
|
|
1201
|
+
break;
|
|
1202
|
+
case "link-typed":
|
|
1203
|
+
typing.setLink(message.href);
|
|
1204
|
+
break;
|
|
1205
|
+
case "stop-typing":
|
|
1041
1206
|
typing.stop();
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
};
|
|
1207
|
+
break;
|
|
1208
|
+
case "scroll-to":
|
|
1209
|
+
scrollToField(page.document, message.path);
|
|
1210
|
+
break;
|
|
1211
|
+
}
|
|
1048
1212
|
}
|
|
1049
1213
|
function scrollToField(document2, path) {
|
|
1050
1214
|
const element = findClosestMarkedElement(document2, path);
|