@stll/folio-react 0.1.0 → 0.2.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/dist/{FindReplaceDialog-CG1VKA_g.js → FindReplaceDialog-EFjmqEbg.js} +1 -1
- package/dist/{FootnotePropertiesDialog-MuW4D3gt.js → FootnotePropertiesDialog-VfF7SQo2.js} +1 -1
- package/dist/{ImagePositionDialog-BQnOnDf4.js → ImagePositionDialog-FGLCOf0Q.js} +1 -1
- package/dist/{ImagePropertiesDialog-Dn4N7qZv.js → ImagePropertiesDialog-Baokui4M.js} +2 -2
- package/dist/{PageSetupDialog-CxJUEkoC.js → PageSetupDialog-Df2HjHiu.js} +1 -1
- package/dist/{TablePropertiesDialog-mQMnGfHu.js → TablePropertiesDialog-ZmzjSqME.js} +1 -1
- package/dist/{TextContextMenu-DzTbp2sn.js → TextContextMenu-CG0V37wi.js} +5 -1
- package/dist/editor.css +1 -1
- package/dist/{folio-ui-o_rftArH.js → folio-ui-CA59webC.js} +13 -6
- package/dist/index.d.ts +59 -3
- package/dist/index.js +223 -48
- package/dist/messages-heGr9XLo.js +2827 -0
- package/dist/messages.d.ts +1 -1
- package/dist/messages.js +1 -2166
- package/dist/standalone.css +1 -0
- package/package.json +6 -3
package/dist/index.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { t as containedHandler } from "./contained-handler-4uiqUVRn.js";
|
|
2
|
-
import { i as cn, n as FolioUIProvider, r as useFolioUI, t as DEFAULT_COMPONENTS } from "./folio-ui-
|
|
2
|
+
import { i as cn, n as FolioUIProvider, r as useFolioUI, t as DEFAULT_COMPONENTS } from "./folio-ui-CA59webC.js";
|
|
3
3
|
import { i as findInDocument, o as scrollToMatch, t as useFindReplace$1 } from "./useFindReplace-MZ8wi31A.js";
|
|
4
|
+
import { n as getFolioMessages } from "./messages-heGr9XLo.js";
|
|
4
5
|
import * as React$1 from "react";
|
|
5
|
-
import { Component, Suspense, cloneElement, createContext, isValidElement, lazy, memo, useCallback, useEffect, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
6
|
+
import React, { Component, Suspense, cloneElement, createContext, isValidElement, lazy, memo, useCallback, useEffect, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
6
7
|
import { AlertCircleIcon, AlertTriangleIcon, AlignCenterIcon, AlignJustifyIcon, AlignLeftIcon, AlignRightIcon, BaselineIcon, BoldIcon, CheckIcon, ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon, ClipboardCopyIcon, EyeIcon, FileIcon, GlobeIcon, ImageIcon, IndentDecreaseIcon, IndentIncreaseIcon, InfoIcon, ItalicIcon, LinkIcon, ListIcon, ListOrderedIcon, MoreHorizontalIcon, PaintbrushIcon, PenLineIcon, PencilIcon, PilcrowIcon, Redo2Icon, RulerIcon, SeparatorHorizontalIcon, SquarePenIcon, StickyNoteIcon, TableIcon, TableOfContentsIcon, TypeIcon, UnderlineIcon, Undo2Icon, UnlinkIcon, XIcon } from "lucide-react";
|
|
7
|
-
import { useTranslations } from "use-intl";
|
|
8
|
+
import { IntlProvider, useTranslations } from "use-intl";
|
|
8
9
|
import { applyFolioAIEditOperations, applyFolioAIEditOperations as applyFolioAIEditOperations$1, createFolioAIEditSnapshot, createFolioAIEditSnapshot as createFolioAIEditSnapshot$1, diffWordSegments, hashFolioAIBlockText, normalizeFolioAIBlockText } from "@stll/folio-core/ai-edits";
|
|
9
10
|
import { normalizeBaseDirection } from "@stll/folio-core/docx/normalizeBaseDirection";
|
|
10
11
|
import { getCachedNumberingMap } from "@stll/folio-core/docx/numberingParser";
|
|
@@ -66,17 +67,18 @@ import { collectRunFontsAtPmPositions, measureContentLeft, projectRangesToRects
|
|
|
66
67
|
import { isReadOnlyEditKey } from "@stll/folio-core/paged-layout/readOnlyEditAttempt";
|
|
67
68
|
import { getPageScrollTarget, prefersReducedMotionBehavior } from "@stll/folio-core/paged-layout/scrollNavigation";
|
|
68
69
|
import { PAGES_CONTAINER_CLASS, scrollFolioPositionIntoView, scrollPagesToPmPosition } from "@stll/folio-core/paged-layout/scrollToPmPosition";
|
|
70
|
+
import { flashParagraphFragmentsByParaId } from "@stll/folio-core/paged-layout/paragraphFlash";
|
|
69
71
|
import { DEFAULT_PAGE_HEIGHT_PX, getMargins, getPageSize, twipsToPixels as twipsToPixels$1 } from "@stll/folio-core/paged-layout/sectionGeometry";
|
|
70
72
|
import { resizeColumnPair } from "@stll/folio-core/paged-layout/tableColumnResize";
|
|
71
73
|
import { tableInsertButtonOffset } from "@stll/folio-core/paged-layout/tableInsertButtonGeometry";
|
|
72
74
|
import { getTransactionDirtyRange } from "@stll/folio-core/paged-layout/transactionDirtyRange";
|
|
75
|
+
import { findParagraphByParaId, findStartPosForParaId } from "@stll/folio-core/prosemirror/utils/findParagraphByParaId";
|
|
73
76
|
import { expectFontFamilyMarkAttrs, expectImageAttrs, expectTableAttrs, expectTableCellAttrs, mergeImageAttrs, mergeTableAttrs, mergeTableCellAttrs, mergeTableRowAttrs } from "@stll/folio-core/prosemirror/attrs";
|
|
74
77
|
import { extractEmbeddedFonts } from "@stll/folio-core/fonts/embeddedFonts";
|
|
75
78
|
import { panic } from "better-result";
|
|
76
79
|
import { collectRemoteSelections, createHiddenEditorManager, createHiddenEditorState } from "@stll/folio-core/controller/hiddenEditorManager";
|
|
77
80
|
import { createFolioAIEditSnapshot as createFolioAIEditSnapshot$2 } from "@stll/folio-core/ai-edits/snapshot";
|
|
78
|
-
import {
|
|
79
|
-
import { deriveBlockId, getFolioParaIdFromBlockId, getFolioParaIdFromBlockId as getFolioParaIdFromBlockId$1, isFolioBlockId, isSequentialFolioBlockId } from "@stll/folio-core/types/block-id";
|
|
81
|
+
import { deriveBlockId, getFolioParaIdFromBlockId, getFolioParaIdFromBlockId as getFolioParaIdFromBlockId$1, getSequentialFolioBlockIdIndex, isFolioBlockId, isSequentialFolioBlockId } from "@stll/folio-core/types/block-id";
|
|
80
82
|
import { CONTENT_CONTROL_WIDGET_EVENT_NAME, dispatchDatePick, dispatchDropdownPick } from "@stll/folio-core/prosemirror/plugins/contentControlWidgets";
|
|
81
83
|
import { ErrorManager } from "@stll/folio-core/managers/ErrorManager";
|
|
82
84
|
import { ContextMenuManager } from "@stll/folio-core/managers/ContextMenuManager";
|
|
@@ -91,6 +93,7 @@ import { applyImagePosition, applyImageProperties, applyImageTransform, applyIma
|
|
|
91
93
|
import { classifyEditorKeydown, deleteSelectedTable, isFocusInInputLike, isMacPlatform } from "@stll/folio-core/managers/editorShortcuts";
|
|
92
94
|
import { getScrollTopForZoomAnchor, getViewportCenterZoomAnchorForZoomChange } from "@stll/folio-core/paged-layout/zoomScrollAnchor";
|
|
93
95
|
import { redo, undo } from "prosemirror-history";
|
|
96
|
+
import { createRoot } from "react-dom/client";
|
|
94
97
|
import { createEmptyDocument } from "@stll/folio-core/utils/createDocument";
|
|
95
98
|
import { createDocx } from "@stll/folio-core/docx/rezip";
|
|
96
99
|
import { DEFAULT_AI_SUGGESTION_PRESETS } from "@stll/folio-core/ai-suggestions/types";
|
|
@@ -623,7 +626,8 @@ const AutocompleteCaretOverlay = ({ caret, text, isStreaming }) => {
|
|
|
623
626
|
position: "absolute",
|
|
624
627
|
left: caret.x,
|
|
625
628
|
top: caret.y,
|
|
626
|
-
lineHeight: `${caret.lineHeight}px
|
|
629
|
+
lineHeight: `${caret.lineHeight}px`,
|
|
630
|
+
...caret.maxWidth !== void 0 ? { maxWidth: `${caret.maxWidth}px` } : {}
|
|
627
631
|
},
|
|
628
632
|
children: [text, /* @__PURE__ */ jsx("span", {
|
|
629
633
|
className: isStreaming ? "folio-autocomplete-caret folio-autocomplete-caret--streaming" : "folio-autocomplete-caret",
|
|
@@ -1849,6 +1853,13 @@ const TRANSACTION_LAYOUT_DEBOUNCE_MS = 32;
|
|
|
1849
1853
|
const TRANSACTION_LAYOUT_MAX_DELAY_MS = 96;
|
|
1850
1854
|
/** Keep the visual caret hidden briefly while typed content relayouts. */
|
|
1851
1855
|
const SELECTION_REVEAL_AFTER_INPUT_DELAY = 120;
|
|
1856
|
+
/**
|
|
1857
|
+
* Readability floor for the autocomplete ghost width: with the caret at the
|
|
1858
|
+
* right content boundary, an unfloored width plus `overflow-wrap: anywhere`
|
|
1859
|
+
* would wrap the suggestion into a one-character-wide vertical column. Near
|
|
1860
|
+
* the margin the ghost overflows slightly past it instead.
|
|
1861
|
+
*/
|
|
1862
|
+
const AUTOCOMPLETE_GHOST_MIN_WIDTH = 100;
|
|
1852
1863
|
const EMPTY_PLUGINS = [];
|
|
1853
1864
|
const EMPTY_TEMPLATE_PREVIEW_ENTRIES = [];
|
|
1854
1865
|
const EMPTY_AI_SUGGESTIONS = [];
|
|
@@ -3219,10 +3230,13 @@ function PagedEditor(props) {
|
|
|
3219
3230
|
const pageOffsetX = (pageRect.left - overlayRect.left) / zoom;
|
|
3220
3231
|
const pageOffsetY = (pageRect.top - overlayRect.top) / zoom;
|
|
3221
3232
|
if (!isCurrentRequest()) return;
|
|
3233
|
+
const caretPage = layout.pages.at(caret.pageIndex);
|
|
3234
|
+
const maxWidth = caretPage ? Math.max(AUTOCOMPLETE_GHOST_MIN_WIDTH, caretPage.size.w - caretPage.margins.right - caret.x) : void 0;
|
|
3222
3235
|
setAutocompleteCaret({
|
|
3223
3236
|
x: caret.x + pageOffsetX,
|
|
3224
3237
|
y: caret.y + pageOffsetY,
|
|
3225
|
-
lineHeight: caret.height
|
|
3238
|
+
lineHeight: caret.height,
|
|
3239
|
+
maxWidth
|
|
3226
3240
|
});
|
|
3227
3241
|
setAutocompleteText(current.text);
|
|
3228
3242
|
setAutocompleteIsStreaming(current.status === "streaming");
|
|
@@ -3558,6 +3572,24 @@ function PagedEditor(props) {
|
|
|
3558
3572
|
inline: "nearest"
|
|
3559
3573
|
});
|
|
3560
3574
|
}, [layout, scrollToPositionImpl]);
|
|
3575
|
+
const scrollToParaIdImpl = useCallback((paraId, options) => {
|
|
3576
|
+
const state = hiddenPMRef.current?.getState();
|
|
3577
|
+
if (!state) return false;
|
|
3578
|
+
const startPos = findStartPosForParaId(state.doc, paraId);
|
|
3579
|
+
if (startPos == null || startPos < 0) return false;
|
|
3580
|
+
scrollToPositionImpl(startPos);
|
|
3581
|
+
requestAnimationFrame(() => {
|
|
3582
|
+
if (options?.highlight) {
|
|
3583
|
+
const pages = pagesContainerRef.current;
|
|
3584
|
+
if (pages) flashParagraphFragmentsByParaId(pages, paraId, options.highlight);
|
|
3585
|
+
}
|
|
3586
|
+
const targetNode = state.doc.nodeAt(startPos);
|
|
3587
|
+
const inner = targetNode?.isTextblock === true ? Math.min(startPos + 1 + targetNode.content.size, state.doc.content.size) : Math.min(startPos + 1, state.doc.content.size);
|
|
3588
|
+
hiddenPMRef.current?.setSelection(inner);
|
|
3589
|
+
hiddenPMRef.current?.focus();
|
|
3590
|
+
});
|
|
3591
|
+
return true;
|
|
3592
|
+
}, [scrollToPositionImpl]);
|
|
3561
3593
|
const focusHiddenEditor = useCallback(() => {
|
|
3562
3594
|
if (readOnly) {
|
|
3563
3595
|
containerRef.current?.focus({ preventScroll: true });
|
|
@@ -4978,6 +5010,7 @@ function PagedEditor(props) {
|
|
|
4978
5010
|
},
|
|
4979
5011
|
scrollToPosition: scrollToPositionImpl,
|
|
4980
5012
|
scrollToPage: scrollToPageImpl,
|
|
5013
|
+
scrollToParaId: scrollToParaIdImpl,
|
|
4981
5014
|
getPageNumberForPmPos(pmPos) {
|
|
4982
5015
|
const container = pagesContainerRef.current;
|
|
4983
5016
|
if (!container) return null;
|
|
@@ -5017,6 +5050,7 @@ function PagedEditor(props) {
|
|
|
5017
5050
|
ensureHiddenEditorView,
|
|
5018
5051
|
folioEditor,
|
|
5019
5052
|
scrollToPageImpl,
|
|
5053
|
+
scrollToParaIdImpl,
|
|
5020
5054
|
scrollToPositionImpl
|
|
5021
5055
|
]);
|
|
5022
5056
|
useEffect(() => {
|
|
@@ -5891,11 +5925,32 @@ const resolveFolioAIBlockRange = ({ blockId, doc, snapshot }) => {
|
|
|
5891
5925
|
to: liveRange.to
|
|
5892
5926
|
};
|
|
5893
5927
|
}
|
|
5894
|
-
const
|
|
5928
|
+
const resolvedSnapshot = snapshot ?? createFolioAIEditSnapshot$2(doc);
|
|
5929
|
+
const anchor = resolvedSnapshot.anchors[blockId] ?? resolveSequentialBlockAnchor(blockId, resolvedSnapshot);
|
|
5895
5930
|
if (!anchor) return null;
|
|
5896
5931
|
return clampRangeToDocSize(doc.content.size, anchor);
|
|
5897
5932
|
};
|
|
5898
5933
|
/**
|
|
5934
|
+
* Resolve a `seq-NNNN` fallback id by document position.
|
|
5935
|
+
*
|
|
5936
|
+
* A sequential id is only minted for a paragraph the source DOCX left
|
|
5937
|
+
* without a `w14:paraId`. The live editor's `ParaIdAllocator` fills
|
|
5938
|
+
* that gap with a fresh random hex paraId, so a snapshot of the live
|
|
5939
|
+
* document keys the block by that hex and never reproduces the
|
|
5940
|
+
* server's `seq-NNNN`: the direct anchor lookup misses, and a
|
|
5941
|
+
* paraId-based live lookup can't match either (no node carries a
|
|
5942
|
+
* `seq-` paraId). The seq number is the block's 1-based position in
|
|
5943
|
+
* the same non-empty-block walk the server extractor and
|
|
5944
|
+
* `createFolioAIEditSnapshot` share, so it indexes the snapshot's
|
|
5945
|
+
* ordered `blocks` directly.
|
|
5946
|
+
*/
|
|
5947
|
+
const resolveSequentialBlockAnchor = (blockId, snapshot) => {
|
|
5948
|
+
const index = getSequentialFolioBlockIdIndex(blockId);
|
|
5949
|
+
if (index === null) return;
|
|
5950
|
+
const block = snapshot.blocks.at(index - 1);
|
|
5951
|
+
return block ? snapshot.anchors[block.id] : void 0;
|
|
5952
|
+
};
|
|
5953
|
+
/**
|
|
5899
5954
|
* Clamp a `{from, to}` pair so both endpoints fit inside a document of
|
|
5900
5955
|
* `docSize` (in PM content positions). Block-boundary snapshots and stale
|
|
5901
5956
|
* range data sometimes produce a `to` one past the last inline position;
|
|
@@ -6206,30 +6261,34 @@ function ContentControlWidgetsOverlay({ getEditorView }) {
|
|
|
6206
6261
|
if (liveView && value.length > 0) dispatchDatePick(liveView, open.pmPos, value);
|
|
6207
6262
|
close();
|
|
6208
6263
|
};
|
|
6209
|
-
return createPortal(/* @__PURE__ */
|
|
6210
|
-
|
|
6211
|
-
|
|
6212
|
-
|
|
6213
|
-
|
|
6214
|
-
|
|
6215
|
-
|
|
6216
|
-
className: "
|
|
6217
|
-
children: open.
|
|
6218
|
-
|
|
6219
|
-
|
|
6220
|
-
|
|
6221
|
-
|
|
6222
|
-
|
|
6223
|
-
|
|
6224
|
-
|
|
6225
|
-
|
|
6226
|
-
|
|
6227
|
-
|
|
6228
|
-
|
|
6229
|
-
|
|
6230
|
-
|
|
6231
|
-
|
|
6232
|
-
|
|
6264
|
+
return createPortal(/* @__PURE__ */ jsx("div", {
|
|
6265
|
+
className: "folio-root",
|
|
6266
|
+
style: { display: "contents" },
|
|
6267
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
6268
|
+
role: "dialog",
|
|
6269
|
+
"aria-label": open.kind === "dropdown" ? t("contentControlDropdownAriaLabel") : t("contentControlDateAriaLabel"),
|
|
6270
|
+
style,
|
|
6271
|
+
className: "bg-popover text-popover-foreground min-w-[10rem] rounded-md border p-1 shadow-md",
|
|
6272
|
+
children: [open.kind === "dropdown" && /* @__PURE__ */ jsx("div", {
|
|
6273
|
+
role: "menu",
|
|
6274
|
+
className: "flex flex-col",
|
|
6275
|
+
children: open.items.length === 0 ? /* @__PURE__ */ jsx("div", {
|
|
6276
|
+
className: "text-muted-foreground px-2 py-1 text-sm",
|
|
6277
|
+
children: t("contentControlDropdownNoOptions")
|
|
6278
|
+
}) : open.items.map((item) => /* @__PURE__ */ jsx(MenuItem, {
|
|
6279
|
+
onClick: () => onDropdownPick(item.value),
|
|
6280
|
+
children: item.displayText
|
|
6281
|
+
}, `${item.value}::${item.displayText}`))
|
|
6282
|
+
}), open.kind === "date" && /* @__PURE__ */ jsx(DatePickerPopover, {
|
|
6283
|
+
clearLabel: t("clearDate"),
|
|
6284
|
+
defaultOpen: true,
|
|
6285
|
+
onChange: (value) => {
|
|
6286
|
+
if (value) onDatePick(value);
|
|
6287
|
+
},
|
|
6288
|
+
showIcon: false,
|
|
6289
|
+
value: null
|
|
6290
|
+
})]
|
|
6291
|
+
})
|
|
6233
6292
|
}), document.body);
|
|
6234
6293
|
}
|
|
6235
6294
|
//#endregion
|
|
@@ -6313,12 +6372,12 @@ const DocumentOutline = ({ headings, scrollContainerRef, topOffset, docSize, onH
|
|
|
6313
6372
|
};
|
|
6314
6373
|
//#endregion
|
|
6315
6374
|
//#region src/components/DocxEditorDialogs.tsx
|
|
6316
|
-
const FindReplaceDialog = lazy(() => import("./FindReplaceDialog-
|
|
6317
|
-
const TablePropertiesDialog = lazy(() => import("./TablePropertiesDialog-
|
|
6318
|
-
const ImagePositionDialog = lazy(() => import("./ImagePositionDialog-
|
|
6319
|
-
const ImagePropertiesDialog = lazy(() => import("./ImagePropertiesDialog-
|
|
6320
|
-
const FootnotePropertiesDialog = lazy(() => import("./FootnotePropertiesDialog-
|
|
6321
|
-
const PageSetupDialog = lazy(() => import("./PageSetupDialog-
|
|
6375
|
+
const FindReplaceDialog = lazy(() => import("./FindReplaceDialog-EFjmqEbg.js").then((m) => ({ default: m.FindReplaceDialog })));
|
|
6376
|
+
const TablePropertiesDialog = lazy(() => import("./TablePropertiesDialog-ZmzjSqME.js").then((m) => ({ default: m.TablePropertiesDialog })));
|
|
6377
|
+
const ImagePositionDialog = lazy(() => import("./ImagePositionDialog-FGLCOf0Q.js").then((m) => ({ default: m.ImagePositionDialog })));
|
|
6378
|
+
const ImagePropertiesDialog = lazy(() => import("./ImagePropertiesDialog-Baokui4M.js").then((m) => ({ default: m.ImagePropertiesDialog })));
|
|
6379
|
+
const FootnotePropertiesDialog = lazy(() => import("./FootnotePropertiesDialog-VfF7SQo2.js").then((m) => ({ default: m.FootnotePropertiesDialog })));
|
|
6380
|
+
const PageSetupDialog = lazy(() => import("./PageSetupDialog-Df2HjHiu.js").then((m) => ({ default: m.PageSetupDialog })));
|
|
6322
6381
|
function DocxEditorDialogs({ findReplace, tableProperties, imagePosition, imageProperties, pageSetup, footnoteProperties }) {
|
|
6323
6382
|
return /* @__PURE__ */ jsxs(Suspense, {
|
|
6324
6383
|
fallback: null,
|
|
@@ -6640,7 +6699,7 @@ function ToolbarButton({ active = false, disabled = false, title, onClick, onDou
|
|
|
6640
6699
|
};
|
|
6641
6700
|
const button = /* @__PURE__ */ jsx("button", {
|
|
6642
6701
|
type: "button",
|
|
6643
|
-
className: cn("flex h-8 w-8 shrink-0 items-center justify-center rounded-md", "transition-colors duration-100", "
|
|
6702
|
+
className: cn("flex h-8 w-8 shrink-0 items-center justify-center rounded-md", "transition-colors duration-100", "antialiased", "text-[var(--doc-text-muted)] hover:bg-[var(--doc-primary-light)] hover:text-[var(--doc-text)]", active && "bg-[var(--doc-primary-light)] text-[var(--doc-text)] hover:bg-[var(--doc-primary-light)]", disabled && "cursor-not-allowed text-[var(--doc-text-subtle)] opacity-[0.16] disabled:hover:bg-transparent disabled:hover:text-[var(--doc-text-subtle)]", className),
|
|
6644
6703
|
onMouseDown: handleMouseDown,
|
|
6645
6704
|
onClick: disabled ? void 0 : onClick,
|
|
6646
6705
|
onDoubleClick: disabled ? void 0 : onDoubleClick,
|
|
@@ -9659,21 +9718,32 @@ const getBuiltinTableStyle = (styleId) => BUILTIN_STYLES.find((s) => s.id === st
|
|
|
9659
9718
|
* (`replaceComments`/`updateComments`) stay in the component because they
|
|
9660
9719
|
* depend on its document-build pipeline.
|
|
9661
9720
|
*/
|
|
9662
|
-
function useFolioComments({ doc, autoOpenReviewSidebar, anchorPositions, editorContentRef }) {
|
|
9721
|
+
function useFolioComments({ doc, autoOpenReviewSidebar, anchorPositions, editorContentRef, commentsProp, onCommentsChange }) {
|
|
9663
9722
|
const [showCommentsSidebar, setShowCommentsSidebar] = useState(false);
|
|
9664
9723
|
const [visibleCommentAuthors, setVisibleCommentAuthors] = useState(null);
|
|
9665
9724
|
const [activeCommentId, setActiveCommentId] = useState(null);
|
|
9666
|
-
const [
|
|
9725
|
+
const [internalComments, setInternalComments] = useState([]);
|
|
9726
|
+
const isControlledComments = commentsProp !== void 0;
|
|
9727
|
+
const comments = isControlledComments ? commentsProp : internalComments;
|
|
9667
9728
|
const commentsDirtyRef = useRef(false);
|
|
9668
|
-
const commentsRef = useRef(
|
|
9729
|
+
const commentsRef = useRef(comments);
|
|
9669
9730
|
commentsRef.current = comments;
|
|
9731
|
+
const onCommentsChangeRef = useRef(onCommentsChange);
|
|
9732
|
+
onCommentsChangeRef.current = onCommentsChange;
|
|
9733
|
+
const setComments = useCallback((next) => {
|
|
9734
|
+
const resolved = typeof next === "function" ? next(commentsRef.current) : next;
|
|
9735
|
+
if (resolved === commentsRef.current) return;
|
|
9736
|
+
commentsRef.current = resolved;
|
|
9737
|
+
if (!isControlledComments) setInternalComments(resolved);
|
|
9738
|
+
onCommentsChangeRef.current?.(resolved);
|
|
9739
|
+
}, [isControlledComments]);
|
|
9670
9740
|
const [isAddingComment, setIsAddingComment] = useState(false);
|
|
9671
9741
|
const [commentSelectionRange, setCommentSelectionRange] = useState(null);
|
|
9672
9742
|
const [addCommentYPosition, setAddCommentYPosition] = useState(null);
|
|
9673
9743
|
const [floatingCommentBtn, setFloatingCommentBtn] = useState(null);
|
|
9674
9744
|
const commentsLoadedRef = useRef(false);
|
|
9675
9745
|
useEffect(() => {
|
|
9676
|
-
if (commentsLoadedRef.current) return;
|
|
9746
|
+
if (isControlledComments || commentsLoadedRef.current) return;
|
|
9677
9747
|
if (!doc) return;
|
|
9678
9748
|
const bodyComments = doc.package.document.comments;
|
|
9679
9749
|
if (bodyComments && bodyComments.length > 0) {
|
|
@@ -9683,7 +9753,12 @@ function useFolioComments({ doc, autoOpenReviewSidebar, anchorPositions, editorC
|
|
|
9683
9753
|
if (autoOpenReviewSidebar) setShowCommentsSidebar(true);
|
|
9684
9754
|
commentsLoadedRef.current = true;
|
|
9685
9755
|
}
|
|
9686
|
-
}, [
|
|
9756
|
+
}, [
|
|
9757
|
+
autoOpenReviewSidebar,
|
|
9758
|
+
doc,
|
|
9759
|
+
isControlledComments,
|
|
9760
|
+
setComments
|
|
9761
|
+
]);
|
|
9687
9762
|
const commentAuthors = useMemo(() => {
|
|
9688
9763
|
const seen = /* @__PURE__ */ new Set();
|
|
9689
9764
|
const authors = [];
|
|
@@ -9769,6 +9844,7 @@ function useFolioComments({ doc, autoOpenReviewSidebar, anchorPositions, editorC
|
|
|
9769
9844
|
return {
|
|
9770
9845
|
comments,
|
|
9771
9846
|
setComments,
|
|
9847
|
+
isControlledComments,
|
|
9772
9848
|
commentsRef,
|
|
9773
9849
|
commentsDirtyRef,
|
|
9774
9850
|
commentsLoadedRef,
|
|
@@ -9804,7 +9880,7 @@ function useFolioComments({ doc, autoOpenReviewSidebar, anchorPositions, editorC
|
|
|
9804
9880
|
* - Loading states
|
|
9805
9881
|
*/
|
|
9806
9882
|
const CommentsSidebar = lazy(() => import("./CommentsSidebar-BsYOInaX.js").then((m) => ({ default: m.CommentsSidebar })));
|
|
9807
|
-
const TextContextMenu = lazy(() => import("./TextContextMenu-
|
|
9883
|
+
const TextContextMenu = lazy(() => import("./TextContextMenu-CG0V37wi.js").then((m) => ({ default: m.TextContextMenu })));
|
|
9808
9884
|
const loadAttemptSelectiveSave = async () => {
|
|
9809
9885
|
const { attemptSelectiveSave } = await import("@stll/folio-core/docx/selectiveSave");
|
|
9810
9886
|
return attemptSelectiveSave;
|
|
@@ -9883,7 +9959,7 @@ function areActiveTrackedChangesEqual(a, b) {
|
|
|
9883
9959
|
/**
|
|
9884
9960
|
* DocxEditor - Complete DOCX editor component
|
|
9885
9961
|
*/
|
|
9886
|
-
function DocxEditor({ ref, documentBuffer, document: initialDocument, documentKey, onSave, author = "User", onChange, onSelectionChange, onSelectionTextChange, onError, onFontsLoaded: onFontsLoadedCallback, fontFamilies, fonts, theme, showToolbar = true, showZoomControl = true, showReviewControls = true, showHeaderFooterEditing = true, showMarginGuides: _showMarginGuides = false, marginGuideColor: _marginGuideColor, showRuler: showRulerProp = false, rulerUnit = "inch", initialZoom = 1, enableWheelZoom = true, readOnly: readOnlyProp = false, autoOpenReviewSidebar = true, components, toolbarExtra, className = "", style, placeholder, loadingIndicator, preserveDocumentWhileLoading = false, initialScrollTop, onScrollTopChange, showOutline: showOutlineProp = true, onPrint, onInsertImage, onInsertTable, showTableInsert = true, onInsertPageBreak, onInsertTOC, onCopy: _onCopy, onCut: _onCut, onPaste: _onPaste, mode: modeProp, onModeChange, onReadonlyEditAttempt, onCompatibilityChange, onEditorViewReady, onAnonymizationMatchesChange, onAnonymizationTermClick, selectedAnonymizationCanonical = null, anonymizationSelectionSeq, showTemplateDirectives = false, onSlashMenuChange, onSlashMenuKeyAction, customContextMenuItems, onCustomContextAction, collaboration, featureFlags, onSelectiveSaveTripwire }) {
|
|
9962
|
+
function DocxEditor({ ref, documentBuffer, document: initialDocument, documentKey, onSave, author = "User", onChange, onSelectionChange, onSelectionTextChange, onError, onFontsLoaded: onFontsLoadedCallback, fontFamilies, fonts, theme, showToolbar = true, showZoomControl = true, showReviewControls = true, showHeaderFooterEditing = true, showMarginGuides: _showMarginGuides = false, marginGuideColor: _marginGuideColor, showRuler: showRulerProp = false, rulerUnit = "inch", initialZoom = 1, enableWheelZoom = true, readOnly: readOnlyProp = false, autoOpenReviewSidebar = true, components, toolbarExtra, className = "", style, placeholder, loadingIndicator, preserveDocumentWhileLoading = false, initialScrollTop, onScrollTopChange, showOutline: showOutlineProp = true, onPrint, onInsertImage, onInsertTable, showTableInsert = true, onInsertPageBreak, onInsertTOC, onCopy: _onCopy, onCut: _onCut, onPaste: _onPaste, mode: modeProp, onModeChange, onReadonlyEditAttempt, onCompatibilityChange, onEditorViewReady, onAnonymizationMatchesChange, onAnonymizationTermClick, selectedAnonymizationCanonical = null, anonymizationSelectionSeq, showTemplateDirectives = false, onSlashMenuChange, onSlashMenuKeyAction, customContextMenuItems, onCustomContextAction, comments: commentsProp, onCommentsChange, collaboration, featureFlags, onSelectiveSaveTripwire }) {
|
|
9887
9963
|
const t = useTranslations("folio");
|
|
9888
9964
|
useEffect(() => {
|
|
9889
9965
|
const onClipboardReadError = () => {
|
|
@@ -10097,7 +10173,9 @@ function DocxEditor({ ref, documentBuffer, document: initialDocument, documentKe
|
|
|
10097
10173
|
doc: history.state,
|
|
10098
10174
|
autoOpenReviewSidebar,
|
|
10099
10175
|
anchorPositions,
|
|
10100
|
-
editorContentRef
|
|
10176
|
+
editorContentRef,
|
|
10177
|
+
commentsProp,
|
|
10178
|
+
onCommentsChange
|
|
10101
10179
|
});
|
|
10102
10180
|
const styleResolverCacheRef = useRef(null);
|
|
10103
10181
|
const getCachedStyleResolver = useCallback((styles) => {
|
|
@@ -11358,6 +11436,7 @@ function DocxEditor({ ref, documentBuffer, document: initialDocument, documentKe
|
|
|
11358
11436
|
scrollToPage: (pageNumber) => {
|
|
11359
11437
|
pagedEditorRef.current?.scrollToPage(pageNumber);
|
|
11360
11438
|
},
|
|
11439
|
+
scrollToParaId: (paraId, options) => pagedEditorRef.current?.scrollToParaId(paraId, options) ?? false,
|
|
11361
11440
|
openPrintPreview: handleDirectPrint,
|
|
11362
11441
|
print: handleDirectPrint,
|
|
11363
11442
|
loadDocument: loadParsedDocument,
|
|
@@ -12286,4 +12365,100 @@ function DocxEditor({ ref, documentBuffer, document: initialDocument, documentKe
|
|
|
12286
12365
|
});
|
|
12287
12366
|
}
|
|
12288
12367
|
//#endregion
|
|
12289
|
-
|
|
12368
|
+
//#region src/renderAsync.tsx
|
|
12369
|
+
/**
|
|
12370
|
+
* Imperative API for mounting a DOCX editor into a DOM element.
|
|
12371
|
+
*
|
|
12372
|
+
* Returns a framework-agnostic {@link EditorHandle} extended with zoom and
|
|
12373
|
+
* scroll helpers. Wraps the editor in `IntlProvider` so `use-intl` works
|
|
12374
|
+
* without extra host setup.
|
|
12375
|
+
*
|
|
12376
|
+
* ```ts
|
|
12377
|
+
* import { renderAsync } from "@stll/folio-react";
|
|
12378
|
+
* import "@stll/folio-react/standalone.css";
|
|
12379
|
+
*
|
|
12380
|
+
* const editor = await renderAsync(buffer, document.getElementById("host")!, {
|
|
12381
|
+
* readOnly: false,
|
|
12382
|
+
* showToolbar: true,
|
|
12383
|
+
* });
|
|
12384
|
+
*
|
|
12385
|
+
* const blob = await editor.save();
|
|
12386
|
+
* editor.destroy();
|
|
12387
|
+
* ```
|
|
12388
|
+
*/
|
|
12389
|
+
/**
|
|
12390
|
+
* Render a DOCX editor into a container element.
|
|
12391
|
+
*
|
|
12392
|
+
* Resolves once the document has parsed and the first `onChange` fires.
|
|
12393
|
+
* Rejects if `onError` runs before that milestone.
|
|
12394
|
+
*/
|
|
12395
|
+
const renderAsync = (input, container, options = {}) => {
|
|
12396
|
+
const { locale = "en", ...editorOptions } = options;
|
|
12397
|
+
return new Promise((resolve, reject) => {
|
|
12398
|
+
const ref = React.createRef();
|
|
12399
|
+
let root = null;
|
|
12400
|
+
let settled = false;
|
|
12401
|
+
try {
|
|
12402
|
+
root = createRoot(container);
|
|
12403
|
+
} catch (error) {
|
|
12404
|
+
reject(error);
|
|
12405
|
+
return;
|
|
12406
|
+
}
|
|
12407
|
+
const handle = {
|
|
12408
|
+
save: async () => {
|
|
12409
|
+
const buffer = await (ref.current?.save() ?? Promise.resolve(null));
|
|
12410
|
+
if (!buffer) return null;
|
|
12411
|
+
return new Blob([buffer], { type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document" });
|
|
12412
|
+
},
|
|
12413
|
+
getDocument: () => ref.current?.getDocument() ?? null,
|
|
12414
|
+
focus: () => {
|
|
12415
|
+
ref.current?.focus();
|
|
12416
|
+
},
|
|
12417
|
+
setZoom: (zoom) => {
|
|
12418
|
+
ref.current?.setZoom(zoom);
|
|
12419
|
+
},
|
|
12420
|
+
scrollToPosition: (pmPos) => {
|
|
12421
|
+
ref.current?.getEditorRef()?.scrollToPosition(pmPos);
|
|
12422
|
+
},
|
|
12423
|
+
scrollToPage: (pageNumber) => {
|
|
12424
|
+
ref.current?.scrollToPage(pageNumber);
|
|
12425
|
+
},
|
|
12426
|
+
destroy: () => {
|
|
12427
|
+
if (!settled) {
|
|
12428
|
+
settled = true;
|
|
12429
|
+
reject(/* @__PURE__ */ new Error("Editor was destroyed before mounting completed."));
|
|
12430
|
+
}
|
|
12431
|
+
root?.unmount();
|
|
12432
|
+
root = null;
|
|
12433
|
+
}
|
|
12434
|
+
};
|
|
12435
|
+
const element = /* @__PURE__ */ jsx(IntlProvider, {
|
|
12436
|
+
locale,
|
|
12437
|
+
messages: getFolioMessages(locale),
|
|
12438
|
+
children: /* @__PURE__ */ jsx(DocxEditor, {
|
|
12439
|
+
...editorOptions,
|
|
12440
|
+
documentBuffer: input,
|
|
12441
|
+
onError: (error) => {
|
|
12442
|
+
editorOptions["onError"]?.(error);
|
|
12443
|
+
if (!settled) {
|
|
12444
|
+
settled = true;
|
|
12445
|
+
root?.unmount();
|
|
12446
|
+
root = null;
|
|
12447
|
+
reject(error);
|
|
12448
|
+
}
|
|
12449
|
+
},
|
|
12450
|
+
onChange: (doc) => {
|
|
12451
|
+
editorOptions["onChange"]?.(doc);
|
|
12452
|
+
if (!settled) {
|
|
12453
|
+
settled = true;
|
|
12454
|
+
resolve(handle);
|
|
12455
|
+
}
|
|
12456
|
+
},
|
|
12457
|
+
ref
|
|
12458
|
+
})
|
|
12459
|
+
});
|
|
12460
|
+
root.render(element);
|
|
12461
|
+
});
|
|
12462
|
+
};
|
|
12463
|
+
//#endregion
|
|
12464
|
+
export { AutocompleteCaretOverlay, DEFAULT_AI_SUGGESTION_PRESETS, DEFAULT_AUTOCOMPLETE_DEAD_ZONE_NODES, DocxEditor, FolioUIProvider, FormattingBar, ZOOM_PRESETS, ZoomControl, acceptAutocompleteSuggestion, acceptAutocompleteWord, anonymizationDecorationsKey, appendAutocompleteToken, applyFolioAIEditOperations, applySuggestions, autocompleteSuggestionKey, autocompleteSuggestionPlugin, buildPositionalText, clampZoom, clearAutocompleteSuggestion, clearTemplateSlashMenu, consumeTemplateSlashQuery, createAICitationDecorationsPlugin, createDocx, createEmptyDocument, createFolioAIEditSnapshot, deriveBlockId, diffWordSegments, finishAutocompleteSuggestion, formatZoom, fromMarkdown, getAnonymizationMatches, getAutocompleteSuggestion, getFolioCaretViewportRect, getFolioParaIdFromBlockId, getFolioSelectionViewportRect, getTemplateDirectives, getTemplateSlashMenu, hashFolioAIBlockText, insertImageFromFile, insertPageBreakInView, insertTableInView, insertTableOfContentsInView, isFolioBlockId, isSequentialFolioBlockId, isSuggestionStale, normalizeFolioAIBlockText, parseZoom, renderAsync, resetTemplateSlashQuery, resolveSuggestionAnchor, scanDirectives, scrollFolioPositionIntoView, setAICitationsMeta, setAISuggestionsMeta, setActiveCitationMeta, setAnonymizationTermsMeta, setFocusedSuggestionMeta, setTemplatePreviewValues, shouldTriggerAutocomplete, startAutocompleteSuggestion, templateSlashMenuKey, toMarkdown, toMarkdownResult, useWheelZoom };
|