@4399ywkf/editor 0.6.0 → 0.8.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 (61) hide show
  1. package/README.md +161 -2
  2. package/dist/{DocView-XQMJUUVK.js → DocView-WEKRSKRU.js} +6 -4
  3. package/dist/DocView-WEKRSKRU.js.map +1 -0
  4. package/dist/SheetView-SDBRSTVS.js +3 -0
  5. package/dist/{SheetView-DSADIZNS.js.map → SheetView-SDBRSTVS.js.map} +1 -1
  6. package/dist/chunk-5HMKRNIH.js +27 -0
  7. package/dist/chunk-5HMKRNIH.js.map +1 -0
  8. package/dist/chunk-DGUNVCOM.js +711 -0
  9. package/dist/chunk-DGUNVCOM.js.map +1 -0
  10. package/dist/chunk-HPSPOM2O.js +128 -0
  11. package/dist/chunk-HPSPOM2O.js.map +1 -0
  12. package/dist/chunk-KRP5V7VF.js +130 -0
  13. package/dist/chunk-KRP5V7VF.js.map +1 -0
  14. package/dist/chunk-SBTKDJAL.js +107 -0
  15. package/dist/chunk-SBTKDJAL.js.map +1 -0
  16. package/dist/{chunk-IUDLMN53.js → chunk-SGLNT6Z2.js} +2416 -176
  17. package/dist/chunk-SGLNT6Z2.js.map +1 -0
  18. package/dist/{chunk-L6ZR5HHW.js → chunk-SYCK6Y5F.js} +15 -10
  19. package/dist/chunk-SYCK6Y5F.js.map +1 -0
  20. package/dist/chunk-WF7PIIFR.js +21 -0
  21. package/dist/chunk-WF7PIIFR.js.map +1 -0
  22. package/dist/chunk-ZY5KLA7T.js +258 -0
  23. package/dist/chunk-ZY5KLA7T.js.map +1 -0
  24. package/dist/code-types-Dqmit3xx.d.ts +65 -0
  25. package/dist/{types-C37MAt8o.d.ts → csv-parse-D0zTBvDB.d.ts} +39 -1
  26. package/dist/csv.d.ts +6 -3
  27. package/dist/csv.js +6 -125
  28. package/dist/csv.js.map +1 -1
  29. package/dist/doc-runtime.d.ts +1 -1
  30. package/dist/doc-runtime.js +7 -1
  31. package/dist/doc-runtime.js.map +1 -1
  32. package/dist/doc.d.ts +113 -6
  33. package/dist/doc.js +2 -1
  34. package/dist/doc.js.map +1 -1
  35. package/dist/docx.d.ts +15 -0
  36. package/dist/docx.js +116 -8
  37. package/dist/docx.js.map +1 -1
  38. package/dist/import-GVZDLWA2.js +6 -0
  39. package/dist/import-GVZDLWA2.js.map +1 -0
  40. package/dist/index.d.ts +4 -2
  41. package/dist/index.js +7 -3
  42. package/dist/index.js.map +1 -1
  43. package/dist/pdf.d.ts +3 -1
  44. package/dist/pptx.d.ts +3 -1
  45. package/dist/{registry-BBL8yeIT.d.ts → registry-CYKivRDh.d.ts} +13 -1
  46. package/dist/sheet-runtime.d.ts +212 -0
  47. package/dist/sheet-runtime.js +416 -0
  48. package/dist/sheet-runtime.js.map +1 -0
  49. package/dist/sheet.d.ts +9 -5
  50. package/dist/sheet.js +1 -1
  51. package/dist/style.css +421 -111
  52. package/dist/xlsx.d.ts +118 -7
  53. package/dist/xlsx.js +7 -78
  54. package/dist/xlsx.js.map +1 -1
  55. package/package.json +11 -5
  56. package/dist/DocView-XQMJUUVK.js.map +0 -1
  57. package/dist/SheetView-DSADIZNS.js +0 -3
  58. package/dist/chunk-32WQX3NR.js +0 -37
  59. package/dist/chunk-32WQX3NR.js.map +0 -1
  60. package/dist/chunk-IUDLMN53.js.map +0 -1
  61. package/dist/chunk-L6ZR5HHW.js.map +0 -1
package/dist/doc.d.ts CHANGED
@@ -1,8 +1,12 @@
1
1
  import * as react from 'react';
2
2
  import { ReactNode, RefObject, useEffect, HTMLAttributes } from 'react';
3
3
  import { JSONContent, Editor, NodeWithPos, Extension as Extension$1, Node as Node$1 } from '@tiptap/react';
4
- import { Extensions, Extension } from '@tiptap/core';
4
+ import * as _tiptap_core from '@tiptap/core';
5
+ import { Extensions, JSONContent as JSONContent$1, Extension } from '@tiptap/core';
5
6
  import { Doc } from 'yjs';
7
+ import { C as ContentFormat, a as CodeBlockOptions, b as CodeHighlighter } from './code-types-Dqmit3xx.js';
8
+ export { c as CodeBlockAttrs, d as CodeGroupAttrs, H as HighlightResult, e as HighlightToken, S as ShikiTheme } from './code-types-Dqmit3xx.js';
9
+ import { CodeBlockOptions as CodeBlockOptions$1 } from '@tiptap/extension-code-block';
6
10
  import { UseFloatingOptions, UseDismissProps, AutoUpdateOptions } from '@floating-ui/react';
7
11
  import { Selection, Transaction } from '@tiptap/pm/state';
8
12
  import { Node } from '@tiptap/pm/model';
@@ -10,6 +14,7 @@ import { B as ButtonProps } from './button-CWX5Uuj-.js';
10
14
  export { a as Button } from './button-CWX5Uuj-.js';
11
15
  import * as PopoverPrimitive from '@radix-ui/react-popover';
12
16
  import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
17
+ import 'shiki';
13
18
 
14
19
  /**
15
20
  * 包消费者通过 `<EditorConfigProvider>` 注入业务侧能力。
@@ -134,6 +139,18 @@ interface NotionEditorProps {
134
139
  * 协同模式下 yjs Doc 接管内容,传入会被忽略以免跟 sync 冲突。
135
140
  */
136
141
  content?: string | JSONContent;
142
+ /**
143
+ * `content` 的格式。不传时沿用旧规则:对象按 JSON、字符串按 HTML。
144
+ * `"markdown"` 时字符串经 Markdown 解析(支持 `::: code-group` 代码分组);
145
+ * `"json"` 只接受对象,不会去解析 JSON 文本。格式与内容类型不匹配时
146
+ * console.error 一条 `[editor]` 信息并保留当前文档不动。
147
+ */
148
+ contentFormat?: ContentFormat;
149
+ /**
150
+ * 代码块高亮配置(Shiki 主题 / 语言别名 / 额外语言注册)。默认 github-light /
151
+ * github-dark 随 `.dark` 切换,语言按需加载,无需配置即可用。
152
+ */
153
+ codeBlock?: CodeBlockOptions;
137
154
  /**
138
155
  * 只读模式:用于展示文章内容。设为 true 时编辑器不可编辑(editable:false),
139
156
  * 且隐藏所有编辑态 UI(头部工具栏、浮动工具栏、拖拽手柄、slash/mention/emoji
@@ -172,6 +189,10 @@ interface EditorProviderProps {
172
189
  placeholder?: string;
173
190
  /** 本地编辑模式下的初始/同步内容,详见 NotionEditorProps.content */
174
191
  content?: string | JSONContent;
192
+ /** 内容格式,详见 NotionEditorProps.contentFormat */
193
+ contentFormat?: ContentFormat;
194
+ /** 代码块高亮配置,详见 NotionEditorProps.codeBlock */
195
+ codeBlock?: CodeBlockOptions;
175
196
  /** 只读模式,详见 NotionEditorProps.readOnly */
176
197
  readOnly?: boolean;
177
198
  /** 隐藏包内置顶部工具条,详见 NotionEditorProps.hideHeader */
@@ -211,13 +232,17 @@ declare function EditorProvider(props: EditorProviderProps): react.JSX.Element;
211
232
  /**
212
233
  * Full editor with all necessary providers, ready to use with just a room ID
213
234
  */
214
- declare function NotionEditor({ room, placeholder, content, readOnly, hideHeader, onEditorReady, extensions, }: NotionEditorProps): react.JSX.Element;
235
+ declare function NotionEditor({ room, placeholder, content, contentFormat, codeBlock, readOnly, hideHeader, onEditorReady, extensions, }: NotionEditorProps): react.JSX.Element;
215
236
  /**
216
237
  * Internal component that handles the editor loading state
217
238
  */
218
- declare function NotionEditorContent({ placeholder, content, readOnly, hideHeader, onEditorReady, extensions, }: {
239
+ declare function NotionEditorContent({ placeholder, content, contentFormat, codeBlock, readOnly, hideHeader, onEditorReady, extensions, }: {
219
240
  placeholder?: string;
220
241
  content?: string | JSONContent;
242
+ /** 内容格式,详见 NotionEditorProps.contentFormat */
243
+ contentFormat?: ContentFormat;
244
+ /** 代码块高亮配置,详见 NotionEditorProps.codeBlock */
245
+ codeBlock?: CodeBlockOptions;
221
246
  readOnly?: boolean;
222
247
  hideHeader?: boolean;
223
248
  onEditorReady?: (editor: Editor) => void;
@@ -231,8 +256,12 @@ declare function NotionEditorContent({ placeholder, content, readOnly, hideHeade
231
256
  extensions?: Extensions;
232
257
  }): react.JSX.Element;
233
258
  interface ArticleViewerProps {
234
- /** 文章内容(HTML 字符串或 tiptap JSONContent),通常由 HTTP 接口拉取后传入 */
259
+ /** 文章内容(HTML 字符串、Markdown 字符串或 tiptap JSONContent),通常由 HTTP 接口拉取后传入 */
235
260
  content?: string | JSONContent;
261
+ /** 内容格式,详见 NotionEditorProps.contentFormat */
262
+ contentFormat?: ContentFormat;
263
+ /** 代码块高亮配置,详见 NotionEditorProps.codeBlock */
264
+ codeBlock?: CodeBlockOptions;
236
265
  /** 空内容时的占位文案,默认空(viewer 一般不显示占位) */
237
266
  placeholder?: string;
238
267
  }
@@ -246,7 +275,85 @@ interface ArticleViewerProps {
246
275
  *
247
276
  * 用法:<ArticleViewer content={articleContent} />
248
277
  */
249
- declare function ArticleViewer({ content, placeholder }: ArticleViewerProps): react.JSX.Element;
278
+ declare function ArticleViewer({ content, contentFormat, codeBlock, placeholder }: ArticleViewerProps): react.JSX.Element;
279
+
280
+ /** 只用到 Editor 的这几个面,避免把 @tiptap/core 的 Editor 类型绑死在签名里 */
281
+ interface EditorLike {
282
+ getJSON(): JSONContent$1;
283
+ extensionManager: {
284
+ extensions: Extensions;
285
+ };
286
+ storage: object;
287
+ }
288
+ type ParsedContent = {
289
+ ok: true;
290
+ json: JSONContent$1 | null;
291
+ } | {
292
+ ok: false;
293
+ error: string;
294
+ };
295
+ /**
296
+ * 把任意输入规范成 Tiptap JSON。`json: null` 表示"没有内容"(undefined / null / 空串),
297
+ * 调用方按"不设置内容"处理。
298
+ */
299
+ declare function parseEditorContent(content: unknown, format: ContentFormat | undefined, extensions: Extensions): ParsedContent;
300
+ declare function serializeEditorMarkdown(json: JSONContent$1, extensions: Extensions): string;
301
+ /**
302
+ * `editor.getMarkdown()` 的类型安全封装。那个方法来自 @tiptap/markdown 的模块扩充,
303
+ * 本包的 d.ts 不会把扩充带给消费方;用这个函数就不需要额外引 @tiptap/markdown 的类型。
304
+ * 优先用编辑器自己的 MarkdownManager(同一套 marked 实例),没有时按扩展集临时建一个。
305
+ */
306
+ declare function getEditorMarkdown(editor: EditorLike): string;
307
+ interface RenderHighlightedHTMLOptions extends CodeBlockOptions {
308
+ /** 传 Editor 时可省略;传 JSON 时必填 */
309
+ extensions?: Extensions;
310
+ /** 复用已有服务(传 Editor 时默认取编辑器里的那份) */
311
+ highlighter?: CodeHighlighter;
312
+ /** 主题:`"light"` / `"dark"` 走 shikiTheme 配置,其它字符串当 Shiki 主题名 */
313
+ theme?: "light" | "dark" | (string & {});
314
+ }
315
+ /**
316
+ * 导出调用时刻的文档快照为静态高亮 HTML:先取 JSON(之后的编辑不会混进来),
317
+ * 每个代码块经 Shiki 输出 token span,再按文档顺序替换语义 HTML 里的 `<pre>…</pre>`。
318
+ * 分组保留 `<div data-code-group>` 与每个 `<pre>` 的 data-language / data-label,
319
+ * 所有标签的代码都在、都可见 —— 不嵌脚本,不含 Decoration span 或按钮。
320
+ */
321
+ declare function renderHighlightedHTML(source: EditorLike | JSONContent$1, options?: RenderHighlightedHTMLOptions): Promise<string>;
322
+
323
+ type CodeBlockSchemaOptions = CodeBlockOptions$1 & CodeBlockOptions;
324
+
325
+ declare const CodeBlockExtension: _tiptap_core.Node<CodeBlockSchemaOptions, any>;
326
+
327
+ declare const CodeGroupExtension: _tiptap_core.Node<any, any>;
328
+
329
+ declare const CalloutExtension: _tiptap_core.Node<any, any>;
330
+
331
+ declare const DetailsExtension: _tiptap_core.Node<any, any>;
332
+
333
+ /**
334
+ * 折叠块 / 提示框 / API 参数节点的公共类型。只有类型与常量,没有运行时依赖。
335
+ */
336
+ declare const CALLOUT_TYPES: readonly ["info", "tip", "warning", "danger"];
337
+ type CalloutType = (typeof CALLOUT_TYPES)[number];
338
+ interface CalloutAttrs {
339
+ id?: string | null;
340
+ type: CalloutType;
341
+ title: string | null;
342
+ }
343
+ interface DetailsAttrs {
344
+ id?: string | null;
345
+ title: string | null;
346
+ }
347
+ interface ApiParamAttrs {
348
+ id?: string | null;
349
+ name: string;
350
+ type: string | null;
351
+ required: boolean;
352
+ default: string | null;
353
+ summary: string | null;
354
+ }
355
+
356
+ declare const ApiParamExtension: _tiptap_core.Node<any, any>;
250
357
 
251
358
  type CollabContextValue = {
252
359
  provider: CollabProviderLike | null;
@@ -1096,4 +1203,4 @@ declare const MediaUploadButton: react.ForwardRefExoticComponent<MediaUploadButt
1096
1203
  */
1097
1204
  declare function pickFile(accept: string, multiple?: boolean): Promise<File[]>;
1098
1205
 
1099
- export { ArticleViewer, type ArticleViewerProps, Audio, ButtonProps, CollabContext, type CollabContextValue, CollabProvider, type CollabProviderLike, Column, type ColumnLayout, Columns, DEFAULT_MAX_FILE_SIZES, DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, type EditorCollabConfig, type EditorConfig, EditorConfigProvider, type EditorConfigProviderProps, EditorContentArea, type EditorProviderProps, type EditorUploadConfig, type EditorUserConfig, FileAttachment, type FileAttachmentAttrs, FloatingElement, HIDE_FLOATING_META, LoadingSpinner, MAC_SYMBOLS, MAX_FILE_SIZE, type MediaKind, type MediaKindForButton, MediaUploadButton, type MediaUploadButtonProps, MediaUploadPlaceholder, NotionEditor, NotionEditorContent, EditorProvider as NotionEditorInner, type NotionEditorProps, type OverflowPosition, PasteDropMedia, type PasteDropMediaOptions, type PasteDropMediaUploadFn, type PasteDropRehostProgressEvent, Popover, PopoverContent, PopoverTrigger, type RehostProgressEvent, SR_ONLY, Separator, Spacer, Toolbar, ToolbarGroup, ToolbarSeparator, UiState, UiState as UiStateType, type User, UserContext, type UserContextValue, UserProvider, Video, clamp, cn, defaultUiState, detectMediaKind, findNodeAtPosition, findNodePosition, findSelectionPosition, focusNextNode, formatShortcutKey, getAvatar, getElementOverflowPosition, getNodeDisplayName, getSelectedBlockNodes, getSelectedNodesOfType, getSelectionBoundingRect, getUrlParam, handleImageUpload, hasContentAbove, isAllowedUri, isExtensionAvailable, isMac, isMarkInSchema, isNodeInSchema, isNodeTypeSelected, isSelectionValid, isTextSelectionValid, isValidPosition, markHideFloatingOnNext, parseShortcutKeys, pickFile, removeEmptyParagraphs, resolveMaxSize, runMediaUpload, sanitizeUrl, selectCurrentBlockContent, selectNodeAndHideFloating, selectionWithinConvertibleTypes, updateNodesAttr, useCollab, useCollaboration, useComposedRef, useCursorVisibility, useEditorConfig, useElementRect, useFloatingElement, useFloatingToolbarVisibility, useIsBreakpoint, useIsomorphicLayoutEffect, useMenuNavigation, useOnClickOutside, useScrolling, useThrottledCallback, useTiptapEditor, useUiEditorState, useUiEditorState as useUiEditorStateDefault, useUnmount, useUser, useWindowSize };
1206
+ export { type ApiParamAttrs, ApiParamExtension, ArticleViewer, type ArticleViewerProps, Audio, ButtonProps, type CalloutAttrs, CalloutExtension, type CalloutType, CodeBlockExtension, CodeBlockOptions, CodeGroupExtension, CollabContext, type CollabContextValue, CollabProvider, type CollabProviderLike, Column, type ColumnLayout, Columns, ContentFormat, DEFAULT_MAX_FILE_SIZES, type DetailsAttrs, DetailsExtension, DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, type EditorCollabConfig, type EditorConfig, EditorConfigProvider, type EditorConfigProviderProps, EditorContentArea, type EditorProviderProps, type EditorUploadConfig, type EditorUserConfig, FileAttachment, type FileAttachmentAttrs, FloatingElement, HIDE_FLOATING_META, LoadingSpinner, MAC_SYMBOLS, MAX_FILE_SIZE, type MediaKind, type MediaKindForButton, MediaUploadButton, type MediaUploadButtonProps, MediaUploadPlaceholder, NotionEditor, NotionEditorContent, EditorProvider as NotionEditorInner, type NotionEditorProps, type OverflowPosition, type ParsedContent, PasteDropMedia, type PasteDropMediaOptions, type PasteDropMediaUploadFn, type PasteDropRehostProgressEvent, Popover, PopoverContent, PopoverTrigger, type RehostProgressEvent, type RenderHighlightedHTMLOptions, SR_ONLY, Separator, Spacer, Toolbar, ToolbarGroup, ToolbarSeparator, UiState, UiState as UiStateType, type User, UserContext, type UserContextValue, UserProvider, Video, clamp, cn, defaultUiState, detectMediaKind, findNodeAtPosition, findNodePosition, findSelectionPosition, focusNextNode, formatShortcutKey, getAvatar, getEditorMarkdown, getElementOverflowPosition, getNodeDisplayName, getSelectedBlockNodes, getSelectedNodesOfType, getSelectionBoundingRect, getUrlParam, handleImageUpload, hasContentAbove, isAllowedUri, isExtensionAvailable, isMac, isMarkInSchema, isNodeInSchema, isNodeTypeSelected, isSelectionValid, isTextSelectionValid, isValidPosition, markHideFloatingOnNext, parseEditorContent, parseShortcutKeys, pickFile, removeEmptyParagraphs, renderHighlightedHTML, resolveMaxSize, runMediaUpload, sanitizeUrl, selectCurrentBlockContent, selectNodeAndHideFloating, selectionWithinConvertibleTypes, serializeEditorMarkdown, updateNodesAttr, useCollab, useCollaboration, useComposedRef, useCursorVisibility, useEditorConfig, useElementRect, useFloatingElement, useFloatingToolbarVisibility, useIsBreakpoint, useIsomorphicLayoutEffect, useMenuNavigation, useOnClickOutside, useScrolling, useThrottledCallback, useTiptapEditor, useUiEditorState, useUiEditorState as useUiEditorStateDefault, useUnmount, useUser, useWindowSize };
package/dist/doc.js CHANGED
@@ -1,5 +1,6 @@
1
- export { ArticleViewer, Audio, CollabContext, CollabProvider, Column, Columns, DEFAULT_MAX_FILE_SIZES, DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, EditorConfigProvider, EditorContentArea, FileAttachment, FloatingElement, HIDE_FLOATING_META, LoadingSpinner, MediaUploadButton, MediaUploadPlaceholder, NotionEditor, NotionEditorContent, EditorProvider as NotionEditorInner, 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, use_ui_editor_state_default as useUiEditorStateDefault, useUnmount, useUser, useWindowSize } from './chunk-IUDLMN53.js';
1
+ export { ApiParamExtension, ArticleViewer, Audio, CalloutExtension, CodeBlockExtension, CodeGroupExtension, CollabContext, CollabProvider, Column, Columns, DEFAULT_MAX_FILE_SIZES, DetailsExtension, DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, EditorConfigProvider, EditorContentArea, FileAttachment, FloatingElement, HIDE_FLOATING_META, LoadingSpinner, MediaUploadButton, MediaUploadPlaceholder, NotionEditor, NotionEditorContent, EditorProvider as NotionEditorInner, PasteDropMedia, Separator, Spacer, Toolbar, ToolbarGroup, ToolbarSeparator, UiState, UserContext, UserProvider, Video, defaultUiState, detectMediaKind, findSelectionPosition, getEditorMarkdown, hasContentAbove, markHideFloatingOnNext, parseEditorContent, pickFile, renderHighlightedHTML, resolveMaxSize, runMediaUpload, selectNodeAndHideFloating, serializeEditorMarkdown, useCollab, useCollaboration, useComposedRef, useCursorVisibility, useEditorConfig, useElementRect, useFloatingElement, useFloatingToolbarVisibility, useIsBreakpoint, useIsomorphicLayoutEffect, useMenuNavigation, useOnClickOutside, useThrottledCallback, useUiEditorState, use_ui_editor_state_default as useUiEditorStateDefault, useUnmount, useUser, useWindowSize } from './chunk-SGLNT6Z2.js';
2
2
  export { Button, MAC_SYMBOLS, MAX_FILE_SIZE, Popover, PopoverContent, PopoverTrigger, SR_ONLY, clamp, cn, findNodeAtPosition, findNodePosition, focusNextNode, formatShortcutKey, getAvatar, getElementOverflowPosition, getNodeDisplayName, getSelectedBlockNodes, getSelectedNodesOfType, getSelectionBoundingRect, getUrlParam, handleImageUpload, isAllowedUri, isExtensionAvailable, isMac, isMarkInSchema, isNodeInSchema, isNodeTypeSelected, isSelectionValid, isTextSelectionValid, isValidPosition, parseShortcutKeys, removeEmptyParagraphs, sanitizeUrl, selectCurrentBlockContent, selectionWithinConvertibleTypes, updateNodesAttr, useTiptapEditor } from './chunk-QMYT5WXO.js';
3
+ import './chunk-ZY5KLA7T.js';
3
4
  import { useState, useEffect } from 'react';
4
5
 
5
6
  function useScrolling(target, options = {}) {
package/dist/doc.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/hooks/use-scrolling.ts"],"names":[],"mappings":";;;;AAWO,SAAS,YAAA,CACd,MAAA,EACA,OAAA,GAA+B,EAAC,EACvB;AACT,EAAA,MAAM,EAAE,QAAA,GAAW,GAAA,EAAK,kBAAA,GAAqB,MAAK,GAAI,OAAA;AACtD,EAAA,MAAM,CAAC,WAAA,EAAa,cAAc,CAAA,GAAI,SAAS,KAAK,CAAA;AAEpD,EAAA,SAAA,CAAU,MAAM;AAEd,IAAA,MAAM,OAAA,GACJ,UAAU,OAAO,MAAA,KAAW,eAAe,MAAA,YAAkB,MAAA,GACzD,MAAA,GACE,MAAA,EAAmC,OAAA,IAAW,MAAA;AAGtD,IAAA,MAAM,cACJ,kBAAA,IACA,OAAA,KAAY,UACZ,OAAO,QAAA,KAAa,cAChB,QAAA,GACA,OAAA;AAEN,IAAA,MAAM,EAAA,GAAK,CACT,EAAA,EACA,KAAA,EACA,YACG,EAAA,CAAG,gBAAA,CAAiB,KAAA,EAAO,OAAA,EAAS,IAAI,CAAA;AAE7C,IAAA,MAAM,GAAA,GAAM,CACV,EAAA,EACA,KAAA,EACA,YACG,EAAA,CAAG,mBAAA,CAAoB,OAAO,OAAO,CAAA;AAE1C,IAAA,IAAI,OAAA;AACJ,IAAA,MAAM,iBAAA,GAAoB,OAAA,KAAY,MAAA,IAAU,aAAA,IAAiB,MAAA;AAEjE,IAAA,MAAM,eAA8B,MAAM;AACxC,MAAA,IAAI,CAAC,WAAA,EAAa,cAAA,CAAe,IAAI,CAAA;AAErC,MAAA,IAAI,CAAC,iBAAA,EAAmB;AACtB,QAAA,YAAA,CAAa,OAAO,CAAA;AACpB,QAAA,OAAA,GAAU,UAAA,CAAW,MAAM,cAAA,CAAe,KAAK,GAAG,QAAQ,CAAA;AAAA,MAC5D;AAAA,IACF,CAAA;AAEA,IAAA,MAAM,eAAA,GAAiC,MAAM,cAAA,CAAe,KAAK,CAAA;AAEjE,IAAA,EAAA,CAAG,WAAA,EAAa,UAAU,YAAY,CAAA;AACtC,IAAA,IAAI,iBAAA,EAAmB;AACrB,MAAA,EAAA,CAAG,WAAA,EAAa,aAAa,eAAe,CAAA;AAAA,IAC9C;AAEA,IAAA,OAAO,MAAM;AACX,MAAA,GAAA,CAAI,WAAA,EAAa,UAAU,YAAY,CAAA;AACvC,MAAA,IAAI,iBAAA,EAAmB;AACrB,QAAA,GAAA,CAAI,WAAA,EAAa,aAAa,eAAe,CAAA;AAAA,MAC/C;AACA,MAAA,YAAA,CAAa,OAAO,CAAA;AAAA,IACtB,CAAA;AAAA,EACF,GAAG,CAAC,MAAA,EAAQ,QAAA,EAAU,kBAAA,EAAoB,WAAW,CAAC,CAAA;AAEtD,EAAA,OAAO,WAAA;AACT","file":"doc.js","sourcesContent":["import type { RefObject } from \"react\"\nimport { useEffect, useState } from \"react\"\n\ntype ScrollTarget = RefObject<HTMLElement> | Window | null | undefined\ntype EventTargetWithScroll = Window | HTMLElement | Document\n\ninterface UseScrollingOptions {\n debounce?: number\n fallbackToDocument?: boolean\n}\n\nexport function useScrolling(\n target?: ScrollTarget,\n options: UseScrollingOptions = {}\n): boolean {\n const { debounce = 150, fallbackToDocument = true } = options\n const [isScrolling, setIsScrolling] = useState(false)\n\n useEffect(() => {\n // Resolve element or window\n const element: EventTargetWithScroll =\n target && typeof Window !== \"undefined\" && target instanceof Window\n ? target\n : ((target as RefObject<HTMLElement>)?.current ?? window)\n\n // Mobile: fallback to document when using window\n const eventTarget: EventTargetWithScroll =\n fallbackToDocument &&\n element === window &&\n typeof document !== \"undefined\"\n ? document\n : element\n\n const on = (\n el: EventTargetWithScroll,\n event: string,\n handler: EventListener\n ) => el.addEventListener(event, handler, true)\n\n const off = (\n el: EventTargetWithScroll,\n event: string,\n handler: EventListener\n ) => el.removeEventListener(event, handler)\n\n let timeout: ReturnType<typeof setTimeout>\n const supportsScrollEnd = element === window && \"onscrollend\" in window\n\n const handleScroll: EventListener = () => {\n if (!isScrolling) setIsScrolling(true)\n\n if (!supportsScrollEnd) {\n clearTimeout(timeout)\n timeout = setTimeout(() => setIsScrolling(false), debounce)\n }\n }\n\n const handleScrollEnd: EventListener = () => setIsScrolling(false)\n\n on(eventTarget, \"scroll\", handleScroll)\n if (supportsScrollEnd) {\n on(eventTarget, \"scrollend\", handleScrollEnd)\n }\n\n return () => {\n off(eventTarget, \"scroll\", handleScroll)\n if (supportsScrollEnd) {\n off(eventTarget, \"scrollend\", handleScrollEnd)\n }\n clearTimeout(timeout)\n }\n }, [target, debounce, fallbackToDocument, isScrolling])\n\n return isScrolling\n}\n"]}
1
+ {"version":3,"sources":["../src/hooks/use-scrolling.ts"],"names":[],"mappings":";;;;;AAWO,SAAS,YAAA,CACd,MAAA,EACA,OAAA,GAA+B,EAAC,EACvB;AACT,EAAA,MAAM,EAAE,QAAA,GAAW,GAAA,EAAK,kBAAA,GAAqB,MAAK,GAAI,OAAA;AACtD,EAAA,MAAM,CAAC,WAAA,EAAa,cAAc,CAAA,GAAI,SAAS,KAAK,CAAA;AAEpD,EAAA,SAAA,CAAU,MAAM;AAEd,IAAA,MAAM,OAAA,GACJ,UAAU,OAAO,MAAA,KAAW,eAAe,MAAA,YAAkB,MAAA,GACzD,MAAA,GACE,MAAA,EAAmC,OAAA,IAAW,MAAA;AAGtD,IAAA,MAAM,cACJ,kBAAA,IACA,OAAA,KAAY,UACZ,OAAO,QAAA,KAAa,cAChB,QAAA,GACA,OAAA;AAEN,IAAA,MAAM,EAAA,GAAK,CACT,EAAA,EACA,KAAA,EACA,YACG,EAAA,CAAG,gBAAA,CAAiB,KAAA,EAAO,OAAA,EAAS,IAAI,CAAA;AAE7C,IAAA,MAAM,GAAA,GAAM,CACV,EAAA,EACA,KAAA,EACA,YACG,EAAA,CAAG,mBAAA,CAAoB,OAAO,OAAO,CAAA;AAE1C,IAAA,IAAI,OAAA;AACJ,IAAA,MAAM,iBAAA,GAAoB,OAAA,KAAY,MAAA,IAAU,aAAA,IAAiB,MAAA;AAEjE,IAAA,MAAM,eAA8B,MAAM;AACxC,MAAA,IAAI,CAAC,WAAA,EAAa,cAAA,CAAe,IAAI,CAAA;AAErC,MAAA,IAAI,CAAC,iBAAA,EAAmB;AACtB,QAAA,YAAA,CAAa,OAAO,CAAA;AACpB,QAAA,OAAA,GAAU,UAAA,CAAW,MAAM,cAAA,CAAe,KAAK,GAAG,QAAQ,CAAA;AAAA,MAC5D;AAAA,IACF,CAAA;AAEA,IAAA,MAAM,eAAA,GAAiC,MAAM,cAAA,CAAe,KAAK,CAAA;AAEjE,IAAA,EAAA,CAAG,WAAA,EAAa,UAAU,YAAY,CAAA;AACtC,IAAA,IAAI,iBAAA,EAAmB;AACrB,MAAA,EAAA,CAAG,WAAA,EAAa,aAAa,eAAe,CAAA;AAAA,IAC9C;AAEA,IAAA,OAAO,MAAM;AACX,MAAA,GAAA,CAAI,WAAA,EAAa,UAAU,YAAY,CAAA;AACvC,MAAA,IAAI,iBAAA,EAAmB;AACrB,QAAA,GAAA,CAAI,WAAA,EAAa,aAAa,eAAe,CAAA;AAAA,MAC/C;AACA,MAAA,YAAA,CAAa,OAAO,CAAA;AAAA,IACtB,CAAA;AAAA,EACF,GAAG,CAAC,MAAA,EAAQ,QAAA,EAAU,kBAAA,EAAoB,WAAW,CAAC,CAAA;AAEtD,EAAA,OAAO,WAAA;AACT","file":"doc.js","sourcesContent":["import type { RefObject } from \"react\"\nimport { useEffect, useState } from \"react\"\n\ntype ScrollTarget = RefObject<HTMLElement> | Window | null | undefined\ntype EventTargetWithScroll = Window | HTMLElement | Document\n\ninterface UseScrollingOptions {\n debounce?: number\n fallbackToDocument?: boolean\n}\n\nexport function useScrolling(\n target?: ScrollTarget,\n options: UseScrollingOptions = {}\n): boolean {\n const { debounce = 150, fallbackToDocument = true } = options\n const [isScrolling, setIsScrolling] = useState(false)\n\n useEffect(() => {\n // Resolve element or window\n const element: EventTargetWithScroll =\n target && typeof Window !== \"undefined\" && target instanceof Window\n ? target\n : ((target as RefObject<HTMLElement>)?.current ?? window)\n\n // Mobile: fallback to document when using window\n const eventTarget: EventTargetWithScroll =\n fallbackToDocument &&\n element === window &&\n typeof document !== \"undefined\"\n ? document\n : element\n\n const on = (\n el: EventTargetWithScroll,\n event: string,\n handler: EventListener\n ) => el.addEventListener(event, handler, true)\n\n const off = (\n el: EventTargetWithScroll,\n event: string,\n handler: EventListener\n ) => el.removeEventListener(event, handler)\n\n let timeout: ReturnType<typeof setTimeout>\n const supportsScrollEnd = element === window && \"onscrollend\" in window\n\n const handleScroll: EventListener = () => {\n if (!isScrolling) setIsScrolling(true)\n\n if (!supportsScrollEnd) {\n clearTimeout(timeout)\n timeout = setTimeout(() => setIsScrolling(false), debounce)\n }\n }\n\n const handleScrollEnd: EventListener = () => setIsScrolling(false)\n\n on(eventTarget, \"scroll\", handleScroll)\n if (supportsScrollEnd) {\n on(eventTarget, \"scrollend\", handleScrollEnd)\n }\n\n return () => {\n off(eventTarget, \"scroll\", handleScroll)\n if (supportsScrollEnd) {\n off(eventTarget, \"scrollend\", handleScrollEnd)\n }\n clearTimeout(timeout)\n }\n }, [target, debounce, fallbackToDocument, isScrolling])\n\n return isScrolling\n}\n"]}
package/dist/docx.d.ts CHANGED
@@ -104,6 +104,14 @@ interface ParseDocxResult {
104
104
  header: Record<string, unknown> | null;
105
105
  /** 默认页脚的结构化内容,同上 */
106
106
  footer: Record<string, unknown> | null;
107
+ /**
108
+ * 脚注。**键是正文上标标记 `[n]` 里的编号**(按引用出现顺序 1 起编),
109
+ * 宿主拿 `footnotes["1"]` 就是正文里 `[1]` 指的那条 —— 不用自己解 w:id。
110
+ * 编辑器没有脚注节点,所以注文不进文档树、导出 docx 时也不会写回。
111
+ */
112
+ footnotes: Record<string, Record<string, unknown>>;
113
+ /** 尾注,键的语义同 `footnotes`(编号序列与脚注各自独立) */
114
+ endnotes: Record<string, Record<string, unknown>>;
107
115
  /** 节属性原件,交给导出侧原样写回 */
108
116
  section: DocxSection;
109
117
  }
@@ -276,7 +284,14 @@ interface ImportDocxContext {
276
284
  * 但它只活在内存里:刷新页面 / 换设备就没了,要跨会话往返得自己存。
277
285
  */
278
286
  section: DocxSection;
287
+ /**
288
+ * 脚注。**键是正文上标标记 `[n]` 里的编号**(按引用出现顺序 1 起编)——
289
+ * 宿主拿 `footnotes["1"]` 就是正文 `[1]` 指的那条,不用自己解 w:id。
290
+ * 编辑器没有脚注节点:注文不进文档树,导出 docx 时也不会写回
291
+ * (导入时 warnings 里会明说)。
292
+ */
279
293
  footnotes: Record<string, JSONContent>;
294
+ /** 尾注,键的语义同 `footnotes`(编号序列与脚注各自独立) */
280
295
  endnotes: Record<string, JSONContent>;
281
296
  }
282
297
  interface ImportDocxCommandArgs {
package/dist/docx.js CHANGED
@@ -1,8 +1,7 @@
1
+ import { getCodeBlockTitle } from './chunk-ZY5KLA7T.js';
1
2
  import { Node, mergeAttributes, Extension } from '@tiptap/core';
2
3
  import { unzipSync, zipSync } from 'fflate';
3
4
 
4
- // src/docx/index.ts
5
-
6
5
  // src/docx/measure.ts
7
6
  function measureContentWidth(dom) {
8
7
  const el = dom;
@@ -142,9 +141,23 @@ function parseDocx(entries, opts) {
142
141
  paraSpacing: 0,
143
142
  paraIndent: 0,
144
143
  lists: 0,
145
- listItems: 0
144
+ listItems: 0,
145
+ footnotes: 0,
146
+ endnotes: 0
146
147
  };
147
148
  const warnings = [];
149
+ const noteNumbers = {
150
+ footnote: /* @__PURE__ */ new Map(),
151
+ endnote: /* @__PURE__ */ new Map()
152
+ };
153
+ function noteNumber(kind, id) {
154
+ const seq = noteNumbers[kind];
155
+ const hit = seq.get(id);
156
+ if (hit !== void 0) return hit;
157
+ const n = seq.size + 1;
158
+ seq.set(id, n);
159
+ return n;
160
+ }
148
161
  const text = (p) => {
149
162
  const b = entries.read(p);
150
163
  return b ? decoder.decode(b) : null;
@@ -379,6 +392,17 @@ function parseDocx(entries, opts) {
379
392
  if (!n) continue;
380
393
  if (n.type === "image") out.blocks.push(n);
381
394
  else out.inline.push(n);
395
+ } else if (t === "w:footnoteReference" || t === "w:endnoteReference") {
396
+ const id = c.getAttribute("w:id");
397
+ if (!id) continue;
398
+ const kind = t === "w:footnoteReference" ? "footnote" : "endnote";
399
+ const n = noteNumber(kind, id);
400
+ const hasSuper = marks.some((m) => m.type === "superscript");
401
+ out.inline.push({
402
+ type: "text",
403
+ text: `[${n}]`,
404
+ marks: hasSuper ? marks : [...marks, { type: "superscript" }]
405
+ });
382
406
  } else if (t === "w:pict") {
383
407
  warnings.push("遇到 VML 图形(w:pict),未处理");
384
408
  }
@@ -706,6 +730,17 @@ function parseDocx(entries, opts) {
706
730
  if (!body) throw new Error("docx 缺少 w:body");
707
731
  const content = blocksOf(body);
708
732
  const doc = remapTypes({ type: "doc", content }, opts.prosemirrorNodes, opts.prosemirrorMarks);
733
+ const footnotes = notesOf("word/footnotes.xml", "w:footnote", "footnote");
734
+ const endnotes = notesOf("word/endnotes.xml", "w:endnote", "endnote");
735
+ if (recovered.footnotes || recovered.endnotes) {
736
+ const parts = [
737
+ ...recovered.footnotes ? [`${recovered.footnotes} 条脚注`] : [],
738
+ ...recovered.endnotes ? [`${recovered.endnotes} 条尾注`] : []
739
+ ];
740
+ warnings.push(
741
+ `文档有 ${parts.join("和")}:正文引用以上标 [n] 文本保留,注文在导入结果的 footnotes / endnotes 里(键即编号 n)。编辑器没有脚注节点,导出 docx 时不会写回`
742
+ );
743
+ }
709
744
  const section = readSection();
710
745
  const defaultHeader = section.refs.find((r) => r.kind === "header" && r.type === "default");
711
746
  const defaultFooter = section.refs.find((r) => r.kind === "footer" && r.type === "default");
@@ -716,8 +751,30 @@ function parseDocx(entries, opts) {
716
751
  ...opts.cssStyles ? { css: extractCss(stylesDoc) } : {},
717
752
  header: defaultHeader ? partContent(defaultHeader.path, "w:hdr") : null,
718
753
  footer: defaultFooter ? partContent(defaultFooter.path, "w:ftr") : null,
754
+ footnotes,
755
+ endnotes,
719
756
  section
720
757
  };
758
+ function notesOf(path, tag, kind) {
759
+ const d = xml(path);
760
+ if (!d) return {};
761
+ const out = {};
762
+ for (const el of Array.from(d.getElementsByTagName(tag))) {
763
+ const type = el.getAttribute("w:type");
764
+ if (type && type !== "normal") continue;
765
+ const id = el.getAttribute("w:id");
766
+ if (id === null) continue;
767
+ const blocks = blocksOf(el);
768
+ if (!blocks.length) continue;
769
+ out[String(noteNumber(kind, id))] = remapTypes(
770
+ { type: "doc", content: blocks },
771
+ opts.prosemirrorNodes,
772
+ opts.prosemirrorMarks
773
+ );
774
+ recovered[`${kind}s`] += 1;
775
+ }
776
+ return out;
777
+ }
721
778
  function partContent(path, root) {
722
779
  const d = xml(path);
723
780
  const el = d?.getElementsByTagName(root)[0];
@@ -1162,7 +1219,26 @@ function serializeDocx(doc, opts = {}) {
1162
1219
  });
1163
1220
  return `${xml}</w:tbl>`;
1164
1221
  }
1165
- function blocks(nodes) {
1222
+ function codeBlock(n) {
1223
+ return `<w:p><w:pPr><w:pStyle w:val="Code"/></w:pPr><w:r><w:rPr><w:rFonts w:ascii="Consolas" w:hAnsi="Consolas"/></w:rPr><w:t xml:space="preserve">${esc(collectText(n))}</w:t></w:r></w:p>`;
1224
+ }
1225
+ function indentPr(indent) {
1226
+ return indent ? `<w:pPr><w:ind w:left="${indent}"/></w:pPr>` : "";
1227
+ }
1228
+ function boldParagraph(text, indent) {
1229
+ return `<w:p>${indentPr(indent)}<w:r><w:rPr><w:b/></w:rPr><w:t xml:space="preserve">${esc(text)}</w:t></w:r></w:p>`;
1230
+ }
1231
+ function apiParamHeader(n, indent) {
1232
+ const a = n.attrs ?? {};
1233
+ const runs = [];
1234
+ if (a.name) runs.push(`<w:r><w:rPr><w:b/></w:rPr><w:t xml:space="preserve">${esc(a.name)}</w:t></w:r>`);
1235
+ if (a.type) runs.push(`<w:r><w:rPr><w:rFonts w:ascii="Consolas" w:hAnsi="Consolas"/></w:rPr><w:t xml:space="preserve"> ${esc(a.type)}</w:t></w:r>`);
1236
+ if (a.required) runs.push(`<w:r><w:t xml:space="preserve"> 必选</w:t></w:r>`);
1237
+ if (a.default != null) runs.push(`<w:r><w:t xml:space="preserve"> 默认值 ${esc(a.default)}</w:t></w:r>`);
1238
+ if (a.summary) runs.push(`<w:r><w:t xml:space="preserve"> | ${esc(a.summary)}</w:t></w:r>`);
1239
+ return `<w:p>${indentPr(indent)}${runs.join("")}</w:p>`;
1240
+ }
1241
+ function blocks(nodes, indent = 0) {
1166
1242
  const out = [];
1167
1243
  for (const n of nodes) {
1168
1244
  switch (n.type) {
@@ -1170,7 +1246,7 @@ function serializeDocx(doc, opts = {}) {
1170
1246
  out.push(heading(n));
1171
1247
  break;
1172
1248
  case "paragraph":
1173
- out.push(paragraph(n));
1249
+ out.push(paragraph(n, [], indent || void 0));
1174
1250
  break;
1175
1251
  case "bulletList":
1176
1252
  case "orderedList":
@@ -1183,7 +1259,32 @@ function serializeDocx(doc, opts = {}) {
1183
1259
  for (const c of n.content ?? []) out.push(paragraph(c, [], 720));
1184
1260
  break;
1185
1261
  case "codeBlock":
1186
- out.push(`<w:p><w:pPr><w:pStyle w:val="Code"/></w:pPr><w:r><w:rPr><w:rFonts w:ascii="Consolas" w:hAnsi="Consolas"/></w:rPr><w:t xml:space="preserve">${esc(collectText(n))}</w:t></w:r></w:p>`);
1262
+ out.push(codeBlock(n));
1263
+ break;
1264
+ case "codeGroup":
1265
+ for (const child of n.content ?? []) {
1266
+ if (child.type !== "codeBlock") {
1267
+ warnings.push(`codeGroup 里出现非代码块节点 "${child.type}",已跳过`);
1268
+ continue;
1269
+ }
1270
+ out.push(`<w:p><w:r><w:rPr><w:b/></w:rPr><w:t xml:space="preserve">${esc(codeLabel(child))}</w:t></w:r></w:p>`);
1271
+ out.push(codeBlock(child));
1272
+ }
1273
+ break;
1274
+ case "details":
1275
+ out.push(boldParagraph(String(n.attrs?.title ?? "") || "详情", indent));
1276
+ out.push(blocks(n.content ?? [], indent));
1277
+ break;
1278
+ case "callout": {
1279
+ const type = String(n.attrs?.type ?? "info");
1280
+ const title = String(n.attrs?.title ?? "") || calloutDefaultTitle(type);
1281
+ out.push(boldParagraph(title, indent));
1282
+ out.push(blocks(n.content ?? [], indent + 720));
1283
+ break;
1284
+ }
1285
+ case "apiParam":
1286
+ out.push(apiParamHeader(n, indent));
1287
+ out.push(blocks(n.content ?? [], indent + 720));
1187
1288
  break;
1188
1289
  case "horizontalRule":
1189
1290
  out.push('<w:p><w:pPr><w:pBdr><w:bottom w:val="single" w:sz="6" w:color="auto"/></w:pBdr></w:pPr></w:p>');
@@ -1280,6 +1381,13 @@ function cssFontStack(v) {
1280
1381
  if (!parts.length) return null;
1281
1382
  return { ascii: parts[0], eastAsia: parts[parts.length - 1] };
1282
1383
  }
1384
+ function calloutDefaultTitle(type) {
1385
+ return { info: "信息", tip: "提示", warning: "注意", danger: "危险" }[type] ?? "信息";
1386
+ }
1387
+ function codeLabel(n) {
1388
+ const attrs = n.attrs ?? {};
1389
+ return getCodeBlockTitle({ language: attrs.language ?? null, label: attrs.label ?? null });
1390
+ }
1283
1391
  function collectText(n) {
1284
1392
  if (n?.type === "text") return n.text ?? "";
1285
1393
  return (n?.content ?? []).map(collectText).join("");
@@ -1542,8 +1650,8 @@ var ImportDocx = Extension.create({
1542
1650
  header: result.header,
1543
1651
  footer: result.footer,
1544
1652
  section: result.section,
1545
- footnotes: {},
1546
- endnotes: {}
1653
+ footnotes: result.footnotes,
1654
+ endnotes: result.endnotes
1547
1655
  });
1548
1656
  } catch (e) {
1549
1657
  const err = e instanceof Error ? e : new Error(String(e));