@seamlet/astro 0.11.2 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -6
- package/components/RichLine.astro +2 -5
- package/components/RichText.astro +2 -4
- package/dist/bridge.d.ts +2 -3
- package/dist/bridge.js +598 -374
- package/dist/{chunk-DRDPKFHZ.js → chunk-7GYJS7ZT.js} +386 -203
- package/dist/{chunk-2E7VDSUK.js → chunk-MW3BE5O6.js} +34 -43
- package/dist/chunk-QCVWHHJC.js +14 -0
- package/dist/{chunk-P2OQOZBC.js → chunk-QL4K4HR6.js} +8 -0
- package/dist/{chunk-3BS5CE26.js → chunk-W2LIOR2C.js} +6 -2
- 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-MW3BE5O6.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-7GYJS7ZT.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;
|
|
@@ -372,30 +445,77 @@ function createLayoutReports(document2, report) {
|
|
|
372
445
|
};
|
|
373
446
|
}
|
|
374
447
|
|
|
448
|
+
// src/bridge/page-presses.ts
|
|
449
|
+
function followPresses(document2, onRelease) {
|
|
450
|
+
const view = document2.defaultView;
|
|
451
|
+
let isPressed = false;
|
|
452
|
+
let releaseTimer;
|
|
453
|
+
const press = () => {
|
|
454
|
+
clearTimeout(releaseTimer);
|
|
455
|
+
isPressed = true;
|
|
456
|
+
};
|
|
457
|
+
const release = () => {
|
|
458
|
+
if (!isPressed) return;
|
|
459
|
+
isPressed = false;
|
|
460
|
+
releaseTimer = setTimeout(onRelease, 0);
|
|
461
|
+
};
|
|
462
|
+
const releaseOnMove = (event) => {
|
|
463
|
+
if (event.buttons === 0) release();
|
|
464
|
+
};
|
|
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
|
+
]);
|
|
472
|
+
return {
|
|
473
|
+
isPressed: () => isPressed,
|
|
474
|
+
stop: () => {
|
|
475
|
+
clearTimeout(releaseTimer);
|
|
476
|
+
stopListening();
|
|
477
|
+
}
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
|
|
375
481
|
// src/bridge/page-rendering.ts
|
|
376
482
|
function createPageRenderer(options) {
|
|
377
483
|
let request;
|
|
378
|
-
|
|
484
|
+
let heldPage;
|
|
485
|
+
const show = (html) => {
|
|
486
|
+
const isPageBusy = options.isPagePressed() || !options.canReplacePage();
|
|
487
|
+
if (isPageBusy) {
|
|
488
|
+
heldPage = html;
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
replacePageBody(options.document, html);
|
|
492
|
+
options.onRendered();
|
|
493
|
+
};
|
|
494
|
+
const render = (content) => {
|
|
379
495
|
request?.abort();
|
|
496
|
+
heldPage = void 0;
|
|
380
497
|
const currentRequest = new AbortController();
|
|
381
498
|
request = currentRequest;
|
|
382
499
|
fetchRenderedPage(
|
|
383
500
|
pageAddress(options.document),
|
|
384
501
|
content,
|
|
385
502
|
currentRequest.signal
|
|
386
|
-
).then((
|
|
387
|
-
if (!options.canReplacePage()) return;
|
|
388
|
-
replacePageBody(options.document, html);
|
|
389
|
-
options.onRendered();
|
|
390
|
-
}).catch((error) => {
|
|
503
|
+
).then(show).catch((error) => {
|
|
391
504
|
if (currentRequest.signal.aborted) return;
|
|
392
505
|
options.onFailed(error);
|
|
393
506
|
});
|
|
394
507
|
};
|
|
508
|
+
const showHeldPage = () => {
|
|
509
|
+
const html = heldPage;
|
|
510
|
+
heldPage = void 0;
|
|
511
|
+
if (html !== void 0) show(html);
|
|
512
|
+
};
|
|
395
513
|
return {
|
|
396
|
-
render
|
|
514
|
+
render,
|
|
515
|
+
showHeldPage,
|
|
397
516
|
stop: () => {
|
|
398
517
|
request?.abort();
|
|
518
|
+
heldPage = void 0;
|
|
399
519
|
}
|
|
400
520
|
};
|
|
401
521
|
}
|
|
@@ -427,12 +547,14 @@ function replacePageBody(document2, html) {
|
|
|
427
547
|
}
|
|
428
548
|
|
|
429
549
|
// src/bridge/editable-elements.ts
|
|
550
|
+
var EDITABLE_AS_PLAIN_TEXT = "plaintext-only";
|
|
551
|
+
var EDITABLE_AS_RICH_TEXT = "true";
|
|
430
552
|
var OUTLINE = "outline";
|
|
431
553
|
var NO_OUTLINE = "none";
|
|
432
554
|
var IMPORTANT = "important";
|
|
433
555
|
var keptOutlines = /* @__PURE__ */ new WeakMap();
|
|
434
|
-
function makeEditable(element,
|
|
435
|
-
element.setAttribute("contenteditable",
|
|
556
|
+
function makeEditable(element, editableAs) {
|
|
557
|
+
element.setAttribute("contenteditable", editableAs);
|
|
436
558
|
if (keptOutlines.has(element)) return;
|
|
437
559
|
const { style } = element;
|
|
438
560
|
keptOutlines.set(element, {
|
|
@@ -514,45 +636,56 @@ function whiteSpaceOf(element) {
|
|
|
514
636
|
return view === null ? "normal" : view.getComputedStyle(element).whiteSpace;
|
|
515
637
|
}
|
|
516
638
|
|
|
517
|
-
// src/bridge/
|
|
518
|
-
|
|
519
|
-
function startInlineTextEditing(element, options) {
|
|
520
|
-
const reportInput = () => {
|
|
521
|
-
options.onInput(readElementText(element));
|
|
522
|
-
};
|
|
639
|
+
// src/bridge/typing-sessions.ts
|
|
640
|
+
function startTypingSession(element, options) {
|
|
523
641
|
const endOnKey = (event) => {
|
|
524
|
-
const
|
|
525
|
-
if (
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
} else if (isEnd) {
|
|
529
|
-
event.preventDefault();
|
|
530
|
-
end("kept");
|
|
531
|
-
}
|
|
532
|
-
};
|
|
533
|
-
const endOnBlur = () => {
|
|
534
|
-
end("kept");
|
|
535
|
-
};
|
|
536
|
-
const stop = () => {
|
|
537
|
-
element.removeEventListener("input", reportInput);
|
|
538
|
-
element.removeEventListener("keydown", endOnKey);
|
|
539
|
-
element.removeEventListener("blur", endOnBlur);
|
|
540
|
-
makeUneditable(element);
|
|
642
|
+
const outcome = outcomeOfKey(event, options.isEndingKey);
|
|
643
|
+
if (outcome === void 0) return;
|
|
644
|
+
event.preventDefault();
|
|
645
|
+
end(outcome);
|
|
541
646
|
};
|
|
542
647
|
let hasEnded = false;
|
|
543
648
|
const end = (outcome) => {
|
|
544
649
|
if (hasEnded) return;
|
|
545
650
|
hasEnded = true;
|
|
546
|
-
|
|
651
|
+
stopListening();
|
|
652
|
+
options.onStop();
|
|
653
|
+
makeUneditable(element);
|
|
547
654
|
options.onEnd(outcome);
|
|
548
655
|
};
|
|
549
|
-
makeEditable(element,
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
656
|
+
makeEditable(element, options.editableAs);
|
|
657
|
+
const stopListening = listenToAll([
|
|
658
|
+
onElement(element, "keydown", endOnKey),
|
|
659
|
+
...options.listeners
|
|
660
|
+
]);
|
|
553
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
|
+
});
|
|
554
687
|
return () => {
|
|
555
|
-
end("kept");
|
|
688
|
+
session.end("kept");
|
|
556
689
|
};
|
|
557
690
|
}
|
|
558
691
|
function placeCaretAt(element, point) {
|
|
@@ -582,145 +715,47 @@ function caretRangeAt(document2, point) {
|
|
|
582
715
|
return lookup.caretRangeFromPoint?.(point.x, point.y) ?? void 0;
|
|
583
716
|
}
|
|
584
717
|
|
|
585
|
-
// src/bridge/
|
|
586
|
-
|
|
587
|
-
var PASTED_LINE_BREAKS = /\s*\n\s*/gu;
|
|
588
|
-
var SPACE = " ";
|
|
589
|
-
function canEditRichTextInPlace(element, value, isInline) {
|
|
590
|
-
if (hasMarkedDescendants(element)) return false;
|
|
591
|
-
const normalized = readRichText(value) ?? [];
|
|
592
|
-
const expected = isInline ? asOneParagraph(normalized) : normalized;
|
|
593
|
-
const shown = readTextOf(element, isInline);
|
|
594
|
-
return JSON.stringify(shown) === JSON.stringify(expected);
|
|
595
|
-
}
|
|
596
|
-
function startRichTextEditing(element, options) {
|
|
718
|
+
// src/bridge/typed-selections.ts
|
|
719
|
+
function createSelectionReports(element, readText, onSelect) {
|
|
597
720
|
const document2 = element.ownerDocument;
|
|
598
721
|
const view = document2.defaultView;
|
|
599
722
|
let lastSelection;
|
|
600
723
|
let reportFrame;
|
|
601
|
-
let blurTimer;
|
|
602
|
-
const readText = () => readTextOf(element, options.isInline);
|
|
603
724
|
const liveSelection = () => isSelectionIn(element) ? readSelectionFrom(element) : void 0;
|
|
604
|
-
const
|
|
725
|
+
const report = () => {
|
|
605
726
|
reportFrame = void 0;
|
|
606
|
-
|
|
727
|
+
onSelect(measureSelection(element, readText(), lastSelection));
|
|
607
728
|
};
|
|
608
|
-
const
|
|
729
|
+
const schedule = () => {
|
|
609
730
|
if (reportFrame !== void 0) return;
|
|
610
|
-
reportFrame = view?.requestAnimationFrame(
|
|
731
|
+
reportFrame = view?.requestAnimationFrame(report);
|
|
611
732
|
};
|
|
612
|
-
const
|
|
733
|
+
const follow = () => {
|
|
613
734
|
const selection = document2.hasFocus() ? liveSelection() : void 0;
|
|
614
735
|
if (selection === void 0) return;
|
|
615
736
|
lastSelection = selection;
|
|
616
|
-
|
|
737
|
+
schedule();
|
|
617
738
|
};
|
|
618
|
-
const
|
|
619
|
-
options.onInput(readText());
|
|
620
|
-
followSelection();
|
|
621
|
-
};
|
|
622
|
-
const change = (changed, selection) => {
|
|
623
|
-
render(element, changed, options.isInline);
|
|
739
|
+
const keep = (selection) => {
|
|
624
740
|
lastSelection = selection;
|
|
625
741
|
if (document2.hasFocus()) selectIn(element, selection);
|
|
626
|
-
|
|
627
|
-
scheduleReport();
|
|
628
|
-
};
|
|
629
|
-
const format = (requested) => {
|
|
630
|
-
const selection = liveSelection() ?? lastSelection;
|
|
631
|
-
const isListInLine = requested === "list" && options.isInline;
|
|
632
|
-
if (selection === void 0 || isListInLine) return;
|
|
633
|
-
const current = readText();
|
|
634
|
-
const changed = requested === "list" ? toggleList(current, selection) : toggleMark(current, selection, requested);
|
|
635
|
-
change(changed, selection);
|
|
742
|
+
schedule();
|
|
636
743
|
};
|
|
637
|
-
const linkTo = (href) => {
|
|
638
|
-
const selection = lastSelection;
|
|
639
|
-
if (selection === void 0) return;
|
|
640
|
-
const current = readText();
|
|
641
|
-
const target = isCollapsed(selection) ? linkSelectionAt(current, selection.start) : selection;
|
|
642
|
-
if (target === void 0) return;
|
|
643
|
-
change(setLink(current, target, href), selection);
|
|
644
|
-
};
|
|
645
|
-
const pressKey = (event) => {
|
|
646
|
-
const isEnd = event.key === "Enter" && options.isInline && !event.shiftKey;
|
|
647
|
-
const shortcut = formatShortcutOf(event);
|
|
648
|
-
if (event.key === "Escape") {
|
|
649
|
-
event.preventDefault();
|
|
650
|
-
end("reverted");
|
|
651
|
-
} else if (isEnd) {
|
|
652
|
-
event.preventDefault();
|
|
653
|
-
end("kept");
|
|
654
|
-
} else if (shortcut !== void 0) {
|
|
655
|
-
event.preventDefault();
|
|
656
|
-
if (shortcut !== "none") format(shortcut);
|
|
657
|
-
}
|
|
658
|
-
};
|
|
659
|
-
const pastePlainText = (event) => {
|
|
660
|
-
event.preventDefault();
|
|
661
|
-
const pasted = event.clipboardData?.getData("text/plain") ?? "";
|
|
662
|
-
const words = options.isInline ? pasted.replace(PASTED_LINE_BREAKS, SPACE) : pasted;
|
|
663
|
-
insertPlainText(element, words);
|
|
664
|
-
reportInput();
|
|
665
|
-
};
|
|
666
|
-
const refuseDrop = (event) => {
|
|
667
|
-
event.preventDefault();
|
|
668
|
-
};
|
|
669
|
-
const endOnBlur = () => {
|
|
670
|
-
clearTimeout(blurTimer);
|
|
671
|
-
blurTimer = setTimeout(() => {
|
|
672
|
-
const isFocusOnPage = document2.hasFocus();
|
|
673
|
-
const isFocusInText = document2.activeElement === element;
|
|
674
|
-
if (isFocusOnPage && !isFocusInText) end("kept");
|
|
675
|
-
});
|
|
676
|
-
};
|
|
677
|
-
const stopListening = () => {
|
|
678
|
-
clearTimeout(blurTimer);
|
|
679
|
-
if (reportFrame !== void 0) view?.cancelAnimationFrame(reportFrame);
|
|
680
|
-
element.removeEventListener("input", reportInput);
|
|
681
|
-
element.removeEventListener("keydown", pressKey);
|
|
682
|
-
element.removeEventListener("paste", pastePlainText);
|
|
683
|
-
element.removeEventListener("drop", refuseDrop);
|
|
684
|
-
element.removeEventListener("blur", endOnBlur);
|
|
685
|
-
document2.removeEventListener("selectionchange", followSelection);
|
|
686
|
-
document2.removeEventListener("scroll", scheduleReport, true);
|
|
687
|
-
view?.removeEventListener("resize", scheduleReport);
|
|
688
|
-
makeUneditable(element);
|
|
689
|
-
};
|
|
690
|
-
let hasEnded = false;
|
|
691
|
-
const end = (outcome) => {
|
|
692
|
-
if (hasEnded) return;
|
|
693
|
-
hasEnded = true;
|
|
694
|
-
stopListening();
|
|
695
|
-
options.onSelect(void 0);
|
|
696
|
-
options.onEnd(outcome);
|
|
697
|
-
};
|
|
698
|
-
makeEditable(element, EDITABLE_AS_RICH_TEXT);
|
|
699
|
-
element.addEventListener("input", reportInput);
|
|
700
|
-
element.addEventListener("keydown", pressKey);
|
|
701
|
-
element.addEventListener("paste", pastePlainText);
|
|
702
|
-
element.addEventListener("drop", refuseDrop);
|
|
703
|
-
element.addEventListener("blur", endOnBlur);
|
|
704
|
-
document2.addEventListener("selectionchange", followSelection);
|
|
705
|
-
document2.addEventListener("scroll", scheduleReport, true);
|
|
706
|
-
view?.addEventListener("resize", scheduleReport);
|
|
707
|
-
element.focus();
|
|
708
744
|
return {
|
|
709
|
-
|
|
710
|
-
|
|
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,
|
|
711
754
|
stop: () => {
|
|
712
|
-
|
|
755
|
+
if (reportFrame !== void 0) view?.cancelAnimationFrame(reportFrame);
|
|
713
756
|
}
|
|
714
757
|
};
|
|
715
758
|
}
|
|
716
|
-
function readTextOf(element, isInline) {
|
|
717
|
-
const read = readRichTextFrom(element);
|
|
718
|
-
return isInline ? asOneParagraph(read) : read;
|
|
719
|
-
}
|
|
720
|
-
function render(element, text, isInline) {
|
|
721
|
-
if (isInline) renderInlineRichText(element, text);
|
|
722
|
-
else renderRichText(element, text);
|
|
723
|
-
}
|
|
724
759
|
function measureSelection(element, text, selection) {
|
|
725
760
|
if (selection === void 0) return void 0;
|
|
726
761
|
const range = isSelectionIn(element) ? element.ownerDocument.getSelection()?.getRangeAt(0) : rangeOf(element, selection);
|
|
@@ -734,7 +769,8 @@ function measureSelection(element, text, selection) {
|
|
|
734
769
|
}
|
|
735
770
|
function boxOfRange(range) {
|
|
736
771
|
const whole = range.getBoundingClientRect();
|
|
737
|
-
const
|
|
772
|
+
const hasOwnBox = whole.width > 0 || whole.height > 0;
|
|
773
|
+
const rectangle = hasOwnBox ? whole : range.getClientRects()[0];
|
|
738
774
|
if (rectangle === void 0) return void 0;
|
|
739
775
|
const isShown = rectangle.width > 0 || rectangle.height > 0;
|
|
740
776
|
if (!isShown) return void 0;
|
|
@@ -751,6 +787,118 @@ function isSelectionIn(element) {
|
|
|
751
787
|
const hasRange = (selection?.rangeCount ?? 0) > 0;
|
|
752
788
|
return hasRange && anchor !== null && element.contains(anchor);
|
|
753
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
|
+
}
|
|
754
902
|
function formatShortcutOf(event) {
|
|
755
903
|
const hasCommandKey = event.metaKey || event.ctrlKey;
|
|
756
904
|
if (!hasCommandKey || event.altKey || event.shiftKey) return void 0;
|
|
@@ -760,12 +908,35 @@ function formatShortcutOf(event) {
|
|
|
760
908
|
case "KeyI":
|
|
761
909
|
return "italic";
|
|
762
910
|
case "KeyU":
|
|
763
|
-
return "
|
|
911
|
+
return "refused";
|
|
764
912
|
default:
|
|
765
913
|
return void 0;
|
|
766
914
|
}
|
|
767
915
|
}
|
|
768
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
|
+
|
|
769
940
|
// src/bridge/page-typing.ts
|
|
770
941
|
var PREPARED_TYPING_TIMEOUT_MS = 3e3;
|
|
771
942
|
var EDITABLE_VALUES = {
|
|
@@ -773,93 +944,111 @@ var EDITABLE_VALUES = {
|
|
|
773
944
|
rich: EDITABLE_AS_RICH_TEXT
|
|
774
945
|
};
|
|
775
946
|
function createPageTyping(options) {
|
|
776
|
-
|
|
777
|
-
let
|
|
947
|
+
const { send } = options;
|
|
948
|
+
let typing;
|
|
778
949
|
let prepared;
|
|
779
|
-
const
|
|
950
|
+
const isTyping = () => typing !== void 0 || prepared !== void 0;
|
|
951
|
+
const reportIfOver = () => {
|
|
952
|
+
if (!isTyping()) options.onTypingOver();
|
|
953
|
+
};
|
|
954
|
+
const releasePrepared = () => {
|
|
780
955
|
if (prepared === void 0) return;
|
|
781
956
|
clearTimeout(prepared.timer);
|
|
782
|
-
if (
|
|
957
|
+
if (typing === void 0) {
|
|
783
958
|
prepared.element.blur();
|
|
784
959
|
makeUneditable(prepared.element);
|
|
785
960
|
}
|
|
786
961
|
prepared = void 0;
|
|
787
962
|
};
|
|
963
|
+
const handOverPrepared = () => {
|
|
964
|
+
if (prepared !== void 0) clearTimeout(prepared.timer);
|
|
965
|
+
prepared = void 0;
|
|
966
|
+
};
|
|
788
967
|
const prepare = (element, kind) => {
|
|
789
968
|
const isTypedAlready = element.hasAttribute("contenteditable");
|
|
790
969
|
if (isTypedAlready) return;
|
|
791
|
-
|
|
970
|
+
releasePrepared();
|
|
792
971
|
makeEditable(element, EDITABLE_VALUES[kind]);
|
|
793
972
|
element.focus({ preventScroll: true });
|
|
794
973
|
const timer = setTimeout(() => {
|
|
795
|
-
|
|
974
|
+
releasePrepared();
|
|
975
|
+
reportIfOver();
|
|
796
976
|
}, PREPARED_TYPING_TIMEOUT_MS);
|
|
797
977
|
prepared = { element, timer };
|
|
798
978
|
};
|
|
799
979
|
const stop = () => {
|
|
800
|
-
|
|
980
|
+
typing?.stop();
|
|
801
981
|
};
|
|
802
|
-
const
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
982
|
+
const endedAt = (path) => (outcome) => {
|
|
983
|
+
typing = void 0;
|
|
984
|
+
send({ type: "typing-ended", path, outcome });
|
|
985
|
+
reportIfOver();
|
|
806
986
|
};
|
|
807
|
-
const
|
|
987
|
+
const begin = (start) => {
|
|
808
988
|
stop();
|
|
809
|
-
const { path } =
|
|
989
|
+
const { path } = start;
|
|
810
990
|
const element = findMarkedElement(options.document, path);
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
991
|
+
if (element === void 0 || !start.canTypeIn(element)) {
|
|
992
|
+
releasePrepared();
|
|
993
|
+
send({ type: "typing-refused", path });
|
|
994
|
+
reportIfOver();
|
|
815
995
|
return;
|
|
816
996
|
}
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
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
|
+
})
|
|
826
1021
|
});
|
|
827
|
-
placeCaretAt(element, request.point);
|
|
828
1022
|
};
|
|
829
|
-
const
|
|
830
|
-
|
|
831
|
-
const
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
},
|
|
850
|
-
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
|
+
}
|
|
851
1043
|
});
|
|
852
|
-
richEditing = editing;
|
|
853
|
-
stopEditing = editing.stop;
|
|
854
|
-
placeCaretAt(element, request.point);
|
|
855
1044
|
};
|
|
856
1045
|
return {
|
|
857
1046
|
prepare,
|
|
858
|
-
isTyping
|
|
859
|
-
start,
|
|
860
|
-
startRich,
|
|
861
|
-
format: (format) =>
|
|
862
|
-
setLink: (href) =>
|
|
1047
|
+
isTyping,
|
|
1048
|
+
start: typePlainText,
|
|
1049
|
+
startRich: typeRichText,
|
|
1050
|
+
format: (format) => typing?.richText?.format(format),
|
|
1051
|
+
setLink: (href) => typing?.richText?.setLink(href),
|
|
863
1052
|
stop
|
|
864
1053
|
};
|
|
865
1054
|
}
|
|
@@ -895,11 +1084,21 @@ function connectPage(document2, schema, send) {
|
|
|
895
1084
|
const layout = createLayoutReports(document2, (measured) => {
|
|
896
1085
|
send({ type: "layout", layout: measured });
|
|
897
1086
|
});
|
|
898
|
-
const
|
|
1087
|
+
const showHeldPage = () => {
|
|
1088
|
+
renderer.showHeldPage();
|
|
1089
|
+
};
|
|
1090
|
+
const typing = createPageTyping({
|
|
1091
|
+
document: document2,
|
|
1092
|
+
send,
|
|
1093
|
+
onInput: layout.schedule,
|
|
1094
|
+
onTypingOver: showHeldPage
|
|
1095
|
+
});
|
|
1096
|
+
const presses = followPresses(document2, showHeldPage);
|
|
899
1097
|
const renderer = createPageRenderer({
|
|
900
1098
|
document: document2,
|
|
901
1099
|
// Replacing the page under a text being typed would throw the typing away.
|
|
902
1100
|
canReplacePage: () => !typing.isTyping(),
|
|
1101
|
+
isPagePressed: presses.isPressed,
|
|
903
1102
|
onRendered: () => {
|
|
904
1103
|
layout.reportNow();
|
|
905
1104
|
send({ type: "rendered" });
|
|
@@ -909,31 +1108,45 @@ function connectPage(document2, schema, send) {
|
|
|
909
1108
|
send({ type: "render-failed" });
|
|
910
1109
|
}
|
|
911
1110
|
});
|
|
912
|
-
const
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
typing
|
|
918
|
-
|
|
1111
|
+
const page = {
|
|
1112
|
+
document: document2,
|
|
1113
|
+
schema,
|
|
1114
|
+
send,
|
|
1115
|
+
layout,
|
|
1116
|
+
typing,
|
|
1117
|
+
renderer
|
|
919
1118
|
};
|
|
920
|
-
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 {
|
|
921
1137
|
onHover: (path) => {
|
|
922
1138
|
send({ type: "hover", path });
|
|
923
1139
|
},
|
|
924
1140
|
onClick: (pointer) => {
|
|
925
|
-
|
|
926
|
-
if (pointer !== void 0 && kind !== void 0) {
|
|
927
|
-
typing.prepare(pointer.element, kind);
|
|
928
|
-
}
|
|
929
|
-
const target = pointer === void 0 ? void 0 : { path: pointer.path, point: pointer.point };
|
|
930
|
-
send({ type: "click", target });
|
|
1141
|
+
clickOn(page, pointer);
|
|
931
1142
|
},
|
|
932
1143
|
onLinkHover: layout.hoverLink,
|
|
933
1144
|
onLinkClick: (link) => {
|
|
934
1145
|
send({ type: "link-click", href: link.href });
|
|
935
1146
|
},
|
|
936
|
-
onKeyDown:
|
|
1147
|
+
onKeyDown: (event) => {
|
|
1148
|
+
pressKey(page, event);
|
|
1149
|
+
},
|
|
937
1150
|
onFileDragOver: (path) => {
|
|
938
1151
|
const isTaken = imageFieldAt(schema, path) !== void 0;
|
|
939
1152
|
send({ type: "file-drag", path: isTaken ? path : void 0 });
|
|
@@ -947,44 +1160,55 @@ function connectPage(document2, schema, send) {
|
|
|
947
1160
|
if (isTaken) send({ type: "file-drop", path, file });
|
|
948
1161
|
},
|
|
949
1162
|
onLayoutChange: layout.schedule
|
|
950
|
-
});
|
|
951
|
-
layout.reportNow();
|
|
952
|
-
const receive = (message) => {
|
|
953
|
-
switch (message.type) {
|
|
954
|
-
case "connect":
|
|
955
|
-
break;
|
|
956
|
-
case "render":
|
|
957
|
-
renderer.render(message.content);
|
|
958
|
-
break;
|
|
959
|
-
case "start-typing":
|
|
960
|
-
typing.start(message);
|
|
961
|
-
break;
|
|
962
|
-
case "start-rich-typing":
|
|
963
|
-
typing.startRich(message);
|
|
964
|
-
break;
|
|
965
|
-
case "format-typed":
|
|
966
|
-
typing.format(message.format);
|
|
967
|
-
break;
|
|
968
|
-
case "link-typed":
|
|
969
|
-
typing.setLink(message.href);
|
|
970
|
-
break;
|
|
971
|
-
case "stop-typing":
|
|
972
|
-
typing.stop();
|
|
973
|
-
break;
|
|
974
|
-
case "scroll-to":
|
|
975
|
-
scrollToField(document2, message.path);
|
|
976
|
-
break;
|
|
977
|
-
}
|
|
978
1163
|
};
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
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":
|
|
982
1206
|
typing.stop();
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
}
|
|
1207
|
+
break;
|
|
1208
|
+
case "scroll-to":
|
|
1209
|
+
scrollToField(page.document, message.path);
|
|
1210
|
+
break;
|
|
1211
|
+
}
|
|
988
1212
|
}
|
|
989
1213
|
function scrollToField(document2, path) {
|
|
990
1214
|
const element = findClosestMarkedElement(document2, path);
|