@4399ywkf/editor 0.4.3 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -36,11 +36,95 @@ Univer sheets 核心 gzip 后 871KB;无条件打进主包会直接毁掉阅读
36
36
  ```ts
37
37
  import { NotionEditor, ArticleViewer } from "@4399ywkf/editor/doc"
38
38
  import { ImportDocx, ExportDocx } from "@4399ywkf/editor/docx"
39
+ import { downloadXlsx } from "@4399ywkf/editor/xlsx"
39
40
  import { SheetView } from "@4399ywkf/editor/sheet"
40
41
  import { PdfView } from "@4399ywkf/editor/pdf"
41
42
  import "@4399ywkf/editor/styles"
42
43
  ```
43
44
 
45
+ ## 表格通道配置
46
+
47
+ Univer 的 UI 是 `univer-` 前缀的原子类,没有稳定选择器 —— **别用 CSS 硬藏工具栏**。
48
+ 开关走 `sheet` 这个 prop,直接透传到 Univer preset:
49
+
50
+ ```tsx
51
+ <DocumentView
52
+ format="sheet"
53
+ content={snapshot}
54
+ readOnly
55
+ sheet={{
56
+ findReplace: true, // Ctrl+F 查找替换
57
+ ui: { footer: true, formulaBar: false }, // 底栏保留(多表切换靠它),公式栏收掉
58
+ }}
59
+ />
60
+ ```
61
+
62
+ `ui` 可用:`header` / `toolbar` / `ribbonType` / `footer` / `formulaBar` / `contextMenu` /
63
+ `statusBarStatistic` / `menu`。
64
+
65
+ - **`readOnly` 时 `toolbar` 默认为 `false`** —— 整条 ribbon 在只读预览里全是死按钮,
66
+ 窄面板里还挤。要它回来就显式传 `ui: { toolbar: true }`。
67
+ - `footer` 别随手关:多工作表时它是唯一的切表入口,关掉用户就只看得见第一张表。
68
+ - `findReplace` 不需要额外装包:`preset-sheets-find-replace` 是 `@univerjs/presets`
69
+ 自己的 dependency,走子路径动态 import,没开就完全不下载。
70
+
71
+ 其它能力(筛选、排序、条件格式、图片……)自己 import preset 传进来。
72
+ `createUniver` **不会**自动收集 preset 自带的 locales,得配套传 `locales`,
73
+ 否则菜单会显示成原始 i18n key:
74
+
75
+ ```tsx
76
+ import { UniverSheetsFilterPreset } from "@univerjs/presets/preset-sheets-filter"
77
+ import zhCN from "@univerjs/presets/preset-sheets-filter/locales/zh-CN"
78
+
79
+ <DocumentView format="sheet" content={snapshot}
80
+ sheet={{ presets: [UniverSheetsFilterPreset()], locales: [zhCN] }} />
81
+ ```
82
+
83
+ `sheet` 里的配置**只在引擎创建时读一次**,挂载后改不生效也不会重建
84
+ (重建 = 重下引擎 + 丢掉选区和滚动位置,而宿主几乎总是传内联字面量)。要换配置请换 `key`。
85
+
86
+ ## xlsx 导出
87
+
88
+ 和 docx 一样**在本地生成,文档不出内网**;不引 SheetJS,只多用已有的 fflate。
89
+
90
+ ```tsx
91
+ import { downloadXlsx, exportXlsx } from "@4399ywkf/editor/xlsx"
92
+
93
+ const api = useRef<unknown>(null)
94
+ <DocumentView format="sheet" content={snapshot} onReady={(a) => (api.current = a)} />
95
+
96
+ // univerAPI 或快照都收
97
+ const { warnings } = downloadXlsx(api.current, "年度报表.xlsx")
98
+ if (warnings.length) toast(warnings.join("\n")) // ← 别吞,见下
99
+ ```
100
+
101
+ 服务端 / 定时任务只有 JSON 也能导 —— 整条链路不碰 DOM,不用起 Univer:
102
+
103
+ ```ts
104
+ import { xlsxFromSnapshot } from "@4399ywkf/editor/xlsx"
105
+ const { bytes, warnings, stats } = xlsxFromSnapshot(snapshot)
106
+ ```
107
+
108
+ 保真范围:
109
+
110
+ | | 状态 |
111
+ |---|---|
112
+ | 值与类型、共享字符串、强制文本(`quotePrefix`) | ✅ |
113
+ | 公式 + 缓存值,填充公式还原成 xlsx 的共享公式 | ✅ |
114
+ | 数字格式、字体、颜色、填充、边框、对齐、换行、旋转 | ✅ |
115
+ | 合并单元格、行高列宽、隐藏行列、冻结窗格 | ✅ |
116
+ | 多工作表、工作表隐藏、标签颜色、网格线开关 | ✅ |
117
+ | 富文本单元格 | ~ 压平成纯文本 |
118
+ | 图片、图表、透视表、条件格式、数据校验、筛选、批注、超链接 | ✕ |
119
+
120
+ 最后一行是 Univer 社区版的结构性限制:这些数据不在 `cellData` 里,而是各插件自己
121
+ 序列化进 `resources` 的私有结构。**所以 `warnings` 必须展示给用户** —— 导出时它会
122
+ 逐条点名丢了什么(「条件格式未随 xlsx 导出」),打开文件才发现要糟得多。
123
+
124
+ `cachedFormulaValues`(默认 true)决定公式格写不写 Univer 算好的值。关掉则只留公式、
125
+ 由 Excel 打开时重算 —— 这时会自动加上 `fullCalcOnLoad`,否则不重算的消费方
126
+ (网页预览、pandas)会看到一片空白。
127
+
44
128
  ## docx 导入导出
45
129
 
46
130
  API 形态对齐 `@tiptap-pro/extension-import-docx` / `-export-docx`,但**在本地解析**,
@@ -1,4 +1,4 @@
1
- import { EditorConfigProvider, NotionEditor } from './chunk-FQCG2HNJ.js';
1
+ import { EditorConfigProvider, NotionEditor } from './chunk-IUDLMN53.js';
2
2
  import './chunk-QMYT5WXO.js';
3
3
  import { jsx } from 'react/jsx-runtime';
4
4
 
@@ -15,5 +15,5 @@ function DocView({ content, readOnly, room, className, onReady }) {
15
15
  }
16
16
 
17
17
  export { DocView as default };
18
- //# sourceMappingURL=DocView-IZ4JBSIA.js.map
19
- //# sourceMappingURL=DocView-IZ4JBSIA.js.map
18
+ //# sourceMappingURL=DocView-XQMJUUVK.js.map
19
+ //# sourceMappingURL=DocView-XQMJUUVK.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/views/DocView.tsx"],"names":[],"mappings":";;;;AAMe,SAAR,QAAyB,EAAE,OAAA,EAAS,UAAU,IAAA,EAAM,SAAA,EAAW,SAAQ,EAAsB;AAClG,EAAA,2BACG,KAAA,EAAA,EAAI,SAAA,EACH,8BAAC,oBAAA,EAAA,EAAqB,MAAA,EAAQ,EAAC,EAC7B,QAAA,kBAAA,GAAA;AAAA,IAAC,YAAA;AAAA,IAAA;AAAA,MACC,MAAM,IAAA,IAAQ,SAAA;AAAA,MACd,OAAA;AAAA,MACA,QAAA;AAAA,MACA,aAAA,EAAe,CAAC,MAAA,KAAmB,OAAA,GAAU,MAAM;AAAA;AAAA,KAEvD,CAAA,EACF,CAAA;AAEJ","file":"DocView-IZ4JBSIA.js","sourcesContent":["import type { Editor } from \"@tiptap/react\"\nimport { EditorConfigProvider } from \"../contexts/editor-config-context\"\nimport { NotionEditor } from \"../components/tiptap-templates/notion-like/notion-like-editor\"\nimport type { DocumentViewProps } from \"../registry\"\n\n/** doc 通道:云文档与本地 doc 是同一套 tiptap,不做区分 */\nexport default function DocView({ content, readOnly, room, className, onReady }: DocumentViewProps) {\n return (\n <div className={className}>\n <EditorConfigProvider config={{}}>\n <NotionEditor\n room={room ?? \"default\"}\n content={content as string}\n readOnly={readOnly}\n onEditorReady={(editor: Editor) => onReady?.(editor)}\n />\n </EditorConfigProvider>\n </div>\n )\n}\n"]}
1
+ {"version":3,"sources":["../src/views/DocView.tsx"],"names":[],"mappings":";;;;AAMe,SAAR,QAAyB,EAAE,OAAA,EAAS,UAAU,IAAA,EAAM,SAAA,EAAW,SAAQ,EAAsB;AAClG,EAAA,2BACG,KAAA,EAAA,EAAI,SAAA,EACH,8BAAC,oBAAA,EAAA,EAAqB,MAAA,EAAQ,EAAC,EAC7B,QAAA,kBAAA,GAAA;AAAA,IAAC,YAAA;AAAA,IAAA;AAAA,MACC,MAAM,IAAA,IAAQ,SAAA;AAAA,MACd,OAAA;AAAA,MACA,QAAA;AAAA,MACA,aAAA,EAAe,CAAC,MAAA,KAAmB,OAAA,GAAU,MAAM;AAAA;AAAA,KAEvD,CAAA,EACF,CAAA;AAEJ","file":"DocView-XQMJUUVK.js","sourcesContent":["import type { Editor } from \"@tiptap/react\"\nimport { EditorConfigProvider } from \"../contexts/editor-config-context\"\nimport { NotionEditor } from \"../components/tiptap-templates/notion-like/notion-like-editor\"\nimport type { DocumentViewProps } from \"../registry\"\n\n/** doc 通道:云文档与本地 doc 是同一套 tiptap,不做区分 */\nexport default function DocView({ content, readOnly, room, className, onReady }: DocumentViewProps) {\n return (\n <div className={className}>\n <EditorConfigProvider config={{}}>\n <NotionEditor\n room={room ?? \"default\"}\n content={content as string}\n readOnly={readOnly}\n onEditorReady={(editor: Editor) => onReady?.(editor)}\n />\n </EditorConfigProvider>\n </div>\n )\n}\n"]}
@@ -0,0 +1,3 @@
1
+ export { SheetView as default } from './chunk-L6ZR5HHW.js';
2
+ //# sourceMappingURL=SheetView-DSADIZNS.js.map
3
+ //# sourceMappingURL=SheetView-DSADIZNS.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":[],"names":[],"mappings":"","file":"SheetView-2EID6B2I.js"}
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"SheetView-DSADIZNS.js"}
@@ -20944,6 +20944,16 @@ function EditorProvider(props) {
20944
20944
  } = props;
20945
20945
  const { user } = useUser();
20946
20946
  const { setTocContent } = useToc();
20947
+ const scrollParentRef = useRef(null);
20948
+ const editorRef = useRef(null);
20949
+ const resolveScrollParent = useCallback(() => {
20950
+ if (scrollParentRef.current) return scrollParentRef.current;
20951
+ const dom = editorRef.current?.view?.dom;
20952
+ if (!dom?.isConnected) return window;
20953
+ const found = getScrollableAncestor(dom);
20954
+ scrollParentRef.current = found;
20955
+ return found;
20956
+ }, []);
20947
20957
  const editorConfig = useEditorConfig();
20948
20958
  const onUpload = editorConfig?.upload?.onUpload ?? handleImageUpload;
20949
20959
  const onUploadError = editorConfig?.upload?.onError ?? ((error) => console.error("Upload failed:", error));
@@ -21040,6 +21050,8 @@ function EditorProvider(props) {
21040
21050
  Image,
21041
21051
  TableOfContents.configure({
21042
21052
  getIndex: getHierarchicalIndexes,
21053
+ // 见 resolveScrollParent 的说明:默认的 window 在「内层 div 滚动」的宿主里收不到事件
21054
+ scrollParent: resolveScrollParent,
21043
21055
  onUpdate(content2) {
21044
21056
  setTocContent(content2);
21045
21057
  }
@@ -21123,6 +21135,24 @@ function EditorProvider(props) {
21123
21135
  ...extraExtensions ?? []
21124
21136
  ]
21125
21137
  });
21138
+ useEffect(() => {
21139
+ if (!editor) return;
21140
+ editorRef.current = editor;
21141
+ const dom = editor.view?.dom;
21142
+ if (!dom?.isConnected) return;
21143
+ const target = getScrollableAncestor(dom);
21144
+ if (target instanceof Window) return;
21145
+ scrollParentRef.current = target;
21146
+ const handler = () => {
21147
+ const storage = editor.storage?.tableOfContents;
21148
+ storage?.scrollHandler?.();
21149
+ };
21150
+ target.addEventListener("scroll", handler, { passive: true });
21151
+ handler();
21152
+ return () => {
21153
+ target.removeEventListener("scroll", handler);
21154
+ };
21155
+ }, [editor]);
21126
21156
  useEffect(() => {
21127
21157
  if (provider) return;
21128
21158
  if (!editor) return;
@@ -21243,5 +21273,5 @@ function ArticleViewer({ content, placeholder = "" }) {
21243
21273
  }
21244
21274
 
21245
21275
  export { ArticleViewer, Audio, CollabContext, CollabProvider, Column, Columns, DEFAULT_MAX_FILE_SIZES, DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, EditorConfigProvider, EditorContentArea, EditorProvider, FileAttachment, FloatingElement, HIDE_FLOATING_META, LoadingSpinner, MediaUploadButton, MediaUploadPlaceholder, NotionEditor, NotionEditorContent, PasteDropMedia, Separator, Spacer, Toolbar, ToolbarGroup, ToolbarSeparator, UiState, UserContext, UserProvider, Video, defaultUiState, detectMediaKind, findSelectionPosition, hasContentAbove, markHideFloatingOnNext, pickFile, resolveMaxSize, runMediaUpload, selectNodeAndHideFloating, useCollab, useCollaboration, useComposedRef, useCursorVisibility, useEditorConfig, useElementRect, useFloatingElement, useFloatingToolbarVisibility, useIsBreakpoint, useIsomorphicLayoutEffect, useMenuNavigation, useOnClickOutside, useThrottledCallback, useUiEditorState, useUnmount, useUser, useWindowSize, use_ui_editor_state_default };
21246
- //# sourceMappingURL=chunk-FQCG2HNJ.js.map
21247
- //# sourceMappingURL=chunk-FQCG2HNJ.js.map
21276
+ //# sourceMappingURL=chunk-IUDLMN53.js.map
21277
+ //# sourceMappingURL=chunk-IUDLMN53.js.map