@a3s-lab/office 0.32.0 → 0.33.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.
Files changed (39) hide show
  1. package/README.md +59 -7
  2. package/dist/0~3539.js +2 -2
  3. package/dist/0~3557.js +1 -1
  4. package/dist/0~3635.js +1 -1
  5. package/dist/0~7048.js +1 -2
  6. package/dist/{0~6090.js → 0~8136.js} +71 -2
  7. package/dist/{0~4595.js → 0~9073.js} +3 -72
  8. package/dist/0~9445.js +1 -1
  9. package/dist/0~document-editor.js +2 -2
  10. package/dist/0~markdown-editor.js +2 -2
  11. package/dist/0~pdf-page-organization-engine.js +187 -0
  12. package/dist/0~pdf-viewer.js +1014 -30
  13. package/dist/0~presentation-editor.js +2 -2
  14. package/dist/0~spreadsheet-editor.js +2 -2
  15. package/dist/core.d.ts +1 -0
  16. package/dist/internal/features/work/editors/pdf-editor-extensions.d.ts +11 -0
  17. package/dist/internal/features/work/editors/pdf-page-organization-engine.d.ts +2 -0
  18. package/dist/internal/features/work/editors/pdf-page-organization-plan.d.ts +10 -0
  19. package/dist/internal/features/work/editors/pdf-page-organization-types.d.ts +68 -0
  20. package/dist/internal/features/work/editors/pdf-page-organization-worker-client.d.ts +2 -0
  21. package/dist/internal/features/work/editors/pdf-page-organization-worker-protocol.d.ts +13 -0
  22. package/dist/internal/features/work/editors/pdf-page-organization.d.ts +3 -0
  23. package/dist/internal/features/work/editors/pdf-page-organization.worker.d.ts +1 -0
  24. package/dist/internal/features/work/editors/pdf-page-organizer-dialog.d.ts +17 -0
  25. package/dist/internal/features/work/editors/pdf-thumbnail-rail.d.ts +4 -0
  26. package/dist/internal/features/work/editors/pdf-toolbar.d.ts +2 -1
  27. package/dist/internal/features/work/editors/pdf-viewer.d.ts +3 -1
  28. package/dist/internal/features/work/editors/use-pdf-page-organization.d.ts +38 -0
  29. package/dist/office-kernel.wasm +0 -0
  30. package/dist/pdf-page-organization.worker.js +19667 -0
  31. package/dist/pdf-page-organization.worker.js.LICENSE.txt +14 -0
  32. package/dist/react.d.ts +1 -0
  33. package/dist/styles.css +315 -0
  34. package/dist/vue.d.ts +3 -1
  35. package/dist/vue.js +2 -0
  36. package/dist/web-component.d.ts +3 -1
  37. package/dist/web-component.js +9 -0
  38. package/docs/latest/en/browser-editor-architecture.md +29 -6
  39. package/package.json +5 -1
@@ -1,13 +1,13 @@
1
1
  import { __webpack_require__ } from "./0~rslib-runtime.js";
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import { AnnotationPlugin, DocumentManagerPlugin, ExportPlugin, FormPlugin, HistoryPlugin, PDFViewer, PdfAnnotationSubtype, ScrollPlugin, SearchPlugin, ThumbnailPlugin, ViewportPlugin, ZoomMode, ZoomPlugin } from "@embedpdf/react-pdf-viewer";
4
- import { AlertCircle, Check, ChevronDown, ChevronLeft, ChevronRight, ChevronUp, Ellipsis, GalleryVerticalEnd, Highlighter, Loader2, Minus, MousePointer2, MoveHorizontal, Pencil, Plus, Redo2, Save, Scan, Search, SlidersHorizontal, Strikethrough, Trash2, Type, Underline, Undo2, X } from "lucide-react";
4
+ import { AlertCircle, Check, ChevronDown, ChevronLeft, ChevronRight, ChevronUp, Download, Ellipsis, FilePlus2, Files, GalleryVerticalEnd, Highlighter, LayoutGrid, Loader2, Minus, MousePointer2, MoveHorizontal, Pencil, Plus, Redo2, RotateCcw, RotateCw, Save, Scan, Scissors, Search, SlidersHorizontal, Strikethrough, Trash2, Type, Underline, Undo2, X } from "lucide-react";
5
5
  import { useCallback, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
6
6
  import { PdfErrorCode } from "@embedpdf/models";
7
7
  import { readOfficePdfCollaborationSource as readWorkOfficePdfCollaborationSource, createOfficePdfCollaborationBinding as createWorkOfficePdfCollaborationBinding, assertPdfCollaborationSource as assertWorkOfficePdfCollaborationSource } from "./3818.js";
8
8
  import { createOfficeEditorExtension, useOfficeEditorRuntime, useOfficeEditorKeyboardShortcuts } from "./0~3557.js";
9
9
  import { isOfficeShortcutBlocked, OfficeColorPicker } from "./0~3635.js";
10
- import { officePresenceColorStyle, moveOfficeMenuFocus, useOfficeRemoteParticipants, WorkOfficeCollaborationParticipants, Popover, OfficePresenceAvatar, useOfficePublishPresenceLocation, button_Button, icon_button_IconButton, office_text_field_OfficeTextField, useDialogFocusScope, useOfficeEditorWheelZoom } from "./0~6090.js";
10
+ import { officePresenceColorStyle, moveOfficeMenuFocus, useOfficeEditorWheelZoom, useOfficeRemoteParticipants, WorkOfficeCollaborationParticipants, Dialog, OfficePresenceAvatar, useOfficePublishPresenceLocation, button_Button, icon_button_IconButton, office_text_field_OfficeTextField, Popover, useDialogFocusScope, office_text_field_OfficeFileInput } from "./0~8136.js";
11
11
  import { StateView, useOfficeCollaborationLocationNavigator, PDF_EVIDENCE_COORDINATE_BASIS } from "./432.js";
12
12
  var pdf_viewer_namespaceObject = {};
13
13
  __webpack_require__.r(pdf_viewer_namespaceObject);
@@ -714,6 +714,83 @@ function sortPdfJson(value) {
714
714
  sortPdfJson(child)
715
715
  ]));
716
716
  }
717
+ const PDF_PAGE_ORGANIZATION_MAX_SOURCE_BYTES = 268435456;
718
+ const PDF_PAGE_ORGANIZATION_MAX_IMPORT_BYTES = 134217728;
719
+ class PdfPageOrganizationError extends Error {
720
+ code;
721
+ constructor(code, message){
722
+ super(message);
723
+ this.name = 'PdfPageOrganizationError';
724
+ this.code = code;
725
+ }
726
+ }
727
+ function validatedPdfPageIndexes(pageIndexes, pageCount, options = {}) {
728
+ if (!Array.isArray(pageIndexes) || !options.allowEmpty && 0 === pageIndexes.length) throw new PdfPageOrganizationError('pdf.pages.invalid-index', 'Select at least one PDF page.');
729
+ const seen = new Set();
730
+ for (const pageIndex of pageIndexes){
731
+ if (!Number.isSafeInteger(pageIndex) || pageIndex < 0 || pageIndex >= pageCount) throw new PdfPageOrganizationError('pdf.pages.invalid-index', `Page index ${String(pageIndex)} is outside the current ${pageCount}-page PDF.`);
732
+ if (seen.has(pageIndex)) throw new PdfPageOrganizationError('pdf.pages.duplicate-index', `Page index ${pageIndex} appears more than once.`);
733
+ seen.add(pageIndex);
734
+ }
735
+ const result = [
736
+ ...pageIndexes
737
+ ];
738
+ return options.preserveOrder ? result : result.sort((left, right)=>left - right);
739
+ }
740
+ function validatedPdfPageOrder(pageOrder, pageCount) {
741
+ if (!Array.isArray(pageOrder) || pageOrder.length !== pageCount) throw invalidOrder(pageCount);
742
+ try {
743
+ const order = validatedPdfPageIndexes(pageOrder, pageCount, {
744
+ preserveOrder: true
745
+ });
746
+ if (order.length === pageCount) return order;
747
+ } catch {}
748
+ throw invalidOrder(pageCount);
749
+ }
750
+ function reorderedPdfPageIndexes(pageCount, pageIndexes, targetIndex) {
751
+ const selected = validatedPdfPageIndexes(pageIndexes, pageCount);
752
+ if (!Number.isSafeInteger(targetIndex) || targetIndex < 0 || targetIndex > pageCount) throw new PdfPageOrganizationError('pdf.pages.invalid-index', `Insertion gap ${String(targetIndex)} is outside the current ${pageCount}-page PDF.`);
753
+ const selectedSet = new Set(selected);
754
+ const remaining = Array.from({
755
+ length: pageCount
756
+ }, (_, index)=>index).filter((index)=>!selectedSet.has(index));
757
+ const removedBeforeTarget = selected.filter((index)=>index < targetIndex).length;
758
+ const insertionIndex = Math.max(0, Math.min(remaining.length, targetIndex - removedBeforeTarget));
759
+ remaining.splice(insertionIndex, 0, ...selected);
760
+ return remaining;
761
+ }
762
+ function validatedPdfInsertionIndex(index, pageCount) {
763
+ if (!Number.isSafeInteger(index) || index < 0 || index > pageCount) throw new PdfPageOrganizationError('pdf.pages.invalid-index', `Insertion gap ${String(index)} is outside the current ${pageCount}-page PDF.`);
764
+ return index;
765
+ }
766
+ function validatedPdfPageSize(size) {
767
+ const candidate = size ?? {
768
+ height: 841.89,
769
+ width: 595.28
770
+ };
771
+ if (!Number.isFinite(candidate.width) || !Number.isFinite(candidate.height) || candidate.width < 18 || candidate.height < 18 || candidate.width > 14400 || candidate.height > 14400) throw new PdfPageOrganizationError('pdf.pages.invalid-page-size', "PDF page dimensions must be between 18 and 14400 points.");
772
+ return {
773
+ height: candidate.height,
774
+ width: candidate.width
775
+ };
776
+ }
777
+ function validatedPdfSplitBoundaries(splitAfterPageIndexes, pageCount) {
778
+ if (!Array.isArray(splitAfterPageIndexes) || 0 === splitAfterPageIndexes.length) throw invalidSplit(pageCount);
779
+ let previous = -1;
780
+ for (const pageIndex of splitAfterPageIndexes){
781
+ if (!Number.isSafeInteger(pageIndex) || pageIndex < 0 || pageIndex >= pageCount - 1 || pageIndex <= previous) throw invalidSplit(pageCount);
782
+ previous = pageIndex;
783
+ }
784
+ return [
785
+ ...splitAfterPageIndexes
786
+ ];
787
+ }
788
+ function invalidOrder(pageCount) {
789
+ return new PdfPageOrganizationError('pdf.pages.invalid-order', `A page order must contain every index from 0 through ${Math.max(0, pageCount - 1)} exactly once.`);
790
+ }
791
+ function invalidSplit(pageCount) {
792
+ return new PdfPageOrganizationError('pdf.pages.invalid-split', `Split boundaries must be unique, ascending, and before the last page of the ${pageCount}-page PDF.`);
793
+ }
717
794
  function createPdfEditorExtensions() {
718
795
  return [
719
796
  createOfficeEditorExtension({
@@ -822,6 +899,65 @@ function createPdfEditorExtensions() {
822
899
  }
823
900
  })
824
901
  }),
902
+ createOfficeEditorExtension({
903
+ name: 'pdfPageOrganization',
904
+ addCommands: ()=>({
905
+ deletePages: {
906
+ canExecute: (context, pageIndexes)=>canOrganizeSelectedPages(context, pageIndexes) && pageIndexes.length < context.viewer.state.totalPages,
907
+ execute: ({ pages }, pageIndexes)=>pages.mutate({
908
+ kind: 'delete',
909
+ pageIndexes
910
+ })
911
+ },
912
+ extractPages: {
913
+ canExecute: canOrganizeSelectedPages,
914
+ execute: ({ pages }, pageIndexes)=>pages.exportPages({
915
+ kind: 'extract',
916
+ pageIndexes
917
+ })
918
+ },
919
+ insertBlankPage: {
920
+ canExecute: (context, index)=>canOrganizePages(context) && safelyValidate(()=>validatedPdfInsertionIndex(index, context.viewer.state.totalPages)),
921
+ execute: ({ pages }, index)=>pages.mutate({
922
+ index,
923
+ kind: 'insert-blank'
924
+ })
925
+ },
926
+ mergePages: {
927
+ canExecute: (context, index, source)=>canOrganizePages(context) && source instanceof Blob && source.size > 0 && safelyValidate(()=>validatedPdfInsertionIndex(index, context.viewer.state.totalPages)),
928
+ execute: ({ pages }, index, source)=>pages.mutate({
929
+ index,
930
+ kind: 'merge'
931
+ }, source)
932
+ },
933
+ openPageOrganizer: {
934
+ canExecute: canOrganizePages,
935
+ execute: ({ openPageOrganizer })=>openPageOrganizer()
936
+ },
937
+ reorderPages: {
938
+ canExecute: (context, pageOrder)=>canOrganizePages(context) && safelyValidate(()=>validatedPdfPageOrder(pageOrder, context.viewer.state.totalPages)),
939
+ execute: ({ pages }, pageOrder)=>pages.mutate({
940
+ kind: 'reorder',
941
+ pageOrder
942
+ })
943
+ },
944
+ rotatePages: {
945
+ canExecute: (context, pageIndexes, degrees)=>canOrganizeSelectedPages(context, pageIndexes) && (90 === degrees || 180 === degrees || 270 === degrees),
946
+ execute: ({ pages }, pageIndexes, degrees)=>pages.mutate({
947
+ degrees,
948
+ kind: 'rotate',
949
+ pageIndexes
950
+ })
951
+ },
952
+ splitPages: {
953
+ canExecute: (context, splitAfterPageIndexes)=>canOrganizePages(context) && safelyValidate(()=>validatedPdfSplitBoundaries(splitAfterPageIndexes, context.viewer.state.totalPages)),
954
+ execute: ({ pages }, splitAfterPageIndexes)=>pages.exportPages({
955
+ kind: 'split',
956
+ splitAfterPageIndexes
957
+ })
958
+ }
959
+ })
960
+ }),
825
961
  createOfficeEditorExtension({
826
962
  name: 'pdfKeyboardShortcuts',
827
963
  addKeyboardShortcuts: ()=>({
@@ -879,6 +1015,20 @@ function canSearch(viewer) {
879
1015
  function canZoom(viewer) {
880
1016
  return documentReady(viewer) && viewer.state.features.zoom;
881
1017
  }
1018
+ function canOrganizePages(context) {
1019
+ return context.editable && documentReady(context.viewer) && context.pages.state.available && !context.pages.state.busy;
1020
+ }
1021
+ function canOrganizeSelectedPages(context, pageIndexes) {
1022
+ return canOrganizePages(context) && safelyValidate(()=>validatedPdfPageIndexes(pageIndexes, context.viewer.state.totalPages));
1023
+ }
1024
+ function safelyValidate(validate) {
1025
+ try {
1026
+ validate();
1027
+ return true;
1028
+ } catch {
1029
+ return false;
1030
+ }
1031
+ }
882
1032
  function canSave({ editable, save, viewer }) {
883
1033
  return editable && save.enabled && documentReady(viewer) && viewer.state.features.export;
884
1034
  }
@@ -1538,6 +1688,381 @@ function pdfThumbnailKeyboardPage(key, page, totalPages) {
1538
1688
  if ('ArrowDown' === key || 'ArrowRight' === key) return Math.min(totalPages, page + 1);
1539
1689
  return null;
1540
1690
  }
1691
+ const PDF_ORGANIZER_ITEM_HEIGHT = 184;
1692
+ const PDF_ORGANIZER_VIEWPORT_HEIGHT = 552;
1693
+ function PdfPageOrganizerDialog({ busy, can, commands, currentPage, diagnostics, error, registry, restoreFocusTarget, totalPages, onClose, onDismissError }) {
1694
+ const initialIndex = Math.min(Math.max(0, totalPages - 1), Math.max(0, currentPage - 1));
1695
+ const [selected, setSelected] = useState(()=>new Set([
1696
+ initialIndex
1697
+ ]));
1698
+ const [anchorIndex, setAnchorIndex] = useState(initialIndex);
1699
+ const [submitting, setSubmitting] = useState(false);
1700
+ const selectionAnchorRef = useRef(initialIndex);
1701
+ const mergeInputRef = useRef(null);
1702
+ const effectiveBusy = busy || submitting;
1703
+ const selectedIndexes = useMemo(()=>[
1704
+ ...selected
1705
+ ].sort((left, right)=>left - right), [
1706
+ selected
1707
+ ]);
1708
+ const range = useMemo(()=>calculatePdfThumbnailRange({
1709
+ anchorIndex,
1710
+ itemHeight: PDF_ORGANIZER_ITEM_HEIGHT,
1711
+ totalPages,
1712
+ viewportHeight: PDF_ORGANIZER_VIEWPORT_HEIGHT
1713
+ }), [
1714
+ anchorIndex,
1715
+ totalPages
1716
+ ]);
1717
+ const visibleIndexes = useMemo(()=>Array.from({
1718
+ length: Math.max(0, range.end - range.start)
1719
+ }, (_, index)=>range.start + index), [
1720
+ range.end,
1721
+ range.start
1722
+ ]);
1723
+ const insertionIndex = selectedIndexes.length > 0 ? Math.min(totalPages, (selectedIndexes.at(-1) ?? 0) + 1) : totalPages;
1724
+ const splitBoundaries = selectedIndexes.filter((pageIndex)=>pageIndex < totalPages - 1);
1725
+ const moveLeftOrder = pageMoveOrder(totalPages, selectedIndexes, 'left');
1726
+ const moveRightOrder = pageMoveOrder(totalPages, selectedIndexes, 'right');
1727
+ const commitMutation = async (execute)=>{
1728
+ if (effectiveBusy) return;
1729
+ setSubmitting(true);
1730
+ const applied = await execute();
1731
+ if (applied) onClose();
1732
+ else setSubmitting(false);
1733
+ };
1734
+ const runExport = async (execute)=>{
1735
+ if (effectiveBusy) return;
1736
+ setSubmitting(true);
1737
+ await execute();
1738
+ setSubmitting(false);
1739
+ };
1740
+ return /*#__PURE__*/ jsxs(Dialog, {
1741
+ title: "组织 PDF 页面",
1742
+ description: "选择页面后插入、删除、旋转、重排、抽取、合并或拆分。",
1743
+ className: "work-pdf-page-organizer-dialog",
1744
+ closeDisabled: effectiveBusy,
1745
+ restoreFocusTarget: restoreFocusTarget,
1746
+ onClose: onClose,
1747
+ footer: /*#__PURE__*/ jsx(button_Button, {
1748
+ tone: "primary",
1749
+ disabled: effectiveBusy,
1750
+ onClick: onClose,
1751
+ children: "完成"
1752
+ }),
1753
+ children: [
1754
+ /*#__PURE__*/ jsx(office_text_field_OfficeFileInput, {
1755
+ ref: mergeInputRef,
1756
+ accept: ".pdf,application/pdf",
1757
+ "aria-label": "选择要合并的 PDF",
1758
+ disabled: effectiveBusy,
1759
+ onFileSelect: (file)=>{
1760
+ if (!can.mergePages(insertionIndex, file)) return;
1761
+ commitMutation(()=>commands.mergePages(insertionIndex, file));
1762
+ }
1763
+ }),
1764
+ /*#__PURE__*/ jsxs("div", {
1765
+ className: "work-pdf-page-organizer-actions",
1766
+ role: "toolbar",
1767
+ "aria-label": "PDF 页面组织命令",
1768
+ children: [
1769
+ /*#__PURE__*/ jsxs(button_Button, {
1770
+ tone: "secondary",
1771
+ "aria-label": "插入空白页",
1772
+ disabled: !can.insertBlankPage(insertionIndex) || effectiveBusy,
1773
+ onClick: ()=>void commitMutation(()=>commands.insertBlankPage(insertionIndex)),
1774
+ children: [
1775
+ /*#__PURE__*/ jsx(FilePlus2, {
1776
+ size: 15
1777
+ }),
1778
+ " 插入空白页"
1779
+ ]
1780
+ }),
1781
+ /*#__PURE__*/ jsxs(button_Button, {
1782
+ tone: "secondary",
1783
+ "aria-label": "合并另一个 PDF",
1784
+ disabled: effectiveBusy,
1785
+ onClick: ()=>mergeInputRef.current?.click(),
1786
+ children: [
1787
+ /*#__PURE__*/ jsx(Files, {
1788
+ size: 15
1789
+ }),
1790
+ " 合并 PDF"
1791
+ ]
1792
+ }),
1793
+ /*#__PURE__*/ jsxs(button_Button, {
1794
+ tone: "secondary",
1795
+ "aria-label": "前移所选页",
1796
+ disabled: !moveLeftOrder || !can.reorderPages(moveLeftOrder) || effectiveBusy,
1797
+ onClick: ()=>{
1798
+ if (moveLeftOrder) commitMutation(()=>commands.reorderPages(moveLeftOrder));
1799
+ },
1800
+ children: [
1801
+ /*#__PURE__*/ jsx(ChevronLeft, {
1802
+ size: 15
1803
+ }),
1804
+ " 前移"
1805
+ ]
1806
+ }),
1807
+ /*#__PURE__*/ jsxs(button_Button, {
1808
+ tone: "secondary",
1809
+ "aria-label": "后移所选页",
1810
+ disabled: !moveRightOrder || !can.reorderPages(moveRightOrder) || effectiveBusy,
1811
+ onClick: ()=>{
1812
+ if (moveRightOrder) commitMutation(()=>commands.reorderPages(moveRightOrder));
1813
+ },
1814
+ children: [
1815
+ /*#__PURE__*/ jsx(ChevronRight, {
1816
+ size: 15
1817
+ }),
1818
+ " 后移"
1819
+ ]
1820
+ }),
1821
+ /*#__PURE__*/ jsxs(button_Button, {
1822
+ tone: "secondary",
1823
+ "aria-label": "向左旋转所选页",
1824
+ disabled: !can.rotatePages(selectedIndexes, 270) || effectiveBusy,
1825
+ onClick: ()=>void commitMutation(()=>commands.rotatePages(selectedIndexes, 270)),
1826
+ children: [
1827
+ /*#__PURE__*/ jsx(RotateCcw, {
1828
+ size: 15
1829
+ }),
1830
+ " 左转"
1831
+ ]
1832
+ }),
1833
+ /*#__PURE__*/ jsxs(button_Button, {
1834
+ tone: "secondary",
1835
+ "aria-label": "向右旋转所选页",
1836
+ disabled: !can.rotatePages(selectedIndexes, 90) || effectiveBusy,
1837
+ onClick: ()=>void commitMutation(()=>commands.rotatePages(selectedIndexes, 90)),
1838
+ children: [
1839
+ /*#__PURE__*/ jsx(RotateCw, {
1840
+ size: 15
1841
+ }),
1842
+ " 右转"
1843
+ ]
1844
+ }),
1845
+ /*#__PURE__*/ jsxs(button_Button, {
1846
+ tone: "secondary",
1847
+ "aria-label": "抽取所选页",
1848
+ disabled: !can.extractPages(selectedIndexes) || effectiveBusy,
1849
+ onClick: ()=>void runExport(()=>commands.extractPages(selectedIndexes)),
1850
+ children: [
1851
+ /*#__PURE__*/ jsx(Download, {
1852
+ size: 15
1853
+ }),
1854
+ " 抽取"
1855
+ ]
1856
+ }),
1857
+ /*#__PURE__*/ jsxs(button_Button, {
1858
+ tone: "secondary",
1859
+ "aria-label": "在所选页后拆分",
1860
+ disabled: !can.splitPages(splitBoundaries) || effectiveBusy,
1861
+ onClick: ()=>void runExport(()=>commands.splitPages(splitBoundaries)),
1862
+ children: [
1863
+ /*#__PURE__*/ jsx(Scissors, {
1864
+ size: 15
1865
+ }),
1866
+ " 拆分"
1867
+ ]
1868
+ }),
1869
+ /*#__PURE__*/ jsxs(button_Button, {
1870
+ tone: "danger",
1871
+ "aria-label": "删除所选页",
1872
+ disabled: !can.deletePages(selectedIndexes) || effectiveBusy,
1873
+ onClick: ()=>void commitMutation(()=>commands.deletePages(selectedIndexes)),
1874
+ children: [
1875
+ /*#__PURE__*/ jsx(Trash2, {
1876
+ size: 15
1877
+ }),
1878
+ " 删除"
1879
+ ]
1880
+ })
1881
+ ]
1882
+ }),
1883
+ /*#__PURE__*/ jsxs("div", {
1884
+ className: "work-pdf-page-organizer-selection",
1885
+ children: [
1886
+ /*#__PURE__*/ jsxs("output", {
1887
+ "aria-live": "polite",
1888
+ children: [
1889
+ "已选择 ",
1890
+ selectedIndexes.length,
1891
+ " / ",
1892
+ totalPages,
1893
+ " 页"
1894
+ ]
1895
+ }),
1896
+ /*#__PURE__*/ jsx("button", {
1897
+ type: "button",
1898
+ disabled: effectiveBusy,
1899
+ onClick: ()=>setSelected(new Set(Array.from({
1900
+ length: totalPages
1901
+ }, (_, index)=>index))),
1902
+ children: "全选"
1903
+ }),
1904
+ /*#__PURE__*/ jsx("button", {
1905
+ type: "button",
1906
+ disabled: effectiveBusy,
1907
+ onClick: ()=>setSelected(new Set([
1908
+ initialIndex
1909
+ ])),
1910
+ children: "仅当前页"
1911
+ })
1912
+ ]
1913
+ }),
1914
+ /*#__PURE__*/ jsx("section", {
1915
+ className: "work-pdf-page-organizer-viewport",
1916
+ "aria-label": "可重排 PDF 页面",
1917
+ onScroll: (event)=>setAnchorIndex(Math.floor(event.currentTarget.scrollTop / PDF_ORGANIZER_ITEM_HEIGHT)),
1918
+ children: /*#__PURE__*/ jsxs("div", {
1919
+ className: "work-pdf-page-organizer-list",
1920
+ children: [
1921
+ /*#__PURE__*/ jsx(OrganizerSpacer, {
1922
+ height: range.start * PDF_ORGANIZER_ITEM_HEIGHT
1923
+ }),
1924
+ visibleIndexes.map((pageIndex)=>/*#__PURE__*/ jsx(OrganizerPage, {
1925
+ current: pageIndex === initialIndex,
1926
+ pageIndex: pageIndex,
1927
+ registry: registry,
1928
+ selected: selected.has(pageIndex),
1929
+ disabled: effectiveBusy,
1930
+ onDrop: (targetIndex)=>{
1931
+ const order = reorderedPdfPageIndexes(totalPages, selectedIndexes, targetIndex);
1932
+ if (can.reorderPages(order)) commitMutation(()=>commands.reorderPages(order));
1933
+ },
1934
+ onSelect: (event)=>{
1935
+ setSelected((current)=>nextPageSelection(current, pageIndex, selectionAnchorRef.current, event));
1936
+ if (!event.shiftKey) selectionAnchorRef.current = pageIndex;
1937
+ }
1938
+ }, pageIndex)),
1939
+ /*#__PURE__*/ jsx(OrganizerSpacer, {
1940
+ height: Math.max(0, totalPages - range.end) * PDF_ORGANIZER_ITEM_HEIGHT
1941
+ })
1942
+ ]
1943
+ })
1944
+ }),
1945
+ error && /*#__PURE__*/ jsxs("div", {
1946
+ className: "work-pdf-page-organizer-error",
1947
+ role: "alert",
1948
+ children: [
1949
+ /*#__PURE__*/ jsxs("div", {
1950
+ children: [
1951
+ /*#__PURE__*/ jsx("strong", {
1952
+ children: "无法安全完成页面操作"
1953
+ }),
1954
+ /*#__PURE__*/ jsx("p", {
1955
+ children: error.message
1956
+ }),
1957
+ /*#__PURE__*/ jsx("code", {
1958
+ children: error.code
1959
+ })
1960
+ ]
1961
+ }),
1962
+ /*#__PURE__*/ jsx("button", {
1963
+ type: "button",
1964
+ "aria-label": "关闭错误提示",
1965
+ onClick: onDismissError,
1966
+ children: "\xd7"
1967
+ })
1968
+ ]
1969
+ }),
1970
+ diagnostics.length > 0 && /*#__PURE__*/ jsx("ul", {
1971
+ className: "work-pdf-page-organizer-diagnostics",
1972
+ role: "status",
1973
+ children: diagnostics.map((diagnostic)=>/*#__PURE__*/ jsx("li", {
1974
+ children: diagnostic.message
1975
+ }, diagnostic.code))
1976
+ }),
1977
+ /*#__PURE__*/ jsx("p", {
1978
+ className: "work-pdf-page-organizer-boundary",
1979
+ children: "页面操作在 Worker 中生成新 PDF,并由一个撤销记录恢复。签名、加密、表单、目录或标记结构无法安全重写时会明确停止。"
1980
+ })
1981
+ ]
1982
+ });
1983
+ }
1984
+ function OrganizerPage({ current, disabled, pageIndex, registry, selected, onDrop, onSelect }) {
1985
+ const { sourceUrl, state } = usePdfThumbnailSource(registry, pageIndex + 1);
1986
+ return /*#__PURE__*/ jsxs("button", {
1987
+ type: "button",
1988
+ className: selected ? 'selected' : void 0,
1989
+ "aria-current": current ? 'page' : void 0,
1990
+ "aria-label": `选择第 ${pageIndex + 1} 页`,
1991
+ "aria-pressed": selected,
1992
+ "data-pdf-organizer-page-index": pageIndex,
1993
+ disabled: disabled,
1994
+ draggable: !disabled,
1995
+ onClick: onSelect,
1996
+ onDragOver: (event)=>{
1997
+ event.preventDefault();
1998
+ event.dataTransfer.dropEffect = 'move';
1999
+ },
2000
+ onDragStart: (event)=>{
2001
+ event.dataTransfer.effectAllowed = 'move';
2002
+ event.dataTransfer.setData('text/plain', String(pageIndex));
2003
+ },
2004
+ onDrop: (event)=>{
2005
+ event.preventDefault();
2006
+ onDrop(pageIndex);
2007
+ },
2008
+ children: [
2009
+ /*#__PURE__*/ jsxs("span", {
2010
+ className: "work-pdf-page-organizer-number",
2011
+ children: [
2012
+ pageIndex + 1,
2013
+ current && /*#__PURE__*/ jsx("small", {
2014
+ children: "当前"
2015
+ })
2016
+ ]
2017
+ }),
2018
+ /*#__PURE__*/ jsx("span", {
2019
+ className: "work-pdf-page-organizer-preview",
2020
+ "data-state": state,
2021
+ children: sourceUrl && /*#__PURE__*/ jsx("img", {
2022
+ src: sourceUrl,
2023
+ alt: "",
2024
+ draggable: false
2025
+ })
2026
+ })
2027
+ ]
2028
+ });
2029
+ }
2030
+ function nextPageSelection(current, pageIndex, anchorIndex, event) {
2031
+ if (event.shiftKey) {
2032
+ const start = Math.min(anchorIndex, pageIndex);
2033
+ const end = Math.max(anchorIndex, pageIndex);
2034
+ return new Set(Array.from({
2035
+ length: end - start + 1
2036
+ }, (_, index)=>start + index));
2037
+ }
2038
+ if (event.metaKey || event.ctrlKey) {
2039
+ const next = new Set(current);
2040
+ if (next.has(pageIndex)) next.delete(pageIndex);
2041
+ else next.add(pageIndex);
2042
+ return next;
2043
+ }
2044
+ return new Set([
2045
+ pageIndex
2046
+ ]);
2047
+ }
2048
+ function pageMoveOrder(pageCount, selected, direction) {
2049
+ if (0 === selected.length) return null;
2050
+ if ('left' === direction) {
2051
+ const first = selected[0] ?? 0;
2052
+ return first <= 0 ? null : reorderedPdfPageIndexes(pageCount, selected, first - 1);
2053
+ }
2054
+ const last = selected.at(-1) ?? pageCount - 1;
2055
+ return last >= pageCount - 1 ? null : reorderedPdfPageIndexes(pageCount, selected, last + 2);
2056
+ }
2057
+ function OrganizerSpacer({ height }) {
2058
+ if (height <= 0) return null;
2059
+ return /*#__PURE__*/ jsx("span", {
2060
+ "aria-hidden": "true",
2061
+ style: {
2062
+ height
2063
+ }
2064
+ });
2065
+ }
1541
2066
  function StatusBadge({ tone = 'neutral', children, className = '' }) {
1542
2067
  return /*#__PURE__*/ jsx("span", {
1543
2068
  className: `ds-status-badge ${tone}${className ? ` ${className}` : ''}`,
@@ -1554,8 +2079,9 @@ const pdfKeyboardShortcuts = {
1554
2079
  zoomIn: 'Control+= Meta+= Control+Shift++ Meta+Shift++',
1555
2080
  zoomOut: 'Control+- Meta+-'
1556
2081
  };
1557
- function PdfToolbar({ annotationState, can, commands, editable, pageNavigation, saveAvailable, saveLabel, saveState, searchInputRef, state }) {
2082
+ function PdfToolbar({ annotationState, can, commands, editable, pageOrganizationAvailable, pageNavigation, saveAvailable, saveLabel, saveState, searchInputRef, state }) {
1558
2083
  const showSave = saveAvailable ?? editable;
2084
+ const showPageOrganization = pageOrganizationAvailable ?? editable;
1559
2085
  const [pageValue, setPageValue] = useState('');
1560
2086
  const [searchValue, setSearchValue] = useState('');
1561
2087
  const cancelPageBlurCommitRef = useRef(false);
@@ -1662,6 +2188,23 @@ function PdfToolbar({ annotationState, can, commands, editable, pageNavigation,
1662
2188
  })
1663
2189
  ]
1664
2190
  }),
2191
+ showPageOrganization && /*#__PURE__*/ jsx("div", {
2192
+ className: "work-pdf-toolbar-group work-pdf-page-organization",
2193
+ children: /*#__PURE__*/ jsxs(button_Button, {
2194
+ tone: "secondary",
2195
+ className: "work-pdf-page-organizer-trigger",
2196
+ "data-pdf-page-organizer-trigger": true,
2197
+ "aria-label": "组织 PDF 页面",
2198
+ disabled: !can.openPageOrganizer(),
2199
+ onClick: commands.openPageOrganizer,
2200
+ children: [
2201
+ /*#__PURE__*/ jsx(LayoutGrid, {
2202
+ size: 15
2203
+ }),
2204
+ "组织页面"
2205
+ ]
2206
+ })
2207
+ }),
1665
2208
  editable && /*#__PURE__*/ jsxs("fieldset", {
1666
2209
  className: "work-pdf-toolbar-group work-pdf-annotation",
1667
2210
  children: [
@@ -1759,6 +2302,7 @@ function PdfToolbar({ annotationState, can, commands, editable, pageNavigation,
1759
2302
  can: can,
1760
2303
  commands: commands,
1761
2304
  editable: editable,
2305
+ pageOrganizationAvailable: showPageOrganization,
1762
2306
  state: state
1763
2307
  }),
1764
2308
  /*#__PURE__*/ jsx("search", {
@@ -2104,7 +2648,7 @@ function movePdfAnnotationStyleOption(event) {
2104
2648
  });
2105
2649
  next?.click();
2106
2650
  }
2107
- function PdfToolbarOverflow({ annotationState, can, commands, editable, state }) {
2651
+ function PdfToolbarOverflow({ annotationState, can, commands, editable, pageOrganizationAvailable, state }) {
2108
2652
  const hasOverflowTools = editable && annotationState.available || editable && state.features.history || state.features.navigation || state.features.search || state.features.zoom;
2109
2653
  return /*#__PURE__*/ jsx(Popover, {
2110
2654
  label: "更多 PDF 工具",
@@ -2254,6 +2798,18 @@ function PdfToolbarOverflow({ annotationState, can, commands, editable, state })
2254
2798
  })
2255
2799
  ]
2256
2800
  }),
2801
+ pageOrganizationAvailable && /*#__PURE__*/ jsx("fieldset", {
2802
+ className: "work-pdf-overflow-group",
2803
+ "aria-label": "页面组织",
2804
+ children: /*#__PURE__*/ jsx(PdfOverflowAction, {
2805
+ label: "组织页面",
2806
+ disabled: !can.openPageOrganizer(),
2807
+ onSelect: ()=>select(commands.openPageOrganizer),
2808
+ children: /*#__PURE__*/ jsx(LayoutGrid, {
2809
+ size: 15
2810
+ })
2811
+ })
2812
+ }),
2257
2813
  state.features.navigation && /*#__PURE__*/ jsxs("fieldset", {
2258
2814
  className: "work-pdf-overflow-group",
2259
2815
  "aria-label": "翻页",
@@ -2740,6 +3296,357 @@ function pdf_viewer_controller_safely(read) {
2740
3296
  function errorMessage(error) {
2741
3297
  return error instanceof Error && error.message ? error.message : 'PDF viewer initialization failed.';
2742
3298
  }
3299
+ const PDF_PAGE_ORGANIZATION_WORKER_TIMEOUT_MS = 120000;
3300
+ async function runPdfPageOrganizationJob(job, signal) {
3301
+ if (signal?.aborted) throw pdfPageOrganizationAbortError();
3302
+ if ("u" < typeof Worker) return runDirectly(job, signal);
3303
+ let worker;
3304
+ try {
3305
+ worker = new Worker(new URL('./pdf-page-organization.worker.js', import.meta.url), {
3306
+ name: 'a3s-office-pdf-page-organization'
3307
+ });
3308
+ } catch {
3309
+ return runDirectly(job, signal);
3310
+ }
3311
+ return new Promise((resolve, reject)=>{
3312
+ let settled = false;
3313
+ const timeout = setTimeout(()=>{
3314
+ finish(void 0, new PdfPageOrganizationError('pdf.pages.invalid-source', 'PDF page organization timed out.'));
3315
+ }, PDF_PAGE_ORGANIZATION_WORKER_TIMEOUT_MS);
3316
+ const finish = (result, error)=>{
3317
+ if (settled) return;
3318
+ settled = true;
3319
+ clearTimeout(timeout);
3320
+ signal?.removeEventListener('abort', handleAbort);
3321
+ worker.removeEventListener('error', handleWorkerError);
3322
+ worker.removeEventListener('message', handleMessage);
3323
+ worker.removeEventListener('messageerror', handleWorkerError);
3324
+ worker.terminate();
3325
+ if (error) reject(error);
3326
+ else if (result) resolve(result);
3327
+ else reject(new Error('PDF page worker returned no result.'));
3328
+ };
3329
+ const handleAbort = ()=>finish(void 0, pdfPageOrganizationAbortError());
3330
+ const handleWorkerError = ()=>finish(void 0, new PdfPageOrganizationError('pdf.pages.invalid-source', 'The PDF page worker failed before producing a result.'));
3331
+ const handleMessage = (event)=>{
3332
+ const response = event.data;
3333
+ if (!response || 'object' != typeof response) return void handleWorkerError();
3334
+ if ('failure' === response.kind) return void finish(void 0, new PdfPageOrganizationError(response.code, response.message));
3335
+ if ('success' === response.kind) finish(response.result);
3336
+ else handleWorkerError();
3337
+ };
3338
+ signal?.addEventListener('abort', handleAbort, {
3339
+ once: true
3340
+ });
3341
+ worker.addEventListener('error', handleWorkerError, {
3342
+ once: true
3343
+ });
3344
+ worker.addEventListener('message', handleMessage);
3345
+ worker.addEventListener('messageerror', handleWorkerError, {
3346
+ once: true
3347
+ });
3348
+ if (signal?.aborted) return void handleAbort();
3349
+ const transferableJob = transferablePdfPageJob(job);
3350
+ const request = {
3351
+ job: transferableJob.job,
3352
+ kind: 'run'
3353
+ };
3354
+ try {
3355
+ worker.postMessage(request, {
3356
+ transfer: transferableJob.transfer
3357
+ });
3358
+ } catch {
3359
+ handleWorkerError();
3360
+ }
3361
+ });
3362
+ }
3363
+ async function runDirectly(job, signal) {
3364
+ if (signal?.aborted) throw pdfPageOrganizationAbortError();
3365
+ const { applyPdfPageOrganizationJob } = await import("./0~pdf-page-organization-engine.js");
3366
+ if (signal?.aborted) throw pdfPageOrganizationAbortError();
3367
+ const result = await applyPdfPageOrganizationJob(job);
3368
+ if (signal?.aborted) throw pdfPageOrganizationAbortError();
3369
+ return result;
3370
+ }
3371
+ function transferablePdfPageJob(job) {
3372
+ const source = transferableSource(job.source);
3373
+ if ('export' === job.kind) return {
3374
+ job: {
3375
+ ...job,
3376
+ source: source.source
3377
+ },
3378
+ transfer: [
3379
+ source.source
3380
+ ]
3381
+ };
3382
+ const imported = job.importSource ? transferableSource(job.importSource) : void 0;
3383
+ return {
3384
+ job: {
3385
+ ...job,
3386
+ ...imported ? {
3387
+ importSource: imported.source
3388
+ } : {},
3389
+ source: source.source
3390
+ },
3391
+ transfer: imported ? [
3392
+ source.source,
3393
+ imported.source
3394
+ ] : [
3395
+ source.source
3396
+ ]
3397
+ };
3398
+ }
3399
+ function transferableSource(source) {
3400
+ if (source instanceof ArrayBuffer) return {
3401
+ source
3402
+ };
3403
+ const copy = new Uint8Array(source.byteLength);
3404
+ copy.set(source);
3405
+ return {
3406
+ source: copy.buffer
3407
+ };
3408
+ }
3409
+ function pdfPageOrganizationAbortError() {
3410
+ if ("u" > typeof DOMException) return new DOMException('PDF page organization was cancelled.', 'AbortError');
3411
+ const error = new Error('PDF page organization was cancelled.');
3412
+ error.name = 'AbortError';
3413
+ return error;
3414
+ }
3415
+ const initialState = {
3416
+ available: false,
3417
+ busy: false,
3418
+ canRedo: false,
3419
+ canUndo: false,
3420
+ diagnostics: [],
3421
+ error: null,
3422
+ revision: 0
3423
+ };
3424
+ function usePdfPageOrganization(options, runJob = runPdfPageOrganizationJob) {
3425
+ const [state, setState] = useState({
3426
+ ...initialState,
3427
+ available: options.enabled
3428
+ });
3429
+ const undoRef = useRef([]);
3430
+ const redoRef = useRef([]);
3431
+ const abortRef = useRef(null);
3432
+ const optionsRef = useRef(options);
3433
+ const runJobRef = useRef(runJob);
3434
+ optionsRef.current = options;
3435
+ runJobRef.current = runJob;
3436
+ useEffect(()=>{
3437
+ abortRef.current?.abort();
3438
+ abortRef.current = null;
3439
+ undoRef.current = [];
3440
+ redoRef.current = [];
3441
+ setState({
3442
+ ...initialState,
3443
+ available: options.enabled
3444
+ });
3445
+ }, [
3446
+ options.enabled,
3447
+ options.resetKey
3448
+ ]);
3449
+ useEffect(()=>()=>{
3450
+ abortRef.current?.abort();
3451
+ }, []);
3452
+ const mutate = useCallback(async (mutation, importSource)=>{
3453
+ const currentOptions = optionsRef.current;
3454
+ if (!currentOptions.enabled || abortRef.current) return false;
3455
+ const abort = new AbortController();
3456
+ abortRef.current = abort;
3457
+ setState((current)=>({
3458
+ ...current,
3459
+ busy: true,
3460
+ diagnostics: [],
3461
+ error: null
3462
+ }));
3463
+ try {
3464
+ const before = await currentOptions.readCurrentSource();
3465
+ const job = {
3466
+ ...importSource ? {
3467
+ importSource: await importSource.arrayBuffer()
3468
+ } : {},
3469
+ kind: 'mutate',
3470
+ mutation,
3471
+ source: await before.arrayBuffer()
3472
+ };
3473
+ const result = await runJobRef.current(job, abort.signal);
3474
+ if ('mutated' !== result.kind) throw new Error('PDF page mutation returned export files.');
3475
+ const after = pdfBlob(result.source);
3476
+ const entry = {
3477
+ after,
3478
+ before
3479
+ };
3480
+ undoRef.current = [
3481
+ ...undoRef.current,
3482
+ entry
3483
+ ];
3484
+ redoRef.current = [];
3485
+ currentOptions.replaceSource(after);
3486
+ setState((current)=>({
3487
+ ...current,
3488
+ busy: false,
3489
+ canRedo: false,
3490
+ canUndo: true,
3491
+ diagnostics: result.diagnostics,
3492
+ error: null,
3493
+ revision: current.revision + 1
3494
+ }));
3495
+ return true;
3496
+ } catch (error) {
3497
+ if (abort.signal.aborted) return false;
3498
+ setState((current)=>({
3499
+ ...current,
3500
+ busy: false,
3501
+ diagnostics: [],
3502
+ error: controllerError(error)
3503
+ }));
3504
+ return false;
3505
+ } finally{
3506
+ if (abortRef.current === abort) abortRef.current = null;
3507
+ }
3508
+ }, []);
3509
+ const exportPages = useCallback(async (operation)=>{
3510
+ const currentOptions = optionsRef.current;
3511
+ if (!currentOptions.enabled || abortRef.current) return false;
3512
+ const abort = new AbortController();
3513
+ abortRef.current = abort;
3514
+ setState((current)=>({
3515
+ ...current,
3516
+ busy: true,
3517
+ diagnostics: [],
3518
+ error: null
3519
+ }));
3520
+ try {
3521
+ const before = await currentOptions.readCurrentSource();
3522
+ const result = await runJobRef.current({
3523
+ kind: 'export',
3524
+ operation,
3525
+ source: await before.arrayBuffer()
3526
+ }, abort.signal);
3527
+ if ('exported' !== result.kind) throw new Error('PDF page export returned a mutated source.');
3528
+ const files = namedPdfExports(currentOptions.fileName, operation.kind, result);
3529
+ const accepted = currentOptions.onExport ? await currentOptions.onExport(files) : downloadPdfExports(files);
3530
+ if (!accepted) throw new Error('The host rejected the PDF page export.');
3531
+ setState((current)=>({
3532
+ ...current,
3533
+ busy: false,
3534
+ diagnostics: result.diagnostics,
3535
+ error: null
3536
+ }));
3537
+ return true;
3538
+ } catch (error) {
3539
+ if (abort.signal.aborted) return false;
3540
+ setState((current)=>({
3541
+ ...current,
3542
+ busy: false,
3543
+ diagnostics: [],
3544
+ error: controllerError(error)
3545
+ }));
3546
+ return false;
3547
+ } finally{
3548
+ if (abortRef.current === abort) abortRef.current = null;
3549
+ }
3550
+ }, []);
3551
+ const undo = useCallback(()=>{
3552
+ if (!optionsRef.current.enabled || abortRef.current) return;
3553
+ const entry = undoRef.current.at(-1);
3554
+ if (!entry) return;
3555
+ undoRef.current = undoRef.current.slice(0, -1);
3556
+ redoRef.current = [
3557
+ ...redoRef.current,
3558
+ entry
3559
+ ];
3560
+ optionsRef.current.replaceSource(entry.before);
3561
+ setState((current)=>({
3562
+ ...current,
3563
+ canRedo: true,
3564
+ canUndo: undoRef.current.length > 0,
3565
+ diagnostics: [],
3566
+ error: null,
3567
+ revision: current.revision + 1
3568
+ }));
3569
+ }, []);
3570
+ const redo = useCallback(()=>{
3571
+ if (!optionsRef.current.enabled || abortRef.current) return;
3572
+ const entry = redoRef.current.at(-1);
3573
+ if (!entry) return;
3574
+ redoRef.current = redoRef.current.slice(0, -1);
3575
+ undoRef.current = [
3576
+ ...undoRef.current,
3577
+ entry
3578
+ ];
3579
+ optionsRef.current.replaceSource(entry.after);
3580
+ setState((current)=>({
3581
+ ...current,
3582
+ canRedo: redoRef.current.length > 0,
3583
+ canUndo: true,
3584
+ diagnostics: [],
3585
+ error: null,
3586
+ revision: current.revision + 1
3587
+ }));
3588
+ }, []);
3589
+ const dismissError = useCallback(()=>{
3590
+ setState((current)=>({
3591
+ ...current,
3592
+ error: null
3593
+ }));
3594
+ }, []);
3595
+ return useMemo(()=>({
3596
+ state,
3597
+ dismissError,
3598
+ exportPages,
3599
+ mutate,
3600
+ redo,
3601
+ undo
3602
+ }), [
3603
+ dismissError,
3604
+ exportPages,
3605
+ mutate,
3606
+ redo,
3607
+ state,
3608
+ undo
3609
+ ]);
3610
+ }
3611
+ function namedPdfExports(fileName, kind, result) {
3612
+ const stem = fileName.replace(/\.pdf$/i, '') || 'document';
3613
+ return result.files.map((file, index)=>({
3614
+ fileName: 'extract' === kind ? `${stem}-extracted.pdf` : `${stem}-part-${index + 1}.pdf`,
3615
+ pageCount: file.pageCount,
3616
+ pdf: pdfBlob(file.source)
3617
+ }));
3618
+ }
3619
+ function pdfBlob(source) {
3620
+ const copy = new Uint8Array(source.byteLength);
3621
+ copy.set(source);
3622
+ return new Blob([
3623
+ copy.buffer
3624
+ ], {
3625
+ type: 'application/pdf'
3626
+ });
3627
+ }
3628
+ function downloadPdfExports(files) {
3629
+ if ("u" < typeof document || 'function' != typeof URL.createObjectURL) return false;
3630
+ for (const file of files){
3631
+ const url = URL.createObjectURL(file.pdf);
3632
+ const anchor = document.createElement('a');
3633
+ anchor.href = url;
3634
+ anchor.download = file.fileName;
3635
+ anchor.hidden = true;
3636
+ document.body.append(anchor);
3637
+ anchor.click();
3638
+ anchor.remove();
3639
+ queueMicrotask(()=>URL.revokeObjectURL(url));
3640
+ }
3641
+ return true;
3642
+ }
3643
+ function controllerError(error) {
3644
+ const candidate = error;
3645
+ return {
3646
+ code: 'string' == typeof candidate?.code && candidate.code.startsWith('pdf.pages.') ? candidate.code : 'pdf.pages.invalid-source',
3647
+ message: 'string' == typeof candidate?.message && candidate.message ? candidate.message.slice(0, 512) : 'Unable to organize this PDF.'
3648
+ };
3649
+ }
2743
3650
  const PDFIUM_WASM_PATH = '/vendor/embedpdf/pdfium.wasm';
2744
3651
  const PDF_VIEWER_READY_TIMEOUT_MS = 45000;
2745
3652
  const PDF_MOBILE_PAGE_NAVIGATION_QUERY = '(max-width: 640px)';
@@ -2753,13 +3660,16 @@ const a3sPdfUiSchema = {
2753
3660
  overlays: {},
2754
3661
  selectionMenus: {}
2755
3662
  };
2756
- function PdfViewer({ collaboration, evidenceOverlay, fileName = 'document.pdf', loadSource, onCollaborationChange, onEvidenceRegionSelect, onPageChange, onSave, saveLabel = '保存', selectedEvidenceRegionId, sourceKey, wasmUrl, worker = true }) {
3663
+ function PdfViewer({ collaboration, evidenceOverlay, fileName = 'document.pdf', loadSource, onCollaborationChange, onEvidenceRegionSelect, onPageChange, onPageExport, onSave, saveLabel = '保存', selectedEvidenceRegionId, sourceKey, wasmUrl, worker = true }) {
3664
+ const [sourceBlob, setSourceBlob] = useState(null);
2757
3665
  const [sourceUrl, setSourceUrl] = useState(null);
2758
3666
  const [loadError, setLoadError] = useState(null);
2759
3667
  const [saveState, setSaveState] = useState('idle');
2760
3668
  const [retryCount, setRetryCount] = useState(0);
2761
3669
  const [registry, setRegistry] = useState(null);
2762
3670
  const [, refreshCollaborationHistory] = useState(0);
3671
+ const [hostSourceGeneration, setHostSourceGeneration] = useState(0);
3672
+ const [pageOrganizerOpen, setPageOrganizerOpen] = useState(false);
2763
3673
  const [mobilePageNavigationOpen, setMobilePageNavigationOpen] = useState(false);
2764
3674
  const pdfRootRef = useRef(null);
2765
3675
  const searchInputRef = useRef(null);
@@ -2767,6 +3677,8 @@ function PdfViewer({ collaboration, evidenceOverlay, fileName = 'document.pdf',
2767
3677
  const mobilePageNavigationToggleRef = useRef(null);
2768
3678
  const mobilePageNavigationCloseRef = useRef(null);
2769
3679
  const collaborationProjectionRef = useRef(void 0);
3680
+ const editable = collaboration ? 'edit' === collaboration.mode : Boolean(onSave);
3681
+ const pageOrganizationEnabled = Boolean(onSave) && !collaboration && !evidenceOverlay;
2770
3682
  const collaborationHistory = collaboration ? {
2771
3683
  canRedo: collaborationProjectionRef.current?.binding.canRedo() ?? false,
2772
3684
  canUndo: collaborationProjectionRef.current?.binding.canUndo() ?? false,
@@ -2774,11 +3686,54 @@ function PdfViewer({ collaboration, evidenceOverlay, fileName = 'document.pdf',
2774
3686
  undo: ()=>collaborationProjectionRef.current?.binding.undo()
2775
3687
  } : void 0;
2776
3688
  const controller = usePdfViewerController(registry, collaborationHistory);
3689
+ const replacePageSource = useCallback((source)=>{
3690
+ setRegistry(null);
3691
+ setSourceUrl(null);
3692
+ setSourceBlob('application/pdf' === source.type ? source : new Blob([
3693
+ source
3694
+ ], {
3695
+ type: 'application/pdf'
3696
+ }));
3697
+ setLoadError(null);
3698
+ setSaveState('idle');
3699
+ setMobilePageNavigationOpen(false);
3700
+ setPageOrganizerOpen(false);
3701
+ }, []);
3702
+ const pageOrganization = usePdfPageOrganization({
3703
+ enabled: pageOrganizationEnabled,
3704
+ fileName,
3705
+ onExport: onPageExport,
3706
+ readCurrentSource: ()=>controller.saveAsCopy(),
3707
+ replaceSource: replacePageSource,
3708
+ resetKey: `${sourceKey ?? 'source'}:${retryCount}:${hostSourceGeneration}`
3709
+ });
3710
+ const viewerController = useMemo(()=>({
3711
+ ...controller,
3712
+ state: {
3713
+ ...controller.state,
3714
+ canRedo: controller.state.canRedo || pageOrganization.state.canRedo,
3715
+ canUndo: controller.state.canUndo || pageOrganization.state.canUndo,
3716
+ features: {
3717
+ ...controller.state.features,
3718
+ history: controller.state.features.history || pageOrganization.state.available
3719
+ }
3720
+ },
3721
+ redo: ()=>{
3722
+ if (controller.state.canRedo) controller.redo();
3723
+ else pageOrganization.redo();
3724
+ },
3725
+ undo: ()=>{
3726
+ if (controller.state.canUndo) controller.undo();
3727
+ else pageOrganization.undo();
3728
+ }
3729
+ }), [
3730
+ controller,
3731
+ pageOrganization
3732
+ ]);
2777
3733
  const annotation = usePdfAnnotationController(registry);
2778
- const viewerReady = controller.state.ready && controller.state.documentOpen;
3734
+ const viewerReady = viewerController.state.ready && viewerController.state.documentOpen;
2779
3735
  const mobilePageNavigationModal = usePdfMobilePageNavigationModal();
2780
3736
  const mobilePageNavigationModalOpen = mobilePageNavigationOpen && mobilePageNavigationModal;
2781
- const editable = collaboration ? 'edit' === collaboration.mode : Boolean(onSave);
2782
3737
  const collaborationRef = useRef(collaboration);
2783
3738
  if (collaborationRef.current !== collaboration) throw new Error('PdfViewer collaboration sessions cannot be replaced while mounted. Remount the viewer for another shared PDF.');
2784
3739
  const closeMobilePageNavigation = useCallback(()=>{
@@ -2796,28 +3751,28 @@ function PdfViewer({ collaboration, evidenceOverlay, fileName = 'document.pdf',
2796
3751
  });
2797
3752
  useEffect(()=>{
2798
3753
  let disposed = false;
2799
- let objectUrl = null;
2800
3754
  setRegistry(null);
2801
3755
  setSaveState('idle');
3756
+ setSourceBlob(null);
2802
3757
  setSourceUrl(null);
2803
3758
  setLoadError(null);
2804
3759
  setMobilePageNavigationOpen(false);
3760
+ setPageOrganizerOpen(false);
2805
3761
  loadSource().then(async (source)=>{
2806
3762
  if (disposed) return;
2807
3763
  if (collaboration) await assertPdfCollaborationBlob(collaboration, source);
2808
3764
  if (disposed) return;
2809
- objectUrl = URL.createObjectURL('application/pdf' === source.type ? source : new Blob([
3765
+ setSourceBlob('application/pdf' === source.type ? source : new Blob([
2810
3766
  source
2811
3767
  ], {
2812
3768
  type: 'application/pdf'
2813
3769
  }));
2814
- setSourceUrl(objectUrl);
3770
+ setHostSourceGeneration((value)=>value + 1);
2815
3771
  }).catch((error)=>{
2816
3772
  if (!disposed) setLoadError(pdfErrorMessage(error));
2817
3773
  });
2818
3774
  return ()=>{
2819
3775
  disposed = true;
2820
- if (objectUrl) URL.revokeObjectURL(objectUrl);
2821
3776
  };
2822
3777
  }, [
2823
3778
  collaboration,
@@ -2825,6 +3780,14 @@ function PdfViewer({ collaboration, evidenceOverlay, fileName = 'document.pdf',
2825
3780
  retryCount,
2826
3781
  sourceKey
2827
3782
  ]);
3783
+ useEffect(()=>{
3784
+ if (!sourceBlob) return void setSourceUrl(null);
3785
+ const objectUrl = URL.createObjectURL(sourceBlob);
3786
+ setSourceUrl(objectUrl);
3787
+ return ()=>URL.revokeObjectURL(objectUrl);
3788
+ }, [
3789
+ sourceBlob
3790
+ ]);
2828
3791
  useEffect(()=>{
2829
3792
  if (controller.state.error) setLoadError(controller.state.error);
2830
3793
  }, [
@@ -2888,10 +3851,10 @@ function PdfViewer({ collaboration, evidenceOverlay, fileName = 'document.pdf',
2888
3851
  onCollaborationChange
2889
3852
  ]);
2890
3853
  useEffect(()=>{
2891
- if (controller.state.currentPage > 0) onPageChange?.(controller.state.currentPage);
3854
+ if (viewerController.state.currentPage > 0) onPageChange?.(viewerController.state.currentPage);
2892
3855
  }, [
2893
- controller.state.currentPage,
2894
- onPageChange
3856
+ onPageChange,
3857
+ viewerController.state.currentPage
2895
3858
  ]);
2896
3859
  useEffect(()=>{
2897
3860
  if (!sourceUrl || viewerReady || loadError) return;
@@ -2927,17 +3890,19 @@ function PdfViewer({ collaboration, evidenceOverlay, fileName = 'document.pdf',
2927
3890
  searchInputRef.current?.focus();
2928
3891
  searchInputRef.current?.select();
2929
3892
  },
3893
+ openPageOrganizer: ()=>setPageOrganizerOpen(true),
3894
+ pages: pageOrganization,
2930
3895
  save: {
2931
3896
  enabled: Boolean(onSave) && 'saving' !== saveState,
2932
3897
  execute: savePdf
2933
3898
  },
2934
- viewer: controller
3899
+ viewer: viewerController
2935
3900
  }, pdfExtensions);
2936
3901
  const pdfCommands = pdfEditor.commands;
2937
3902
  const selectedAnnotationLocation = annotation.getSelectionLocation();
2938
- const pdfPresenceLocation = viewerReady && controller.state.currentPage > 0 ? {
3903
+ const pdfPresenceLocation = viewerReady && viewerController.state.currentPage > 0 ? {
2939
3904
  kind: 'pdf',
2940
- pageIndex: selectedAnnotationLocation?.pageIndex ?? controller.state.currentPage - 1,
3905
+ pageIndex: selectedAnnotationLocation?.pageIndex ?? viewerController.state.currentPage - 1,
2941
3906
  ...selectedAnnotationLocation ? {
2942
3907
  annotationId: selectedAnnotationLocation.annotationId
2943
3908
  } : {}
@@ -2945,8 +3910,8 @@ function PdfViewer({ collaboration, evidenceOverlay, fileName = 'document.pdf',
2945
3910
  useOfficePublishPresenceLocation(pdfPresenceLocation);
2946
3911
  const navigateToPdfParticipant = useCallback((participant)=>{
2947
3912
  const location = participant.location;
2948
- if (location?.kind !== 'pdf' || !viewerReady || location.pageIndex >= controller.state.totalPages) return false;
2949
- controller.goToPage(location.pageIndex + 1);
3913
+ if (location?.kind !== 'pdf' || !viewerReady || location.pageIndex >= viewerController.state.totalPages) return false;
3914
+ viewerController.goToPage(location.pageIndex + 1);
2950
3915
  if (location.annotationId) annotation.locateAnnotation(location.pageIndex, location.annotationId);
2951
3916
  requestAnimationFrame(()=>{
2952
3917
  const target = pdfRootRef.current?.querySelector('.work-pdf-embed');
@@ -2957,7 +3922,7 @@ function PdfViewer({ collaboration, evidenceOverlay, fileName = 'document.pdf',
2957
3922
  return true;
2958
3923
  }, [
2959
3924
  annotation,
2960
- controller,
3925
+ viewerController,
2961
3926
  viewerReady
2962
3927
  ]);
2963
3928
  useOfficeCollaborationLocationNavigator(navigateToPdfParticipant);
@@ -3006,8 +3971,9 @@ function PdfViewer({ collaboration, evidenceOverlay, fileName = 'document.pdf',
3006
3971
  can: pdfEditor.can(),
3007
3972
  commands: pdfCommands,
3008
3973
  editable: editable,
3974
+ pageOrganizationAvailable: pageOrganization.state.available,
3009
3975
  saveAvailable: editable && Boolean(onSave),
3010
- pageNavigation: viewerReady && registry && controller.state.totalPages > 0 ? {
3976
+ pageNavigation: viewerReady && registry && viewerController.state.totalPages > 0 ? {
3011
3977
  controlsId: mobilePageNavigationId,
3012
3978
  expanded: mobilePageNavigationOpen,
3013
3979
  onOpen: ()=>setMobilePageNavigationOpen(true),
@@ -3016,22 +3982,35 @@ function PdfViewer({ collaboration, evidenceOverlay, fileName = 'document.pdf',
3016
3982
  searchInputRef: searchInputRef,
3017
3983
  saveLabel: saveLabel,
3018
3984
  saveState: saveState,
3019
- state: controller.state
3985
+ state: viewerController.state
3986
+ }),
3987
+ pageOrganizerOpen && viewerReady && registry && viewerController.state.totalPages > 0 && /*#__PURE__*/ jsx(PdfPageOrganizerDialog, {
3988
+ busy: pageOrganization.state.busy,
3989
+ can: pdfEditor.can(),
3990
+ commands: pdfCommands,
3991
+ currentPage: viewerController.state.currentPage,
3992
+ diagnostics: pageOrganization.state.diagnostics,
3993
+ error: pageOrganization.state.error,
3994
+ registry: registry,
3995
+ restoreFocusTarget: ()=>pdfPageOrganizerRestoreFocusTarget(pdfRootRef.current),
3996
+ totalPages: viewerController.state.totalPages,
3997
+ onClose: ()=>setPageOrganizerOpen(false),
3998
+ onDismissError: pageOrganization.dismissError
3020
3999
  }),
3021
4000
  /*#__PURE__*/ jsxs("div", {
3022
4001
  className: "work-pdf-workspace",
3023
4002
  "data-mobile-page-navigation": mobilePageNavigationOpen ? 'open' : 'closed',
3024
4003
  children: [
3025
- viewerReady && registry && controller.state.totalPages > 0 && /*#__PURE__*/ jsx(PdfThumbnailRail, {
3026
- currentPage: controller.state.currentPage,
4004
+ viewerReady && registry && viewerController.state.totalPages > 0 && /*#__PURE__*/ jsx(PdfThumbnailRail, {
4005
+ currentPage: viewerController.state.currentPage,
3027
4006
  mobileCloseButtonRef: mobilePageNavigationCloseRef,
3028
4007
  mobileNavigationId: mobilePageNavigationId,
3029
4008
  mobileNavigationModal: mobilePageNavigationModalOpen,
3030
4009
  registry: registry,
3031
- totalPages: controller.state.totalPages,
4010
+ totalPages: viewerController.state.totalPages,
3032
4011
  onCloseMobileNavigation: closeMobilePageNavigation,
3033
4012
  onSelectPage: (page)=>{
3034
- controller.goToPage(page);
4013
+ viewerController.goToPage(page);
3035
4014
  if (mobilePageNavigationModalOpen) closeMobilePageNavigation();
3036
4015
  }
3037
4016
  }),
@@ -3109,11 +4088,11 @@ function PdfViewer({ collaboration, evidenceOverlay, fileName = 'document.pdf',
3109
4088
  },
3110
4089
  onReady: setRegistry
3111
4090
  }, sourceUrl),
3112
- viewerReady && controller.state.currentPage > 0 && /*#__PURE__*/ jsx(PdfCollaborationPresenceLayer, {
3113
- pageIndex: controller.state.currentPage - 1
4091
+ viewerReady && viewerController.state.currentPage > 0 && /*#__PURE__*/ jsx(PdfCollaborationPresenceLayer, {
4092
+ pageIndex: viewerController.state.currentPage - 1
3114
4093
  }),
3115
4094
  evidenceOverlay && registry && viewerReady && /*#__PURE__*/ jsx(PdfEvidenceOverlayLayer, {
3116
- currentPage: controller.state.currentPage,
4095
+ currentPage: viewerController.state.currentPage,
3117
4096
  evidenceOverlay: evidenceOverlay,
3118
4097
  onEvidenceRegionSelect: onEvidenceRegionSelect,
3119
4098
  registry: registry,
@@ -3142,6 +4121,11 @@ function pdfErrorMessage(error) {
3142
4121
  if (error instanceof Error && error.message) return error.message;
3143
4122
  return 'Unable to read this PDF file.';
3144
4123
  }
4124
+ function pdfPageOrganizerRestoreFocusTarget(root) {
4125
+ const directTrigger = root?.querySelector('[data-pdf-page-organizer-trigger]');
4126
+ if (directTrigger && directTrigger.getClientRects().length > 0) return directTrigger;
4127
+ return root?.querySelector('.work-pdf-overflow-trigger') ?? null;
4128
+ }
3145
4129
  async function assertPdfCollaborationBlob(session, source) {
3146
4130
  const expected = readWorkOfficePdfCollaborationSource(session);
3147
4131
  if (source.size !== expected.byteLength) throw new Error(`The loaded PDF source does not match collaboration artifact '${session.artifactId}'.`);
@@ -3176,4 +4160,4 @@ function pdfMediaQueryMatches(query) {
3176
4160
  if ("u" < typeof window || 'function' != typeof window.matchMedia) return true;
3177
4161
  return window.matchMedia(query).matches;
3178
4162
  }
3179
- export { pdf_viewer_namespaceObject };
4163
+ export { PDF_PAGE_ORGANIZATION_MAX_IMPORT_BYTES, PDF_PAGE_ORGANIZATION_MAX_SOURCE_BYTES, PdfPageOrganizationError, pdf_viewer_namespaceObject, validatedPdfInsertionIndex, validatedPdfPageIndexes, validatedPdfPageOrder, validatedPdfPageSize, validatedPdfSplitBoundaries };