@spacecorexdev/core 0.0.2 → 0.0.5

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 (345) hide show
  1. package/package.json +20 -38
  2. package/public/cmaps/78-EUC-H.bcmap +0 -0
  3. package/public/cmaps/78-EUC-V.bcmap +0 -0
  4. package/public/cmaps/78-H.bcmap +0 -0
  5. package/public/cmaps/78-RKSJ-H.bcmap +0 -0
  6. package/public/cmaps/78-RKSJ-V.bcmap +0 -0
  7. package/public/cmaps/78-V.bcmap +0 -0
  8. package/public/cmaps/78ms-RKSJ-H.bcmap +0 -0
  9. package/public/cmaps/78ms-RKSJ-V.bcmap +0 -0
  10. package/public/cmaps/83pv-RKSJ-H.bcmap +0 -0
  11. package/public/cmaps/90ms-RKSJ-H.bcmap +0 -0
  12. package/public/cmaps/90ms-RKSJ-V.bcmap +0 -0
  13. package/public/cmaps/90msp-RKSJ-H.bcmap +0 -0
  14. package/public/cmaps/90msp-RKSJ-V.bcmap +0 -0
  15. package/public/cmaps/90pv-RKSJ-H.bcmap +0 -0
  16. package/public/cmaps/90pv-RKSJ-V.bcmap +0 -0
  17. package/public/cmaps/Add-H.bcmap +0 -0
  18. package/public/cmaps/Add-RKSJ-H.bcmap +0 -0
  19. package/public/cmaps/Add-RKSJ-V.bcmap +0 -0
  20. package/public/cmaps/Add-V.bcmap +0 -0
  21. package/public/cmaps/Adobe-CNS1-0.bcmap +0 -0
  22. package/public/cmaps/Adobe-CNS1-1.bcmap +0 -0
  23. package/public/cmaps/Adobe-CNS1-2.bcmap +0 -0
  24. package/public/cmaps/Adobe-CNS1-3.bcmap +0 -0
  25. package/public/cmaps/Adobe-CNS1-4.bcmap +0 -0
  26. package/public/cmaps/Adobe-CNS1-5.bcmap +0 -0
  27. package/public/cmaps/Adobe-CNS1-6.bcmap +0 -0
  28. package/public/cmaps/Adobe-CNS1-UCS2.bcmap +0 -0
  29. package/public/cmaps/Adobe-GB1-0.bcmap +0 -0
  30. package/public/cmaps/Adobe-GB1-1.bcmap +0 -0
  31. package/public/cmaps/Adobe-GB1-2.bcmap +0 -0
  32. package/public/cmaps/Adobe-GB1-3.bcmap +0 -0
  33. package/public/cmaps/Adobe-GB1-4.bcmap +0 -0
  34. package/public/cmaps/Adobe-GB1-5.bcmap +0 -0
  35. package/public/cmaps/Adobe-GB1-UCS2.bcmap +0 -0
  36. package/public/cmaps/Adobe-Japan1-0.bcmap +0 -0
  37. package/public/cmaps/Adobe-Japan1-1.bcmap +0 -0
  38. package/public/cmaps/Adobe-Japan1-2.bcmap +0 -0
  39. package/public/cmaps/Adobe-Japan1-3.bcmap +0 -0
  40. package/public/cmaps/Adobe-Japan1-4.bcmap +0 -0
  41. package/public/cmaps/Adobe-Japan1-5.bcmap +0 -0
  42. package/public/cmaps/Adobe-Japan1-6.bcmap +0 -0
  43. package/public/cmaps/Adobe-Japan1-UCS2.bcmap +0 -0
  44. package/public/cmaps/Adobe-Korea1-0.bcmap +0 -0
  45. package/public/cmaps/Adobe-Korea1-1.bcmap +0 -0
  46. package/public/cmaps/Adobe-Korea1-2.bcmap +0 -0
  47. package/public/cmaps/Adobe-Korea1-UCS2.bcmap +0 -0
  48. package/public/cmaps/B5-H.bcmap +0 -0
  49. package/public/cmaps/B5-V.bcmap +0 -0
  50. package/public/cmaps/B5pc-H.bcmap +0 -0
  51. package/public/cmaps/B5pc-V.bcmap +0 -0
  52. package/public/cmaps/CNS-EUC-H.bcmap +0 -0
  53. package/public/cmaps/CNS-EUC-V.bcmap +0 -0
  54. package/public/cmaps/CNS1-H.bcmap +0 -0
  55. package/public/cmaps/CNS1-V.bcmap +0 -0
  56. package/public/cmaps/CNS2-H.bcmap +0 -0
  57. package/public/cmaps/CNS2-V.bcmap +3 -0
  58. package/public/cmaps/ETHK-B5-H.bcmap +0 -0
  59. package/public/cmaps/ETHK-B5-V.bcmap +0 -0
  60. package/public/cmaps/ETen-B5-H.bcmap +0 -0
  61. package/public/cmaps/ETen-B5-V.bcmap +0 -0
  62. package/public/cmaps/ETenms-B5-H.bcmap +3 -0
  63. package/public/cmaps/ETenms-B5-V.bcmap +0 -0
  64. package/public/cmaps/EUC-H.bcmap +0 -0
  65. package/public/cmaps/EUC-V.bcmap +0 -0
  66. package/public/cmaps/Ext-H.bcmap +0 -0
  67. package/public/cmaps/Ext-RKSJ-H.bcmap +0 -0
  68. package/public/cmaps/Ext-RKSJ-V.bcmap +0 -0
  69. package/public/cmaps/Ext-V.bcmap +0 -0
  70. package/public/cmaps/GB-EUC-H.bcmap +0 -0
  71. package/public/cmaps/GB-EUC-V.bcmap +0 -0
  72. package/public/cmaps/GB-H.bcmap +4 -0
  73. package/public/cmaps/GB-V.bcmap +0 -0
  74. package/public/cmaps/GBK-EUC-H.bcmap +0 -0
  75. package/public/cmaps/GBK-EUC-V.bcmap +0 -0
  76. package/public/cmaps/GBK2K-H.bcmap +0 -0
  77. package/public/cmaps/GBK2K-V.bcmap +0 -0
  78. package/public/cmaps/GBKp-EUC-H.bcmap +0 -0
  79. package/public/cmaps/GBKp-EUC-V.bcmap +0 -0
  80. package/public/cmaps/GBT-EUC-H.bcmap +0 -0
  81. package/public/cmaps/GBT-EUC-V.bcmap +0 -0
  82. package/public/cmaps/GBT-H.bcmap +0 -0
  83. package/public/cmaps/GBT-V.bcmap +0 -0
  84. package/public/cmaps/GBTpc-EUC-H.bcmap +0 -0
  85. package/public/cmaps/GBTpc-EUC-V.bcmap +0 -0
  86. package/public/cmaps/GBpc-EUC-H.bcmap +0 -0
  87. package/public/cmaps/GBpc-EUC-V.bcmap +0 -0
  88. package/public/cmaps/H.bcmap +0 -0
  89. package/public/cmaps/HKdla-B5-H.bcmap +0 -0
  90. package/public/cmaps/HKdla-B5-V.bcmap +0 -0
  91. package/public/cmaps/HKdlb-B5-H.bcmap +0 -0
  92. package/public/cmaps/HKdlb-B5-V.bcmap +0 -0
  93. package/public/cmaps/HKgccs-B5-H.bcmap +0 -0
  94. package/public/cmaps/HKgccs-B5-V.bcmap +0 -0
  95. package/public/cmaps/HKm314-B5-H.bcmap +0 -0
  96. package/public/cmaps/HKm314-B5-V.bcmap +0 -0
  97. package/public/cmaps/HKm471-B5-H.bcmap +0 -0
  98. package/public/cmaps/HKm471-B5-V.bcmap +0 -0
  99. package/public/cmaps/HKscs-B5-H.bcmap +0 -0
  100. package/public/cmaps/HKscs-B5-V.bcmap +0 -0
  101. package/public/cmaps/Hankaku.bcmap +0 -0
  102. package/public/cmaps/Hiragana.bcmap +0 -0
  103. package/public/cmaps/KSC-EUC-H.bcmap +0 -0
  104. package/public/cmaps/KSC-EUC-V.bcmap +0 -0
  105. package/public/cmaps/KSC-H.bcmap +0 -0
  106. package/public/cmaps/KSC-Johab-H.bcmap +0 -0
  107. package/public/cmaps/KSC-Johab-V.bcmap +0 -0
  108. package/public/cmaps/KSC-V.bcmap +0 -0
  109. package/public/cmaps/KSCms-UHC-H.bcmap +0 -0
  110. package/public/cmaps/KSCms-UHC-HW-H.bcmap +0 -0
  111. package/public/cmaps/KSCms-UHC-HW-V.bcmap +0 -0
  112. package/public/cmaps/KSCms-UHC-V.bcmap +0 -0
  113. package/public/cmaps/KSCpc-EUC-H.bcmap +0 -0
  114. package/public/cmaps/KSCpc-EUC-V.bcmap +0 -0
  115. package/public/cmaps/Katakana.bcmap +0 -0
  116. package/public/cmaps/LICENSE +36 -0
  117. package/public/cmaps/NWP-H.bcmap +0 -0
  118. package/public/cmaps/NWP-V.bcmap +0 -0
  119. package/public/cmaps/RKSJ-H.bcmap +0 -0
  120. package/public/cmaps/RKSJ-V.bcmap +0 -0
  121. package/public/cmaps/Roman.bcmap +0 -0
  122. package/public/cmaps/UniCNS-UCS2-H.bcmap +0 -0
  123. package/public/cmaps/UniCNS-UCS2-V.bcmap +0 -0
  124. package/public/cmaps/UniCNS-UTF16-H.bcmap +0 -0
  125. package/public/cmaps/UniCNS-UTF16-V.bcmap +0 -0
  126. package/public/cmaps/UniCNS-UTF32-H.bcmap +0 -0
  127. package/public/cmaps/UniCNS-UTF32-V.bcmap +0 -0
  128. package/public/cmaps/UniCNS-UTF8-H.bcmap +0 -0
  129. package/public/cmaps/UniCNS-UTF8-V.bcmap +0 -0
  130. package/public/cmaps/UniGB-UCS2-H.bcmap +0 -0
  131. package/public/cmaps/UniGB-UCS2-V.bcmap +0 -0
  132. package/public/cmaps/UniGB-UTF16-H.bcmap +0 -0
  133. package/public/cmaps/UniGB-UTF16-V.bcmap +0 -0
  134. package/public/cmaps/UniGB-UTF32-H.bcmap +0 -0
  135. package/public/cmaps/UniGB-UTF32-V.bcmap +0 -0
  136. package/public/cmaps/UniGB-UTF8-H.bcmap +0 -0
  137. package/public/cmaps/UniGB-UTF8-V.bcmap +0 -0
  138. package/public/cmaps/UniJIS-UCS2-H.bcmap +0 -0
  139. package/public/cmaps/UniJIS-UCS2-HW-H.bcmap +0 -0
  140. package/public/cmaps/UniJIS-UCS2-HW-V.bcmap +0 -0
  141. package/public/cmaps/UniJIS-UCS2-V.bcmap +0 -0
  142. package/public/cmaps/UniJIS-UTF16-H.bcmap +0 -0
  143. package/public/cmaps/UniJIS-UTF16-V.bcmap +0 -0
  144. package/public/cmaps/UniJIS-UTF32-H.bcmap +0 -0
  145. package/public/cmaps/UniJIS-UTF32-V.bcmap +0 -0
  146. package/public/cmaps/UniJIS-UTF8-H.bcmap +0 -0
  147. package/public/cmaps/UniJIS-UTF8-V.bcmap +0 -0
  148. package/public/cmaps/UniJIS2004-UTF16-H.bcmap +0 -0
  149. package/public/cmaps/UniJIS2004-UTF16-V.bcmap +0 -0
  150. package/public/cmaps/UniJIS2004-UTF32-H.bcmap +0 -0
  151. package/public/cmaps/UniJIS2004-UTF32-V.bcmap +0 -0
  152. package/public/cmaps/UniJIS2004-UTF8-H.bcmap +0 -0
  153. package/public/cmaps/UniJIS2004-UTF8-V.bcmap +0 -0
  154. package/public/cmaps/UniJISPro-UCS2-HW-V.bcmap +0 -0
  155. package/public/cmaps/UniJISPro-UCS2-V.bcmap +0 -0
  156. package/public/cmaps/UniJISPro-UTF8-V.bcmap +0 -0
  157. package/public/cmaps/UniJISX0213-UTF32-H.bcmap +0 -0
  158. package/public/cmaps/UniJISX0213-UTF32-V.bcmap +0 -0
  159. package/public/cmaps/UniJISX02132004-UTF32-H.bcmap +0 -0
  160. package/public/cmaps/UniJISX02132004-UTF32-V.bcmap +0 -0
  161. package/public/cmaps/UniKS-UCS2-H.bcmap +0 -0
  162. package/public/cmaps/UniKS-UCS2-V.bcmap +0 -0
  163. package/public/cmaps/UniKS-UTF16-H.bcmap +0 -0
  164. package/public/cmaps/UniKS-UTF16-V.bcmap +0 -0
  165. package/public/cmaps/UniKS-UTF32-H.bcmap +0 -0
  166. package/public/cmaps/UniKS-UTF32-V.bcmap +0 -0
  167. package/public/cmaps/UniKS-UTF8-H.bcmap +0 -0
  168. package/public/cmaps/UniKS-UTF8-V.bcmap +0 -0
  169. package/public/cmaps/V.bcmap +0 -0
  170. package/public/cmaps/WP-Symbol.bcmap +0 -0
  171. package/public/polyfills/polyfills.js +802 -0
  172. package/public/vendor/docx/docx.worker.js +5 -0
  173. package/public/vendor/docx/jszip.min.js +13 -0
  174. package/public/vendor/libarchive/libarchive.wasm +0 -0
  175. package/public/vendor/libarchive/worker-bundle.js +6 -0
  176. package/public/vendor/pptx/pptx.worker.js +26 -0
  177. package/public/wasm/cad/0.8.2/dwfv-render.wasm +0 -0
  178. package/public/wasm/cad/0.8.2/dwg-worker.js +24 -0
  179. package/public/wasm/cad/0.8.2/dwg-worker.js.map +1 -0
  180. package/public/wasm/cad/0.8.2/libredwg-web.js +2 -0
  181. package/public/wasm/cad/0.8.2/libredwg-web.wasm +0 -0
  182. package/public/wasm/cad/dwfv-render.wasm +0 -0
  183. package/public/wasm/cad/dwg-worker.js +24 -0
  184. package/public/wasm/cad/dwg-worker.js.map +1 -0
  185. package/public/wasm/cad/libredwg-web.js +2 -0
  186. package/public/wasm/cad/libredwg-web.wasm +0 -0
  187. package/public/wasm/data/sql-wasm.wasm +0 -0
  188. package/public/wasm/hdiff.wasm +0 -0
  189. package/public/wasm/model/LICENSE.occt-import-js.txt +504 -0
  190. package/public/wasm/model/LICENSE.occt.txt +502 -0
  191. package/public/wasm/model/occt-import-js.js +19 -0
  192. package/public/wasm/model/occt-import-js.wasm +0 -0
  193. package/public/wasm/model/occt-worker.js +83 -0
  194. package/public/wasm/typst/fonts/DejaVuSansMono-Bold.ttf +0 -0
  195. package/public/wasm/typst/fonts/DejaVuSansMono-BoldOblique.ttf +0 -0
  196. package/public/wasm/typst/fonts/DejaVuSansMono-Oblique.ttf +0 -0
  197. package/public/wasm/typst/fonts/DejaVuSansMono.ttf +0 -0
  198. package/public/wasm/typst/fonts/LibertinusSerif-Bold.otf +0 -0
  199. package/public/wasm/typst/fonts/LibertinusSerif-BoldItalic.otf +0 -0
  200. package/public/wasm/typst/fonts/LibertinusSerif-Italic.otf +0 -0
  201. package/public/wasm/typst/fonts/LibertinusSerif-Regular.otf +0 -0
  202. package/public/wasm/typst/fonts/LibertinusSerif-Semibold.otf +0 -0
  203. package/public/wasm/typst/fonts/LibertinusSerif-SemiboldItalic.otf +0 -0
  204. package/public/wasm/typst/fonts/NewCM10-Bold.otf +0 -0
  205. package/public/wasm/typst/fonts/NewCM10-BoldItalic.otf +0 -0
  206. package/public/wasm/typst/fonts/NewCM10-Italic.otf +0 -0
  207. package/public/wasm/typst/fonts/NewCM10-Regular.otf +0 -0
  208. package/public/wasm/typst/fonts/NewCMMath-Bold.otf +0 -0
  209. package/public/wasm/typst/fonts/NewCMMath-Book.otf +0 -0
  210. package/public/wasm/typst/fonts/NewCMMath-Regular.otf +0 -0
  211. package/public/wasm/typst/fonts/README.md +7 -0
  212. package/public/wasm/typst/typst_ts_renderer_bg.wasm +0 -0
  213. package/public/wasm/typst/typst_ts_web_compiler_bg.wasm +0 -0
  214. package/scripts/patch-docx-grid.cjs +412 -0
  215. package/src/api/_base.ts +243 -0
  216. package/src/api/index.ts +17 -0
  217. package/src/api/project.ts +1345 -0
  218. package/src/api/user.ts +1174 -0
  219. package/src/contract.ts +50 -0
  220. package/src/hooks/index.ts +2 -0
  221. package/{dist/hooks/index.js → src/hooks/useLongPress.ts} +53 -49
  222. package/src/hooks/useOnlineStatus.ts +22 -0
  223. package/src/index.ts +14 -0
  224. package/src/types/id.ts +2 -0
  225. package/src/types/index.ts +5 -0
  226. package/src/types/project/base.ts +63 -0
  227. package/src/types/project/contract.ts +79 -0
  228. package/src/types/project/contractStatus.tsx +15 -0
  229. package/src/types/project/index.ts +5 -0
  230. package/src/types/project/member.ts +46 -0
  231. package/src/types/project/progress.ts +43 -0
  232. package/src/types/projectRole.ts +17 -0
  233. package/src/types/theme.ts +1 -0
  234. package/src/types/user/index.ts +1 -0
  235. package/src/types/user/role.ts +7 -0
  236. package/src/ui/CommentTimeline/index.tsx +330 -0
  237. package/src/ui/FileManager/FileContextMenu.tsx +628 -0
  238. package/src/ui/FileManager/FileGridView.tsx +529 -0
  239. package/src/ui/FileManager/FileTableView.tsx +405 -0
  240. package/src/ui/FileManager/FileToolbar.tsx +119 -0
  241. package/src/ui/FileManager/FileVersionHistoryModal.tsx +175 -0
  242. package/src/ui/FileManager/MoveCopyToProjectModal.tsx +119 -0
  243. package/src/ui/FileManager/ShareToWechatModal.tsx +419 -0
  244. package/src/ui/FileManager/hooks/useFileManagerActions.ts +2532 -0
  245. package/src/ui/FileManager/hooks/useFileManagerState.ts +419 -0
  246. package/src/ui/FileManager/index.tsx +580 -0
  247. package/src/ui/FileManager/projectFileManagerApi.ts +57 -0
  248. package/src/ui/FileManager/types.ts +184 -0
  249. package/src/ui/FileManager/utils/fileDeltaWorker.ts +193 -0
  250. package/src/ui/FileManager/utils/fileTree.ts +178 -0
  251. package/src/ui/FileManager/utils/fileUpload.ts +714 -0
  252. package/src/ui/FileManager/utils/fileUtils.ts +65 -0
  253. package/src/ui/InvoiceRecordsList/index.tsx +813 -0
  254. package/src/ui/MyFileSelectorModal.tsx +261 -0
  255. package/src/ui/ProjectSelector/index.tsx +472 -0
  256. package/src/ui/ProjectSelector/projectCache.ts +40 -0
  257. package/src/ui/RoleSelector/index.tsx +473 -0
  258. package/src/ui/RoleSelector/roleCache.ts +40 -0
  259. package/src/ui/UIAlert.tsx +399 -0
  260. package/src/ui/UIAlertExample.tsx +129 -0
  261. package/src/ui/UIConfirm.tsx +335 -0
  262. package/src/ui/UIConfirmExample.tsx +165 -0
  263. package/src/ui/UIDatePicker.tsx +146 -0
  264. package/src/ui/UIDateRangePicker.tsx +38 -0
  265. package/src/ui/UIFilePreview/ExcelEditor/index.tsx +617 -0
  266. package/src/ui/UIFilePreview/ExcelEditor/types.ts +24 -0
  267. package/src/ui/UIFilePreview/ExportDropdown.css +85 -0
  268. package/src/ui/UIFilePreview/ExportDropdown.tsx +109 -0
  269. package/src/ui/UIFilePreview/IndexedDBUtils.ts +564 -0
  270. package/src/ui/UIFilePreview/MarkdownEditor/exportMarkdownPdf.ts +576 -0
  271. package/src/ui/UIFilePreview/MarkdownEditor/index.tsx +677 -0
  272. package/src/ui/UIFilePreview/MarkdownEditor/markdownEditor.css +1005 -0
  273. package/src/ui/UIFilePreview/MarkdownEditor/types.ts +15 -0
  274. package/src/ui/UIFilePreview/PPTXEditor/index.tsx +602 -0
  275. package/src/ui/UIFilePreview/RichDocEditor/exportRDocMarkdown.ts +77 -0
  276. package/src/ui/UIFilePreview/RichDocEditor/exportRDocPdf.ts +374 -0
  277. package/src/ui/UIFilePreview/RichDocEditor/index.tsx +730 -0
  278. package/src/ui/UIFilePreview/RichDocEditor/richdoc-editor.css +59 -0
  279. package/src/ui/UIFilePreview/UIFilePreview.tsx +611 -0
  280. package/src/ui/UIFilePreview/WordEditor/index.tsx +3933 -0
  281. package/src/ui/UIFilePreview/WordEditor/style.ts +1213 -0
  282. package/src/ui/UIFilePreview/WordEditor/word-editor.css +999 -0
  283. package/src/ui/UIFilePreview/filePreview.css +193 -0
  284. package/src/ui/UIFilePreview/filetypes/archive.tsx +66 -0
  285. package/src/ui/UIFilePreview/filetypes/cad.tsx +287 -0
  286. package/src/ui/UIFilePreview/filetypes/excel.tsx +355 -0
  287. package/src/ui/UIFilePreview/filetypes/image.tsx +20 -0
  288. package/src/ui/UIFilePreview/filetypes/imageLoader.ts +63 -0
  289. package/src/ui/UIFilePreview/filetypes/pdf.tsx +156 -0
  290. package/src/ui/UIFilePreview/filetypes/ppt.tsx +535 -0
  291. package/src/ui/UIFilePreview/filetypes/pptExportPdf.ts +523 -0
  292. package/src/ui/UIFilePreview/filetypes/video.tsx +482 -0
  293. package/src/ui/UIFilePreview/filetypes/word.tsx +245 -0
  294. package/src/ui/UIFilePreview/filetypes/wordExportMarkdown.ts +86 -0
  295. package/src/ui/UIFilePreview/filetypes/wordExportPdf.tsx +427 -0
  296. package/src/ui/UIFilePreview/index.ts +35 -0
  297. package/src/ui/UIFilePreview/pdfPagination.ts +143 -0
  298. package/src/ui/UIFilePreview/previewUtils.tsx +531 -0
  299. package/src/ui/UIFileUpload.tsx +382 -0
  300. package/src/ui/UIImageUpload.tsx +195 -0
  301. package/src/ui/UILoading.tsx +255 -0
  302. package/src/ui/UILoadingExample.tsx +139 -0
  303. package/src/ui/UISelect.tsx +105 -0
  304. package/src/ui/UIToast.tsx +381 -0
  305. package/src/ui/UIToastExample.tsx +69 -0
  306. package/src/ui/UIToastSingle.tsx +279 -0
  307. package/src/ui/UIViewToggle.tsx +64 -0
  308. package/src/ui/UserSelector/index.tsx +583 -0
  309. package/src/ui/index.ts +84 -0
  310. package/src/ui/upload/image.ts +135 -0
  311. package/src/ui/upload/index.ts +73 -0
  312. package/src/ui/upload/submitFile.ts +106 -0
  313. package/src/ui/upload/submitForm.ts +124 -0
  314. package/src/ui/upload/upload.err.ts +15 -0
  315. package/src/ui/upload/upload.ts +250 -0
  316. package/src/utils/CacheDepartmentInfo.ts +372 -0
  317. package/src/utils/CacheFileContent.ts +315 -0
  318. package/src/utils/CachePermissionInfo.ts +235 -0
  319. package/src/utils/CacheRoleInfo.ts +303 -0
  320. package/src/utils/CacheUserInfo.ts +818 -0
  321. package/src/utils/CacheViewToggleInfo.ts +182 -0
  322. package/src/utils/MessageContentUtils.ts +268 -0
  323. package/src/utils/clipboard.ts +70 -0
  324. package/src/utils/companyEmail.ts +19 -0
  325. package/src/utils/forbidDebugger.ts +59 -0
  326. package/src/utils/formatters.ts +94 -0
  327. package/src/utils/htmlUtils.tsx +59 -0
  328. package/src/utils/index.ts +18 -0
  329. package/src/utils/keyboard.ts +114 -0
  330. package/src/utils/mammothApi.ts +16 -0
  331. package/src/utils/polyfills.ts +928 -0
  332. package/src/utils/regex.ts +14 -0
  333. package/src/utils/storage.ts +95 -0
  334. package/src/utils/workerBootstrap.ts +87 -0
  335. package/dist/api/index.js +0 -1553
  336. package/dist/contract.js +0 -5
  337. package/dist/index.js +0 -4492
  338. package/dist/types/index.js +0 -49
  339. package/dist/ui/index.css +0 -1876
  340. package/dist/ui/index.js +0 -22245
  341. package/dist/utils/index.js +0 -4296
  342. /package/{dist → src}/styles/ant-modal.css +0 -0
  343. /package/{dist → src}/styles/ant-spin.css +0 -0
  344. /package/{dist → src}/styles/ant-table.css +0 -0
  345. /package/{dist → src}/styles/index.css +0 -0
@@ -0,0 +1,730 @@
1
+ import React, { useState, useRef, useEffect, useCallback, useMemo } from 'react';
2
+ import { Button, Modal, Space, message, Popconfirm, Tag } from 'antd';
3
+ import { SaveOutlined, UploadOutlined, CloseOutlined, CloudOutlined, CloudServerOutlined, FileTextFilled, FileMarkdownOutlined, ImportOutlined } from '@ant-design/icons';
4
+ import { useCreateBlockNote } from '@blocknote/react';
5
+ import { BlockNoteView } from '@blocknote/mantine';
6
+ import { zh as zhLocale } from '@blocknote/core/locales';
7
+ import { BlockNoteSchema, defaultBlockSpecs, type Block } from '@blocknote/core';
8
+ import '@blocknote/core/fonts/inter.css';
9
+ import '@blocknote/mantine/style.css';
10
+ import './richdoc-editor.css';
11
+ import { saveFile, deleteFile } from '../IndexedDBUtils';
12
+ import { exportRDocPdf } from './exportRDocPdf';
13
+ import { exportRDocMarkdown } from './exportRDocMarkdown';
14
+ import { ExportDropdown } from '../ExportDropdown';
15
+ import { downloadBlob } from '../previewUtils';
16
+ import { CacheUserInfo } from '../../../utils/CacheUserInfo';
17
+
18
+ export interface RichDocEditorProps {
19
+ /** 文件内容:BlockNote 块数组的 JSON 字符串;空字符串表示新建文档 */
20
+ value?: string;
21
+ hiddenClose?: boolean;
22
+ visible?: boolean;
23
+ isCreate?: boolean; // 是否为新建模式
24
+ onClose?: () => void;
25
+ onSaveLocal?: (value: string) => void;
26
+ onChange?: (value: string) => void;
27
+ fileName?: string;
28
+ onUpload?: (oldValue: string, value: string, fileName: string) => Promise<void>;
29
+ readOnly?: boolean;
30
+ uploadPercent?: number;
31
+ uploadSuccess?: boolean | undefined; // 上传成功.
32
+ isOffline?: boolean;
33
+ isLocalOnly?: boolean;
34
+ hasUnsyncedChanges?: boolean;
35
+ onSaveLocalMarkdown?: (content: string, fileName: string, markUnsynced?: boolean) => Promise<void>;
36
+ }
37
+
38
+ /**
39
+ * 去掉 file 块:本编辑器不提供任意文件上传,斜杠菜单里的“文件”项也会随之消失。
40
+ * 老文档若含 file 块,解析前会被剔除(见 parseBlocks)。
41
+ */
42
+ const rdocBlockSpecs = Object.fromEntries(
43
+ Object.entries(defaultBlockSpecs).filter(([type]) => type !== 'file'),
44
+ ) as unknown as typeof defaultBlockSpecs;
45
+
46
+ const rdocSchema = BlockNoteSchema.create({ blockSpecs: rdocBlockSpecs });
47
+
48
+ /** 将 .rdoc 文件内容解析为 BlockNote 块;空/非法内容返回 undefined(空白文档) */
49
+ function parseBlocks(raw: string): Block[] | undefined {
50
+ if (!raw || !raw.trim()) return undefined;
51
+ try {
52
+ const parsed = JSON.parse(raw);
53
+ if (!Array.isArray(parsed)) return undefined;
54
+ const blocks = stripFileBlocks(parsed as Block[]);
55
+ return blocks.length > 0 ? blocks : undefined;
56
+ } catch {
57
+ return undefined;
58
+ }
59
+ }
60
+
61
+ /** 递归剔除已不支持的 file 块(直接传给编辑器会因未知块类型解析失败) */
62
+ function stripFileBlocks(blocks: Block[]): Block[] {
63
+ return blocks
64
+ .filter((block) => block.type !== 'file')
65
+ .map((block) =>
66
+ block.children && block.children.length > 0
67
+ ? { ...block, children: stripFileBlocks(block.children) }
68
+ : block,
69
+ );
70
+ }
71
+
72
+ /**
73
+ * 各媒体块的体积上限:媒体以 base64 内嵌在 .rdoc JSON 中,过大不便保存与上传。
74
+ * 图片 600KB、视频 2MB、音频 1MB。
75
+ */
76
+ const MEDIA_SIZE_LIMITS: Record<string, { limit: number; message: string }> = {
77
+ image: { limit: 600 * 1024, message: '图片大小不能超过 600KB,请压缩后再插入' },
78
+ video: { limit: 2 * 1024 * 1024, message: '视频大小不能超过 2MB,请压缩后再插入' },
79
+ audio: { limit: 1 * 1024 * 1024, message: '音频大小不能超过 1MB,请压缩后再插入' },
80
+ };
81
+
82
+ /**
83
+ * 媒体块的上传实现:转成 dataURL 内嵌进文档 JSON。
84
+ * 不配置 uploadFile 时 BlockNote 只显示"嵌入URL"标签,用户无法插入本地媒体,
85
+ * 因此必须提供该函数以启用"上传"标签。
86
+ */
87
+ function mediaToDataUrl(file: File, blockType?: string): Promise<string> {
88
+ const rule = blockType ? MEDIA_SIZE_LIMITS[blockType] : undefined;
89
+ if (rule && file.size > rule.limit) {
90
+ message.error(rule.message);
91
+ // 不能用 reject:BlockNote 的文件面板捕获异常后会进入 upload_failed 状态,
92
+ // 该面板之后选择合规文件也无法再上传。这里以空 URL resolve,
93
+ // 块保持"待添加文件"状态,用户可立即重新选择。
94
+ return Promise.resolve('');
95
+ }
96
+ return new Promise<string>((resolve, reject) => {
97
+ const reader = new FileReader();
98
+ reader.onload = () => resolve(String(reader.result));
99
+ reader.onerror = () => reject(reader.error ?? new Error('读取文件失败'));
100
+ reader.readAsDataURL(file);
101
+ });
102
+ }
103
+
104
+ type EditorInstance = ReturnType<typeof useCreateBlockNote>;
105
+
106
+ type ColorScheme = 'light' | 'dark';
107
+
108
+ /** 应用主题写在 <html> 上(class="dark" / data-theme),无全局 Context,这里按同样的规则读取 */
109
+ const getCurrentColorScheme = (): ColorScheme => {
110
+ if (typeof document === 'undefined') return 'light';
111
+ const root = document.documentElement;
112
+ return root.dataset.theme === 'dark' || root.classList.contains('dark') ? 'dark' : 'light';
113
+ };
114
+
115
+ export interface RichDocInlineEditorProps {
116
+ /** BlockNote 块数组的 JSON 字符串;空字符串表示空白文档 */
117
+ value?: string;
118
+ readOnly?: boolean;
119
+ /** 内容变化回调,返回块数组 JSON 字符串 */
120
+ onChange?: (value: string) => void;
121
+ className?: string;
122
+ }
123
+
124
+ /**
125
+ * 内嵌版块文档(.rdoc)编辑器:不包裹全屏 Modal,直接渲染编辑区,
126
+ * 供笔记等需要"左右分栏、右侧常驻编辑"的场景复用同一套 .rdoc 文档能力。
127
+ */
128
+ export const RichDocInlineEditor: React.FC<RichDocInlineEditorProps> = ({
129
+ value = '',
130
+ readOnly = false,
131
+ onChange,
132
+ className = '',
133
+ }) => {
134
+ const editorInstanceRef = useRef<EditorInstance | null>(null);
135
+ const [colorScheme, setColorScheme] = useState<ColorScheme>(getCurrentColorScheme);
136
+
137
+ // 跟随应用主题(与全屏版编辑器保持一致)
138
+ useEffect(() => {
139
+ const root = document.documentElement;
140
+ const observer = new MutationObserver(() => setColorScheme(getCurrentColorScheme()));
141
+ observer.observe(root, { attributes: true, attributeFilter: ['class', 'data-theme'] });
142
+ return () => observer.disconnect();
143
+ }, []);
144
+
145
+ // 仅在挂载时解析初始内容,后续内容由调用方通过重挂载(key)切换
146
+ const initialContent = useMemo(() => parseBlocks(value), []);
147
+ const markdownFallback = useMemo(() => (initialContent ? undefined : value), [initialContent, value]);
148
+
149
+ const handleDocChange = useCallback(
150
+ (blocks: Block[]) => onChange?.(JSON.stringify(blocks)),
151
+ [onChange],
152
+ );
153
+
154
+ return (
155
+ <div className={`richdoc-editor-host ${readOnly ? 'richdoc-readonly' : ''} ${className}`}>
156
+ <BlockNoteInner
157
+ initialContent={initialContent}
158
+ markdownFallback={markdownFallback}
159
+ readOnly={readOnly}
160
+ colorScheme={colorScheme}
161
+ onDocChange={handleDocChange}
162
+ editorInstanceRef={editorInstanceRef}
163
+ />
164
+ </div>
165
+ );
166
+ };
167
+
168
+
169
+ interface BlockNoteInnerProps {
170
+ initialContent?: Block[];
171
+ /** JSON 解析失败时的原始文本:尝试按 markdown 导入(兼容 .md 改名为 .rdoc 的场景) */
172
+ markdownFallback?: string;
173
+ readOnly: boolean;
174
+ colorScheme: ColorScheme;
175
+ onDocChange: (blocks: Block[]) => void;
176
+ /** 暴露编辑器实例,供外层触发 PDF 导出 */
177
+ editorInstanceRef: { current: EditorInstance | null };
178
+ }
179
+
180
+ /** 内部编辑器:每次 key 变化整体重建(对应打开不同文件),保证 initialContent 生效 */
181
+ const BlockNoteInner: React.FC<BlockNoteInnerProps> = ({ initialContent, markdownFallback, readOnly, colorScheme, onDocChange, editorInstanceRef }) => {
182
+ const editor = useCreateBlockNote({
183
+ schema: rdocSchema,
184
+ initialContent,
185
+ dictionary: zhLocale,
186
+ // 按块类型限制媒体体积:图片 600KB / 视频 2MB / 音频 1MB(挂载后 ref 已就绪,用户上传时必定可用)
187
+ uploadFile: (file: File, blockId?: string) => {
188
+ const blockType = blockId ? editorInstanceRef.current?.getBlock(blockId)?.type : undefined;
189
+ return mediaToDataUrl(file, blockType);
190
+ },
191
+ });
192
+
193
+ useEffect(() => {
194
+ editorInstanceRef.current = editor;
195
+ return () => {
196
+ editorInstanceRef.current = null;
197
+ };
198
+ }, [editor, editorInstanceRef]);
199
+
200
+ // JSON 非法且存在原始文本时,尝试按 Markdown 解析导入
201
+ useEffect(() => {
202
+ if (initialContent || !markdownFallback || !markdownFallback.trim()) return;
203
+ let cancelled = false;
204
+ (async () => {
205
+ try {
206
+ const blocks = await editor.tryParseMarkdownToBlocks(markdownFallback);
207
+ if (!cancelled && blocks.length > 0) {
208
+ editor.replaceBlocks(editor.document, blocks);
209
+ }
210
+ } catch (err) {
211
+ console.warn('[RichDocEditor] Markdown 导入失败,按空白文档处理:', err);
212
+ }
213
+ })();
214
+ return () => { cancelled = true; };
215
+ // 仅在挂载时执行一次
216
+ // eslint-disable-next-line react-hooks/exhaustive-deps
217
+ }, []);
218
+
219
+ return (
220
+ <BlockNoteView
221
+ editor={editor}
222
+ editable={!readOnly}
223
+ theme={colorScheme}
224
+ onChange={() => onDocChange(editor.document)}
225
+ />
226
+ );
227
+ };
228
+
229
+ const RichDocEditor: React.FC<RichDocEditorProps> = ({
230
+ hiddenClose = false,
231
+ value = '',
232
+ visible,
233
+ isCreate,
234
+ onClose,
235
+ onSaveLocal,
236
+ fileName = 'document.rdoc',
237
+ onUpload,
238
+ onChange,
239
+ readOnly = false,
240
+ uploadPercent,
241
+ isOffline,
242
+ isLocalOnly,
243
+ hasUnsyncedChanges,
244
+ onSaveLocalMarkdown,
245
+ }) => {
246
+ const [content, setContent] = useState(value);
247
+ const [userId, setUserId] = useState<string | null>(null);
248
+ const [editorKey, setEditorKey] = useState(0);
249
+ const [hasLocalCache, setHasLocalCache] = useState(false);
250
+ const [hasUploaded, setHasUploaded] = useState(!isCreate);
251
+ const [exportingPdf, setExportingPdf] = useState(false);
252
+ // 跟随应用主题(切换主题时编辑器需同步切换 BlockNote 的明暗配色)
253
+ const [colorScheme, setColorScheme] = useState<ColorScheme>(getCurrentColorScheme);
254
+ const editorInstanceRef = useRef<EditorInstance | null>(null);
255
+ const saveTimerRef = useRef<NodeJS.Timeout | null>(null);
256
+ // 跟踪"标记为未同步"是否已在本次编辑会话中执行过,避免每次 onChange 都写 IndexedDB
257
+ const markUnsyncedTriggeredRef = useRef(false);
258
+ const contentRef = useRef(value);
259
+ contentRef.current = content;
260
+
261
+ // 差分上传的基准内容(服务器最新文件内容),上传成功后更新。与 MarkdownEditor 行为对齐
262
+ const baseContentRef = useRef(value);
263
+
264
+ // 用 ref 跟踪"状态标记"属性,避免其变化触发编辑器相关回调重建导致画面闪动
265
+ const hasUnsyncedChangesRef = useRef(hasUnsyncedChanges);
266
+ const onSaveLocalMarkdownRef = useRef(onSaveLocalMarkdown);
267
+ hasUnsyncedChangesRef.current = hasUnsyncedChanges;
268
+ onSaveLocalMarkdownRef.current = onSaveLocalMarkdown;
269
+
270
+ // 监听 <html> 上的主题标记,用户切换明暗主题时同步更新编辑器配色
271
+ useEffect(() => {
272
+ const root = document.documentElement;
273
+ const observer = new MutationObserver(() => setColorScheme(getCurrentColorScheme()));
274
+ observer.observe(root, { attributes: true, attributeFilter: ['class', 'data-theme'] });
275
+ return () => observer.disconnect();
276
+ }, []);
277
+
278
+ // 获取当前登录用户
279
+ useEffect(() => {
280
+ if (visible) {
281
+ (async () => {
282
+ try {
283
+ const userInfo = CacheUserInfo.currentLocalUser();
284
+ setUserId(userInfo?.id || null);
285
+ } catch (error) {
286
+ console.error('获取用户信息失败:', error);
287
+ setUserId(null);
288
+ }
289
+ })();
290
+ }
291
+ }, [visible]);
292
+
293
+ // 加载文件内容(value 优先;新建/无缓存时直接用 value)
294
+ useEffect(() => {
295
+ if (visible && userId) {
296
+ if (value) {
297
+ setContent(value);
298
+ setEditorKey((prev) => prev + 1);
299
+ setHasLocalCache(false);
300
+ } else if (isCreate && fileName) {
301
+ setContent(value);
302
+ setHasLocalCache(false);
303
+ }
304
+ } else if (visible && fileName && !userId) {
305
+ setContent(value);
306
+ setHasLocalCache(false);
307
+ }
308
+ }, [visible, fileName, userId, value, isCreate, readOnly]);
309
+
310
+ // value 变化时重置差分基准
311
+ useEffect(() => {
312
+ baseContentRef.current = value;
313
+ }, [value]);
314
+
315
+ // 内容变化:防抖自动保存到 IndexedDB + 同步 localFiles store
316
+ const handleDocChange = useCallback((blocks: Block[]) => {
317
+ const newContent = JSON.stringify(blocks);
318
+ // 内容未实际变化时跳过(如编辑器初始化触发的 onChange)
319
+ if (newContent === contentRef.current) return;
320
+ setContent(newContent);
321
+ setHasUploaded(false);
322
+ onChange?.(newContent);
323
+
324
+ if (!fileName || !userId) return;
325
+
326
+ // 首次编辑时立即标记未同步
327
+ if (!markUnsyncedTriggeredRef.current && !hasUnsyncedChangesRef.current && onSaveLocalMarkdownRef.current) {
328
+ markUnsyncedTriggeredRef.current = true;
329
+ onSaveLocalMarkdownRef.current(newContent, fileName).catch(err => {
330
+ console.warn('编辑时立即标记未同步失败(不影响编辑,等待自动/手动保存即可):', err);
331
+ });
332
+ }
333
+
334
+ if (saveTimerRef.current) {
335
+ clearTimeout(saveTimerRef.current);
336
+ }
337
+
338
+ setHasLocalCache(true);
339
+
340
+ saveTimerRef.current = setTimeout(async () => {
341
+ try {
342
+ await saveFile(userId, fileName, 'rdoc', newContent);
343
+ setHasLocalCache(false);
344
+ if (onSaveLocalMarkdownRef.current) {
345
+ try {
346
+ await onSaveLocalMarkdownRef.current(newContent, fileName, false);
347
+ } catch (err) {
348
+ console.warn('自动保存时同步 localFiles store 失败(不影响本地保存):', err);
349
+ }
350
+ }
351
+ } catch (error) {
352
+ console.error('自动保存到 IndexedDB 失败:', error);
353
+ }
354
+ }, 20000);
355
+ }, [fileName, userId, onChange]);
356
+
357
+ // 保存到 IndexedDB
358
+ const handleSaveLocal = useCallback(async () => {
359
+ const newContent = contentRef.current;
360
+ if (onSaveLocal && !userId) {
361
+ onSaveLocal(newContent);
362
+ return;
363
+ }
364
+ try {
365
+ if (!userId) {
366
+ message.error('无法获取用户信息');
367
+ return;
368
+ }
369
+
370
+ await saveFile(userId, fileName, 'rdoc', newContent);
371
+ setHasLocalCache(false);
372
+
373
+ // 通知父组件更新 localFiles store
374
+ if (onSaveLocalMarkdownRef.current) {
375
+ try {
376
+ await onSaveLocalMarkdownRef.current(newContent, fileName);
377
+ } catch (err) {
378
+ console.warn('同步本地文件内容到 localFiles store 失败:', err);
379
+ }
380
+ }
381
+
382
+ message.success('已保存到本地缓存');
383
+ } catch (error) {
384
+ console.error('保存到本地缓存失败:', error);
385
+ message.error('保存失败');
386
+ }
387
+ }, [fileName, userId, onSaveLocal]);
388
+
389
+ // 上传到服务器
390
+ const handleUpload = useCallback(async () => {
391
+ if (!onUpload) {
392
+ message.error('未提供上传功能');
393
+ return;
394
+ }
395
+ try {
396
+ const newContent = contentRef.current;
397
+ const oldContent = baseContentRef.current;
398
+ await onUpload(oldContent, newContent, fileName);
399
+
400
+ // 上传成功后更新差分基准
401
+ baseContentRef.current = newContent;
402
+
403
+ // 清空本地缓存
404
+ if (userId) {
405
+ await deleteFile(userId, fileName, 'rdoc');
406
+ setHasLocalCache(false);
407
+ setHasUploaded(true);
408
+ }
409
+ markUnsyncedTriggeredRef.current = false;
410
+ } catch (error) {
411
+ console.error('上传失败:', error);
412
+ message.error('上传失败');
413
+ }
414
+ }, [fileName, onUpload, userId]);
415
+
416
+ // 导出为 PDF(块文档独立导出实现,使用块文档自身排版样式)
417
+ const handleExportPdf = useCallback(async () => {
418
+ const editor = editorInstanceRef.current;
419
+ if (!editor) {
420
+ message.warning('编辑器尚未就绪,请稍后再试');
421
+ return;
422
+ }
423
+ const editorElement = editor.domElement;
424
+ if (!editorElement || editorElement.querySelectorAll('.bn-block-outer').length === 0) {
425
+ message.warning('内容为空,无法导出 PDF');
426
+ return;
427
+ }
428
+
429
+ setExportingPdf(true);
430
+ message.loading({ key: 'rdoc-export-pdf', content: '正在生成 PDF...', duration: 0 });
431
+ try {
432
+ await exportRDocPdf({
433
+ editorElement,
434
+ fileName,
435
+ onProgress: (page, total) =>
436
+ message.loading({
437
+ key: 'rdoc-export-pdf',
438
+ content: `正在生成 PDF(${page}/${total} 页)...`,
439
+ duration: 0,
440
+ }),
441
+ });
442
+ message.success({ key: 'rdoc-export-pdf', content: 'PDF 已导出' });
443
+ } catch (error) {
444
+ console.error('导出 PDF 失败:', error);
445
+ message.error({
446
+ key: 'rdoc-export-pdf',
447
+ content: (error as Error)?.message || '导出 PDF 失败',
448
+ });
449
+ } finally {
450
+ setExportingPdf(false);
451
+ }
452
+ }, [fileName]);
453
+
454
+ // 导出为 Markdown(图片不参与导出)
455
+ const handleExportMarkdown = useCallback(() => {
456
+ const editor = editorInstanceRef.current;
457
+ if (!editor) {
458
+ message.warning('编辑器尚未就绪,请稍后再试');
459
+ return;
460
+ }
461
+ if (editor.document.length === 0) {
462
+ message.warning('内容为空,无法导出 Markdown');
463
+ return;
464
+ }
465
+
466
+ try {
467
+ const { skippedImages } = exportRDocMarkdown(editor, editor.document, fileName);
468
+ message.success(
469
+ skippedImages > 0 ? `Markdown 已导出(已跳过 ${skippedImages} 张图片)` : 'Markdown 已导出',
470
+ );
471
+ } catch (error) {
472
+ console.error('导出 Markdown 失败:', error);
473
+ message.error((error as Error)?.message || '导出 Markdown 失败');
474
+ }
475
+ }, [fileName]);
476
+
477
+ // 下载原始文件:.rdoc 文件内容即块 JSON 文本
478
+ const handleDownload = useCallback(() => {
479
+ const downloadContent = contentRef.current;
480
+ if (!downloadContent || !downloadContent.trim()) {
481
+ message.warning('内容为空,无法下载');
482
+ return;
483
+ }
484
+ downloadBlob(new Blob([downloadContent], { type: 'application/json;charset=utf-8' }), fileName);
485
+ }, [fileName]);
486
+ const onDownload = !isCreate || content.trim() ? handleDownload : undefined;
487
+
488
+ // ============================================================================
489
+ // 导入 Markdown:选 .md 文件 → BlockNote 的 tryParseMarkdownToBlocks 解析成块,
490
+ // 再整体替换当前文档(该操作会触发 onChange,进而走正常的保存/上传流程)。
491
+ // ============================================================================
492
+ const importInputRef = useRef<HTMLInputElement | null>(null);
493
+ const [importingMarkdown, setImportingMarkdown] = useState(false);
494
+
495
+ const runImportMarkdown = useCallback(async (file: File) => {
496
+ const editor = editorInstanceRef.current;
497
+ if (!editor) {
498
+ message.warning('编辑器尚未就绪,请稍后再试');
499
+ return;
500
+ }
501
+ setImportingMarkdown(true);
502
+ message.loading({ key: 'rdoc-import-md', content: '正在解析 Markdown…', duration: 0 });
503
+ try {
504
+ const text = await file.text();
505
+ if (!text.trim()) {
506
+ message.warning('文件内容为空');
507
+ return;
508
+ }
509
+ const blocks = await editor.tryParseMarkdownToBlocks(text);
510
+ if (!blocks || blocks.length === 0) {
511
+ message.warning('未能从文件中解析出内容');
512
+ return;
513
+ }
514
+ // 保留当前选区信息不重要,整体替换即可;replaceBlocks 会触发 onChange
515
+ editor.replaceBlocks(editor.document, blocks);
516
+ message.success(`已导入 Markdown,共 ${blocks.length} 个内容块`);
517
+ } catch (error) {
518
+ console.error('[RichDocEditor] 导入 Markdown 失败:', error);
519
+ message.error(`导入 Markdown 失败:${(error as Error)?.message || '未知错误'}`);
520
+ } finally {
521
+ message.destroy('rdoc-import-md');
522
+ setImportingMarkdown(false);
523
+ }
524
+ }, []);
525
+
526
+ const handleImportMarkdown = useCallback(() => {
527
+ const editor = editorInstanceRef.current;
528
+ if (!editor) {
529
+ message.warning('编辑器尚未就绪,请稍后再试');
530
+ return;
531
+ }
532
+ importInputRef.current?.click();
533
+ }, []);
534
+
535
+ const handleImportFileChange = useCallback(
536
+ (e: React.ChangeEvent<HTMLInputElement>) => {
537
+ const file = e.target.files?.[0];
538
+ // 允许重复选择同一个文件
539
+ e.target.value = '';
540
+ if (!file) return;
541
+
542
+ const editor = editorInstanceRef.current;
543
+ const hasContent =
544
+ !!editor &&
545
+ (editor.document.length > 1 ||
546
+ (editor.document.length === 1 &&
547
+ Array.isArray(editor.document[0].content) &&
548
+ editor.document[0].content.length > 0));
549
+ if (!hasContent) {
550
+ void runImportMarkdown(file);
551
+ return;
552
+ }
553
+ Modal.confirm({
554
+ title: '导入会覆盖当前内容',
555
+ content: '当前文档已有内容,导入 Markdown 后将整体替换,且无法撤销。是否继续?',
556
+ okText: '继续导入',
557
+ cancelText: '取消',
558
+ okButtonProps: { danger: true },
559
+ onOk: () => runImportMarkdown(file),
560
+ });
561
+ },
562
+ [runImportMarkdown],
563
+ );
564
+
565
+ // 「导出/导入Markdown」作为额外菜单项,与统一的导出下拉合并
566
+ // (顺序:导出PDF、导出Markdown、导入Markdown、下载)
567
+ const exportMenuItems = useMemo(
568
+ () => [
569
+ { key: 'markdown', icon: <FileMarkdownOutlined />, label: '导出Markdown', onClick: handleExportMarkdown },
570
+ ...(readOnly
571
+ ? []
572
+ : [
573
+ {
574
+ key: 'import-markdown',
575
+ icon: <ImportOutlined />,
576
+ label: '导入Markdown',
577
+ disabled: importingMarkdown,
578
+ onClick: handleImportMarkdown,
579
+ },
580
+ ]),
581
+ ],
582
+ [handleExportMarkdown, handleImportMarkdown, importingMarkdown, readOnly],
583
+ );
584
+
585
+ // Ctrl+S / Cmd+S 保存到本地缓存
586
+ useEffect(() => {
587
+ if (!visible) return;
588
+ const handleKeyDown = (e: KeyboardEvent) => {
589
+ if ((e.ctrlKey || e.metaKey) && e.key === 's') {
590
+ e.preventDefault();
591
+ handleSaveLocal();
592
+ }
593
+ };
594
+ window.addEventListener('keydown', handleKeyDown);
595
+ return () => window.removeEventListener('keydown', handleKeyDown);
596
+ }, [visible, handleSaveLocal]);
597
+
598
+ // 重置状态和清理定时器(编辑器打开/关闭时)
599
+ useEffect(() => {
600
+ if (visible) {
601
+ setHasUploaded(!isCreate);
602
+ markUnsyncedTriggeredRef.current = false;
603
+ if (saveTimerRef.current) {
604
+ clearTimeout(saveTimerRef.current);
605
+ saveTimerRef.current = null;
606
+ }
607
+ }
608
+ return () => {
609
+ if (saveTimerRef.current) {
610
+ clearTimeout(saveTimerRef.current);
611
+ saveTimerRef.current = null;
612
+ }
613
+ };
614
+ }, [visible, isCreate]);
615
+
616
+ // 编辑器初始内容:优先 JSON 块;失败则走 markdown 导入兜底
617
+ const initialContent = useMemo(() => parseBlocks(content), [editorKey]); // eslint-disable-line react-hooks/exhaustive-deps
618
+ const markdownFallback = useMemo(
619
+ () => (initialContent ? undefined : content),
620
+ [initialContent, content],
621
+ );
622
+
623
+ return (
624
+ <Modal
625
+ open={visible}
626
+ onCancel={onClose}
627
+ keyboard={false}
628
+ closeIcon={null}
629
+ footer={null}
630
+ width="100%"
631
+ style={{ top: 0, maxWidth: '100vw' }}
632
+ styles={{
633
+ body: {
634
+ height: '100dvh',
635
+ padding: 0,
636
+ },
637
+ }}
638
+ className="ant-modal-nopadding ant-modal-fullscreen relative"
639
+ transitionName='none'
640
+ >
641
+ <div className="h-full flex flex-col bg-white p-0 m-0">
642
+ <div className={`flex items-center justify-between flex-wrap px-4 py-2 pr-0 flex-shrink-0 ${isOffline ? 'bg-orange-50 border-b border-orange-200' : 'bg-gray-50 border-b border-gray-200'}`}>
643
+ <span>&nbsp;</span>
644
+ {!readOnly && <span>&nbsp;</span>}
645
+ <div className="flex items-center gap-2 flex-wrap min-w-[40%]">
646
+ {!isOffline && isLocalOnly && (
647
+ <Tag icon={<CloudOutlined />} color="orange" style={{ margin: 0 }}>
648
+ 本地文件
649
+ </Tag>
650
+ )}
651
+ {!isOffline && !isLocalOnly && hasUnsyncedChanges && (
652
+ <Tag icon={<CloudOutlined />} color="warning" style={{ margin: 0 }}>
653
+ 有未同步修改
654
+ </Tag>
655
+ )}
656
+ {!isOffline && !isLocalOnly && !hasUnsyncedChanges && !onUpload ? (
657
+ <Tag icon={<CloudServerOutlined />} color="default" style={{ margin: 0 }}>
658
+ 同步完成
659
+ </Tag>
660
+ ) : null}
661
+ <FileTextFilled className="text-yellow-600" />
662
+ <span className="text-sm font-medium">{hasLocalCache ? '*' : ''}{fileName}</span>
663
+ </div>
664
+ {!readOnly && !hiddenClose && (<Space wrap={true} align="center" direction="horizontal">
665
+ <ExportDropdown
666
+ onExportPdf={handleExportPdf}
667
+ onDownload={onDownload}
668
+ exportingPdf={exportingPdf}
669
+ extraItems={exportMenuItems}
670
+ buttonProps={{ style: { paddingLeft: 2, paddingRight: 2 } }}
671
+ />
672
+ {onUpload && (
673
+ <Button type="text" style={{ paddingLeft: 4, paddingRight: 4 }} title="每次上传将保留一个历史版本,最多保留10个版本" icon={<UploadOutlined />} onClick={handleUpload} disabled={uploadPercent !== undefined && uploadPercent < 100}>
674
+ {uploadPercent !== undefined && uploadPercent < 100 ? `上传中 ${Math.round(uploadPercent * 100)}%` : '上传'}
675
+ </Button>
676
+ )}
677
+ <Button type="primary" icon={<SaveOutlined />} onClick={handleSaveLocal}>
678
+ 保存
679
+ </Button>
680
+ {(hasLocalCache || !hasUploaded) ?
681
+ (<Popconfirm
682
+ title="确定要退出吗?"
683
+ description={hasLocalCache ? "修改未保存,确定要退出吗?" : "修改未上传,确定要退出吗?"}
684
+ onConfirm={onClose}
685
+ okText="确定"
686
+ cancelText="取消"
687
+ >
688
+ <Button type="text" className="ml-3 mr-2 text-gray-600 hover:text-gray-900" icon={<CloseOutlined className="w-6 h-6" />} />
689
+ </Popconfirm>) : (<CloseOutlined onClick={onClose} className='ml-3 mr-2 w-6 h-6' />)}
690
+ </Space>)}
691
+ {readOnly && !hiddenClose && (
692
+ <Space className='ml-3 mr-2'>
693
+ <ExportDropdown
694
+ onExportPdf={handleExportPdf}
695
+ onDownload={onDownload}
696
+ exportingPdf={exportingPdf}
697
+ extraItems={exportMenuItems}
698
+ buttonProps={{ style: { } }}
699
+ />
700
+ <CloseOutlined onClick={onClose} className='w-6 h-6' />
701
+ </Space>)}
702
+ {hiddenClose && <span>&nbsp;</span>}
703
+ {/* 「导入Markdown」用的隐藏文件选择器 */}
704
+ <input
705
+ ref={importInputRef}
706
+ type="file"
707
+ accept=".md,.markdown,.txt,text/markdown,text/plain"
708
+ style={{ display: 'none' }}
709
+ onChange={handleImportFileChange}
710
+ />
711
+ </div>
712
+
713
+ <div className={`flex-1 richdoc-editor-host ${readOnly ? 'richdoc-readonly' : ''}`}>
714
+ <BlockNoteInner
715
+ key={editorKey}
716
+ initialContent={initialContent}
717
+ markdownFallback={markdownFallback}
718
+ readOnly={readOnly}
719
+ colorScheme={colorScheme}
720
+ onDocChange={handleDocChange}
721
+ editorInstanceRef={editorInstanceRef}
722
+ />
723
+ </div>
724
+ </div>
725
+ </Modal>
726
+ );
727
+ };
728
+
729
+ export default RichDocEditor;
730
+ export { RichDocEditor };