@dou-ui/editor 0.0.0-stage → 0.1.0-alpha.1

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 (324) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +10 -2
  3. package/dist/catalog.js +18 -0
  4. package/dist/catalog.js.map +1 -0
  5. package/dist/code/code-editor-block.js +6 -0
  6. package/dist/code/code-editor-block.js.map +1 -0
  7. package/dist/code/code-editor-block.vue_vue_type_script_setup_true_lang.js +116 -0
  8. package/dist/code/code-editor-block.vue_vue_type_script_setup_true_lang.js.map +1 -0
  9. package/dist/code/index.js +69 -0
  10. package/dist/code/index.js.map +1 -0
  11. package/dist/code/languages.js +51 -0
  12. package/dist/code/languages.js.map +1 -0
  13. package/dist/code/view.js +255 -0
  14. package/dist/code/view.js.map +1 -0
  15. package/dist/collaboration/binding.js +381 -0
  16. package/dist/collaboration/binding.js.map +1 -0
  17. package/dist/collaboration/cursors.js +150 -0
  18. package/dist/collaboration/cursors.js.map +1 -0
  19. package/dist/collaboration/index.js +108 -0
  20. package/dist/collaboration/index.js.map +1 -0
  21. package/dist/collaboration/order.js +88 -0
  22. package/dist/collaboration/order.js.map +1 -0
  23. package/dist/collaboration/table.js +234 -0
  24. package/dist/collaboration/table.js.map +1 -0
  25. package/dist/collaboration/text.js +91 -0
  26. package/dist/collaboration/text.js.map +1 -0
  27. package/dist/components/block-handle.js +6 -0
  28. package/dist/components/block-handle.js.map +1 -0
  29. package/dist/components/block-handle.vue_vue_type_script_setup_true_lang.js +474 -0
  30. package/dist/components/block-handle.vue_vue_type_script_setup_true_lang.js.map +1 -0
  31. package/dist/components/bubble-toolbar.js +6 -0
  32. package/dist/components/bubble-toolbar.js.map +1 -0
  33. package/dist/components/bubble-toolbar.vue_vue_type_script_setup_true_lang.js +381 -0
  34. package/dist/components/bubble-toolbar.vue_vue_type_script_setup_true_lang.js.map +1 -0
  35. package/dist/components/comment-button.js +6 -0
  36. package/dist/components/comment-button.js.map +1 -0
  37. package/dist/components/comment-button.vue_vue_type_script_setup_true_lang.js +83 -0
  38. package/dist/components/comment-button.vue_vue_type_script_setup_true_lang.js.map +1 -0
  39. package/dist/components/comment-card.js +6 -0
  40. package/dist/components/comment-card.js.map +1 -0
  41. package/dist/components/comment-card.vue_vue_type_script_setup_true_lang.js +104 -0
  42. package/dist/components/comment-card.vue_vue_type_script_setup_true_lang.js.map +1 -0
  43. package/dist/components/menu-escape.js +25 -0
  44. package/dist/components/menu-escape.js.map +1 -0
  45. package/dist/components/search-bar.js +6 -0
  46. package/dist/components/search-bar.js.map +1 -0
  47. package/dist/components/search-bar.vue_vue_type_script_setup_true_lang.js +192 -0
  48. package/dist/components/search-bar.vue_vue_type_script_setup_true_lang.js.map +1 -0
  49. package/dist/components/slash-menu.js +6 -0
  50. package/dist/components/slash-menu.js.map +1 -0
  51. package/dist/components/slash-menu.vue_vue_type_script_setup_true_lang.js +164 -0
  52. package/dist/components/slash-menu.vue_vue_type_script_setup_true_lang.js.map +1 -0
  53. package/dist/core/align.js +94 -0
  54. package/dist/core/align.js.map +1 -0
  55. package/dist/core/block-selection.js +155 -0
  56. package/dist/core/block-selection.js.map +1 -0
  57. package/dist/core/blocks.js +132 -0
  58. package/dist/core/blocks.js.map +1 -0
  59. package/dist/core/changes.js +85 -0
  60. package/dist/core/changes.js.map +1 -0
  61. package/dist/core/colors.js +16 -0
  62. package/dist/core/colors.js.map +1 -0
  63. package/dist/core/commands.js +379 -0
  64. package/dist/core/commands.js.map +1 -0
  65. package/dist/core/comments.js +186 -0
  66. package/dist/core/comments.js.map +1 -0
  67. package/dist/core/compare.js +106 -0
  68. package/dist/core/compare.js.map +1 -0
  69. package/dist/core/convert.js +28 -0
  70. package/dist/core/convert.js.map +1 -0
  71. package/dist/core/diff.js +141 -0
  72. package/dist/core/diff.js.map +1 -0
  73. package/dist/core/editor.js +138 -0
  74. package/dist/core/editor.js.map +1 -0
  75. package/dist/core/extension.js +99 -0
  76. package/dist/core/extension.js.map +1 -0
  77. package/dist/core/file-drop.js +56 -0
  78. package/dist/core/file-drop.js.map +1 -0
  79. package/dist/core/highlight.js +120 -0
  80. package/dist/core/highlight.js.map +1 -0
  81. package/dist/core/input-rules.js +61 -0
  82. package/dist/core/input-rules.js.map +1 -0
  83. package/dist/core/keymap.js +38 -0
  84. package/dist/core/keymap.js.map +1 -0
  85. package/dist/core/model.js +196 -0
  86. package/dist/core/model.js.map +1 -0
  87. package/dist/core/output.js +263 -0
  88. package/dist/core/output.js.map +1 -0
  89. package/dist/core/plugins.js +102 -0
  90. package/dist/core/plugins.js.map +1 -0
  91. package/dist/core/prepare.js +102 -0
  92. package/dist/core/prepare.js.map +1 -0
  93. package/dist/core/schema.js +158 -0
  94. package/dist/core/schema.js.map +1 -0
  95. package/dist/core/search.js +197 -0
  96. package/dist/core/search.js.map +1 -0
  97. package/dist/core/selection.js +16 -0
  98. package/dist/core/selection.js.map +1 -0
  99. package/dist/core/slash.js +83 -0
  100. package/dist/core/slash.js.map +1 -0
  101. package/dist/core/transaction.js +311 -0
  102. package/dist/core/transaction.js.map +1 -0
  103. package/dist/define-block.js +177 -0
  104. package/dist/define-block.js.map +1 -0
  105. package/dist/document.js +157 -0
  106. package/dist/document.js.map +1 -0
  107. package/dist/dom.js +48 -0
  108. package/dist/dom.js.map +1 -0
  109. package/dist/editor.js +33 -0
  110. package/dist/editor.js.map +1 -0
  111. package/dist/editor.vue_vue_type_script_setup_true_lang.js +564 -0
  112. package/dist/editor.vue_vue_type_script_setup_true_lang.js.map +1 -0
  113. package/dist/editor2.js +6 -0
  114. package/dist/editor2.js.map +1 -0
  115. package/dist/extensions/align/align-menu.js +6 -0
  116. package/dist/extensions/align/align-menu.js.map +1 -0
  117. package/dist/extensions/align/align-menu.vue_vue_type_script_setup_true_lang.js +82 -0
  118. package/dist/extensions/align/align-menu.vue_vue_type_script_setup_true_lang.js.map +1 -0
  119. package/dist/extensions/align/align-sub.js +6 -0
  120. package/dist/extensions/align/align-sub.js.map +1 -0
  121. package/dist/extensions/align/align-sub.vue_vue_type_script_setup_true_lang.js +57 -0
  122. package/dist/extensions/align/align-sub.vue_vue_type_script_setup_true_lang.js.map +1 -0
  123. package/dist/extensions/align/commands.js +40 -0
  124. package/dist/extensions/align/commands.js.map +1 -0
  125. package/dist/extensions/align/index.js +27 -0
  126. package/dist/extensions/align/index.js.map +1 -0
  127. package/dist/extensions/attachment/attachment-block.js +6 -0
  128. package/dist/extensions/attachment/attachment-block.js.map +1 -0
  129. package/dist/extensions/attachment/attachment-block.vue_vue_type_script_setup_true_lang.js +119 -0
  130. package/dist/extensions/attachment/attachment-block.vue_vue_type_script_setup_true_lang.js.map +1 -0
  131. package/dist/extensions/attachment/files.js +66 -0
  132. package/dist/extensions/attachment/files.js.map +1 -0
  133. package/dist/extensions/attachment/index.js +132 -0
  134. package/dist/extensions/attachment/index.js.map +1 -0
  135. package/dist/extensions/blocks.js +170 -0
  136. package/dist/extensions/blocks.js.map +1 -0
  137. package/dist/extensions/callout/callout-block.js +6 -0
  138. package/dist/extensions/callout/callout-block.js.map +1 -0
  139. package/dist/extensions/callout/callout-block.vue_vue_type_script_setup_true_lang.js +71 -0
  140. package/dist/extensions/callout/callout-block.vue_vue_type_script_setup_true_lang.js.map +1 -0
  141. package/dist/extensions/callout/html.js +61 -0
  142. package/dist/extensions/callout/html.js.map +1 -0
  143. package/dist/extensions/callout/index.js +52 -0
  144. package/dist/extensions/callout/index.js.map +1 -0
  145. package/dist/extensions/callout/tones.js +12 -0
  146. package/dist/extensions/callout/tones.js.map +1 -0
  147. package/dist/extensions/code-block/code-block-view.js +6 -0
  148. package/dist/extensions/code-block/code-block-view.js.map +1 -0
  149. package/dist/extensions/code-block/code-block-view.vue_vue_type_script_setup_true_lang.js +87 -0
  150. package/dist/extensions/code-block/code-block-view.vue_vue_type_script_setup_true_lang.js.map +1 -0
  151. package/dist/extensions/code-block/index.js +98 -0
  152. package/dist/extensions/code-block/index.js.map +1 -0
  153. package/dist/extensions/code-block/languages.js +116 -0
  154. package/dist/extensions/code-block/languages.js.map +1 -0
  155. package/dist/extensions/colors/color-menu.js +6 -0
  156. package/dist/extensions/colors/color-menu.js.map +1 -0
  157. package/dist/extensions/colors/color-menu.vue_vue_type_script_setup_true_lang.js +113 -0
  158. package/dist/extensions/colors/color-menu.vue_vue_type_script_setup_true_lang.js.map +1 -0
  159. package/dist/extensions/colors/index.js +49 -0
  160. package/dist/extensions/colors/index.js.map +1 -0
  161. package/dist/extensions/colors/palette.js +26 -0
  162. package/dist/extensions/colors/palette.js.map +1 -0
  163. package/dist/extensions/editor-kit.js +35 -0
  164. package/dist/extensions/editor-kit.js.map +1 -0
  165. package/dist/extensions/embed/embed-block.js +6 -0
  166. package/dist/extensions/embed/embed-block.js.map +1 -0
  167. package/dist/extensions/embed/embed-block.vue_vue_type_script_setup_true_lang.js +210 -0
  168. package/dist/extensions/embed/embed-block.vue_vue_type_script_setup_true_lang.js.map +1 -0
  169. package/dist/extensions/embed/index.js +118 -0
  170. package/dist/extensions/embed/index.js.map +1 -0
  171. package/dist/extensions/embed/targets.js +79 -0
  172. package/dist/extensions/embed/targets.js.map +1 -0
  173. package/dist/extensions/formatting.js +153 -0
  174. package/dist/extensions/formatting.js.map +1 -0
  175. package/dist/extensions/image/image-block.js +6 -0
  176. package/dist/extensions/image/image-block.js.map +1 -0
  177. package/dist/extensions/image/image-block.vue_vue_type_script_setup_true_lang.js +206 -0
  178. package/dist/extensions/image/image-block.vue_vue_type_script_setup_true_lang.js.map +1 -0
  179. package/dist/extensions/image/index.js +125 -0
  180. package/dist/extensions/image/index.js.map +1 -0
  181. package/dist/extensions/image/uploads.js +96 -0
  182. package/dist/extensions/image/uploads.js.map +1 -0
  183. package/dist/extensions/list-html.js +58 -0
  184. package/dist/extensions/list-html.js.map +1 -0
  185. package/dist/extensions/lists.js +182 -0
  186. package/dist/extensions/lists.js.map +1 -0
  187. package/dist/extensions/mention/index.js +56 -0
  188. package/dist/extensions/mention/index.js.map +1 -0
  189. package/dist/extensions/mention/mention-menu.js +6 -0
  190. package/dist/extensions/mention/mention-menu.js.map +1 -0
  191. package/dist/extensions/mention/mention-menu.vue_vue_type_script_setup_true_lang.js +132 -0
  192. package/dist/extensions/mention/mention-menu.vue_vue_type_script_setup_true_lang.js.map +1 -0
  193. package/dist/extensions/mention/state.js +113 -0
  194. package/dist/extensions/mention/state.js.map +1 -0
  195. package/dist/extensions/table/commands.js +356 -0
  196. package/dist/extensions/table/commands.js.map +1 -0
  197. package/dist/extensions/table/html.js +134 -0
  198. package/dist/extensions/table/html.js.map +1 -0
  199. package/dist/extensions/table/index.js +56 -0
  200. package/dist/extensions/table/index.js.map +1 -0
  201. package/dist/extensions/table/plugin.js +81 -0
  202. package/dist/extensions/table/plugin.js.map +1 -0
  203. package/dist/extensions/table/schema.js +258 -0
  204. package/dist/extensions/table/schema.js.map +1 -0
  205. package/dist/extensions/table/table-controls.js +6 -0
  206. package/dist/extensions/table/table-controls.js.map +1 -0
  207. package/dist/extensions/table/table-controls.vue_vue_type_script_setup_true_lang.js +698 -0
  208. package/dist/extensions/table/table-controls.vue_vue_type_script_setup_true_lang.js.map +1 -0
  209. package/dist/extensions/table/view.js +19 -0
  210. package/dist/extensions/table/view.js.map +1 -0
  211. package/dist/extensions/toggle/index.js +87 -0
  212. package/dist/extensions/toggle/index.js.map +1 -0
  213. package/dist/extensions/toggle/state.js +120 -0
  214. package/dist/extensions/toggle/state.js.map +1 -0
  215. package/dist/extensions/toggle/toggle-block.js +6 -0
  216. package/dist/extensions/toggle/toggle-block.js.map +1 -0
  217. package/dist/extensions/toggle/toggle-block.vue_vue_type_script_setup_true_lang.js +42 -0
  218. package/dist/extensions/toggle/toggle-block.vue_vue_type_script_setup_true_lang.js.map +1 -0
  219. package/dist/index.js +49 -0
  220. package/dist/index.js.map +1 -0
  221. package/dist/locale.js +522 -0
  222. package/dist/locale.js.map +1 -0
  223. package/dist/markdown/index.js +24 -0
  224. package/dist/markdown/index.js.map +1 -0
  225. package/dist/style.css +1 -0
  226. package/dist/style.d.ts +1 -0
  227. package/dist/style.js +8 -0
  228. package/dist/types/catalog.d.ts +8 -0
  229. package/dist/types/code/code-editor-block.vue.d.ts +17 -0
  230. package/dist/types/code/index.d.ts +18 -0
  231. package/dist/types/code/languages.d.ts +7 -0
  232. package/dist/types/code/types.d.ts +37 -0
  233. package/dist/types/code/view.d.ts +12 -0
  234. package/dist/types/collaboration/binding.d.ts +32 -0
  235. package/dist/types/collaboration/cursors.d.ts +26 -0
  236. package/dist/types/collaboration/index.d.ts +47 -0
  237. package/dist/types/collaboration/order.d.ts +20 -0
  238. package/dist/types/collaboration/table.d.ts +25 -0
  239. package/dist/types/collaboration/text.d.ts +9 -0
  240. package/dist/types/components/block-handle.vue.d.ts +3 -0
  241. package/dist/types/components/bubble-toolbar.vue.d.ts +7 -0
  242. package/dist/types/components/comment-button.vue.d.ts +3 -0
  243. package/dist/types/components/comment-card.vue.d.ts +3 -0
  244. package/dist/types/components/menu-escape.d.ts +7 -0
  245. package/dist/types/components/search-bar.vue.d.ts +22 -0
  246. package/dist/types/components/slash-menu.vue.d.ts +11 -0
  247. package/dist/types/core/align.d.ts +16 -0
  248. package/dist/types/core/block-selection.d.ts +22 -0
  249. package/dist/types/core/blocks.d.ts +52 -0
  250. package/dist/types/core/changes.d.ts +14 -0
  251. package/dist/types/core/colors.d.ts +4 -0
  252. package/dist/types/core/commands.d.ts +61 -0
  253. package/dist/types/core/comments.d.ts +44 -0
  254. package/dist/types/core/compare.d.ts +10 -0
  255. package/dist/types/core/convert.d.ts +12 -0
  256. package/dist/types/core/diff.d.ts +49 -0
  257. package/dist/types/core/editor.d.ts +64 -0
  258. package/dist/types/core/extension.d.ts +167 -0
  259. package/dist/types/core/file-drop.d.ts +19 -0
  260. package/dist/types/core/highlight.d.ts +32 -0
  261. package/dist/types/core/input-rules.d.ts +21 -0
  262. package/dist/types/core/keymap.d.ts +8 -0
  263. package/dist/types/core/model.d.ts +158 -0
  264. package/dist/types/core/output.d.ts +64 -0
  265. package/dist/types/core/plugins.d.ts +24 -0
  266. package/dist/types/core/prepare.d.ts +23 -0
  267. package/dist/types/core/schema.d.ts +38 -0
  268. package/dist/types/core/search.d.ts +83 -0
  269. package/dist/types/core/selection.d.ts +8 -0
  270. package/dist/types/core/slash.d.ts +21 -0
  271. package/dist/types/core/transaction.d.ts +97 -0
  272. package/dist/types/define-block.d.ts +66 -0
  273. package/dist/types/document.d.ts +46 -0
  274. package/dist/types/dom.d.ts +17 -0
  275. package/dist/types/editor.d.ts +252 -0
  276. package/dist/types/editor.vue.d.ts +210 -0
  277. package/dist/types/extensions/align/align-menu.vue.d.ts +3 -0
  278. package/dist/types/extensions/align/align-sub.vue.d.ts +7 -0
  279. package/dist/types/extensions/align/commands.d.ts +13 -0
  280. package/dist/types/extensions/align/index.d.ts +7 -0
  281. package/dist/types/extensions/attachment/attachment-block.vue.d.ts +17 -0
  282. package/dist/types/extensions/attachment/files.d.ts +6 -0
  283. package/dist/types/extensions/attachment/index.d.ts +20 -0
  284. package/dist/types/extensions/blocks.d.ts +8 -0
  285. package/dist/types/extensions/callout/callout-block.vue.d.ts +14 -0
  286. package/dist/types/extensions/callout/html.d.ts +4 -0
  287. package/dist/types/extensions/callout/index.d.ts +3 -0
  288. package/dist/types/extensions/callout/tones.d.ts +8 -0
  289. package/dist/types/extensions/code-block/code-block-view.vue.d.ts +16 -0
  290. package/dist/types/extensions/code-block/index.d.ts +19 -0
  291. package/dist/types/extensions/code-block/languages.d.ts +9 -0
  292. package/dist/types/extensions/colors/color-menu.vue.d.ts +3 -0
  293. package/dist/types/extensions/colors/index.d.ts +7 -0
  294. package/dist/types/extensions/colors/palette.d.ts +7 -0
  295. package/dist/types/extensions/editor-kit.d.ts +37 -0
  296. package/dist/types/extensions/embed/embed-block.vue.d.ts +16 -0
  297. package/dist/types/extensions/embed/index.d.ts +18 -0
  298. package/dist/types/extensions/embed/targets.d.ts +32 -0
  299. package/dist/types/extensions/formatting.d.ts +8 -0
  300. package/dist/types/extensions/image/image-block.vue.d.ts +16 -0
  301. package/dist/types/extensions/image/index.d.ts +16 -0
  302. package/dist/types/extensions/image/uploads.d.ts +35 -0
  303. package/dist/types/extensions/list-html.d.ts +11 -0
  304. package/dist/types/extensions/lists.d.ts +6 -0
  305. package/dist/types/extensions/mention/index.d.ts +11 -0
  306. package/dist/types/extensions/mention/mention-menu.vue.d.ts +7 -0
  307. package/dist/types/extensions/mention/state.d.ts +28 -0
  308. package/dist/types/extensions/table/commands.d.ts +69 -0
  309. package/dist/types/extensions/table/html.d.ts +4 -0
  310. package/dist/types/extensions/table/index.d.ts +8 -0
  311. package/dist/types/extensions/table/plugin.d.ts +4 -0
  312. package/dist/types/extensions/table/schema.d.ts +23 -0
  313. package/dist/types/extensions/table/table-controls.vue.d.ts +3 -0
  314. package/dist/types/extensions/table/view.d.ts +13 -0
  315. package/dist/types/extensions/toggle/index.d.ts +7 -0
  316. package/dist/types/extensions/toggle/state.d.ts +17 -0
  317. package/dist/types/extensions/toggle/toggle-block.vue.d.ts +14 -0
  318. package/dist/types/index.d.ts +266 -0
  319. package/dist/types/locale.d.ts +195 -0
  320. package/dist/types/markdown/index.d.ts +14 -0
  321. package/dist/types/use-comments.d.ts +17 -0
  322. package/dist/use-comments.js +141 -0
  323. package/dist/use-comments.js.map +1 -0
  324. package/package.json +98 -4
@@ -0,0 +1 @@
1
+ {"version":3,"file":"editor2.js","names":["_sfc_main"],"sources":["../src/editor.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { isApplePlatform, useGlobalConfig, useNamespace } from 'dou-ui'\nimport { keymap } from 'prosemirror-keymap'\nimport { Plugin, type EditorState, type Transaction } from 'prosemirror-state'\nimport type { EditorView } from 'prosemirror-view'\nimport {\n computed,\n getCurrentInstance,\n nextTick,\n onBeforeUnmount,\n onMounted,\n provide,\n ref,\n shallowRef,\n toRaw,\n useTemplateRef,\n watch,\n h,\n useId,\n type Component,\n} from 'vue'\nimport BlockHandle from './components/block-handle.vue'\nimport BubbleToolbar from './components/bubble-toolbar.vue'\nimport SearchBar from './components/search-bar.vue'\nimport CommentButton from './components/comment-button.vue'\nimport CommentCard from './components/comment-card.vue'\nimport SlashMenu from './components/slash-menu.vue'\nimport { setFallbackHighlighter, type CodeHighlighter } from './core/highlight'\nimport { comparePlugin, compareKey } from './core/compare'\nimport {\n createEditorView,\n getBlocks,\n historyCommands,\n originKey,\n replaceBlocks,\n restoreBlocks,\n runCommand,\n setEditable,\n} from './core/editor'\nimport { diffBlocks } from './core/changes'\nimport { isRemote } from './core/plugins'\nimport { selectionOf, sameSelection } from './core/selection'\nimport { selectBlocks } from './core/block-selection'\nimport { applyEditorTransaction, EditorTransaction } from './core/transaction'\nimport {\n blocksToDoc,\n docToBlocks,\n type Block,\n type EditorDocument,\n type PartialBlock,\n} from './core/model'\nimport { resolveEditorExtensions } from './core/extension'\nimport {\n previewMatch,\n searchKey,\n searchPlugin,\n setSearch,\n type EditorSearchQuery,\n} from './core/search'\nimport { slashPlugin, type SlashState } from './core/slash'\nimport { writeHTML } from './core/output'\nimport { parseHTML } from './core/prepare'\nimport { componentMounter, vueNodeView } from './define-block'\nimport { editorKit } from './extensions/editor-kit'\nimport { readBlocks } from './document'\nimport {\n editorKey,\n type EditorEmits,\n type EditorExpose,\n type EditorProps,\n type EditorOrigin,\n type EditorLinkOpenEvent,\n type EditorSelection,\n type EditorTransactionResult,\n} from './editor'\nimport { useEditorMessages } from './locale'\nimport { useEditorComments } from './use-comments'\n\ndefineOptions({ name: 'DouEditor' })\n\nconst props = withDefaults(defineProps<EditorProps>(), {\n modelValue: undefined,\n extensions: undefined,\n readonly: false,\n searchable: true,\n placeholder: undefined,\n messages: undefined,\n ariaLabel: undefined,\n autofocus: false,\n plugins: undefined,\n history: true,\n undo: undefined,\n redo: undefined,\n selection: undefined,\n comments: undefined,\n author: undefined,\n activeComment: undefined,\n commentable: true,\n compare: undefined,\n})\nconst emit = defineEmits<EditorEmits>()\n\n// read once: changing the schema of a live editor would invalidate its document\nconst kit = resolveEditorExtensions(props.extensions ?? [editorKit()])\n// say so once, instead of silently keeping the old extensions\nconst stopWatchingExtensions = watch(\n () => props.extensions,\n () => {\n console.warn(\n '[@dou-ui/editor] `extensions` changed after mount and is ignored: it is read once. Create the list once outside the template, or give the editor a new `key` to rebuild it.',\n )\n stopWatchingExtensions()\n },\n)\n\nconst ns = useNamespace('editor')\nconst messages = useEditorMessages(() => props.messages)\nconst config = useGlobalConfig()\nconst root = useTemplateRef<HTMLElement>('root')\nconst mount = useTemplateRef<HTMLElement>('mount')\nconst view = shallowRef<EditorView | null>(null)\nconst state = shallowRef<EditorState | null>(null)\nconst focused = shallowRef(false)\nconst dragging = shallowRef(false)\n/** Showing what changed since an older version: read-only, without comments. */\nconst comparing = computed(() => !!props.compare)\n/**\n * The older version as whole blocks, matched to the current ones by id. A\n * block saved without an id gets a new one, so it shows as removed.\n */\nconst compareBlocks = computed(() => {\n const blocks = props.compare?.blocks\n return Array.isArray(blocks) ? docToBlocks(blocksToDoc(kit.schema, blocks)) : null\n})\nconst editable = computed(() => !props.readonly && !comparing.value)\n\nconst {\n comments,\n plugin: commentPlugin,\n anchors,\n} = useEditorComments({\n props,\n emit,\n view,\n provider: kit.comments,\n})\n\nconst context = { view, state, focused, dragging, editable, messages, root, kit, comments }\nprovide(editorKey, context)\n\n// block components render outside this component's tree: hand them the context\nconst appContext = getCurrentInstance()?.appContext ?? null\nconst mountComponent = componentMounter(appContext, [[editorKey, context]])\nconst componentViews = {\n ...Object.fromEntries(\n Object.entries(kit.schema.nodes)\n .filter(([, type]) => type.spec.component)\n .map(([name, type]) => [\n name,\n vueNodeView(type.spec.component as Component, appContext, [[editorKey, context]]),\n ]),\n ),\n ...Object.fromEntries(\n Object.entries(kit.componentViews).map(([name, create]) => [name, create(mountComponent)]),\n ),\n}\n\n// find and replace: the panel over the text, the matches marked in it\nconst searchOpen = ref(false)\nconst searchReplacing = ref(false)\nconst searchBar = useTemplateRef<InstanceType<typeof SearchBar>>('searchBar')\n/** Blocks that draw their own text, such as a code block in the code editor, are not searched. */\nconst searchSkip = (node: { type: { name: string } }) => node.type.name in kit.componentViews\n\n/** Opens the panel, with the replace box for `replace`. A selection on one line becomes the search. */\nfunction openSearch(replace = false, given?: Partial<EditorSearchQuery>) {\n const v = view.value\n if (!v || !props.searchable) return false\n const old = searchKey.getState(v.state)?.query ?? { search: '' }\n const query = { ...old, ...given }\n const { from, to, empty } = v.state.selection\n if (given?.search === undefined && !empty) {\n const text = v.state.doc.textBetween(from, to, '\\n')\n if (text && !text.includes('\\n')) query.search = text\n }\n if ((replace || given?.replace !== undefined) && editable.value) searchReplacing.value = true\n searchOpen.value = true\n v.dispatch(setSearch(v.state.tr, { query, active: true }))\n const preview = previewMatch(v.state)\n if (preview) v.dispatch(preview)\n void nextTick(() => searchBar.value?.focus(replace && editable.value ? 'replace' : 'find'))\n return true\n}\n\nfunction closeSearch() {\n const v = view.value\n if (!searchOpen.value || !v) return false\n searchOpen.value = false\n v.dispatch(setSearch(v.state.tr, { active: false }))\n v.focus()\n return true\n}\n\n/** Goes to the next (or previous) match; with the panel shut, opens it on the last search. */\nfunction findAgain(step: 1 | -1) {\n if (!searchOpen.value) {\n const v = view.value\n if (!v || !searchKey.getState(v.state)?.query.search || !openSearch()) return false\n }\n void nextTick(() => (step > 0 ? searchBar.value?.next() : searchBar.value?.previous()))\n return true\n}\n\nconst replaceKey = isApplePlatform() ? 'Mod-Alt-f' : 'Mod-h'\nconst searchKeys = keymap({\n 'Mod-f': () => openSearch(),\n [replaceKey]: () => openSearch(true),\n F3: () => findAgain(1),\n 'Mod-g': () => findAgain(1),\n 'Shift-F3': () => findAgain(-1),\n 'Mod-Shift-g': () => findAgain(-1),\n Escape: () => closeSearch(),\n})\n\nconst slash = shallowRef<SlashState | null>(null)\nconst slashMenu = useTemplateRef<InstanceType<typeof SlashMenu>>('slashMenu')\nconst bubble = useTemplateRef<InstanceType<typeof BubbleToolbar>>('bubble')\n\n// The document last sent through `v-model`: when the same object comes back,\n// it is not loaded again (that would move the cursor).\nlet emitted: EditorDocument | undefined\n\n// the result of the last change, for `transact` to hand back\nlet lastResult: EditorTransactionResult | null = null\n\nfunction onChange(current: EditorView, tr: Transaction, before: EditorState) {\n const blocks = getBlocks(current)\n const doc: EditorDocument = { version: 1, blocks }\n emitted = doc\n const history = tr.getMeta('history$') as { redo: boolean } | undefined\n const origin =\n (tr.getMeta(originKey) as EditorOrigin | undefined) ??\n (history ? (history.redo ? 'redo' : 'undo') : isRemote(tr) ? 'remote' : undefined)\n const meta: EditorTransactionResult['meta'] = {\n history:\n tr.getMeta('addToHistory') !== false &&\n origin !== 'remote' &&\n origin !== 'undo' &&\n origin !== 'redo',\n }\n if (origin !== undefined) meta.origin = origin\n lastResult = { blocks, changes: diffBlocks(before.doc, current.state.doc), meta }\n emit('transaction', lastResult)\n emit('update:modelValue', doc)\n emit('change', doc)\n}\n\nlet selection: EditorSelection = { block: null, blocks: [] }\n\nconst warn = (message: string) => {\n if (process.env.NODE_ENV !== 'production') console.warn(`[@dou-ui/editor] ${message}`)\n}\n\nconst lacksId = (blocks: PartialBlock[]): boolean =>\n blocks.some((block) => !block.id || lacksId(block.children ?? []))\n// until the editor is focused (or a block is, by the program) its selection is no cursor\nlet cursorKnown = false\n\nfunction onUpdate(current: EditorView) {\n state.value = current.state\n if (!cursorKnown) {\n if (!focused.value) return\n cursorKnown = true\n }\n const next = selectionOf(current.state)\n if (sameSelection(next, selection)) return\n selection = next\n emit('update:selection', next)\n}\n\n// a click on a link places the cursor; with Ctrl or ⌘ held, or while read-only, it opens the link\nconst openLinks = new Plugin({\n props: {\n handleDOMEvents: {\n click(clicked, event) {\n const link = (event.target as HTMLElement).closest<HTMLAnchorElement>('a.dou-editor__link')\n if (!link?.href || !(event.metaKey || event.ctrlKey || !clicked.editable)) return false\n event.preventDefault()\n const $pos = clicked.state.doc.resolve(clicked.posAtDOM(link, 0))\n let block = ''\n for (let depth = $pos.depth; depth > 0; depth--)\n if ($pos.node(depth).type.name === 'block') {\n block = $pos.node(depth).attrs.id as string\n break\n }\n const opening: EditorLinkOpenEvent = {\n link: link.getAttribute('href') ?? link.href,\n block,\n defaultPrevented: false,\n preventDefault() {\n this.defaultPrevented = true\n },\n }\n emit('linkOpen', opening)\n if (!opening.defaultPrevented) window.open(link.href, '_blank', 'noopener,noreferrer')\n return true\n },\n },\n },\n})\n\nonMounted(() => {\n const instance = createEditorView({\n element: mount.value!,\n kit,\n nodeViews: componentViews,\n // with collaboration the document comes from the shared document\n blocks: kit.externalDocument ? [] : readBlocks(props.modelValue),\n editable: editable.value,\n placeholder: {\n text: () => props.placeholder ?? messages.value.placeholder,\n heading: () => messages.value.headingPlaceholder,\n empty: () => props.placeholder ?? messages.value.emptyPlaceholder,\n },\n plugins: [\n ...(props.plugins ?? []),\n slashPlugin({\n onChange: (value) => (slash.value = value),\n onKey: (event) => slashMenu.value?.onKey(event) ?? false,\n }),\n keymap({\n 'Mod-k': () => bubble.value?.editLink() ?? false,\n 'Mod-Alt-m': () => comments.start(),\n // with the thread card open, Escape in the text closes it, and only that\n Escape: () => {\n if (!comments.pending.value && !comments.active.value) return false\n comments.close()\n return true\n },\n }),\n commentPlugin,\n comparePlugin({\n before: () => compareBlocks.value,\n label: () => messages.value.removedBlock,\n }),\n openLinks,\n searchPlugin({ skip: searchSkip }),\n ],\n keymaps: [searchKeys],\n history: props.history,\n undo: props.undo,\n redo: props.redo,\n attributes: () => {\n const attrs: Record<string, string> = {}\n if (props.ariaLabel) attrs['aria-label'] = props.ariaLabel\n if (!editable.value) attrs['aria-readonly'] = 'true'\n return attrs\n },\n onChange,\n onUpdate,\n })\n view.value = instance\n state.value = instance.state\n // code blocks without their own highlighter use the one DouCodeBlock uses\n watch(\n () =>\n (config.value.defaults as { DouCodeBlock?: { highlight?: CodeHighlighter } }).DouCodeBlock\n ?.highlight ?? null,\n (highlight) => setFallbackHighlighter(instance, highlight),\n { immediate: true },\n )\n instance.dom.addEventListener('focus', onFocus)\n instance.dom.addEventListener('blur', onBlur)\n if (props.autofocus) instance.focus()\n // blocks given without ids got them now: hand those back, so events' ids can be looked up\n const given = kit.externalDocument ? undefined : readBlocks(props.modelValue)\n if (given && lacksId(given)) {\n const doc: EditorDocument = { version: 1, blocks: getBlocks(instance) }\n emitted = doc\n emit('update:modelValue', doc)\n }\n})\n\nfunction onFocus(event: FocusEvent) {\n focused.value = true\n if (!cursorKnown && view.value) {\n cursorKnown = true\n const next = selectionOf(view.value.state)\n if (!sameSelection(next, selection)) {\n selection = next\n emit('update:selection', next)\n }\n }\n emit('focus', event)\n}\n\nfunction onBlur(event: FocusEvent) {\n focused.value = false\n emit('blur', event)\n}\n\nonBeforeUnmount(() => {\n view.value?.destroy()\n view.value = null\n})\n\nwatch(\n () => props.modelValue,\n (value) => {\n const current = view.value\n if (!current || value === undefined || kit.externalDocument || toRaw(value) === emitted) return\n replaceBlocks(current, readBlocks(value) ?? [])\n },\n)\n\nwatch(editable, (value) => view.value && setEditable(view.value, value))\nwatch(compareBlocks, () => view.value?.dispatch(view.value.state.tr.setMeta(compareKey, true)))\n\n/** The change last scrolled to, by its place among them. */\nlet changeIndex = -1\nwatch(\n () => props.compare,\n () => (changeIndex = -1),\n)\nfunction goToChange(step: 1 | -1) {\n const dom = view.value?.dom\n if (!dom || !props.compare) return false\n // a block's own mark, or the first piece of text changed in it\n const marks = [\n ...dom.querySelectorAll<HTMLElement>(\n '.dou-editor__diff-added, .dou-editor__diff-changed, .dou-editor__diff-removed-block, .dou-editor__diff-added-text, .dou-editor__diff-removed',\n ),\n ].filter((el, index, list) => {\n const block = el.closest('[data-block-id]')\n const previous = list[index - 1]\n return !previous || previous.closest('[data-block-id]') !== block || !block\n })\n const next = changeIndex + step\n const target = marks[next]\n if (!target) return false\n changeIndex = next\n target.scrollIntoView({ block: 'center' })\n const m = messages.value\n // a block added or removed, or one whose text or kind changed\n const kind = target.matches('.dou-editor__diff-added')\n ? m.changeAdded\n : target.matches('.dou-editor__diff-removed-block')\n ? m.changeRemoved\n : m.changeChanged\n announce(`${kind} ${changeText(target)}`)\n return true\n}\n\n/** The text of a change: what was removed, or its block as it is now. */\nfunction changeText(target: HTMLElement) {\n if (target.matches('.dou-editor__diff-removed-block')) return target.textContent ?? ''\n const block = (target.closest('[data-block-id]') ?? target).cloneNode(true) as HTMLElement\n for (const el of block.querySelectorAll('[data-diff=\"removed\"]')) el.remove()\n return block.textContent ?? ''\n}\n\n/** What the live region reads out: a version restored, the change gone to. */\nconst spoken = ref('')\nfunction announce(text: string) {\n // the same words twice still get read\n spoken.value = ''\n void nextTick(() => (spoken.value = text))\n}\n\nfunction restore(doc: EditorDocument<PartialBlock>) {\n // like the other methods it works while `readonly`, and it is the change comparing allows\n if (!view.value || !restoreBlocks(view.value, readBlocks(doc) ?? [])) return false\n announce(messages.value.restored)\n return true\n}\n\n// a selection from outside: the cursor goes to the block, or the blocks are selected\nwatch(\n () => props.selection,\n (value) => {\n const current = view.value\n if (!current || !value || sameSelection(value, selection)) return\n const known = cursorKnown\n cursorKnown = true\n let applied = false\n if (value.blocks.length > 1) {\n applied = selectBlocks(value.blocks[0]!, value.blocks.at(-1)!)(\n current.state,\n current.dispatch,\n )\n if (!applied) warn('`selection.blocks` must be sibling blocks that exist in the document')\n } else {\n const id = value.block ?? value.blocks[0]\n const transaction = new EditorTransaction(current.state)\n if (id && transaction.focusBlock(id)) {\n current.dispatch(transaction.tr)\n applied = true\n } else if (!id) warn('a selection without blocks cannot be set; the cursor stays where it is')\n }\n if (applied) return\n cursorKnown = known\n // hand back where the selection really is, so the binding doesn't keep a value that never applied\n emit('update:selection', { ...selection, blocks: [...selection.blocks] })\n },\n)\n\nfunction transact(\n fn: (tr: EditorTransaction) => void,\n meta?: EditorTransaction['meta'],\n): EditorTransactionResult | null {\n const current = view.value\n // comparing shows a version: nothing changes it but `restore`\n if (!current || props.compare) return null\n const transaction = new EditorTransaction(current.state, meta, cursorKnown)\n fn(transaction)\n lastResult = null\n // `focusBlock` places the cursor\n if (transaction.focused) cursorKnown = true\n applyEditorTransaction(current, transaction)\n if (transaction.focused) current.focus()\n const result = lastResult\n lastResult = null\n return result\n}\n\nconst history = (action: 'undo' | 'redo', dispatch: boolean) => {\n const current = view.value\n if (!current || props.compare) return false\n const command = historyCommands(current.state)[action]\n return dispatch ? runCommand(current, command) : command(current.state)\n}\n\ndefineExpose<EditorExpose>({\n get view() {\n return view.value\n },\n focus: () => view.value?.focus(),\n blur: () => (view.value?.dom as HTMLElement | undefined)?.blur(),\n getBlocks: () => (view.value ? getBlocks(view.value) : []),\n setBlocks: (blocks: PartialBlock[]) => view.value && replaceBlocks(view.value, blocks),\n getHTML: () => (view.value ? writeHTML(getBlocks(view.value), kit) : ''),\n setHTML: (html: string) => view.value && replaceBlocks(view.value, parseHTML(html, kit)),\n run: (command) => (view.value ? runCommand(view.value, command) : false),\n openSearch: (query) => openSearch(query?.replace !== undefined, query),\n closeSearch,\n transact,\n getBlock: (id) => (view.value ? new EditorTransaction(view.value.state).getBlock(id) : null),\n insertBlocks: (blocks, placement) => {\n let ids: string[] = []\n transact((tr) => (ids = tr.insertBlocks(blocks, placement)))\n return ids\n },\n updateBlock: (id, patch) => {\n let ok = false\n transact((tr) => (ok = tr.updateBlock(id, patch)))\n return ok\n },\n removeBlocks: (ids) => {\n let ok = false\n transact((tr) => (ok = tr.removeBlocks(ids)))\n return ok\n },\n moveBlocks: (ids, placement) => {\n let ok = false\n transact((tr) => (ok = tr.moveBlocks(ids, placement)))\n return ok\n },\n focusBlock: (id, edge) => {\n let ok = false\n transact((tr) => (ok = tr.focusBlock(id, edge)))\n return ok\n },\n getSelection: () => ({ ...selection, blocks: [...selection.blocks] }),\n undo: () => history('undo', true),\n redo: () => history('redo', true),\n get canUndo() {\n return !!state.value && history('undo', false)\n },\n get canRedo() {\n return !!state.value && history('redo', false)\n },\n comment: () => comments.start(),\n getCommentAnchors: anchors,\n restore,\n nextChange: () => goToChange(1),\n previousChange: () => goToChange(-1),\n})\n\n/*\n * Highlights for the threads that are open: by thread id, in one rule,\n * so a thread resolved or opened restyles its text without redrawing it.\n */\nconst scope = useId()\n/** An id as a CSS identifier: no quotes or `<`, which server rendering would escape. */\nconst ident = (id: string) =>\n id.replace(/[^a-zA-Z0-9_]|^[\\d-]/gu, (char) => `\\\\${char.codePointAt(0)!.toString(16)} `)\nconst highlighted = (id: string) => `[data-dou-editor=${ident(scope)}] [data-comment=${ident(id)}]`\nconst highlightCSS = computed(() => {\n if (!comments.enabled.value) return ''\n const open = comments.threads.value.filter((thread) => !thread.resolved)\n if (!open.length) return ''\n let css = `${open.map((thread) => highlighted(thread.id)).join(',')}{background-color:var(--dou-editor-comment-bg);border-bottom-color:var(--dou-editor-comment-line)}`\n const active = comments.active.value\n if (active && open.some((thread) => thread.id === active))\n css += `${highlighted(active)}{background-color:var(--dou-editor-comment-bg-active)}`\n return css\n})\nconst HighlightStyle = () => (highlightCSS.value ? h('style', highlightCSS.value) : null)\n</script>\n\n<template>\n <div\n ref=\"root\"\n :class=\"[\n ns.b(),\n {\n // comparing keeps the editor's own gutter, so the text doesn't move\n 'is-readonly': readonly,\n 'is-comparing': comparing,\n 'is-focused': focused,\n 'is-dragging': dragging,\n },\n ]\"\n :data-dou-editor=\"scope\"\n >\n <div v-if=\"searchOpen && view\" :class=\"ns.e('search')\">\n <SearchBar\n ref=\"searchBar\"\n v-model:replacing=\"searchReplacing\"\n :readonly=\"!editable\"\n :skip=\"searchSkip\"\n @close=\"closeSearch\"\n />\n </div>\n <HighlightStyle />\n <div ref=\"mount\" :class=\"ns.e('mount')\" />\n <template v-if=\"editable && view\">\n <BlockHandle />\n <SlashMenu ref=\"slashMenu\" :slash=\"slash\" />\n <BubbleToolbar ref=\"bubble\" />\n <component :is=\"overlay\" v-for=\"(overlay, index) in kit.overlays\" :key=\"index\" />\n </template>\n <div :class=\"ns.e('live')\" aria-live=\"polite\">{{ spoken }}</div>\n <template v-if=\"view && comments.enabled.value && !comparing\">\n <CommentButton v-if=\"readonly\" />\n <CommentCard />\n </template>\n </div>\n</template>\n"],"mappings":""}
@@ -0,0 +1,6 @@
1
+ import align_menu_vue_vue_type_script_setup_true_lang_default from "./align-menu.vue_vue_type_script_setup_true_lang.js";
2
+ // src/extensions/align/align-menu.vue
3
+ var align_menu_default = align_menu_vue_vue_type_script_setup_true_lang_default;
4
+ export { align_menu_default as default };
5
+
6
+ //# sourceMappingURL=align-menu.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"align-menu.js","names":["_sfc_main"],"sources":["../../../src/extensions/align/align-menu.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { TextAlignCenterIcon, TextAlignLeftIcon, TextAlignRightIcon } from '@dou-ui/icons'\nimport { DouButton, DouDropdown, DouDropdownItem, DouDropdownRadioGroup, DouTooltip } from 'dou-ui'\nimport { computed } from 'vue'\nimport type { BlockAlign } from '../../core/align'\nimport { useEditor, useToolbarMenu } from '../../editor'\nimport { currentAlign, setAlign } from './commands'\n\nconst { view, state, messages } = useEditor()\n\nconst icons = { left: TextAlignLeftIcon, center: TextAlignCenterIcon, right: TextAlignRightIcon }\nconst shortcuts = { left: 'mod+shift+l', center: 'mod+shift+e', right: 'mod+shift+r' }\nconst aligns = ['left', 'center', 'right'] as const\n\nconst align = computed(() => (state.value ? currentAlign(state.value) : undefined))\n\nfunction choose(value: unknown) {\n const current = view.value\n if (!current) return\n // left is the reading direction: store nothing\n const next = value === 'left' ? null : (value as BlockAlign)\n setAlign(next)(current.state, current.dispatch)\n current.focus()\n}\n\nconst open = useToolbarMenu()\n</script>\n\n<template>\n <DouTooltip\n v-if=\"align !== undefined\"\n :content=\"messages.align.label\"\n :disabled=\"open\"\n placement=\"bottom\"\n >\n <span class=\"dou-editor-bubble__trigger\">\n <DouDropdown v-model:open=\"open\" placement=\"bottom-start\" class=\"dou-editor-menu\">\n <DouButton size=\"sm\" variant=\"ghost\" :aria-label=\"messages.align.label\">\n <component :is=\"icons[align === 'mixed' || !align ? 'left' : align]\" />\n </DouButton>\n <template #menu>\n <DouDropdownRadioGroup\n :model-value=\"align === 'mixed' ? undefined : (align ?? 'left')\"\n @update:model-value=\"choose\"\n >\n <DouDropdownItem\n v-for=\"name in aligns\"\n :key=\"name\"\n :value=\"name\"\n :shortcut=\"shortcuts[name]\"\n >\n <template #prefix><component :is=\"icons[name]\" /></template>\n {{ messages.align[name] }}\n </DouDropdownItem>\n </DouDropdownRadioGroup>\n </template>\n </DouDropdown>\n </span>\n </DouTooltip>\n</template>\n"],"mappings":""}
@@ -0,0 +1,82 @@
1
+ import { useEditor, useToolbarMenu } from "../../editor.js";
2
+ import { currentAlign, setAlign } from "./commands.js";
3
+ import { DouButton, DouDropdown, DouDropdownItem, DouDropdownRadioGroup, DouTooltip } from "dou-ui";
4
+ import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, isRef, openBlock, renderList, resolveDynamicComponent, toDisplayString, unref, withCtx } from "vue";
5
+ import { TextAlignCenterIcon, TextAlignLeftIcon, TextAlignRightIcon } from "@dou-ui/icons";
6
+ // src/extensions/align/align-menu.vue?vue&type=script&setup=true&lang.ts
7
+ var _hoisted_1 = { class: "dou-editor-bubble__trigger" };
8
+ var align_menu_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineComponent({
9
+ __name: "align-menu",
10
+ setup(__props) {
11
+ const { view, state, messages } = useEditor();
12
+ const icons = {
13
+ left: TextAlignLeftIcon,
14
+ center: TextAlignCenterIcon,
15
+ right: TextAlignRightIcon
16
+ };
17
+ const shortcuts = {
18
+ left: "mod+shift+l",
19
+ center: "mod+shift+e",
20
+ right: "mod+shift+r"
21
+ };
22
+ const aligns = [
23
+ "left",
24
+ "center",
25
+ "right"
26
+ ];
27
+ const align = computed(() => state.value ? currentAlign(state.value) : void 0);
28
+ function choose(value) {
29
+ const current = view.value;
30
+ if (!current) return;
31
+ setAlign(value === "left" ? null : value)(current.state, current.dispatch);
32
+ current.focus();
33
+ }
34
+ const open = useToolbarMenu();
35
+ return (_ctx, _cache) => {
36
+ return align.value !== void 0 ? (openBlock(), createBlock(unref(DouTooltip), {
37
+ key: 0,
38
+ content: unref(messages).align.label,
39
+ disabled: unref(open),
40
+ placement: "bottom"
41
+ }, {
42
+ default: withCtx(() => [createElementVNode("span", _hoisted_1, [createVNode(unref(DouDropdown), {
43
+ open: unref(open),
44
+ "onUpdate:open": _cache[0] || (_cache[0] = ($event) => isRef(open) ? open.value = $event : null),
45
+ placement: "bottom-start",
46
+ class: "dou-editor-menu"
47
+ }, {
48
+ menu: withCtx(() => [createVNode(unref(DouDropdownRadioGroup), {
49
+ "model-value": align.value === "mixed" ? void 0 : align.value ?? "left",
50
+ "onUpdate:modelValue": choose
51
+ }, {
52
+ default: withCtx(() => [(openBlock(), createElementBlock(Fragment, null, renderList(aligns, (name) => {
53
+ return createVNode(unref(DouDropdownItem), {
54
+ key: name,
55
+ value: name,
56
+ shortcut: shortcuts[name]
57
+ }, {
58
+ prefix: withCtx(() => [(openBlock(), createBlock(resolveDynamicComponent(icons[name])))]),
59
+ default: withCtx(() => [createTextVNode(" " + toDisplayString(unref(messages).align[name]), 1)]),
60
+ _: 2
61
+ }, 1032, ["value", "shortcut"]);
62
+ }), 64))]),
63
+ _: 1
64
+ }, 8, ["model-value"])]),
65
+ default: withCtx(() => [createVNode(unref(DouButton), {
66
+ size: "sm",
67
+ variant: "ghost",
68
+ "aria-label": unref(messages).align.label
69
+ }, {
70
+ default: withCtx(() => [(openBlock(), createBlock(resolveDynamicComponent(icons[align.value === "mixed" || !align.value ? "left" : align.value])))]),
71
+ _: 1
72
+ }, 8, ["aria-label"])]),
73
+ _: 1
74
+ }, 8, ["open"])])]),
75
+ _: 1
76
+ }, 8, ["content", "disabled"])) : createCommentVNode("", true);
77
+ };
78
+ }
79
+ });
80
+ export { align_menu_vue_vue_type_script_setup_true_lang_default as default };
81
+
82
+ //# sourceMappingURL=align-menu.vue_vue_type_script_setup_true_lang.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"align-menu.vue_vue_type_script_setup_true_lang.js","names":["_openBlock","_createBlock","_unref","_withCtx","_createElementVNode","_createVNode","_isRef","_createElementBlock","_Fragment","_renderList","_resolveDynamicComponent","_createTextVNode","_toDisplayString","_createCommentVNode"],"sources":["../../../src/extensions/align/align-menu.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { TextAlignCenterIcon, TextAlignLeftIcon, TextAlignRightIcon } from '@dou-ui/icons'\nimport { DouButton, DouDropdown, DouDropdownItem, DouDropdownRadioGroup, DouTooltip } from 'dou-ui'\nimport { computed } from 'vue'\nimport type { BlockAlign } from '../../core/align'\nimport { useEditor, useToolbarMenu } from '../../editor'\nimport { currentAlign, setAlign } from './commands'\n\nconst { view, state, messages } = useEditor()\n\nconst icons = { left: TextAlignLeftIcon, center: TextAlignCenterIcon, right: TextAlignRightIcon }\nconst shortcuts = { left: 'mod+shift+l', center: 'mod+shift+e', right: 'mod+shift+r' }\nconst aligns = ['left', 'center', 'right'] as const\n\nconst align = computed(() => (state.value ? currentAlign(state.value) : undefined))\n\nfunction choose(value: unknown) {\n const current = view.value\n if (!current) return\n // left is the reading direction: store nothing\n const next = value === 'left' ? null : (value as BlockAlign)\n setAlign(next)(current.state, current.dispatch)\n current.focus()\n}\n\nconst open = useToolbarMenu()\n</script>\n\n<template>\n <DouTooltip\n v-if=\"align !== undefined\"\n :content=\"messages.align.label\"\n :disabled=\"open\"\n placement=\"bottom\"\n >\n <span class=\"dou-editor-bubble__trigger\">\n <DouDropdown v-model:open=\"open\" placement=\"bottom-start\" class=\"dou-editor-menu\">\n <DouButton size=\"sm\" variant=\"ghost\" :aria-label=\"messages.align.label\">\n <component :is=\"icons[align === 'mixed' || !align ? 'left' : align]\" />\n </DouButton>\n <template #menu>\n <DouDropdownRadioGroup\n :model-value=\"align === 'mixed' ? undefined : (align ?? 'left')\"\n @update:model-value=\"choose\"\n >\n <DouDropdownItem\n v-for=\"name in aligns\"\n :key=\"name\"\n :value=\"name\"\n :shortcut=\"shortcuts[name]\"\n >\n <template #prefix><component :is=\"icons[name]\" /></template>\n {{ messages.align[name] }}\n </DouDropdownItem>\n </DouDropdownRadioGroup>\n </template>\n </DouDropdown>\n </span>\n </DouTooltip>\n</template>\n"],"mappings":";;;;;;;;;;EAQA,MAAM,EAAE,MAAM,OAAO,aAAa,UAAU;EAE5C,MAAM,QAAQ;GAAE,MAAM;GAAmB,QAAQ;GAAqB,OAAO;EAAmB;EAChG,MAAM,YAAY;GAAE,MAAM;GAAe,QAAQ;GAAe,OAAO;EAAc;EACrF,MAAM,SAAS;GAAC;GAAQ;GAAU;EAAO;EAEzC,MAAM,QAAQ,eAAgB,MAAM,QAAQ,aAAa,MAAM,KAAK,IAAI,KAAA,CAAU;EAElF,SAAS,OAAO,OAAgB;GAC9B,MAAM,UAAU,KAAK;GACrB,IAAI,CAAC,SAAS;GAGd,SADa,UAAU,SAAS,OAAQ,KAC3B,CAAC,CAAC,QAAQ,OAAO,QAAQ,QAAQ;GAC9C,QAAQ,MAAM;EAChB;EAEA,MAAM,OAAO,eAAe;;GAKlB,OAAA,MAAA,UAAU,KAAA,KADlBA,UAAA,GAAAC,YA6BaC,MAAA,UAAA,GAAA;;IA3BV,SAASA,MAAA,QAAA,CAAQ,CAAC,MAAM;IACxB,UAAUA,MAAA,IAAA;IACX,WAAU;;IAEV,SAAAC,cAsBO,CAtBPC,mBAsBO,QAtBP,YAsBO,CArBLC,YAoBcH,MAAA,WAAA,GAAA;KApBO,MAAMA,MAAA,IAAA;KAAA,iBAAA,OAAA,OAAA,OAAA,MAAA,WAAAI,MAAA,IAAA,IAAA,KAAI,QAAA,SAAA;KAAE,WAAU;KAAe,OAAM;;KAInD,MAAIH,cAcW,CAbxBE,YAawBH,MAAA,qBAAA,GAAA;MAZrB,eAAa,MAAA,UAAK,UAAe,KAAA,IAAa,MAAA,SAAK;MACnD,uBAAoB;;MAGnB,SAAAC,cAAsB,EADxBH,UAAA,GAAAO,mBAQkBC,UAAA,MAAAC,WAPD,SAAR,SAAI;OADb,OAAAJ,YAQkBH,MAAA,eAAA,GAAA;QANf,KAAK;QACL,OAAO;QACP,UAAU,UAAU;;QAEV,QAAMC,cAAgC,EAA/BH,UAAA,GAAAC,YAA+BS,wBAAf,MAAM,KAAI,CAAA,E;QAAgB,SAAAP,cAC5D,CAD4DQ,gBAAA,MAC5DC,gBAAGV,MAAA,QAAA,CAAQ,CAAC,MAAM,KAAI,GAAA,CAAA,C;;;;;;KAf5B,SAAAC,cAEY,CAFZE,YAEYH,MAAA,SAAA,GAAA;MAFD,MAAK;MAAK,SAAQ;MAAS,cAAYA,MAAA,QAAA,CAAQ,CAAC,MAAM;;MAC/D,SAAAC,cAAuE,EAAvEH,UAAA,GAAAC,YAAuES,wBAAvD,MAAM,MAAA,UAAK,WAAA,CAAiB,MAAA,QAAK,SAAY,MAAA,MAAK,CAAA,E"}
@@ -0,0 +1,6 @@
1
+ import align_sub_vue_vue_type_script_setup_true_lang_default from "./align-sub.vue_vue_type_script_setup_true_lang.js";
2
+ // src/extensions/align/align-sub.vue
3
+ var align_sub_default = align_sub_vue_vue_type_script_setup_true_lang_default;
4
+ export { align_sub_default as default };
5
+
6
+ //# sourceMappingURL=align-sub.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"align-sub.js","names":["_sfc_main"],"sources":["../../../src/extensions/align/align-sub.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { TextAlignCenterIcon, TextAlignLeftIcon, TextAlignRightIcon } from '@dou-ui/icons'\nimport { DouDropdownItem, DouDropdownRadioGroup, DouDropdownSub } from 'dou-ui'\nimport { computed } from 'vue'\nimport type { BlockAlign } from '../../core/align'\nimport type { Block } from '../../core/model'\nimport { useEditor } from '../../editor'\nimport { setAlign } from './commands'\n\nconst props = defineProps<{ block: Block }>()\nconst { view, kit, messages } = useEditor()\n\nconst icons = { left: TextAlignLeftIcon, center: TextAlignCenterIcon, right: TextAlignRightIcon }\nconst aligns = ['left', 'center', 'right'] as const\n\nconst alignable = computed(() => !!kit.schema.nodes[props.block.type]?.spec.attrs?.align)\n\nfunction choose(value: unknown) {\n const current = view.value\n if (!current) return\n setAlign(value === 'left' ? null : (value as BlockAlign), props.block.id)(\n current.state,\n current.dispatch,\n )\n}\n</script>\n\n<template>\n <DouDropdownSub v-if=\"alignable\" :title=\"messages.align.label\">\n <DouDropdownRadioGroup :model-value=\"block.props.align ?? 'left'\" @update:model-value=\"choose\">\n <DouDropdownItem v-for=\"name in aligns\" :key=\"name\" :value=\"name\">\n <template #prefix><component :is=\"icons[name]\" /></template>\n {{ messages.align[name] }}\n </DouDropdownItem>\n </DouDropdownRadioGroup>\n </DouDropdownSub>\n</template>\n"],"mappings":""}
@@ -0,0 +1,57 @@
1
+ import { useEditor } from "../../editor.js";
2
+ import { setAlign } from "./commands.js";
3
+ import { DouDropdownItem, DouDropdownRadioGroup, DouDropdownSub } from "dou-ui";
4
+ import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createTextVNode, createVNode, defineComponent, openBlock, renderList, resolveDynamicComponent, toDisplayString, unref, withCtx } from "vue";
5
+ import { TextAlignCenterIcon, TextAlignLeftIcon, TextAlignRightIcon } from "@dou-ui/icons";
6
+ // src/extensions/align/align-sub.vue?vue&type=script&setup=true&lang.ts
7
+ var align_sub_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineComponent({
8
+ __name: "align-sub",
9
+ props: { block: {} },
10
+ setup(__props) {
11
+ const props = __props;
12
+ const { view, kit, messages } = useEditor();
13
+ const icons = {
14
+ left: TextAlignLeftIcon,
15
+ center: TextAlignCenterIcon,
16
+ right: TextAlignRightIcon
17
+ };
18
+ const aligns = [
19
+ "left",
20
+ "center",
21
+ "right"
22
+ ];
23
+ const alignable = computed(() => !!kit.schema.nodes[props.block.type]?.spec.attrs?.align);
24
+ function choose(value) {
25
+ const current = view.value;
26
+ if (!current) return;
27
+ setAlign(value === "left" ? null : value, props.block.id)(current.state, current.dispatch);
28
+ }
29
+ return (_ctx, _cache) => {
30
+ return alignable.value ? (openBlock(), createBlock(unref(DouDropdownSub), {
31
+ key: 0,
32
+ title: unref(messages).align.label
33
+ }, {
34
+ default: withCtx(() => [createVNode(unref(DouDropdownRadioGroup), {
35
+ "model-value": __props.block.props.align ?? "left",
36
+ "onUpdate:modelValue": choose
37
+ }, {
38
+ default: withCtx(() => [(openBlock(), createElementBlock(Fragment, null, renderList(aligns, (name) => {
39
+ return createVNode(unref(DouDropdownItem), {
40
+ key: name,
41
+ value: name
42
+ }, {
43
+ prefix: withCtx(() => [(openBlock(), createBlock(resolveDynamicComponent(icons[name])))]),
44
+ default: withCtx(() => [createTextVNode(" " + toDisplayString(unref(messages).align[name]), 1)]),
45
+ _: 2
46
+ }, 1032, ["value"]);
47
+ }), 64))]),
48
+ _: 1
49
+ }, 8, ["model-value"])]),
50
+ _: 1
51
+ }, 8, ["title"])) : createCommentVNode("", true);
52
+ };
53
+ }
54
+ });
55
+ export { align_sub_vue_vue_type_script_setup_true_lang_default as default };
56
+
57
+ //# sourceMappingURL=align-sub.vue_vue_type_script_setup_true_lang.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"align-sub.vue_vue_type_script_setup_true_lang.js","names":["_openBlock","_createBlock","_unref","_withCtx","_createVNode","_createElementBlock","_Fragment","_renderList","_resolveDynamicComponent","_createTextVNode","_toDisplayString","_createCommentVNode"],"sources":["../../../src/extensions/align/align-sub.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { TextAlignCenterIcon, TextAlignLeftIcon, TextAlignRightIcon } from '@dou-ui/icons'\nimport { DouDropdownItem, DouDropdownRadioGroup, DouDropdownSub } from 'dou-ui'\nimport { computed } from 'vue'\nimport type { BlockAlign } from '../../core/align'\nimport type { Block } from '../../core/model'\nimport { useEditor } from '../../editor'\nimport { setAlign } from './commands'\n\nconst props = defineProps<{ block: Block }>()\nconst { view, kit, messages } = useEditor()\n\nconst icons = { left: TextAlignLeftIcon, center: TextAlignCenterIcon, right: TextAlignRightIcon }\nconst aligns = ['left', 'center', 'right'] as const\n\nconst alignable = computed(() => !!kit.schema.nodes[props.block.type]?.spec.attrs?.align)\n\nfunction choose(value: unknown) {\n const current = view.value\n if (!current) return\n setAlign(value === 'left' ? null : (value as BlockAlign), props.block.id)(\n current.state,\n current.dispatch,\n )\n}\n</script>\n\n<template>\n <DouDropdownSub v-if=\"alignable\" :title=\"messages.align.label\">\n <DouDropdownRadioGroup :model-value=\"block.props.align ?? 'left'\" @update:model-value=\"choose\">\n <DouDropdownItem v-for=\"name in aligns\" :key=\"name\" :value=\"name\">\n <template #prefix><component :is=\"icons[name]\" /></template>\n {{ messages.align[name] }}\n </DouDropdownItem>\n </DouDropdownRadioGroup>\n </DouDropdownSub>\n</template>\n"],"mappings":";;;;;;;;;;EASA,MAAM,QAAQ;EACd,MAAM,EAAE,MAAM,KAAK,aAAa,UAAU;EAE1C,MAAM,QAAQ;GAAE,MAAM;GAAmB,QAAQ;GAAqB,OAAO;EAAmB;EAChG,MAAM,SAAS;GAAC;GAAQ;GAAU;EAAO;EAEzC,MAAM,YAAY,eAAe,CAAC,CAAC,IAAI,OAAO,MAAM,MAAM,MAAM,KAAK,EAAE,KAAK,OAAO,KAAK;EAExF,SAAS,OAAO,OAAgB;GAC9B,MAAM,UAAU,KAAK;GACrB,IAAI,CAAC,SAAS;GACd,SAAS,UAAU,SAAS,OAAQ,OAAsB,MAAM,MAAM,EAAE,CAAC,CACvE,QAAQ,OACR,QAAQ,QACV;EACF;;GAIwB,OAAA,UAAA,SAAtBA,UAAA,GAAAC,YAOiBC,MAAA,cAAA,GAAA;;IAPiB,OAAOA,MAAA,QAAA,CAAQ,CAAC,MAAM;;IACtD,SAAAC,cAKwB,CALxBC,YAKwBF,MAAA,qBAAA,GAAA;KALA,eAAa,QAAA,MAAM,MAAM,SAAK;KAAa,uBAAoB;;KACpE,SAAAC,cAAsB,EAAvCH,UAAA,GAAAK,mBAGkBC,UAAA,MAAAC,WAHc,SAAR,SAAI;MAA5B,OAAAH,YAGkBF,MAAA,eAAA,GAAA;OAHuB,KAAK;OAAO,OAAO;;OAC/C,QAAMC,cAAgC,EAA/BH,UAAA,GAAAC,YAA+BO,wBAAf,MAAM,KAAI,CAAA,E;OAAgB,SAAAL,cAC5D,CAD4DM,gBAAA,MAC5DC,gBAAGR,MAAA,QAAA,CAAQ,CAAC,MAAM,KAAI,GAAA,CAAA,C"}
@@ -0,0 +1,40 @@
1
+ import { blocksIn, findBlockById, selectedRange } from "../../core/blocks.js";
2
+ import { blockSelectionRange } from "../../core/block-selection.js";
3
+ // src/extensions/align/commands.ts
4
+ /** Whether a content node can be aligned. */
5
+ var alignable = (state, name) => !!state.schema.nodes[name]?.spec.attrs?.align;
6
+ /**
7
+ * Align the selected blocks (or the block with `id`). `'left'` and `null`
8
+ * both go back to the reading direction, stored as no `align` at all.
9
+ * Blocks that can't be aligned are left alone.
10
+ */
11
+ function setAlign(align, id) {
12
+ const value = align === "left" ? null : align;
13
+ return (state, dispatch) => {
14
+ const targets = (id ? [findBlockById(state.doc, id)].filter((info) => !!info) : (() => {
15
+ const range = blockSelectionRange(state) ?? selectedRange(state);
16
+ return range ? blocksIn(range) : [];
17
+ })()).filter((info) => alignable(state, info.content.type.name));
18
+ if (!targets.length) return false;
19
+ if (dispatch) {
20
+ const tr = state.tr;
21
+ for (const info of targets) tr.setNodeAttribute(info.contentPos, "align", value);
22
+ dispatch(tr);
23
+ }
24
+ return true;
25
+ };
26
+ }
27
+ /**
28
+ * The alignment the selected blocks share: `null` for none set, `'mixed'`
29
+ * when they differ, `undefined` when none can be aligned.
30
+ */
31
+ function currentAlign(state) {
32
+ const range = blockSelectionRange(state) ?? selectedRange(state);
33
+ const blocks = range ? blocksIn(range).filter((info) => alignable(state, info.content.type.name)) : [];
34
+ if (!blocks.length) return void 0;
35
+ const first = blocks[0].content.attrs.align ?? null;
36
+ return blocks.every((info) => (info.content.attrs.align ?? null) === first) ? first : "mixed";
37
+ }
38
+ export { currentAlign, setAlign };
39
+
40
+ //# sourceMappingURL=commands.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"commands.js","names":[],"sources":["../../../src/extensions/align/commands.ts"],"sourcesContent":["import type { Command, EditorState } from 'prosemirror-state'\nimport type { BlockAlign } from '../../core/align'\nimport { blockSelectionRange } from '../../core/block-selection'\nimport { blocksIn, findBlockById, selectedRange } from '../../core/blocks'\n\n/** Whether a content node can be aligned. */\nconst alignable = (state: EditorState, name: string) =>\n !!state.schema.nodes[name]?.spec.attrs?.align\n\n/**\n * Align the selected blocks (or the block with `id`). `'left'` and `null`\n * both go back to the reading direction, stored as no `align` at all.\n * Blocks that can't be aligned are left alone.\n */\nexport function setAlign(align: BlockAlign | null, id?: string): Command {\n const value = align === 'left' ? null : align\n return (state, dispatch) => {\n const blocks = id\n ? [findBlockById(state.doc, id)].filter((info) => !!info)\n : (() => {\n const range = blockSelectionRange(state) ?? selectedRange(state)\n return range ? blocksIn(range) : []\n })()\n const targets = blocks.filter((info) => alignable(state, info.content.type.name))\n if (!targets.length) return false\n if (dispatch) {\n const tr = state.tr\n for (const info of targets) tr.setNodeAttribute(info.contentPos, 'align', value)\n dispatch(tr)\n }\n return true\n }\n}\n\n/**\n * The alignment the selected blocks share: `null` for none set, `'mixed'`\n * when they differ, `undefined` when none can be aligned.\n */\nexport function currentAlign(state: EditorState): BlockAlign | 'mixed' | null | undefined {\n const range = blockSelectionRange(state) ?? selectedRange(state)\n const blocks = range\n ? blocksIn(range).filter((info) => alignable(state, info.content.type.name))\n : []\n if (!blocks.length) return undefined\n const first = (blocks[0]!.content.attrs.align as BlockAlign | null) ?? null\n return blocks.every((info) => (info.content.attrs.align ?? null) === first) ? first : 'mixed'\n}\n"],"mappings":";;;;AAMA,IAAM,aAAa,OAAoB,SACrC,CAAC,CAAC,MAAM,OAAO,MAAM,KAAK,EAAE,KAAK,OAAO;;;;;;AAO1C,SAAgB,SAAS,OAA0B,IAAsB;CACvE,MAAM,QAAQ,UAAU,SAAS,OAAO;CACxC,QAAQ,OAAO,aAAa;EAO1B,MAAM,WANS,KACX,CAAC,cAAc,MAAM,KAAK,EAAE,CAAC,CAAC,CAAC,QAAQ,SAAS,CAAC,CAAC,IAAI,WAC/C;GACL,MAAM,QAAQ,oBAAoB,KAAK,KAAK,cAAc,KAAK;GAC/D,OAAO,QAAQ,SAAS,KAAK,IAAI,CAAC;EACpC,EAAA,CAAG,EAAA,CACgB,QAAQ,SAAS,UAAU,OAAO,KAAK,QAAQ,KAAK,IAAI,CAAC;EAChF,IAAI,CAAC,QAAQ,QAAQ,OAAO;EAC5B,IAAI,UAAU;GACZ,MAAM,KAAK,MAAM;GACjB,KAAK,MAAM,QAAQ,SAAS,GAAG,iBAAiB,KAAK,YAAY,SAAS,KAAK;GAC/E,SAAS,EAAE;EACb;EACA,OAAO;CACT;AACF;;;;;AAMA,SAAgB,aAAa,OAA6D;CACxF,MAAM,QAAQ,oBAAoB,KAAK,KAAK,cAAc,KAAK;CAC/D,MAAM,SAAS,QACX,SAAS,KAAK,CAAC,CAAC,QAAQ,SAAS,UAAU,OAAO,KAAK,QAAQ,KAAK,IAAI,CAAC,IACzE,CAAC;CACL,IAAI,CAAC,OAAO,QAAQ,OAAO,KAAA;CAC3B,MAAM,QAAS,OAAO,EAAE,CAAE,QAAQ,MAAM,SAA+B;CACvE,OAAO,OAAO,OAAO,UAAU,KAAK,QAAQ,MAAM,SAAS,UAAU,KAAK,IAAI,QAAQ;AACxF"}
@@ -0,0 +1,27 @@
1
+ import { defineEditorExtension } from "../../core/extension.js";
2
+ import { currentAlign, setAlign } from "./commands.js";
3
+ import align_menu_default from "./align-menu.js";
4
+ import align_sub_default from "./align-sub.js";
5
+ // src/extensions/align/index.ts
6
+ var always = (command) => (state, dispatch, view) => {
7
+ command(state, dispatch, view);
8
+ return true;
9
+ };
10
+ /**
11
+ * Aligning paragraphs, headings and quotes left, centered or right: a menu
12
+ * in the formatting toolbar and in the block menu, and `Mod+Shift+L/E/R`.
13
+ * The blocks keep it as `props.align`.
14
+ */
15
+ var align = defineEditorExtension({
16
+ name: "align",
17
+ keymap: () => ({
18
+ "mod+shift+l": always(setAlign(null)),
19
+ "mod+shift+e": always(setAlign("center")),
20
+ "mod+shift+r": always(setAlign("right"))
21
+ }),
22
+ toolbar: [align_menu_default],
23
+ blockMenu: [align_sub_default]
24
+ });
25
+ export { align, currentAlign, setAlign };
26
+
27
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["AlignMenu","AlignSub"],"sources":["../../../src/extensions/align/index.ts"],"sourcesContent":["import type { Command } from 'prosemirror-state'\nimport { defineEditorExtension } from '../../core/extension'\nimport AlignMenu from './align-menu.vue'\nimport AlignSub from './align-sub.vue'\nimport { setAlign } from './commands'\n\nexport { currentAlign, setAlign } from './commands'\n\nconst always =\n (command: Command): Command =>\n (state, dispatch, view) => {\n command(state, dispatch, view)\n return true\n }\n\n/**\n * Aligning paragraphs, headings and quotes left, centered or right: a menu\n * in the formatting toolbar and in the block menu, and `Mod+Shift+L/E/R`.\n * The blocks keep it as `props.align`.\n */\nexport const align = defineEditorExtension({\n name: 'align',\n // taken in the editor even where nothing can be aligned: Mod+Shift+R would reload the page\n keymap: () => ({\n 'mod+shift+l': always(setAlign(null)),\n 'mod+shift+e': always(setAlign('center')),\n 'mod+shift+r': always(setAlign('right')),\n }),\n toolbar: [AlignMenu],\n blockMenu: [AlignSub],\n})\n"],"mappings":";;;;;AAQA,IAAM,UACH,aACA,OAAO,UAAU,SAAS;CACzB,QAAQ,OAAO,UAAU,IAAI;CAC7B,OAAO;AACT;;;;;;AAOF,IAAa,QAAQ,sBAAsB;CACzC,MAAM;CAEN,eAAe;EACb,eAAe,OAAO,SAAS,IAAI,CAAC;EACpC,eAAe,OAAO,SAAS,QAAQ,CAAC;EACxC,eAAe,OAAO,SAAS,OAAO,CAAC;CACzC;CACA,SAAS,CAACA,kBAAS;CACnB,WAAW,CAACC,iBAAQ;AACtB,CAAC"}
@@ -0,0 +1,6 @@
1
+ import attachment_block_vue_vue_type_script_setup_true_lang_default from "./attachment-block.vue_vue_type_script_setup_true_lang.js";
2
+ // src/extensions/attachment/attachment-block.vue
3
+ var attachment_block_default = attachment_block_vue_vue_type_script_setup_true_lang_default;
4
+ export { attachment_block_default as default };
5
+
6
+ //# sourceMappingURL=attachment-block.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attachment-block.js","names":["_sfc_main"],"sources":["../../../src/extensions/attachment/attachment-block.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport {\n DownloadIcon,\n FileArchiveIcon,\n FileAudioIcon,\n FileIcon,\n FileImageIcon,\n FileSpreadsheetIcon,\n FileTextIcon,\n FileVideoIcon,\n PaperclipIcon,\n UploadIcon,\n} from '@dou-ui/icons'\nimport { DouButton, DouSpinner, DouTooltip } from 'dou-ui'\nimport { computed, useTemplateRef } from 'vue'\nimport type { BlockProps } from '../../core/model'\nimport { useEditor } from '../../editor'\nimport type { Uploads } from '../image/uploads'\nimport { fileKind, formatFileSize, isSafeFileSrc } from './files'\n\nconst props = defineProps<{\n block: { id: string; type: string; props: BlockProps }\n updateProps: (props: BlockProps) => void\n selected: boolean\n editable: boolean\n uploads: Uploads\n accept?: string\n}>()\n\nconst { messages, view } = useEditor()\nconst text = computed(() => messages.value.attachment)\n\nconst src = computed(() => {\n const value = String(props.block.props.src ?? '')\n return value && isSafeFileSrc(value) ? value : ''\n})\nconst name = computed(\n () =>\n String(props.block.props.name ?? '') || src.value.split(/[/?#]/).filter(Boolean).at(-1) || '',\n)\nconst size = computed(() => formatFileSize(props.block.props.size ?? ''))\nconst upload = computed(() => props.uploads.items.get(props.block.id))\nconst icons = {\n image: FileImageIcon,\n video: FileVideoIcon,\n audio: FileAudioIcon,\n archive: FileArchiveIcon,\n spreadsheet: FileSpreadsheetIcon,\n text: FileTextIcon,\n other: FileIcon,\n}\nconst icon = computed(() => icons[fileKind(String(props.block.props.mime ?? ''), name.value)])\n\nconst fileInput = useTemplateRef<HTMLInputElement>('fileInput')\n\nfunction pick() {\n fileInput.value?.click()\n}\n\nfunction onFile(event: Event) {\n const input = event.target as HTMLInputElement\n const file = input.files?.[0]\n input.value = ''\n if (file && view.value) props.uploads.start(view.value, props.block.id, file)\n}\n</script>\n\n<template>\n <div class=\"dou-editor-attachment\" :class=\"{ 'is-selected': selected }\">\n <div v-if=\"src || upload || name\" class=\"dou-editor-attachment__card\">\n <component :is=\"icon\" class=\"dou-editor-attachment__icon\" />\n <div class=\"dou-editor-attachment__text\">\n <span class=\"dou-editor-attachment__name\">{{ name }}</span>\n <span class=\"dou-editor-attachment__meta\">\n <template v-if=\"upload && !upload.failed\">\n <DouSpinner size=\"sm\" />\n {{ text.uploading }}\n </template>\n <template v-else-if=\"upload?.failed\">{{ text.failed }}</template>\n <template v-else>{{ size }}</template>\n </span>\n </div>\n <DouButton v-if=\"upload?.failed && editable\" size=\"sm\" variant=\"outline\" @click=\"pick\">\n {{ text.retry }}\n </DouButton>\n <DouTooltip v-else-if=\"src\" :content=\"text.download\">\n <a\n class=\"dou-editor-attachment__download\"\n :href=\"src\"\n :download=\"name || ''\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n :aria-label=\"`${text.download} ${name}`\"\n data-editor-control\n >\n <DownloadIcon />\n </a>\n </DouTooltip>\n </div>\n\n <div v-else-if=\"editable\" class=\"dou-editor-image__empty\">\n <PaperclipIcon class=\"dou-editor-image__empty-icon\" />\n <span class=\"dou-editor-image__empty-text\">{{ text.empty }}</span>\n <DouButton size=\"sm\" variant=\"outline\" @click=\"pick\">\n <template #leading><UploadIcon /></template>\n {{ text.upload }}\n </DouButton>\n </div>\n\n <input v-if=\"editable\" ref=\"fileInput\" type=\"file\" :accept=\"accept\" hidden @change=\"onFile\" />\n </div>\n</template>\n"],"mappings":""}
@@ -0,0 +1,119 @@
1
+ import { useEditor } from "../../editor.js";
2
+ import { fileKind, formatFileSize, isSafeFileSrc } from "./files.js";
3
+ import { DouButton, DouSpinner, DouTooltip } from "dou-ui";
4
+ import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, normalizeClass, openBlock, resolveDynamicComponent, toDisplayString, unref, useTemplateRef, withCtx } from "vue";
5
+ import { DownloadIcon, FileArchiveIcon, FileAudioIcon, FileIcon, FileImageIcon, FileSpreadsheetIcon, FileTextIcon, FileVideoIcon, PaperclipIcon, UploadIcon } from "@dou-ui/icons";
6
+ // src/extensions/attachment/attachment-block.vue?vue&type=script&setup=true&lang.ts
7
+ var _hoisted_1 = {
8
+ key: 0,
9
+ class: "dou-editor-attachment__card"
10
+ };
11
+ var _hoisted_2 = { class: "dou-editor-attachment__text" };
12
+ var _hoisted_3 = { class: "dou-editor-attachment__name" };
13
+ var _hoisted_4 = { class: "dou-editor-attachment__meta" };
14
+ var _hoisted_5 = [
15
+ "href",
16
+ "download",
17
+ "aria-label"
18
+ ];
19
+ var _hoisted_6 = {
20
+ key: 1,
21
+ class: "dou-editor-image__empty"
22
+ };
23
+ var _hoisted_7 = { class: "dou-editor-image__empty-text" };
24
+ var _hoisted_8 = ["accept"];
25
+ var attachment_block_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineComponent({
26
+ __name: "attachment-block",
27
+ props: {
28
+ block: {},
29
+ updateProps: { type: Function },
30
+ selected: { type: Boolean },
31
+ editable: { type: Boolean },
32
+ uploads: {},
33
+ accept: {}
34
+ },
35
+ setup(__props) {
36
+ const props = __props;
37
+ const { messages, view } = useEditor();
38
+ const text = computed(() => messages.value.attachment);
39
+ const src = computed(() => {
40
+ const value = String(props.block.props.src ?? "");
41
+ return value && isSafeFileSrc(value) ? value : "";
42
+ });
43
+ const name = computed(() => String(props.block.props.name ?? "") || src.value.split(/[/?#]/).filter(Boolean).at(-1) || "");
44
+ const size = computed(() => formatFileSize(props.block.props.size ?? ""));
45
+ const upload = computed(() => props.uploads.items.get(props.block.id));
46
+ const icons = {
47
+ image: FileImageIcon,
48
+ video: FileVideoIcon,
49
+ audio: FileAudioIcon,
50
+ archive: FileArchiveIcon,
51
+ spreadsheet: FileSpreadsheetIcon,
52
+ text: FileTextIcon,
53
+ other: FileIcon
54
+ };
55
+ const icon = computed(() => icons[fileKind(String(props.block.props.mime ?? ""), name.value)]);
56
+ const fileInput = useTemplateRef("fileInput");
57
+ function pick() {
58
+ fileInput.value?.click();
59
+ }
60
+ function onFile(event) {
61
+ const input = event.target;
62
+ const file = input.files?.[0];
63
+ input.value = "";
64
+ if (file && view.value) props.uploads.start(view.value, props.block.id, file);
65
+ }
66
+ return (_ctx, _cache) => {
67
+ return openBlock(), createElementBlock("div", { class: normalizeClass(["dou-editor-attachment", { "is-selected": __props.selected }]) }, [src.value || upload.value || name.value ? (openBlock(), createElementBlock("div", _hoisted_1, [
68
+ (openBlock(), createBlock(resolveDynamicComponent(icon.value), { class: "dou-editor-attachment__icon" })),
69
+ createElementVNode("div", _hoisted_2, [createElementVNode("span", _hoisted_3, toDisplayString(name.value), 1), createElementVNode("span", _hoisted_4, [upload.value && !upload.value.failed ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [createVNode(unref(DouSpinner), { size: "sm" }), createTextVNode(" " + toDisplayString(text.value.uploading), 1)], 64)) : upload.value?.failed ? (openBlock(), createElementBlock(Fragment, { key: 1 }, [createTextVNode(toDisplayString(text.value.failed), 1)], 64)) : (openBlock(), createElementBlock(Fragment, { key: 2 }, [createTextVNode(toDisplayString(size.value), 1)], 64))])]),
70
+ upload.value?.failed && __props.editable ? (openBlock(), createBlock(unref(DouButton), {
71
+ key: 0,
72
+ size: "sm",
73
+ variant: "outline",
74
+ onClick: pick
75
+ }, {
76
+ default: withCtx(() => [createTextVNode(toDisplayString(text.value.retry), 1)]),
77
+ _: 1
78
+ })) : src.value ? (openBlock(), createBlock(unref(DouTooltip), {
79
+ key: 1,
80
+ content: text.value.download
81
+ }, {
82
+ default: withCtx(() => [createElementVNode("a", {
83
+ class: "dou-editor-attachment__download",
84
+ href: src.value,
85
+ download: name.value || "",
86
+ target: "_blank",
87
+ rel: "noopener noreferrer",
88
+ "aria-label": `${text.value.download} ${name.value}`,
89
+ "data-editor-control": ""
90
+ }, [createVNode(unref(DownloadIcon))], 8, _hoisted_5)]),
91
+ _: 1
92
+ }, 8, ["content"])) : createCommentVNode("", true)
93
+ ])) : __props.editable ? (openBlock(), createElementBlock("div", _hoisted_6, [
94
+ createVNode(unref(PaperclipIcon), { class: "dou-editor-image__empty-icon" }),
95
+ createElementVNode("span", _hoisted_7, toDisplayString(text.value.empty), 1),
96
+ createVNode(unref(DouButton), {
97
+ size: "sm",
98
+ variant: "outline",
99
+ onClick: pick
100
+ }, {
101
+ leading: withCtx(() => [createVNode(unref(UploadIcon))]),
102
+ default: withCtx(() => [createTextVNode(" " + toDisplayString(text.value.upload), 1)]),
103
+ _: 1
104
+ })
105
+ ])) : createCommentVNode("", true), __props.editable ? (openBlock(), createElementBlock("input", {
106
+ key: 2,
107
+ ref_key: "fileInput",
108
+ ref: fileInput,
109
+ type: "file",
110
+ accept: __props.accept,
111
+ hidden: "",
112
+ onChange: onFile
113
+ }, null, 40, _hoisted_8)) : createCommentVNode("", true)], 2);
114
+ };
115
+ }
116
+ });
117
+ export { attachment_block_vue_vue_type_script_setup_true_lang_default as default };
118
+
119
+ //# sourceMappingURL=attachment-block.vue_vue_type_script_setup_true_lang.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attachment-block.vue_vue_type_script_setup_true_lang.js","names":["_openBlock","_createElementBlock","_normalizeClass","_createBlock","_resolveDynamicComponent","_createElementVNode","_toDisplayString","_Fragment","_createVNode","_unref","_createTextVNode","_withCtx","_createCommentVNode"],"sources":["../../../src/extensions/attachment/attachment-block.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport {\n DownloadIcon,\n FileArchiveIcon,\n FileAudioIcon,\n FileIcon,\n FileImageIcon,\n FileSpreadsheetIcon,\n FileTextIcon,\n FileVideoIcon,\n PaperclipIcon,\n UploadIcon,\n} from '@dou-ui/icons'\nimport { DouButton, DouSpinner, DouTooltip } from 'dou-ui'\nimport { computed, useTemplateRef } from 'vue'\nimport type { BlockProps } from '../../core/model'\nimport { useEditor } from '../../editor'\nimport type { Uploads } from '../image/uploads'\nimport { fileKind, formatFileSize, isSafeFileSrc } from './files'\n\nconst props = defineProps<{\n block: { id: string; type: string; props: BlockProps }\n updateProps: (props: BlockProps) => void\n selected: boolean\n editable: boolean\n uploads: Uploads\n accept?: string\n}>()\n\nconst { messages, view } = useEditor()\nconst text = computed(() => messages.value.attachment)\n\nconst src = computed(() => {\n const value = String(props.block.props.src ?? '')\n return value && isSafeFileSrc(value) ? value : ''\n})\nconst name = computed(\n () =>\n String(props.block.props.name ?? '') || src.value.split(/[/?#]/).filter(Boolean).at(-1) || '',\n)\nconst size = computed(() => formatFileSize(props.block.props.size ?? ''))\nconst upload = computed(() => props.uploads.items.get(props.block.id))\nconst icons = {\n image: FileImageIcon,\n video: FileVideoIcon,\n audio: FileAudioIcon,\n archive: FileArchiveIcon,\n spreadsheet: FileSpreadsheetIcon,\n text: FileTextIcon,\n other: FileIcon,\n}\nconst icon = computed(() => icons[fileKind(String(props.block.props.mime ?? ''), name.value)])\n\nconst fileInput = useTemplateRef<HTMLInputElement>('fileInput')\n\nfunction pick() {\n fileInput.value?.click()\n}\n\nfunction onFile(event: Event) {\n const input = event.target as HTMLInputElement\n const file = input.files?.[0]\n input.value = ''\n if (file && view.value) props.uploads.start(view.value, props.block.id, file)\n}\n</script>\n\n<template>\n <div class=\"dou-editor-attachment\" :class=\"{ 'is-selected': selected }\">\n <div v-if=\"src || upload || name\" class=\"dou-editor-attachment__card\">\n <component :is=\"icon\" class=\"dou-editor-attachment__icon\" />\n <div class=\"dou-editor-attachment__text\">\n <span class=\"dou-editor-attachment__name\">{{ name }}</span>\n <span class=\"dou-editor-attachment__meta\">\n <template v-if=\"upload && !upload.failed\">\n <DouSpinner size=\"sm\" />\n {{ text.uploading }}\n </template>\n <template v-else-if=\"upload?.failed\">{{ text.failed }}</template>\n <template v-else>{{ size }}</template>\n </span>\n </div>\n <DouButton v-if=\"upload?.failed && editable\" size=\"sm\" variant=\"outline\" @click=\"pick\">\n {{ text.retry }}\n </DouButton>\n <DouTooltip v-else-if=\"src\" :content=\"text.download\">\n <a\n class=\"dou-editor-attachment__download\"\n :href=\"src\"\n :download=\"name || ''\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n :aria-label=\"`${text.download} ${name}`\"\n data-editor-control\n >\n <DownloadIcon />\n </a>\n </DouTooltip>\n </div>\n\n <div v-else-if=\"editable\" class=\"dou-editor-image__empty\">\n <PaperclipIcon class=\"dou-editor-image__empty-icon\" />\n <span class=\"dou-editor-image__empty-text\">{{ text.empty }}</span>\n <DouButton size=\"sm\" variant=\"outline\" @click=\"pick\">\n <template #leading><UploadIcon /></template>\n {{ text.upload }}\n </DouButton>\n </div>\n\n <input v-if=\"editable\" ref=\"fileInput\" type=\"file\" :accept=\"accept\" hidden @change=\"onFile\" />\n </div>\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAoBA,MAAM,QAAQ;EASd,MAAM,EAAE,UAAU,SAAS,UAAU;EACrC,MAAM,OAAO,eAAe,SAAS,MAAM,UAAU;EAErD,MAAM,MAAM,eAAe;GACzB,MAAM,QAAQ,OAAO,MAAM,MAAM,MAAM,OAAO,EAAE;GAChD,OAAO,SAAS,cAAc,KAAK,IAAI,QAAQ;EACjD,CAAC;EACD,MAAM,OAAO,eAET,OAAO,MAAM,MAAM,MAAM,QAAQ,EAAE,KAAK,IAAI,MAAM,MAAM,OAAO,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,GAAG,EAAE,KAAK,EAC/F;EACA,MAAM,OAAO,eAAe,eAAe,MAAM,MAAM,MAAM,QAAQ,EAAE,CAAC;EACxE,MAAM,SAAS,eAAe,MAAM,QAAQ,MAAM,IAAI,MAAM,MAAM,EAAE,CAAC;EACrE,MAAM,QAAQ;GACZ,OAAO;GACP,OAAO;GACP,OAAO;GACP,SAAS;GACT,aAAa;GACb,MAAM;GACN,OAAO;EACT;EACA,MAAM,OAAO,eAAe,MAAM,SAAS,OAAO,MAAM,MAAM,MAAM,QAAQ,EAAE,GAAG,KAAK,KAAK,EAAE;EAE7F,MAAM,YAAY,eAAiC,WAAW;EAE9D,SAAS,OAAO;GACd,UAAU,OAAO,MAAM;EACzB;EAEA,SAAS,OAAO,OAAc;GAC5B,MAAM,QAAQ,MAAM;GACpB,MAAM,OAAO,MAAM,QAAQ;GAC3B,MAAM,QAAQ;GACd,IAAI,QAAQ,KAAK,OAAO,MAAM,QAAQ,MAAM,KAAK,OAAO,MAAM,MAAM,IAAI,IAAI;EAC9E;;GAIE,OAAAA,UAAA,GAAAC,mBA0CM,OAAA,EA1CD,OAAKC,eAAA,CAAC,yBAAuB,EAAA,eAA0B,QAAA,SAAQ,CAAA,CAAA,E,IACvD,IAAA,SAAO,OAAA,SAAU,KAAA,SAA5BF,UAAA,GAAAC,mBA6BM,OA7BN,YA6BM;KA5BJD,UAAA,GAAAG,YAA4DC,wBAA5C,KAAA,KAAI,GAAA,EAAE,OAAM,8BAA6B,CAAA;IACzDC,mBAUM,OAVN,YAUM,CATJA,mBAA2D,QAA3D,YAA2DC,gBAAd,KAAA,KAAI,GAAA,CAAA,GACjDD,mBAOO,QAPP,YAOO,CANW,OAAA,SAAM,CAAK,OAAA,MAAO,UAAlCL,UAAA,GAAAC,mBAGWM,UAAA,EAAA,KAAA,EAAA,GAAA,CAFTC,YAAwBC,MAAA,UAAA,GAAA,EAAZ,MAAK,KAAI,CAAA,GAAGC,gBAAA,MACxBJ,gBAAG,KAAA,MAAK,SAAS,GAAA,CAAA,C,UAEE,OAAA,OAAQ,UAA7BN,UAAA,GAAAC,mBAAiEM,UAAA,EAAA,KAAA,EAAA,GAAA,CAAzBG,gBAAAJ,gBAAA,KAAA,MAAK,MAAM,GAAA,CAAA,C,WACnDN,UAAA,GAAAC,mBAAsCM,UAAA,EAAA,KAAA,EAAA,GAAA,CAAlBG,gBAAAJ,gBAAA,KAAA,KAAI,GAAA,CAAA,C;IAGX,OAAA,OAAQ,UAAU,QAAA,YAAnCN,UAAA,GAAAG,YAEYM,MAAA,SAAA,GAAA;;KAFiC,MAAK;KAAK,SAAQ;KAAW,SAAO;;KAC/E,SAAAE,cAAgB,CAAbD,gBAAAJ,gBAAA,KAAA,MAAK,KAAK,GAAA,CAAA,C;;I,MAEQ,IAAA,SAAvBN,UAAA,GAAAG,YAYaM,MAAA,UAAA,GAAA;;KAZgB,SAAS,KAAA,MAAK;;KACzC,SAAAE,cAUI,CAVJN,mBAUI,KAAA;MATF,OAAM;MACL,MAAM,IAAA;MACN,UAAU,KAAA,SAAI;MACf,QAAO;MACP,KAAI;MACH,cAAU,GAAK,KAAA,MAAK,SAAQ,GAAI,KAAA;MACjC,uBAAA;K,IAEAG,YAAgBC,MAAA,YAAA,CAAA,C;;;G,MAKN,QAAA,YAAhBT,UAAA,GAAAC,mBAOM,OAPN,YAOM;IANJO,YAAsDC,MAAA,aAAA,GAAA,EAAvC,OAAM,+BAA8B,CAAA;IACnDJ,mBAAkE,QAAlE,YAAkEC,gBAApB,KAAA,MAAK,KAAK,GAAA,CAAA;IACxDE,YAGYC,MAAA,SAAA,GAAA;KAHD,MAAK;KAAK,SAAQ;KAAW,SAAO;;KAClC,SAAOE,cAAe,CAAdH,YAAcC,MAAA,UAAA,CAAA,C;KAAW,SAAAE,cAC5C,CAD4CD,gBAAA,MAC5CJ,gBAAG,KAAA,MAAK,MAAM,GAAA,CAAA,C;;;G,oCAIL,QAAA,YAAbN,UAAA,GAAAC,mBAA8F,SAAA;;IAAnE,SAAA;IAAJ,KAAI;IAAY,MAAK;IAAQ,QAAQ,QAAA;IAAQ,QAAA;IAAQ,UAAQ"}
@@ -0,0 +1,66 @@
1
+ // src/extensions/attachment/files.ts
2
+ /** Addresses an attachment may link to: web, relative, and files held in the page. */
3
+ function isSafeFileSrc(src) {
4
+ const value = src.replace(/[\u0000- ]/g, "");
5
+ const scheme = /^([a-z][a-z0-9+.-]*):/i.exec(value)?.[1]?.toLowerCase();
6
+ if (!scheme) return true;
7
+ return [
8
+ "http",
9
+ "https",
10
+ "blob",
11
+ "data"
12
+ ].includes(scheme);
13
+ }
14
+ /** `1.2 MB`, `340 KB`, `12 B`. */
15
+ function formatFileSize(size) {
16
+ if (size === "" || size == null) return "";
17
+ const bytes = Number(size);
18
+ if (!Number.isFinite(bytes) || bytes < 0) return "";
19
+ const units = [
20
+ "B",
21
+ "KB",
22
+ "MB",
23
+ "GB",
24
+ "TB"
25
+ ];
26
+ let value = bytes;
27
+ let unit = 0;
28
+ while (value >= 1024 && unit < units.length - 1) {
29
+ value /= 1024;
30
+ unit++;
31
+ }
32
+ const digits = unit === 0 || value >= 10 ? 0 : 1;
33
+ return `${value.toFixed(digits)} ${units[unit]}`;
34
+ }
35
+ /** The kind of file, for its icon. */
36
+ function fileKind(mime, name) {
37
+ const ext = /\.([a-z0-9]+)$/i.exec(name)?.[1]?.toLowerCase() ?? "";
38
+ if (mime.startsWith("image/")) return "image";
39
+ if (mime.startsWith("video/")) return "video";
40
+ if (mime.startsWith("audio/")) return "audio";
41
+ if (/zip|compressed|tar|rar|7z/.test(mime) || [
42
+ "zip",
43
+ "rar",
44
+ "7z",
45
+ "gz",
46
+ "tar"
47
+ ].includes(ext)) return "archive";
48
+ if (/sheet|excel|csv/.test(mime) || [
49
+ "xlsx",
50
+ "xls",
51
+ "csv"
52
+ ].includes(ext)) return "spreadsheet";
53
+ if (mime.startsWith("text/") || /pdf|word|document|presentation/.test(mime) || [
54
+ "pdf",
55
+ "doc",
56
+ "docx",
57
+ "txt",
58
+ "md",
59
+ "ppt",
60
+ "pptx"
61
+ ].includes(ext)) return "text";
62
+ return "other";
63
+ }
64
+ export { fileKind, formatFileSize, isSafeFileSrc };
65
+
66
+ //# sourceMappingURL=files.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"files.js","names":[],"sources":["../../../src/extensions/attachment/files.ts"],"sourcesContent":["/** Addresses an attachment may link to: web, relative, and files held in the page. */\nexport function isSafeFileSrc(src: string): boolean {\n // eslint-disable-next-line no-control-regex\n const value = src.replace(/[\\u0000- ]/g, '')\n const scheme = /^([a-z][a-z0-9+.-]*):/i.exec(value)?.[1]?.toLowerCase()\n if (!scheme) return true\n // a data URL is only ever downloaded (the link has `download`), never opened\n return ['http', 'https', 'blob', 'data'].includes(scheme)\n}\n\n/** `1.2 MB`, `340 KB`, `12 B`. */\nexport function formatFileSize(size: unknown): string {\n if (size === '' || size == null) return ''\n const bytes = Number(size)\n if (!Number.isFinite(bytes) || bytes < 0) return ''\n const units = ['B', 'KB', 'MB', 'GB', 'TB']\n let value = bytes\n let unit = 0\n while (value >= 1024 && unit < units.length - 1) {\n value /= 1024\n unit++\n }\n const digits = unit === 0 || value >= 10 ? 0 : 1\n return `${value.toFixed(digits)} ${units[unit]}`\n}\n\n/** The kind of file, for its icon. */\nexport function fileKind(\n mime: string,\n name: string,\n): 'image' | 'video' | 'audio' | 'archive' | 'text' | 'spreadsheet' | 'other' {\n const ext = /\\.([a-z0-9]+)$/i.exec(name)?.[1]?.toLowerCase() ?? ''\n if (mime.startsWith('image/')) return 'image'\n if (mime.startsWith('video/')) return 'video'\n if (mime.startsWith('audio/')) return 'audio'\n if (/zip|compressed|tar|rar|7z/.test(mime) || ['zip', 'rar', '7z', 'gz', 'tar'].includes(ext))\n return 'archive'\n if (/sheet|excel|csv/.test(mime) || ['xlsx', 'xls', 'csv'].includes(ext)) return 'spreadsheet'\n if (\n mime.startsWith('text/') ||\n /pdf|word|document|presentation/.test(mime) ||\n ['pdf', 'doc', 'docx', 'txt', 'md', 'ppt', 'pptx'].includes(ext)\n )\n return 'text'\n return 'other'\n}\n"],"mappings":";;AACA,SAAgB,cAAc,KAAsB;CAElD,MAAM,QAAQ,IAAI,QAAQ,eAAe,EAAE;CAC3C,MAAM,SAAS,yBAAyB,KAAK,KAAK,CAAC,GAAG,EAAE,EAAE,YAAY;CACtE,IAAI,CAAC,QAAQ,OAAO;CAEpB,OAAO;EAAC;EAAQ;EAAS;EAAQ;CAAM,CAAC,CAAC,SAAS,MAAM;AAC1D;;AAGA,SAAgB,eAAe,MAAuB;CACpD,IAAI,SAAS,MAAM,QAAQ,MAAM,OAAO;CACxC,MAAM,QAAQ,OAAO,IAAI;CACzB,IAAI,CAAC,OAAO,SAAS,KAAK,KAAK,QAAQ,GAAG,OAAO;CACjD,MAAM,QAAQ;EAAC;EAAK;EAAM;EAAM;EAAM;CAAI;CAC1C,IAAI,QAAQ;CACZ,IAAI,OAAO;CACX,OAAO,SAAS,QAAQ,OAAO,MAAM,SAAS,GAAG;EAC/C,SAAS;EACT;CACF;CACA,MAAM,SAAS,SAAS,KAAK,SAAS,KAAK,IAAI;CAC/C,OAAO,GAAG,MAAM,QAAQ,MAAM,EAAE,GAAG,MAAM;AAC3C;;AAGA,SAAgB,SACd,MACA,MAC4E;CAC5E,MAAM,MAAM,kBAAkB,KAAK,IAAI,CAAC,GAAG,EAAE,EAAE,YAAY,KAAK;CAChE,IAAI,KAAK,WAAW,QAAQ,GAAG,OAAO;CACtC,IAAI,KAAK,WAAW,QAAQ,GAAG,OAAO;CACtC,IAAI,KAAK,WAAW,QAAQ,GAAG,OAAO;CACtC,IAAI,4BAA4B,KAAK,IAAI,KAAK;EAAC;EAAO;EAAO;EAAM;EAAM;CAAK,CAAC,CAAC,SAAS,GAAG,GAC1F,OAAO;CACT,IAAI,kBAAkB,KAAK,IAAI,KAAK;EAAC;EAAQ;EAAO;CAAK,CAAC,CAAC,SAAS,GAAG,GAAG,OAAO;CACjF,IACE,KAAK,WAAW,OAAO,KACvB,iCAAiC,KAAK,IAAI,KAC1C;EAAC;EAAO;EAAO;EAAQ;EAAO;EAAM;EAAO;CAAM,CAAC,CAAC,SAAS,GAAG,GAE/D,OAAO;CACT,OAAO;AACT"}