@stll/folio-react 0.1.1 → 0.3.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 +51 -3
- package/dist/index.js +383 -86
- package/dist/messages.d.ts +1 -13
- package/dist/messages.js +29 -2163
- 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 { t as messages_exports } from "./messages.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";
|
|
@@ -62,20 +63,21 @@ import { recordLayoutPhase } from "@stll/folio-core/layout-engine/layoutInstrume
|
|
|
62
63
|
import { LayoutPainter } from "@stll/folio-core/layout-painter";
|
|
63
64
|
import { PAINTER_PAINTED_EVENT, findPageShellForPmPos } from "@stll/folio-core/layout-painter/renderPage";
|
|
64
65
|
import { LayoutSelectionGate } from "@stll/folio-core/paged-layout/LayoutSelectionGate";
|
|
65
|
-
import { collectRunFontsAtPmPositions,
|
|
66
|
+
import { collectRunFontsAtPmPositions, measureDirectiveGutter, projectRangesToRects } from "@stll/folio-core/paged-layout/rangeProjection";
|
|
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 { findParagraphByParaId } from "@stll/folio-core/prosemirror/utils/findParagraphByParaId";
|
|
79
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";
|
|
@@ -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";
|
|
@@ -1447,63 +1450,213 @@ const overlayStyles = {
|
|
|
1447
1450
|
};
|
|
1448
1451
|
const BLOCK_OPENERS = /* @__PURE__ */ new Set(["if", "each"]);
|
|
1449
1452
|
const BLOCK_CLOSERS = /* @__PURE__ */ new Set(["endif", "endeach"]);
|
|
1450
|
-
const
|
|
1453
|
+
const RAIL_WIDTH = 2.5;
|
|
1454
|
+
/** Horizontal gap between adjacent nesting depths' rails. */
|
|
1451
1455
|
const RAIL_DEPTH_GAP = 5;
|
|
1452
|
-
|
|
1456
|
+
/** Minimum clear space between the innermost (deepest) rail and the text edge. */
|
|
1457
|
+
const RAIL_TEXT_CLEARANCE = 6;
|
|
1458
|
+
/** Keep the outermost rail this far off the physical page-left edge. */
|
|
1459
|
+
const RAIL_EDGE_PAD = 2;
|
|
1460
|
+
/** Drop per-page segments thinner than this (sub-pixel slivers at a page seam). */
|
|
1461
|
+
const RAIL_SEGMENT_MIN_HEIGHT = 2;
|
|
1462
|
+
const bandKindOf = (kind) => kind === "each" ? "each" : "if";
|
|
1453
1463
|
/**
|
|
1454
|
-
*
|
|
1455
|
-
*
|
|
1456
|
-
*
|
|
1457
|
-
* the
|
|
1458
|
-
* stays put while text above reflows; only its vertical span tracks the rects.
|
|
1464
|
+
* Deepest nesting level whose rail still fits inside the left margin with a
|
|
1465
|
+
* `RAIL_TEXT_CLEARANCE` gap to the text and a `RAIL_EDGE_PAD` gap to the page
|
|
1466
|
+
* edge. Depths past this reuse the deepest offset, so a 5-level nest can never
|
|
1467
|
+
* march a rail into the text column. Pure function of the measured margin width.
|
|
1459
1468
|
*/
|
|
1460
|
-
const
|
|
1461
|
-
const
|
|
1462
|
-
|
|
1469
|
+
const railBudgetDepth = (marginWidth) => {
|
|
1470
|
+
const usable = marginWidth - RAIL_TEXT_CLEARANCE - RAIL_WIDTH - RAIL_EDGE_PAD;
|
|
1471
|
+
if (usable <= 0) return 0;
|
|
1472
|
+
return Math.floor(usable / RAIL_DEPTH_GAP);
|
|
1473
|
+
};
|
|
1474
|
+
/**
|
|
1475
|
+
* Left edge (container space) of a depth-`depth` rail. The deepest rail the
|
|
1476
|
+
* budget allows sits `RAIL_TEXT_CLEARANCE + RAIL_WIDTH` left of the text edge;
|
|
1477
|
+
* shallower (outer) blocks fan further left into the margin — nested blocks read
|
|
1478
|
+
* as concentric brackets `[ [ [ text ] ] ]`. Depths beyond the budget clamp to
|
|
1479
|
+
* the deepest offset.
|
|
1480
|
+
*/
|
|
1481
|
+
const railXForDepth = (depth, contentLeft, marginWidth) => {
|
|
1482
|
+
const budget = railBudgetDepth(marginWidth);
|
|
1483
|
+
const capped = Math.min(Math.max(0, depth), budget);
|
|
1484
|
+
return contentLeft - RAIL_TEXT_CLEARANCE - RAIL_WIDTH - (budget - capped) * RAIL_DEPTH_GAP;
|
|
1485
|
+
};
|
|
1486
|
+
/**
|
|
1487
|
+
* Clip a band's vertical span to each page's body-content band so a rail is
|
|
1488
|
+
* drawn as one rounded segment per page and never paints across the inter-page
|
|
1489
|
+
* gap, header, or footer. With no page bands (geometry not ready) the whole span
|
|
1490
|
+
* is returned as a single segment so rails still show. Pure math.
|
|
1491
|
+
*/
|
|
1492
|
+
const segmentBandByPages = (band, pageBands) => {
|
|
1493
|
+
if (pageBands.length === 0) return band.bottom - band.top >= RAIL_SEGMENT_MIN_HEIGHT ? [band] : [];
|
|
1494
|
+
const segments = [];
|
|
1495
|
+
for (const page of pageBands) {
|
|
1496
|
+
const top = Math.max(band.top, page.top);
|
|
1497
|
+
const bottom = Math.min(band.bottom, page.bottom);
|
|
1498
|
+
if (bottom - top >= RAIL_SEGMENT_MIN_HEIGHT) segments.push({
|
|
1499
|
+
top,
|
|
1500
|
+
bottom
|
|
1501
|
+
});
|
|
1502
|
+
}
|
|
1503
|
+
return segments;
|
|
1504
|
+
};
|
|
1505
|
+
/**
|
|
1506
|
+
* Pair block openers with their closers via a kind-aware stack, so both chips of
|
|
1507
|
+
* a pair share a `blockId`, the rail knows its opener→closer PM span, and each
|
|
1508
|
+
* pairing carries the `depth` recorded in this very walk (never a second,
|
|
1509
|
+
* possibly-divergent depth pass). A closer matches the nearest opener of the same
|
|
1510
|
+
* family ({{/if}} ⇒ {{#if}}, {{/each}} ⇒ {{#each}}) and drops any still-open
|
|
1511
|
+
* openers nested above it; a closer with no matching opener is ignored. This keeps
|
|
1512
|
+
* a mid-edit / unbalanced template from pairing a {{/if}} with an {{#each}}.
|
|
1513
|
+
* Inline (block:false) markers are excluded. Pure function of the ranges.
|
|
1514
|
+
*/
|
|
1515
|
+
const pairBlockRanges = (ranges) => {
|
|
1516
|
+
const blocks = ranges.filter((r) => r.block && (BLOCK_OPENERS.has(r.kind) || BLOCK_CLOSERS.has(r.kind))).slice().sort((a, b) => a.from - b.from);
|
|
1517
|
+
const pairings = [];
|
|
1463
1518
|
const stack = [];
|
|
1464
|
-
for (const
|
|
1465
|
-
if (BLOCK_OPENERS.has(
|
|
1519
|
+
for (const range of blocks) {
|
|
1520
|
+
if (BLOCK_OPENERS.has(range.kind)) {
|
|
1466
1521
|
stack.push({
|
|
1467
|
-
|
|
1522
|
+
range,
|
|
1468
1523
|
depth: stack.length
|
|
1469
1524
|
});
|
|
1470
1525
|
continue;
|
|
1471
1526
|
}
|
|
1472
|
-
const
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1527
|
+
const wantOpener = range.kind === "endif" ? "if" : "each";
|
|
1528
|
+
let matched;
|
|
1529
|
+
let matchIdx = -1;
|
|
1530
|
+
for (let i = stack.length - 1; i >= 0; i -= 1) {
|
|
1531
|
+
const entry = stack[i];
|
|
1532
|
+
if (entry?.range.kind === wantOpener) {
|
|
1533
|
+
matched = entry;
|
|
1534
|
+
matchIdx = i;
|
|
1535
|
+
break;
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
if (!matched) continue;
|
|
1539
|
+
stack.length = matchIdx;
|
|
1540
|
+
pairings.push({
|
|
1541
|
+
blockId: matched.range.from,
|
|
1542
|
+
kind: bandKindOf(matched.range.kind),
|
|
1543
|
+
depth: matched.depth,
|
|
1544
|
+
openerFrom: matched.range.from,
|
|
1545
|
+
closerFrom: range.from,
|
|
1546
|
+
closerTo: range.to,
|
|
1547
|
+
openerExpr: matched.range.expr
|
|
1548
|
+
});
|
|
1549
|
+
}
|
|
1550
|
+
return pairings;
|
|
1551
|
+
};
|
|
1552
|
+
/** Hover hint for a closer chip: what it closes, e.g. `/if · hasVerdicts`. */
|
|
1553
|
+
const closerHintLabel = (kind, openerExpr) => {
|
|
1554
|
+
const head = kind === "each" ? "/each" : "/if";
|
|
1555
|
+
return openerExpr ? `${head} · ${openerExpr}` : head;
|
|
1556
|
+
};
|
|
1557
|
+
/**
|
|
1558
|
+
* Block id of the innermost pairing whose opener→closer span contains `caretPos`
|
|
1559
|
+
* (largest opener position wins), or null. Drives the caret emphasis.
|
|
1560
|
+
*/
|
|
1561
|
+
const innermostBlockAt = (pairings, caretPos) => {
|
|
1562
|
+
if (caretPos === null) return null;
|
|
1563
|
+
let bestBlockId = null;
|
|
1564
|
+
let bestOpener = Number.NEGATIVE_INFINITY;
|
|
1565
|
+
for (const pairing of pairings) if (caretPos >= pairing.openerFrom && caretPos <= pairing.closerTo && pairing.openerFrom > bestOpener) {
|
|
1566
|
+
bestOpener = pairing.openerFrom;
|
|
1567
|
+
bestBlockId = pairing.blockId;
|
|
1568
|
+
}
|
|
1569
|
+
return bestBlockId;
|
|
1570
|
+
};
|
|
1571
|
+
/**
|
|
1572
|
+
* Assemble the per-page rail segments for every balanced block: look up
|
|
1573
|
+
* opener/closer rects by `from`, place the rail by the pairing's own budgeted
|
|
1574
|
+
* depth, and clip its span to each page. Depth comes from the pairing (same walk
|
|
1575
|
+
* that matched opener→closer), so a rail's indentation and its span always agree.
|
|
1576
|
+
* O(pairings); all geometry comes from the props.
|
|
1577
|
+
*/
|
|
1578
|
+
const computeRailBands = (pairings, groups, gutter) => {
|
|
1579
|
+
const groupByFrom = new Map(groups.map((group) => [group.range.from, group]));
|
|
1580
|
+
const bands = [];
|
|
1581
|
+
for (const pairing of pairings) {
|
|
1582
|
+
const openerRect = groupByFrom.get(pairing.openerFrom)?.rects[0];
|
|
1583
|
+
const closerRect = groupByFrom.get(pairing.closerFrom)?.rects[0];
|
|
1584
|
+
if (!openerRect || !closerRect) continue;
|
|
1585
|
+
const segments = segmentBandByPages({
|
|
1478
1586
|
top: openerRect.y,
|
|
1479
|
-
bottom: closerRect.y + closerRect.height
|
|
1480
|
-
|
|
1587
|
+
bottom: closerRect.y + closerRect.height
|
|
1588
|
+
}, gutter.pageBands);
|
|
1589
|
+
if (segments.length === 0) continue;
|
|
1590
|
+
bands.push({
|
|
1591
|
+
blockId: pairing.blockId,
|
|
1592
|
+
kind: pairing.kind,
|
|
1593
|
+
railX: railXForDepth(pairing.depth, gutter.contentLeft, gutter.marginWidth),
|
|
1594
|
+
segments
|
|
1481
1595
|
});
|
|
1482
1596
|
}
|
|
1483
1597
|
return bands;
|
|
1484
1598
|
};
|
|
1485
|
-
const
|
|
1599
|
+
const blockIdFromTarget = (target) => {
|
|
1600
|
+
if (!(target instanceof HTMLElement)) return null;
|
|
1601
|
+
const raw = target.dataset["folioBlockId"];
|
|
1602
|
+
return raw === void 0 ? null : Number(raw);
|
|
1603
|
+
};
|
|
1604
|
+
const TemplateDirectivesOverlay = ({ groups, gutter, caretPos, ranges }) => {
|
|
1605
|
+
const [hoveredBlockId, setHoveredBlockId] = useState(null);
|
|
1486
1606
|
if (groups.length === 0) return null;
|
|
1607
|
+
const pairings = pairBlockRanges(ranges);
|
|
1608
|
+
const railBands = gutter ? computeRailBands(pairings, groups, gutter) : [];
|
|
1609
|
+
const blockIdByFrom = /* @__PURE__ */ new Map();
|
|
1610
|
+
const closerLabelByFrom = /* @__PURE__ */ new Map();
|
|
1611
|
+
for (const pairing of pairings) {
|
|
1612
|
+
blockIdByFrom.set(pairing.openerFrom, pairing.blockId);
|
|
1613
|
+
blockIdByFrom.set(pairing.closerFrom, pairing.blockId);
|
|
1614
|
+
closerLabelByFrom.set(pairing.closerFrom, closerHintLabel(pairing.kind, pairing.openerExpr));
|
|
1615
|
+
}
|
|
1616
|
+
const caretBlockId = innermostBlockAt(pairings, caretPos);
|
|
1617
|
+
const isActive = (blockId) => blockId === hoveredBlockId || blockId === caretBlockId;
|
|
1618
|
+
const handlePointerOver = (event) => {
|
|
1619
|
+
const blockId = blockIdFromTarget(event.target);
|
|
1620
|
+
if (blockId !== null) setHoveredBlockId(blockId);
|
|
1621
|
+
};
|
|
1622
|
+
const handlePointerOut = (event) => {
|
|
1623
|
+
const from = blockIdFromTarget(event.target);
|
|
1624
|
+
if (from === null) return;
|
|
1625
|
+
const to = blockIdFromTarget(event.relatedTarget);
|
|
1626
|
+
if (to === from) return;
|
|
1627
|
+
setHoveredBlockId((current) => current === from ? to : current);
|
|
1628
|
+
};
|
|
1487
1629
|
return /* @__PURE__ */ jsxs("div", {
|
|
1488
1630
|
style: overlayStyles,
|
|
1489
1631
|
"data-folio-template-directives-overlay": "",
|
|
1490
|
-
|
|
1491
|
-
|
|
1632
|
+
onPointerOver: handlePointerOver,
|
|
1633
|
+
onPointerOut: handlePointerOut,
|
|
1634
|
+
children: [railBands.flatMap((band) => band.segments.map((segment, segmentIdx) => /* @__PURE__ */ jsx("div", {
|
|
1635
|
+
className: `folio-template-band-rail folio-template-band-rail--${band.kind}${isActive(band.blockId) ? " folio-template-band-rail--active" : ""}`,
|
|
1636
|
+
"data-folio-block-id": band.blockId,
|
|
1492
1637
|
style: {
|
|
1493
1638
|
left: band.railX,
|
|
1494
|
-
top:
|
|
1639
|
+
top: segment.top,
|
|
1495
1640
|
width: RAIL_WIDTH,
|
|
1496
|
-
height: Math.max(0,
|
|
1497
|
-
}
|
|
1498
|
-
}, `rail:${
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1641
|
+
height: Math.max(0, segment.bottom - segment.top)
|
|
1642
|
+
}
|
|
1643
|
+
}, `rail:${band.blockId}:${segmentIdx}`))), groups.flatMap(({ range, rects }, groupIdx) => {
|
|
1644
|
+
const blockId = range.block ? blockIdByFrom.get(range.from) : void 0;
|
|
1645
|
+
const isBlockChip = range.block && (BLOCK_OPENERS.has(range.kind) || BLOCK_CLOSERS.has(range.kind));
|
|
1646
|
+
const closerLabel = range.block ? closerLabelByFrom.get(range.from) : void 0;
|
|
1647
|
+
const active = blockId !== void 0 && isActive(blockId);
|
|
1648
|
+
return rects.map((rect, idx) => /* @__PURE__ */ jsx("span", {
|
|
1649
|
+
className: `folio-template-directive folio-template-directive--${range.kind}${isBlockChip ? " folio-template-directive--block" : ""}${active ? " folio-template-directive--active" : ""}`,
|
|
1650
|
+
...blockId !== void 0 ? { "data-folio-block-id": blockId } : {},
|
|
1651
|
+
...closerLabel !== void 0 ? { title: closerLabel } : {},
|
|
1652
|
+
style: {
|
|
1653
|
+
left: rect.x - 1,
|
|
1654
|
+
top: rect.y + 1,
|
|
1655
|
+
width: rect.width + 2,
|
|
1656
|
+
height: Math.max(0, rect.height - 2)
|
|
1657
|
+
}
|
|
1658
|
+
}, `d:${groupIdx}:${idx}`));
|
|
1659
|
+
})]
|
|
1507
1660
|
});
|
|
1508
1661
|
};
|
|
1509
1662
|
//#endregion
|
|
@@ -2561,7 +2714,8 @@ function PagedEditor(props) {
|
|
|
2561
2714
|
const [autocompleteText, setAutocompleteText] = useState("");
|
|
2562
2715
|
const [autocompleteIsStreaming, setAutocompleteIsStreaming] = useState(false);
|
|
2563
2716
|
const [directiveRectGroups, setDirectiveRectGroups] = useState([]);
|
|
2564
|
-
const [
|
|
2717
|
+
const [directiveGutter, setDirectiveGutter] = useState(null);
|
|
2718
|
+
const [directiveCaretPos, setDirectiveCaretPos] = useState(null);
|
|
2565
2719
|
const directivesRef = useRef([]);
|
|
2566
2720
|
const directivesOverlayRequestSeqRef = useRef(0);
|
|
2567
2721
|
const templatePreviewRef = useRef({
|
|
@@ -2948,6 +3102,7 @@ function PagedEditor(props) {
|
|
|
2948
3102
|
const requestSeq = selectionOverlayRequestSeqRef.current + 1;
|
|
2949
3103
|
selectionOverlayRequestSeqRef.current = requestSeq;
|
|
2950
3104
|
const isCurrentRequest = () => selectionOverlayRequestSeqRef.current === requestSeq;
|
|
3105
|
+
if (directivesRef.current.length > 0) setDirectiveCaretPos(state.selection.head);
|
|
2951
3106
|
onSelectionChangeRef.current?.(from, to);
|
|
2952
3107
|
folioEmitterRef.current.emit("selectionChange", {
|
|
2953
3108
|
from,
|
|
@@ -3252,10 +3407,10 @@ function PagedEditor(props) {
|
|
|
3252
3407
|
const pagesContainer = pagesContainerRef.current;
|
|
3253
3408
|
if (ranges.length === 0 || !pagesContainer) {
|
|
3254
3409
|
setDirectiveRectGroups([]);
|
|
3255
|
-
|
|
3410
|
+
setDirectiveGutter(null);
|
|
3256
3411
|
return;
|
|
3257
3412
|
}
|
|
3258
|
-
|
|
3413
|
+
setDirectiveGutter(measureDirectiveGutter(pagesContainer, zoom));
|
|
3259
3414
|
(async () => {
|
|
3260
3415
|
const projected = await projectRangesToRects(ranges, {
|
|
3261
3416
|
pagesContainer,
|
|
@@ -3569,6 +3724,24 @@ function PagedEditor(props) {
|
|
|
3569
3724
|
inline: "nearest"
|
|
3570
3725
|
});
|
|
3571
3726
|
}, [layout, scrollToPositionImpl]);
|
|
3727
|
+
const scrollToParaIdImpl = useCallback((paraId, options) => {
|
|
3728
|
+
const state = hiddenPMRef.current?.getState();
|
|
3729
|
+
if (!state) return false;
|
|
3730
|
+
const startPos = findStartPosForParaId(state.doc, paraId);
|
|
3731
|
+
if (startPos == null || startPos < 0) return false;
|
|
3732
|
+
scrollToPositionImpl(startPos);
|
|
3733
|
+
requestAnimationFrame(() => {
|
|
3734
|
+
if (options?.highlight) {
|
|
3735
|
+
const pages = pagesContainerRef.current;
|
|
3736
|
+
if (pages) flashParagraphFragmentsByParaId(pages, paraId, options.highlight);
|
|
3737
|
+
}
|
|
3738
|
+
const targetNode = state.doc.nodeAt(startPos);
|
|
3739
|
+
const inner = targetNode?.isTextblock === true ? Math.min(startPos + 1 + targetNode.content.size, state.doc.content.size) : Math.min(startPos + 1, state.doc.content.size);
|
|
3740
|
+
hiddenPMRef.current?.setSelection(inner);
|
|
3741
|
+
hiddenPMRef.current?.focus();
|
|
3742
|
+
});
|
|
3743
|
+
return true;
|
|
3744
|
+
}, [scrollToPositionImpl]);
|
|
3572
3745
|
const focusHiddenEditor = useCallback(() => {
|
|
3573
3746
|
if (readOnly) {
|
|
3574
3747
|
containerRef.current?.focus({ preventScroll: true });
|
|
@@ -4989,6 +5162,7 @@ function PagedEditor(props) {
|
|
|
4989
5162
|
},
|
|
4990
5163
|
scrollToPosition: scrollToPositionImpl,
|
|
4991
5164
|
scrollToPage: scrollToPageImpl,
|
|
5165
|
+
scrollToParaId: scrollToParaIdImpl,
|
|
4992
5166
|
getPageNumberForPmPos(pmPos) {
|
|
4993
5167
|
const container = pagesContainerRef.current;
|
|
4994
5168
|
if (!container) return null;
|
|
@@ -5028,6 +5202,7 @@ function PagedEditor(props) {
|
|
|
5028
5202
|
ensureHiddenEditorView,
|
|
5029
5203
|
folioEditor,
|
|
5030
5204
|
scrollToPageImpl,
|
|
5205
|
+
scrollToParaIdImpl,
|
|
5031
5206
|
scrollToPositionImpl
|
|
5032
5207
|
]);
|
|
5033
5208
|
useEffect(() => {
|
|
@@ -5144,8 +5319,10 @@ function PagedEditor(props) {
|
|
|
5144
5319
|
}),
|
|
5145
5320
|
/* @__PURE__ */ jsx(AISuggestionRectsOverlay, { groups: aiSuggestionRectGroups }),
|
|
5146
5321
|
showTemplateDirectives && /* @__PURE__ */ jsx(TemplateDirectivesOverlay, {
|
|
5147
|
-
|
|
5148
|
-
groups: directiveRectGroups
|
|
5322
|
+
gutter: directiveGutter,
|
|
5323
|
+
groups: directiveRectGroups,
|
|
5324
|
+
caretPos: directiveCaretPos,
|
|
5325
|
+
ranges: directivesRef.current
|
|
5149
5326
|
}),
|
|
5150
5327
|
/* @__PURE__ */ jsx(SelectionOverlay, {
|
|
5151
5328
|
selectionRects: hfEditMode ? EMPTY_SELECTION_RECTS : selectionRects,
|
|
@@ -6238,30 +6415,34 @@ function ContentControlWidgetsOverlay({ getEditorView }) {
|
|
|
6238
6415
|
if (liveView && value.length > 0) dispatchDatePick(liveView, open.pmPos, value);
|
|
6239
6416
|
close();
|
|
6240
6417
|
};
|
|
6241
|
-
return createPortal(/* @__PURE__ */
|
|
6242
|
-
|
|
6243
|
-
|
|
6244
|
-
|
|
6245
|
-
|
|
6246
|
-
|
|
6247
|
-
|
|
6248
|
-
className: "
|
|
6249
|
-
children: open.
|
|
6250
|
-
|
|
6251
|
-
|
|
6252
|
-
|
|
6253
|
-
|
|
6254
|
-
|
|
6255
|
-
|
|
6256
|
-
|
|
6257
|
-
|
|
6258
|
-
|
|
6259
|
-
|
|
6260
|
-
|
|
6261
|
-
|
|
6262
|
-
|
|
6263
|
-
|
|
6264
|
-
|
|
6418
|
+
return createPortal(/* @__PURE__ */ jsx("div", {
|
|
6419
|
+
className: "folio-root",
|
|
6420
|
+
style: { display: "contents" },
|
|
6421
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
6422
|
+
role: "dialog",
|
|
6423
|
+
"aria-label": open.kind === "dropdown" ? t("contentControlDropdownAriaLabel") : t("contentControlDateAriaLabel"),
|
|
6424
|
+
style,
|
|
6425
|
+
className: "bg-popover text-popover-foreground min-w-[10rem] rounded-md border p-1 shadow-md",
|
|
6426
|
+
children: [open.kind === "dropdown" && /* @__PURE__ */ jsx("div", {
|
|
6427
|
+
role: "menu",
|
|
6428
|
+
className: "flex flex-col",
|
|
6429
|
+
children: open.items.length === 0 ? /* @__PURE__ */ jsx("div", {
|
|
6430
|
+
className: "text-muted-foreground px-2 py-1 text-sm",
|
|
6431
|
+
children: t("contentControlDropdownNoOptions")
|
|
6432
|
+
}) : open.items.map((item) => /* @__PURE__ */ jsx(MenuItem, {
|
|
6433
|
+
onClick: () => onDropdownPick(item.value),
|
|
6434
|
+
children: item.displayText
|
|
6435
|
+
}, `${item.value}::${item.displayText}`))
|
|
6436
|
+
}), open.kind === "date" && /* @__PURE__ */ jsx(DatePickerPopover, {
|
|
6437
|
+
clearLabel: t("clearDate"),
|
|
6438
|
+
defaultOpen: true,
|
|
6439
|
+
onChange: (value) => {
|
|
6440
|
+
if (value) onDatePick(value);
|
|
6441
|
+
},
|
|
6442
|
+
showIcon: false,
|
|
6443
|
+
value: null
|
|
6444
|
+
})]
|
|
6445
|
+
})
|
|
6265
6446
|
}), document.body);
|
|
6266
6447
|
}
|
|
6267
6448
|
//#endregion
|
|
@@ -6345,12 +6526,12 @@ const DocumentOutline = ({ headings, scrollContainerRef, topOffset, docSize, onH
|
|
|
6345
6526
|
};
|
|
6346
6527
|
//#endregion
|
|
6347
6528
|
//#region src/components/DocxEditorDialogs.tsx
|
|
6348
|
-
const FindReplaceDialog = lazy(() => import("./FindReplaceDialog-
|
|
6349
|
-
const TablePropertiesDialog = lazy(() => import("./TablePropertiesDialog-
|
|
6350
|
-
const ImagePositionDialog = lazy(() => import("./ImagePositionDialog-
|
|
6351
|
-
const ImagePropertiesDialog = lazy(() => import("./ImagePropertiesDialog-
|
|
6352
|
-
const FootnotePropertiesDialog = lazy(() => import("./FootnotePropertiesDialog-
|
|
6353
|
-
const PageSetupDialog = lazy(() => import("./PageSetupDialog-
|
|
6529
|
+
const FindReplaceDialog = lazy(() => import("./FindReplaceDialog-EFjmqEbg.js").then((m) => ({ default: m.FindReplaceDialog })));
|
|
6530
|
+
const TablePropertiesDialog = lazy(() => import("./TablePropertiesDialog-ZmzjSqME.js").then((m) => ({ default: m.TablePropertiesDialog })));
|
|
6531
|
+
const ImagePositionDialog = lazy(() => import("./ImagePositionDialog-FGLCOf0Q.js").then((m) => ({ default: m.ImagePositionDialog })));
|
|
6532
|
+
const ImagePropertiesDialog = lazy(() => import("./ImagePropertiesDialog-Baokui4M.js").then((m) => ({ default: m.ImagePropertiesDialog })));
|
|
6533
|
+
const FootnotePropertiesDialog = lazy(() => import("./FootnotePropertiesDialog-VfF7SQo2.js").then((m) => ({ default: m.FootnotePropertiesDialog })));
|
|
6534
|
+
const PageSetupDialog = lazy(() => import("./PageSetupDialog-Df2HjHiu.js").then((m) => ({ default: m.PageSetupDialog })));
|
|
6354
6535
|
function DocxEditorDialogs({ findReplace, tableProperties, imagePosition, imageProperties, pageSetup, footnoteProperties }) {
|
|
6355
6536
|
return /* @__PURE__ */ jsxs(Suspense, {
|
|
6356
6537
|
fallback: null,
|
|
@@ -6672,7 +6853,7 @@ function ToolbarButton({ active = false, disabled = false, title, onClick, onDou
|
|
|
6672
6853
|
};
|
|
6673
6854
|
const button = /* @__PURE__ */ jsx("button", {
|
|
6674
6855
|
type: "button",
|
|
6675
|
-
className: cn("flex h-8 w-8 shrink-0 items-center justify-center rounded-md", "transition-colors duration-100", "
|
|
6856
|
+
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),
|
|
6676
6857
|
onMouseDown: handleMouseDown,
|
|
6677
6858
|
onClick: disabled ? void 0 : onClick,
|
|
6678
6859
|
onDoubleClick: disabled ? void 0 : onDoubleClick,
|
|
@@ -9691,21 +9872,32 @@ const getBuiltinTableStyle = (styleId) => BUILTIN_STYLES.find((s) => s.id === st
|
|
|
9691
9872
|
* (`replaceComments`/`updateComments`) stay in the component because they
|
|
9692
9873
|
* depend on its document-build pipeline.
|
|
9693
9874
|
*/
|
|
9694
|
-
function useFolioComments({ doc, autoOpenReviewSidebar, anchorPositions, editorContentRef }) {
|
|
9875
|
+
function useFolioComments({ doc, autoOpenReviewSidebar, anchorPositions, editorContentRef, commentsProp, onCommentsChange }) {
|
|
9695
9876
|
const [showCommentsSidebar, setShowCommentsSidebar] = useState(false);
|
|
9696
9877
|
const [visibleCommentAuthors, setVisibleCommentAuthors] = useState(null);
|
|
9697
9878
|
const [activeCommentId, setActiveCommentId] = useState(null);
|
|
9698
|
-
const [
|
|
9879
|
+
const [internalComments, setInternalComments] = useState([]);
|
|
9880
|
+
const isControlledComments = commentsProp !== void 0;
|
|
9881
|
+
const comments = isControlledComments ? commentsProp : internalComments;
|
|
9699
9882
|
const commentsDirtyRef = useRef(false);
|
|
9700
|
-
const commentsRef = useRef(
|
|
9883
|
+
const commentsRef = useRef(comments);
|
|
9701
9884
|
commentsRef.current = comments;
|
|
9885
|
+
const onCommentsChangeRef = useRef(onCommentsChange);
|
|
9886
|
+
onCommentsChangeRef.current = onCommentsChange;
|
|
9887
|
+
const setComments = useCallback((next) => {
|
|
9888
|
+
const resolved = typeof next === "function" ? next(commentsRef.current) : next;
|
|
9889
|
+
if (resolved === commentsRef.current) return;
|
|
9890
|
+
commentsRef.current = resolved;
|
|
9891
|
+
if (!isControlledComments) setInternalComments(resolved);
|
|
9892
|
+
onCommentsChangeRef.current?.(resolved);
|
|
9893
|
+
}, [isControlledComments]);
|
|
9702
9894
|
const [isAddingComment, setIsAddingComment] = useState(false);
|
|
9703
9895
|
const [commentSelectionRange, setCommentSelectionRange] = useState(null);
|
|
9704
9896
|
const [addCommentYPosition, setAddCommentYPosition] = useState(null);
|
|
9705
9897
|
const [floatingCommentBtn, setFloatingCommentBtn] = useState(null);
|
|
9706
9898
|
const commentsLoadedRef = useRef(false);
|
|
9707
9899
|
useEffect(() => {
|
|
9708
|
-
if (commentsLoadedRef.current) return;
|
|
9900
|
+
if (isControlledComments || commentsLoadedRef.current) return;
|
|
9709
9901
|
if (!doc) return;
|
|
9710
9902
|
const bodyComments = doc.package.document.comments;
|
|
9711
9903
|
if (bodyComments && bodyComments.length > 0) {
|
|
@@ -9715,7 +9907,12 @@ function useFolioComments({ doc, autoOpenReviewSidebar, anchorPositions, editorC
|
|
|
9715
9907
|
if (autoOpenReviewSidebar) setShowCommentsSidebar(true);
|
|
9716
9908
|
commentsLoadedRef.current = true;
|
|
9717
9909
|
}
|
|
9718
|
-
}, [
|
|
9910
|
+
}, [
|
|
9911
|
+
autoOpenReviewSidebar,
|
|
9912
|
+
doc,
|
|
9913
|
+
isControlledComments,
|
|
9914
|
+
setComments
|
|
9915
|
+
]);
|
|
9719
9916
|
const commentAuthors = useMemo(() => {
|
|
9720
9917
|
const seen = /* @__PURE__ */ new Set();
|
|
9721
9918
|
const authors = [];
|
|
@@ -9801,6 +9998,7 @@ function useFolioComments({ doc, autoOpenReviewSidebar, anchorPositions, editorC
|
|
|
9801
9998
|
return {
|
|
9802
9999
|
comments,
|
|
9803
10000
|
setComments,
|
|
10001
|
+
isControlledComments,
|
|
9804
10002
|
commentsRef,
|
|
9805
10003
|
commentsDirtyRef,
|
|
9806
10004
|
commentsLoadedRef,
|
|
@@ -9836,7 +10034,7 @@ function useFolioComments({ doc, autoOpenReviewSidebar, anchorPositions, editorC
|
|
|
9836
10034
|
* - Loading states
|
|
9837
10035
|
*/
|
|
9838
10036
|
const CommentsSidebar = lazy(() => import("./CommentsSidebar-BsYOInaX.js").then((m) => ({ default: m.CommentsSidebar })));
|
|
9839
|
-
const TextContextMenu = lazy(() => import("./TextContextMenu-
|
|
10037
|
+
const TextContextMenu = lazy(() => import("./TextContextMenu-CG0V37wi.js").then((m) => ({ default: m.TextContextMenu })));
|
|
9840
10038
|
const loadAttemptSelectiveSave = async () => {
|
|
9841
10039
|
const { attemptSelectiveSave } = await import("@stll/folio-core/docx/selectiveSave");
|
|
9842
10040
|
return attemptSelectiveSave;
|
|
@@ -9915,7 +10113,7 @@ function areActiveTrackedChangesEqual(a, b) {
|
|
|
9915
10113
|
/**
|
|
9916
10114
|
* DocxEditor - Complete DOCX editor component
|
|
9917
10115
|
*/
|
|
9918
|
-
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 }) {
|
|
10116
|
+
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 }) {
|
|
9919
10117
|
const t = useTranslations("folio");
|
|
9920
10118
|
useEffect(() => {
|
|
9921
10119
|
const onClipboardReadError = () => {
|
|
@@ -10129,7 +10327,9 @@ function DocxEditor({ ref, documentBuffer, document: initialDocument, documentKe
|
|
|
10129
10327
|
doc: history.state,
|
|
10130
10328
|
autoOpenReviewSidebar,
|
|
10131
10329
|
anchorPositions,
|
|
10132
|
-
editorContentRef
|
|
10330
|
+
editorContentRef,
|
|
10331
|
+
commentsProp,
|
|
10332
|
+
onCommentsChange
|
|
10133
10333
|
});
|
|
10134
10334
|
const styleResolverCacheRef = useRef(null);
|
|
10135
10335
|
const getCachedStyleResolver = useCallback((styles) => {
|
|
@@ -11390,6 +11590,7 @@ function DocxEditor({ ref, documentBuffer, document: initialDocument, documentKe
|
|
|
11390
11590
|
scrollToPage: (pageNumber) => {
|
|
11391
11591
|
pagedEditorRef.current?.scrollToPage(pageNumber);
|
|
11392
11592
|
},
|
|
11593
|
+
scrollToParaId: (paraId, options) => pagedEditorRef.current?.scrollToParaId(paraId, options) ?? false,
|
|
11393
11594
|
openPrintPreview: handleDirectPrint,
|
|
11394
11595
|
print: handleDirectPrint,
|
|
11395
11596
|
loadDocument: loadParsedDocument,
|
|
@@ -12318,4 +12519,100 @@ function DocxEditor({ ref, documentBuffer, document: initialDocument, documentKe
|
|
|
12318
12519
|
});
|
|
12319
12520
|
}
|
|
12320
12521
|
//#endregion
|
|
12321
|
-
|
|
12522
|
+
//#region src/renderAsync.tsx
|
|
12523
|
+
/**
|
|
12524
|
+
* Imperative API for mounting a DOCX editor into a DOM element.
|
|
12525
|
+
*
|
|
12526
|
+
* Returns a framework-agnostic {@link EditorHandle} extended with zoom and
|
|
12527
|
+
* scroll helpers. Wraps the editor in `IntlProvider` so `use-intl` works
|
|
12528
|
+
* without extra host setup.
|
|
12529
|
+
*
|
|
12530
|
+
* ```ts
|
|
12531
|
+
* import { renderAsync } from "@stll/folio-react";
|
|
12532
|
+
* import "@stll/folio-react/standalone.css";
|
|
12533
|
+
*
|
|
12534
|
+
* const editor = await renderAsync(buffer, document.getElementById("host")!, {
|
|
12535
|
+
* readOnly: false,
|
|
12536
|
+
* showToolbar: true,
|
|
12537
|
+
* });
|
|
12538
|
+
*
|
|
12539
|
+
* const blob = await editor.save();
|
|
12540
|
+
* editor.destroy();
|
|
12541
|
+
* ```
|
|
12542
|
+
*/
|
|
12543
|
+
/**
|
|
12544
|
+
* Render a DOCX editor into a container element.
|
|
12545
|
+
*
|
|
12546
|
+
* Resolves once the document has parsed and the first `onChange` fires.
|
|
12547
|
+
* Rejects if `onError` runs before that milestone.
|
|
12548
|
+
*/
|
|
12549
|
+
const renderAsync = (input, container, options = {}) => {
|
|
12550
|
+
const { locale = "en", ...editorOptions } = options;
|
|
12551
|
+
return new Promise((resolve, reject) => {
|
|
12552
|
+
const ref = React.createRef();
|
|
12553
|
+
let root = null;
|
|
12554
|
+
let settled = false;
|
|
12555
|
+
try {
|
|
12556
|
+
root = createRoot(container);
|
|
12557
|
+
} catch (error) {
|
|
12558
|
+
reject(error);
|
|
12559
|
+
return;
|
|
12560
|
+
}
|
|
12561
|
+
const handle = {
|
|
12562
|
+
save: async () => {
|
|
12563
|
+
const buffer = await (ref.current?.save() ?? Promise.resolve(null));
|
|
12564
|
+
if (!buffer) return null;
|
|
12565
|
+
return new Blob([buffer], { type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document" });
|
|
12566
|
+
},
|
|
12567
|
+
getDocument: () => ref.current?.getDocument() ?? null,
|
|
12568
|
+
focus: () => {
|
|
12569
|
+
ref.current?.focus();
|
|
12570
|
+
},
|
|
12571
|
+
setZoom: (zoom) => {
|
|
12572
|
+
ref.current?.setZoom(zoom);
|
|
12573
|
+
},
|
|
12574
|
+
scrollToPosition: (pmPos) => {
|
|
12575
|
+
ref.current?.getEditorRef()?.scrollToPosition(pmPos);
|
|
12576
|
+
},
|
|
12577
|
+
scrollToPage: (pageNumber) => {
|
|
12578
|
+
ref.current?.scrollToPage(pageNumber);
|
|
12579
|
+
},
|
|
12580
|
+
destroy: () => {
|
|
12581
|
+
if (!settled) {
|
|
12582
|
+
settled = true;
|
|
12583
|
+
reject(/* @__PURE__ */ new Error("Editor was destroyed before mounting completed."));
|
|
12584
|
+
}
|
|
12585
|
+
root?.unmount();
|
|
12586
|
+
root = null;
|
|
12587
|
+
}
|
|
12588
|
+
};
|
|
12589
|
+
const element = /* @__PURE__ */ jsx(IntlProvider, {
|
|
12590
|
+
locale,
|
|
12591
|
+
messages: (0, messages_exports.getFolioMessages)(locale),
|
|
12592
|
+
children: /* @__PURE__ */ jsx(DocxEditor, {
|
|
12593
|
+
...editorOptions,
|
|
12594
|
+
documentBuffer: input,
|
|
12595
|
+
onError: (error) => {
|
|
12596
|
+
editorOptions["onError"]?.(error);
|
|
12597
|
+
if (!settled) {
|
|
12598
|
+
settled = true;
|
|
12599
|
+
root?.unmount();
|
|
12600
|
+
root = null;
|
|
12601
|
+
reject(error);
|
|
12602
|
+
}
|
|
12603
|
+
},
|
|
12604
|
+
onChange: (doc) => {
|
|
12605
|
+
editorOptions["onChange"]?.(doc);
|
|
12606
|
+
if (!settled) {
|
|
12607
|
+
settled = true;
|
|
12608
|
+
resolve(handle);
|
|
12609
|
+
}
|
|
12610
|
+
},
|
|
12611
|
+
ref
|
|
12612
|
+
})
|
|
12613
|
+
});
|
|
12614
|
+
root.render(element);
|
|
12615
|
+
});
|
|
12616
|
+
};
|
|
12617
|
+
//#endregion
|
|
12618
|
+
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 };
|