@37signals/lexxy 0.9.15-alpha.1 → 0.9.15-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lexxy.esm.js +67 -24
- package/package.json +1 -1
package/dist/lexxy.esm.js
CHANGED
|
@@ -5,7 +5,7 @@ import { SKIP_DOM_SELECTION_TAG, CAN_UNDO_COMMAND, COMMAND_PRIORITY_LOW, CAN_RED
|
|
|
5
5
|
import { LinkNode, $createAutoLinkNode, $toggleLink, $createLinkNode, $isLinkNode, AutoLinkNode } from '@lexical/link';
|
|
6
6
|
import { buildEditorFromExtensions } from '@lexical/extension';
|
|
7
7
|
import { ListNode, ListItemNode, $getListDepth, INSERT_UNORDERED_LIST_COMMAND, INSERT_ORDERED_LIST_COMMAND, $isListItemNode, $isListNode, registerList } from '@lexical/list';
|
|
8
|
-
import { $getNearestNodeOfType, $wrapNodeInElement, $lastToFirstIterator, $descendantsMatching, mergeRegister, $insertFirst, $unwrapAndFilterDescendants, $firstToLastIterator, $getNearestBlockElementAncestorOrThrow, IS_APPLE } from '@lexical/utils';
|
|
8
|
+
import { $getNearestNodeOfType, $wrapNodeInElement, $lastToFirstIterator, $descendantsMatching, mergeRegister, $insertNodeToNearestRoot, $insertFirst, $unwrapAndFilterDescendants, $firstToLastIterator, $getNearestBlockElementAncestorOrThrow, IS_APPLE } from '@lexical/utils';
|
|
9
9
|
import { registerPlainText } from '@lexical/plain-text';
|
|
10
10
|
import { RichTextExtension, $isQuoteNode, $isHeadingNode, $createHeadingNode, $createQuoteNode, QuoteNode, HeadingNode, registerRichText } from '@lexical/rich-text';
|
|
11
11
|
import { $generateNodesFromDOM, $generateHtmlFromNodes } from '@lexical/html';
|
|
@@ -1968,6 +1968,11 @@ function safeCloneEditorState(editorState) {
|
|
|
1968
1968
|
return clone
|
|
1969
1969
|
}
|
|
1970
1970
|
|
|
1971
|
+
const INITIAL_PREVIEW_POLL_DELAY_MS = 3000;
|
|
1972
|
+
const MAX_PREVIEW_POLL_DELAY_MS = 120000;
|
|
1973
|
+
const MAX_PREVIEW_POLL_ATTEMPTS = 20;
|
|
1974
|
+
|
|
1975
|
+
|
|
1971
1976
|
class ActionTextAttachmentNode extends DecoratorNode {
|
|
1972
1977
|
static getType() {
|
|
1973
1978
|
return "action_text_attachment"
|
|
@@ -2042,7 +2047,7 @@ class ActionTextAttachmentNode extends DecoratorNode {
|
|
|
2042
2047
|
return Lexxy.global.get("attachmentTagName")
|
|
2043
2048
|
}
|
|
2044
2049
|
|
|
2045
|
-
constructor({ tagName, sgid, src, previewSrc, previewable, pendingPreview, altText, caption, contentType, fileName, fileSize, width, height, uploadError }, key) {
|
|
2050
|
+
constructor({ tagName, sgid, src, previewSrc, previewable, previewStatusUrl, pendingPreview, altText, caption, contentType, fileName, fileSize, width, height, uploadError }, key) {
|
|
2046
2051
|
super(key);
|
|
2047
2052
|
|
|
2048
2053
|
this.tagName = tagName || ActionTextAttachmentNode.TAG_NAME;
|
|
@@ -2050,6 +2055,7 @@ class ActionTextAttachmentNode extends DecoratorNode {
|
|
|
2050
2055
|
this.src = src;
|
|
2051
2056
|
this.previewSrc = previewSrc;
|
|
2052
2057
|
this.previewable = parseBoolean(previewable);
|
|
2058
|
+
this.previewStatusUrl = previewStatusUrl;
|
|
2053
2059
|
this.pendingPreview = pendingPreview;
|
|
2054
2060
|
this.altText = altText || "";
|
|
2055
2061
|
this.caption = caption || "";
|
|
@@ -2128,6 +2134,8 @@ class ActionTextAttachmentNode extends DecoratorNode {
|
|
|
2128
2134
|
sgid: this.sgid,
|
|
2129
2135
|
src: this.src,
|
|
2130
2136
|
previewable: this.previewable,
|
|
2137
|
+
previewStatusUrl: this.previewStatusUrl,
|
|
2138
|
+
pendingPreview: this.pendingPreview,
|
|
2131
2139
|
altText: this.altText,
|
|
2132
2140
|
caption: this.caption,
|
|
2133
2141
|
contentType: this.contentType,
|
|
@@ -2246,41 +2254,68 @@ class ActionTextAttachmentNode extends DecoratorNode {
|
|
|
2246
2254
|
});
|
|
2247
2255
|
}
|
|
2248
2256
|
|
|
2257
|
+
// While the file-icon is shown, watch for the preview to become ready.
|
|
2258
|
+
// With a status URL, poll it (2xx = processing, anything else = ready).
|
|
2259
|
+
// Without one, preload the preview URL once and swap on load.
|
|
2249
2260
|
#pollForPreview(figure) {
|
|
2261
|
+
if (this.previewStatusUrl) {
|
|
2262
|
+
this.#waitForPreviewByPollingStatus(figure);
|
|
2263
|
+
} else {
|
|
2264
|
+
this.#waitForPreviewByPreloadingImage(figure);
|
|
2265
|
+
}
|
|
2266
|
+
}
|
|
2267
|
+
|
|
2268
|
+
#waitForPreviewByPollingStatus(figure) {
|
|
2250
2269
|
let attempt = 0;
|
|
2251
|
-
const maxAttempts = 10;
|
|
2252
2270
|
|
|
2253
|
-
const
|
|
2271
|
+
const tryStatus = async () => {
|
|
2254
2272
|
if (!this.editor.read(() => this.isAttached())) return
|
|
2255
2273
|
|
|
2256
|
-
|
|
2257
|
-
|
|
2274
|
+
try {
|
|
2275
|
+
// redirect: "manual" prevents fetch from transparently following a
|
|
2276
|
+
// 3xx response — without it, a status endpoint that redirected to,
|
|
2277
|
+
// say, the preview URL would resolve to a 200 and look like
|
|
2278
|
+
// "still processing." The contract is "any non-2xx means done."
|
|
2279
|
+
const response = await fetch(this.previewStatusUrl, { credentials: "include", redirect: "manual" });
|
|
2258
2280
|
|
|
2259
|
-
img.onload = () => {
|
|
2260
2281
|
if (!this.editor.read(() => this.isAttached())) return
|
|
2261
2282
|
|
|
2262
|
-
|
|
2263
|
-
// generated from PDF/video content is significantly larger.
|
|
2264
|
-
if (img.naturalWidth > 150 && img.naturalHeight > 150) {
|
|
2265
|
-
this.#swapToPreviewDOM(figure, cacheBustedSrc);
|
|
2266
|
-
} else {
|
|
2283
|
+
if (response.ok) {
|
|
2267
2284
|
retry();
|
|
2285
|
+
} else {
|
|
2286
|
+
this.#swapToPreviewDOM(figure, this.src);
|
|
2268
2287
|
}
|
|
2269
|
-
}
|
|
2270
|
-
|
|
2271
|
-
|
|
2288
|
+
} catch {
|
|
2289
|
+
retry();
|
|
2290
|
+
}
|
|
2272
2291
|
};
|
|
2273
2292
|
|
|
2274
2293
|
const retry = () => {
|
|
2275
2294
|
attempt++;
|
|
2276
|
-
if (attempt <
|
|
2277
|
-
const delay = Math.min(2000 * Math.pow(1.5, attempt),
|
|
2278
|
-
setTimeout(
|
|
2295
|
+
if (attempt < MAX_PREVIEW_POLL_ATTEMPTS && this.editor.read(() => this.isAttached())) {
|
|
2296
|
+
const delay = Math.min(2000 * Math.pow(1.5, attempt), MAX_PREVIEW_POLL_DELAY_MS);
|
|
2297
|
+
setTimeout(tryStatus, delay);
|
|
2279
2298
|
}
|
|
2280
2299
|
};
|
|
2281
2300
|
|
|
2282
2301
|
// Give the server time to start processing before the first attempt
|
|
2283
|
-
setTimeout(
|
|
2302
|
+
setTimeout(tryStatus, INITIAL_PREVIEW_POLL_DELAY_MS);
|
|
2303
|
+
}
|
|
2304
|
+
|
|
2305
|
+
#waitForPreviewByPreloadingImage(figure) {
|
|
2306
|
+
const img = new Image();
|
|
2307
|
+
img.onload = () => {
|
|
2308
|
+
if (!this.editor.read(() => this.isAttached())) return
|
|
2309
|
+
this.#swapToPreviewDOM(figure, this.src);
|
|
2310
|
+
};
|
|
2311
|
+
img.onerror = () => {
|
|
2312
|
+
// Clear pendingPreview so undo/redo or any JSON round-trip doesn't
|
|
2313
|
+
// re-enter the pending flow and issue another fetch. The file icon
|
|
2314
|
+
// stays as the stable fallback.
|
|
2315
|
+
if (!this.editor.read(() => this.isAttached())) return
|
|
2316
|
+
this.patchAndRewriteHistory({ pendingPreview: false });
|
|
2317
|
+
};
|
|
2318
|
+
img.src = this.src;
|
|
2284
2319
|
}
|
|
2285
2320
|
|
|
2286
2321
|
#swapToPreviewDOM(figure, previewSrc) {
|
|
@@ -3625,8 +3660,7 @@ class CommandDispatcher {
|
|
|
3625
3660
|
}
|
|
3626
3661
|
|
|
3627
3662
|
dispatchInsertHorizontalDivider() {
|
|
3628
|
-
|
|
3629
|
-
this.editor.focus();
|
|
3663
|
+
$insertNodeToNearestRoot(new HorizontalDividerNode);
|
|
3630
3664
|
}
|
|
3631
3665
|
|
|
3632
3666
|
dispatchSetFormatHeadingLarge() {
|
|
@@ -4859,7 +4893,7 @@ class ImageGalleryNode extends ElementNode {
|
|
|
4859
4893
|
replaceWithSingularChild() {
|
|
4860
4894
|
if (this.#hasSingularChild) {
|
|
4861
4895
|
const child = this.getFirstChild();
|
|
4862
|
-
return this.replace(child)
|
|
4896
|
+
return this.replace($makeSafeForRoot(child))
|
|
4863
4897
|
}
|
|
4864
4898
|
}
|
|
4865
4899
|
|
|
@@ -5287,6 +5321,7 @@ class AttachmentNodeConversion {
|
|
|
5287
5321
|
fileName: blob.filename,
|
|
5288
5322
|
fileSize: blob.byte_size,
|
|
5289
5323
|
previewable: blob.previewable,
|
|
5324
|
+
previewStatusUrl: blob.preview_status_url
|
|
5290
5325
|
}
|
|
5291
5326
|
}
|
|
5292
5327
|
|
|
@@ -8638,6 +8673,10 @@ class LexicalPromptElement extends HTMLElement {
|
|
|
8638
8673
|
return this.getAttribute("only-at")
|
|
8639
8674
|
}
|
|
8640
8675
|
|
|
8676
|
+
get verticalDirection() {
|
|
8677
|
+
return this.getAttribute("vertical-direction")
|
|
8678
|
+
}
|
|
8679
|
+
|
|
8641
8680
|
get open() {
|
|
8642
8681
|
return this.popoverElement?.classList?.contains("lexxy-prompt-menu--visible")
|
|
8643
8682
|
}
|
|
@@ -8866,11 +8905,15 @@ class LexicalPromptElement extends HTMLElement {
|
|
|
8866
8905
|
|
|
8867
8906
|
const popoverRect = this.popoverElement.getBoundingClientRect();
|
|
8868
8907
|
|
|
8869
|
-
if (popoverRect.right >
|
|
8908
|
+
if (popoverRect.right > editorRect.right) {
|
|
8870
8909
|
this.popoverElement.toggleAttribute("data-clipped-at-right", true);
|
|
8871
8910
|
}
|
|
8872
8911
|
|
|
8873
|
-
|
|
8912
|
+
const forceTop = this.verticalDirection === "top";
|
|
8913
|
+
const forceBottom = this.verticalDirection === "bottom";
|
|
8914
|
+
const overflowsWindow = popoverRect.bottom > window.innerHeight;
|
|
8915
|
+
|
|
8916
|
+
if (!forceBottom && (forceTop || overflowsWindow)) {
|
|
8874
8917
|
this.#setPopoverOffsetY(contentRect.height - y + fontSize);
|
|
8875
8918
|
this.popoverElement.toggleAttribute("data-clipped-at-bottom", true);
|
|
8876
8919
|
}
|