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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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":"embed-block.js","names":["_sfc_main"],"sources":["../../../src/extensions/embed/embed-block.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { AppWindowIcon, LinkIcon, PencilIcon } from '@dou-ui/icons'\nimport { DouButton, DouInput, DouTooltip } from 'dou-ui'\nimport { computed, shallowRef } from 'vue'\nimport type { BlockProps } from '../../core/model'\nimport { useEditor } from '../../editor'\nimport { frameAllow, frameSandbox, httpsUrl, ratioOf, type EmbedTarget } from './targets'\n\nconst props = defineProps<{\n block: { id: string; type: string; props: BlockProps }\n updateProps: (props: BlockProps) => void\n selected: boolean\n editable: boolean\n resolve: (url: string) => EmbedTarget | null\n}>()\n\nconst { messages } = useEditor()\nconst text = computed(() => messages.value.embed)\n\nconst link = computed(() => String(props.block.props.src ?? ''))\nconst target = computed(() => (link.value ? props.resolve(link.value) : null))\nconst caption = computed(() => String(props.block.props.caption ?? ''))\nconst frameStyle = computed(() => {\n const value = target.value\n if (!value) return undefined\n return value.height ? { height: `${value.height}px` } : { aspectRatio: String(ratioOf(value)) }\n})\n\nconst url = shallowRef('')\nconst invalid = shallowRef(false)\n/** Why a link can't be embedded: only https, or refused for another reason. */\nconst reason = (value: string) => (httpsUrl(value) ? text.value.invalid : text.value.insecure)\nconst error = shallowRef('')\n\n/* changing the link */\nconst editing = shallowRef(false)\nfunction edit() {\n url.value = link.value\n invalid.value = false\n editing.value = true\n}\nfunction cancel() {\n editing.value = false\n url.value = ''\n invalid.value = false\n}\n\nfunction apply() {\n const value = url.value.trim()\n if (!value) return\n if (!props.resolve(value)) {\n invalid.value = true\n error.value = reason(value)\n return\n }\n props.updateProps({ src: value })\n url.value = ''\n invalid.value = false\n editing.value = false\n}\n\n/** Enter ends the caption, unless it picks a word in an input method. */\nfunction onCaptionEnter(event: KeyboardEvent) {\n if (event.isComposing || event.keyCode === 229) return\n event.preventDefault()\n ;(event.target as HTMLInputElement).blur()\n}\n\nfunction onCaption(value: string) {\n props.updateProps({ caption: value })\n}\n</script>\n\n<template>\n <div class=\"dou-editor-embed\" :class=\"{ 'is-selected': selected }\">\n <div v-if=\"editable && (editing || !link)\" class=\"dou-editor-image__empty\">\n <AppWindowIcon class=\"dou-editor-image__empty-icon\" />\n <span class=\"dou-editor-image__empty-text\">{{ text.empty }}</span>\n <form class=\"dou-editor-image__embed\" @submit.prevent=\"apply\">\n <DouInput\n v-model=\"url\"\n size=\"sm\"\n :placeholder=\"text.urlPlaceholder\"\n :invalid=\"invalid\"\n :autofocus=\"editing\"\n @update:model-value=\"invalid = false\"\n @keydown.esc=\"editing && cancel()\"\n />\n <DouButton v-if=\"editing\" size=\"sm\" variant=\"ghost\" @click=\"cancel\">\n {{ text.cancel }}\n </DouButton>\n <DouButton size=\"sm\" variant=\"solid\" native-type=\"submit\">{{ text.apply }}</DouButton>\n </form>\n <span v-if=\"invalid\" class=\"dou-editor-embed__error\" role=\"alert\">{{ error }}</span>\n </div>\n\n <figure v-else-if=\"target\" class=\"dou-editor-embed__figure\">\n <div class=\"dou-editor-embed__frame\" :class=\"`is-${target.kind}`\" :style=\"frameStyle\">\n <iframe\n v-if=\"target.kind === 'frame'\"\n :src=\"target.src\"\n :title=\"caption || link\"\n :sandbox=\"frameSandbox\"\n :allow=\"frameAllow\"\n loading=\"lazy\"\n referrerpolicy=\"strict-origin-when-cross-origin\"\n />\n <video v-else-if=\"target.kind === 'video'\" :src=\"target.src\" controls preload=\"metadata\" />\n <audio v-else :src=\"target.src\" controls preload=\"metadata\" />\n </div>\n <figcaption v-if=\"editable\" class=\"dou-editor-image__caption dou-editor-embed__caption\">\n <input\n class=\"dou-editor-image__caption-input\"\n :value=\"caption\"\n :placeholder=\"text.caption\"\n @change=\"onCaption(($event.target as HTMLInputElement).value)\"\n @keydown.enter=\"onCaptionEnter\"\n />\n <DouTooltip :content=\"text.edit\">\n <DouButton\n size=\"sm\"\n variant=\"ghost\"\n class=\"dou-editor-embed__edit\"\n :aria-label=\"text.edit\"\n data-editor-control\n @click=\"edit\"\n >\n <PencilIcon />\n </DouButton>\n </DouTooltip>\n </figcaption>\n <figcaption v-else-if=\"caption\" class=\"dou-editor-image__caption\">{{ caption }}</figcaption>\n </figure>\n\n <div v-else-if=\"link\" class=\"dou-editor-image__empty\">\n <LinkIcon class=\"dou-editor-image__empty-icon\" />\n <span class=\"dou-editor-image__empty-text\">{{ reason(link) }}</span>\n <a\n class=\"dou-editor-embed__link\"\n :href=\"/^https?:/i.test(link) ? link : undefined\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n data-editor-control\n >\n {{ link }}\n </a>\n <DouButton v-if=\"editable\" size=\"sm\" variant=\"outline\" @click=\"edit\">\n <template #leading><PencilIcon /></template>\n {{ text.edit }}\n </DouButton>\n </div>\n </div>\n</template>\n"],"mappings":""}
@@ -0,0 +1,210 @@
1
+ import { useEditor } from "../../editor.js";
2
+ import { frameAllow, frameSandbox, httpsUrl, ratioOf } from "./targets.js";
3
+ import { DouButton, DouInput, DouTooltip } from "dou-ui";
4
+ import { computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, normalizeClass, normalizeStyle, openBlock, shallowRef, toDisplayString, unref, withCtx, withKeys, withModifiers } from "vue";
5
+ import { AppWindowIcon, LinkIcon, PencilIcon } from "@dou-ui/icons";
6
+ // src/extensions/embed/embed-block.vue?vue&type=script&setup=true&lang.ts
7
+ var _hoisted_1 = {
8
+ key: 0,
9
+ class: "dou-editor-image__empty"
10
+ };
11
+ var _hoisted_2 = { class: "dou-editor-image__empty-text" };
12
+ var _hoisted_3 = {
13
+ key: 0,
14
+ class: "dou-editor-embed__error",
15
+ role: "alert"
16
+ };
17
+ var _hoisted_4 = {
18
+ key: 1,
19
+ class: "dou-editor-embed__figure"
20
+ };
21
+ var _hoisted_5 = [
22
+ "src",
23
+ "title",
24
+ "sandbox",
25
+ "allow"
26
+ ];
27
+ var _hoisted_6 = ["src"];
28
+ var _hoisted_7 = ["src"];
29
+ var _hoisted_8 = {
30
+ key: 0,
31
+ class: "dou-editor-image__caption dou-editor-embed__caption"
32
+ };
33
+ var _hoisted_9 = ["value", "placeholder"];
34
+ var _hoisted_10 = {
35
+ key: 1,
36
+ class: "dou-editor-image__caption"
37
+ };
38
+ var _hoisted_11 = {
39
+ key: 2,
40
+ class: "dou-editor-image__empty"
41
+ };
42
+ var _hoisted_12 = { class: "dou-editor-image__empty-text" };
43
+ var _hoisted_13 = ["href"];
44
+ var embed_block_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineComponent({
45
+ __name: "embed-block",
46
+ props: {
47
+ block: {},
48
+ updateProps: { type: Function },
49
+ selected: { type: Boolean },
50
+ editable: { type: Boolean },
51
+ resolve: { type: Function }
52
+ },
53
+ setup(__props) {
54
+ const props = __props;
55
+ const { messages } = useEditor();
56
+ const text = computed(() => messages.value.embed);
57
+ const link = computed(() => String(props.block.props.src ?? ""));
58
+ const target = computed(() => link.value ? props.resolve(link.value) : null);
59
+ const caption = computed(() => String(props.block.props.caption ?? ""));
60
+ const frameStyle = computed(() => {
61
+ const value = target.value;
62
+ if (!value) return void 0;
63
+ return value.height ? { height: `${value.height}px` } : { aspectRatio: String(ratioOf(value)) };
64
+ });
65
+ const url = shallowRef("");
66
+ const invalid = shallowRef(false);
67
+ /** Why a link can't be embedded: only https, or refused for another reason. */
68
+ const reason = (value) => httpsUrl(value) ? text.value.invalid : text.value.insecure;
69
+ const error = shallowRef("");
70
+ const editing = shallowRef(false);
71
+ function edit() {
72
+ url.value = link.value;
73
+ invalid.value = false;
74
+ editing.value = true;
75
+ }
76
+ function cancel() {
77
+ editing.value = false;
78
+ url.value = "";
79
+ invalid.value = false;
80
+ }
81
+ function apply() {
82
+ const value = url.value.trim();
83
+ if (!value) return;
84
+ if (!props.resolve(value)) {
85
+ invalid.value = true;
86
+ error.value = reason(value);
87
+ return;
88
+ }
89
+ props.updateProps({ src: value });
90
+ url.value = "";
91
+ invalid.value = false;
92
+ editing.value = false;
93
+ }
94
+ /** Enter ends the caption, unless it picks a word in an input method. */
95
+ function onCaptionEnter(event) {
96
+ if (event.isComposing || event.keyCode === 229) return;
97
+ event.preventDefault();
98
+ event.target.blur();
99
+ }
100
+ function onCaption(value) {
101
+ props.updateProps({ caption: value });
102
+ }
103
+ return (_ctx, _cache) => {
104
+ return openBlock(), createElementBlock("div", { class: normalizeClass(["dou-editor-embed", { "is-selected": __props.selected }]) }, [__props.editable && (editing.value || !link.value) ? (openBlock(), createElementBlock("div", _hoisted_1, [
105
+ createVNode(unref(AppWindowIcon), { class: "dou-editor-image__empty-icon" }),
106
+ createElementVNode("span", _hoisted_2, toDisplayString(text.value.empty), 1),
107
+ createElementVNode("form", {
108
+ class: "dou-editor-image__embed",
109
+ onSubmit: withModifiers(apply, ["prevent"])
110
+ }, [
111
+ createVNode(unref(DouInput), {
112
+ modelValue: url.value,
113
+ "onUpdate:modelValue": [_cache[0] || (_cache[0] = ($event) => url.value = $event), _cache[1] || (_cache[1] = ($event) => invalid.value = false)],
114
+ size: "sm",
115
+ placeholder: text.value.urlPlaceholder,
116
+ invalid: invalid.value,
117
+ autofocus: editing.value,
118
+ onKeydown: _cache[2] || (_cache[2] = withKeys(($event) => editing.value && cancel(), ["esc"]))
119
+ }, null, 8, [
120
+ "modelValue",
121
+ "placeholder",
122
+ "invalid",
123
+ "autofocus"
124
+ ]),
125
+ editing.value ? (openBlock(), createBlock(unref(DouButton), {
126
+ key: 0,
127
+ size: "sm",
128
+ variant: "ghost",
129
+ onClick: cancel
130
+ }, {
131
+ default: withCtx(() => [createTextVNode(toDisplayString(text.value.cancel), 1)]),
132
+ _: 1
133
+ })) : createCommentVNode("", true),
134
+ createVNode(unref(DouButton), {
135
+ size: "sm",
136
+ variant: "solid",
137
+ "native-type": "submit"
138
+ }, {
139
+ default: withCtx(() => [createTextVNode(toDisplayString(text.value.apply), 1)]),
140
+ _: 1
141
+ })
142
+ ], 32),
143
+ invalid.value ? (openBlock(), createElementBlock("span", _hoisted_3, toDisplayString(error.value), 1)) : createCommentVNode("", true)
144
+ ])) : target.value ? (openBlock(), createElementBlock("figure", _hoisted_4, [createElementVNode("div", {
145
+ class: normalizeClass(["dou-editor-embed__frame", `is-${target.value.kind}`]),
146
+ style: normalizeStyle(frameStyle.value)
147
+ }, [target.value.kind === "frame" ? (openBlock(), createElementBlock("iframe", {
148
+ key: 0,
149
+ src: target.value.src,
150
+ title: caption.value || link.value,
151
+ sandbox: unref(frameSandbox),
152
+ allow: unref(frameAllow),
153
+ loading: "lazy",
154
+ referrerpolicy: "strict-origin-when-cross-origin"
155
+ }, null, 8, _hoisted_5)) : target.value.kind === "video" ? (openBlock(), createElementBlock("video", {
156
+ key: 1,
157
+ src: target.value.src,
158
+ controls: "",
159
+ preload: "metadata"
160
+ }, null, 8, _hoisted_6)) : (openBlock(), createElementBlock("audio", {
161
+ key: 2,
162
+ src: target.value.src,
163
+ controls: "",
164
+ preload: "metadata"
165
+ }, null, 8, _hoisted_7))], 6), __props.editable ? (openBlock(), createElementBlock("figcaption", _hoisted_8, [createElementVNode("input", {
166
+ class: "dou-editor-image__caption-input",
167
+ value: caption.value,
168
+ placeholder: text.value.caption,
169
+ onChange: _cache[3] || (_cache[3] = ($event) => onCaption($event.target.value)),
170
+ onKeydown: withKeys(onCaptionEnter, ["enter"])
171
+ }, null, 40, _hoisted_9), createVNode(unref(DouTooltip), { content: text.value.edit }, {
172
+ default: withCtx(() => [createVNode(unref(DouButton), {
173
+ size: "sm",
174
+ variant: "ghost",
175
+ class: "dou-editor-embed__edit",
176
+ "aria-label": text.value.edit,
177
+ "data-editor-control": "",
178
+ onClick: edit
179
+ }, {
180
+ default: withCtx(() => [createVNode(unref(PencilIcon))]),
181
+ _: 1
182
+ }, 8, ["aria-label"])]),
183
+ _: 1
184
+ }, 8, ["content"])])) : caption.value ? (openBlock(), createElementBlock("figcaption", _hoisted_10, toDisplayString(caption.value), 1)) : createCommentVNode("", true)])) : link.value ? (openBlock(), createElementBlock("div", _hoisted_11, [
185
+ createVNode(unref(LinkIcon), { class: "dou-editor-image__empty-icon" }),
186
+ createElementVNode("span", _hoisted_12, toDisplayString(reason(link.value)), 1),
187
+ createElementVNode("a", {
188
+ class: "dou-editor-embed__link",
189
+ href: /^https?:/i.test(link.value) ? link.value : void 0,
190
+ target: "_blank",
191
+ rel: "noopener noreferrer",
192
+ "data-editor-control": ""
193
+ }, toDisplayString(link.value), 9, _hoisted_13),
194
+ __props.editable ? (openBlock(), createBlock(unref(DouButton), {
195
+ key: 0,
196
+ size: "sm",
197
+ variant: "outline",
198
+ onClick: edit
199
+ }, {
200
+ leading: withCtx(() => [createVNode(unref(PencilIcon))]),
201
+ default: withCtx(() => [createTextVNode(" " + toDisplayString(text.value.edit), 1)]),
202
+ _: 1
203
+ })) : createCommentVNode("", true)
204
+ ])) : createCommentVNode("", true)], 2);
205
+ };
206
+ }
207
+ });
208
+ export { embed_block_vue_vue_type_script_setup_true_lang_default as default };
209
+
210
+ //# sourceMappingURL=embed-block.vue_vue_type_script_setup_true_lang.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"embed-block.vue_vue_type_script_setup_true_lang.js","names":["_openBlock","_createElementBlock","_normalizeClass","_createVNode","_unref","_createElementVNode","_toDisplayString","_withModifiers","_withKeys","_createBlock","_withCtx","_createTextVNode","_createCommentVNode","_normalizeStyle"],"sources":["../../../src/extensions/embed/embed-block.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { AppWindowIcon, LinkIcon, PencilIcon } from '@dou-ui/icons'\nimport { DouButton, DouInput, DouTooltip } from 'dou-ui'\nimport { computed, shallowRef } from 'vue'\nimport type { BlockProps } from '../../core/model'\nimport { useEditor } from '../../editor'\nimport { frameAllow, frameSandbox, httpsUrl, ratioOf, type EmbedTarget } from './targets'\n\nconst props = defineProps<{\n block: { id: string; type: string; props: BlockProps }\n updateProps: (props: BlockProps) => void\n selected: boolean\n editable: boolean\n resolve: (url: string) => EmbedTarget | null\n}>()\n\nconst { messages } = useEditor()\nconst text = computed(() => messages.value.embed)\n\nconst link = computed(() => String(props.block.props.src ?? ''))\nconst target = computed(() => (link.value ? props.resolve(link.value) : null))\nconst caption = computed(() => String(props.block.props.caption ?? ''))\nconst frameStyle = computed(() => {\n const value = target.value\n if (!value) return undefined\n return value.height ? { height: `${value.height}px` } : { aspectRatio: String(ratioOf(value)) }\n})\n\nconst url = shallowRef('')\nconst invalid = shallowRef(false)\n/** Why a link can't be embedded: only https, or refused for another reason. */\nconst reason = (value: string) => (httpsUrl(value) ? text.value.invalid : text.value.insecure)\nconst error = shallowRef('')\n\n/* changing the link */\nconst editing = shallowRef(false)\nfunction edit() {\n url.value = link.value\n invalid.value = false\n editing.value = true\n}\nfunction cancel() {\n editing.value = false\n url.value = ''\n invalid.value = false\n}\n\nfunction apply() {\n const value = url.value.trim()\n if (!value) return\n if (!props.resolve(value)) {\n invalid.value = true\n error.value = reason(value)\n return\n }\n props.updateProps({ src: value })\n url.value = ''\n invalid.value = false\n editing.value = false\n}\n\n/** Enter ends the caption, unless it picks a word in an input method. */\nfunction onCaptionEnter(event: KeyboardEvent) {\n if (event.isComposing || event.keyCode === 229) return\n event.preventDefault()\n ;(event.target as HTMLInputElement).blur()\n}\n\nfunction onCaption(value: string) {\n props.updateProps({ caption: value })\n}\n</script>\n\n<template>\n <div class=\"dou-editor-embed\" :class=\"{ 'is-selected': selected }\">\n <div v-if=\"editable && (editing || !link)\" class=\"dou-editor-image__empty\">\n <AppWindowIcon class=\"dou-editor-image__empty-icon\" />\n <span class=\"dou-editor-image__empty-text\">{{ text.empty }}</span>\n <form class=\"dou-editor-image__embed\" @submit.prevent=\"apply\">\n <DouInput\n v-model=\"url\"\n size=\"sm\"\n :placeholder=\"text.urlPlaceholder\"\n :invalid=\"invalid\"\n :autofocus=\"editing\"\n @update:model-value=\"invalid = false\"\n @keydown.esc=\"editing && cancel()\"\n />\n <DouButton v-if=\"editing\" size=\"sm\" variant=\"ghost\" @click=\"cancel\">\n {{ text.cancel }}\n </DouButton>\n <DouButton size=\"sm\" variant=\"solid\" native-type=\"submit\">{{ text.apply }}</DouButton>\n </form>\n <span v-if=\"invalid\" class=\"dou-editor-embed__error\" role=\"alert\">{{ error }}</span>\n </div>\n\n <figure v-else-if=\"target\" class=\"dou-editor-embed__figure\">\n <div class=\"dou-editor-embed__frame\" :class=\"`is-${target.kind}`\" :style=\"frameStyle\">\n <iframe\n v-if=\"target.kind === 'frame'\"\n :src=\"target.src\"\n :title=\"caption || link\"\n :sandbox=\"frameSandbox\"\n :allow=\"frameAllow\"\n loading=\"lazy\"\n referrerpolicy=\"strict-origin-when-cross-origin\"\n />\n <video v-else-if=\"target.kind === 'video'\" :src=\"target.src\" controls preload=\"metadata\" />\n <audio v-else :src=\"target.src\" controls preload=\"metadata\" />\n </div>\n <figcaption v-if=\"editable\" class=\"dou-editor-image__caption dou-editor-embed__caption\">\n <input\n class=\"dou-editor-image__caption-input\"\n :value=\"caption\"\n :placeholder=\"text.caption\"\n @change=\"onCaption(($event.target as HTMLInputElement).value)\"\n @keydown.enter=\"onCaptionEnter\"\n />\n <DouTooltip :content=\"text.edit\">\n <DouButton\n size=\"sm\"\n variant=\"ghost\"\n class=\"dou-editor-embed__edit\"\n :aria-label=\"text.edit\"\n data-editor-control\n @click=\"edit\"\n >\n <PencilIcon />\n </DouButton>\n </DouTooltip>\n </figcaption>\n <figcaption v-else-if=\"caption\" class=\"dou-editor-image__caption\">{{ caption }}</figcaption>\n </figure>\n\n <div v-else-if=\"link\" class=\"dou-editor-image__empty\">\n <LinkIcon class=\"dou-editor-image__empty-icon\" />\n <span class=\"dou-editor-image__empty-text\">{{ reason(link) }}</span>\n <a\n class=\"dou-editor-embed__link\"\n :href=\"/^https?:/i.test(link) ? link : undefined\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n data-editor-control\n >\n {{ link }}\n </a>\n <DouButton v-if=\"editable\" size=\"sm\" variant=\"outline\" @click=\"edit\">\n <template #leading><PencilIcon /></template>\n {{ text.edit }}\n </DouButton>\n </div>\n </div>\n</template>\n"],"mappings":"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"}
@@ -0,0 +1,118 @@
1
+ import { escapeHTML, escapeMarkdown } from "../../core/output.js";
2
+ import { defineBlock } from "../../define-block.js";
3
+ import { checkTarget, frameAllow, frameSandbox, httpsUrl, ratioOf, resolveEmbed } from "./targets.js";
4
+ import embed_block_default from "./embed-block.js";
5
+ import { defineComponent, h } from "vue";
6
+ import { AppWindowIcon } from "@dou-ui/icons";
7
+ // src/extensions/embed/index.ts
8
+ function attrsOf(dom) {
9
+ const src = dom.dataset.embed ?? dom.getAttribute("src") ?? dom.querySelector("[src]")?.getAttribute("src");
10
+ if (!src || !httpsUrl(src)) return false;
11
+ return {
12
+ src,
13
+ caption: dom.querySelector("figcaption")?.textContent?.trim() ?? dom.getAttribute("title") ?? ""
14
+ };
15
+ }
16
+ function playerHTML(target, title) {
17
+ const src = escapeHTML(target.src);
18
+ if (target.kind === "video") return `<video src="${src}" controls preload="metadata" style="aspect-ratio: ${ratioOf(target)}"></video>`;
19
+ if (target.kind === "audio") return `<audio src="${src}" controls preload="metadata"></audio>`;
20
+ const size = target.height ? `height: ${Number(target.height)}px` : `aspect-ratio: ${ratioOf(target)}`;
21
+ return `<iframe src="${src}" title="${escapeHTML(title)}" sandbox="${frameSandbox}" allow="${frameAllow}" loading="lazy" referrerpolicy="strict-origin-when-cross-origin" style="width: 100%; ${size}"></iframe>`;
22
+ }
23
+ /**
24
+ * Embed blocks: a web page, a video or an audio file from a link, with a
25
+ * caption. Only https links are embedded.
26
+ */
27
+ function embed(options = {}) {
28
+ const resolve = (url) => {
29
+ if (!httpsUrl(url)) return null;
30
+ return checkTarget((options.resolve ?? resolveEmbed)(url.trim()));
31
+ };
32
+ const View = defineComponent({
33
+ name: "DouEditorEmbedView",
34
+ inheritAttrs: false,
35
+ setup(_props, { attrs }) {
36
+ return () => h(embed_block_default, {
37
+ ...attrs,
38
+ resolve
39
+ });
40
+ }
41
+ });
42
+ return {
43
+ ...defineBlock({
44
+ type: "embed",
45
+ props: {
46
+ src: "",
47
+ caption: ""
48
+ },
49
+ content: "none",
50
+ component: View,
51
+ menu: {
52
+ label: (m) => m.types.embed,
53
+ hint: (m) => m.hints.embed,
54
+ icon: AppWindowIcon,
55
+ keywords: [
56
+ "embed",
57
+ "video",
58
+ "audio",
59
+ "iframe",
60
+ "qr",
61
+ "qianru",
62
+ "shipin",
63
+ "yinpin"
64
+ ]
65
+ },
66
+ spec: {
67
+ parseDOM: [
68
+ {
69
+ tag: "figure[data-embed]",
70
+ priority: 60,
71
+ getAttrs: (dom) => attrsOf(dom)
72
+ },
73
+ {
74
+ tag: "iframe[src]",
75
+ getAttrs: (dom) => attrsOf(dom)
76
+ },
77
+ {
78
+ tag: "video",
79
+ getAttrs: (dom) => attrsOf(dom)
80
+ },
81
+ {
82
+ tag: "audio",
83
+ getAttrs: (dom) => attrsOf(dom)
84
+ }
85
+ ],
86
+ toDOM: (node) => [
87
+ "figure",
88
+ {
89
+ class: "dou-editor__content dou-editor__content--embed",
90
+ "data-content-type": "embed",
91
+ "data-embed": httpsUrl(String(node.attrs.src)) ? node.attrs.src : null
92
+ },
93
+ ...node.attrs.caption ? [["figcaption", node.attrs.caption]] : []
94
+ ]
95
+ }
96
+ }),
97
+ output: { blocks: { embed: {
98
+ html: (node, { children }) => {
99
+ const link = String(node.props.src ?? "");
100
+ const caption = String(node.props.caption ?? "");
101
+ const target = resolve(link);
102
+ if (!target) return children;
103
+ const figcaption = caption ? `<figcaption>${escapeHTML(caption)}</figcaption>` : "";
104
+ return `<figure data-embed="${escapeHTML(link)}">${playerHTML(target, caption || link)}${figcaption}</figure>${children}`;
105
+ },
106
+ markdown: (node, { children }) => {
107
+ const link = String(node.props.src ?? "");
108
+ if (!resolve(link)) return children;
109
+ const address = /[\s()<>]/.test(link) ? `<${link}>` : link;
110
+ const written = `[${escapeMarkdown(String(node.props.caption || link))}](${address})`;
111
+ return children ? `${written}\n\n${children}` : written;
112
+ }
113
+ } } }
114
+ };
115
+ }
116
+ export { embed, resolveEmbed };
117
+
118
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["EmbedBlock"],"sources":["../../../src/extensions/embed/index.ts"],"sourcesContent":["import { AppWindowIcon } from '@dou-ui/icons'\nimport { defineComponent, h } from 'vue'\nimport type { EditorExtension } from '../../core/extension'\nimport { escapeHTML, escapeMarkdown } from '../../core/output'\nimport { defineBlock } from '../../define-block'\nimport EmbedBlock from './embed-block.vue'\nimport {\n checkTarget,\n frameAllow,\n frameSandbox,\n httpsUrl,\n ratioOf,\n resolveEmbed,\n type EmbedResolver,\n type EmbedTarget,\n} from './targets'\n\nexport interface EmbedOptions {\n /**\n * Turn a link into what the block shows: a frame, a video or an audio\n * player, or `null` to refuse it. The block keeps the link itself, so a\n * changed resolver applies to documents already written. Only https\n * addresses are ever loaded. By default media files play in the browser's\n * player and other pages show in a frame.\n */\n resolve?: EmbedResolver\n}\n\nfunction attrsOf(dom: HTMLElement) {\n const src =\n dom.dataset.embed ?? dom.getAttribute('src') ?? dom.querySelector('[src]')?.getAttribute('src')\n if (!src || !httpsUrl(src)) return false\n return {\n src,\n caption:\n dom.querySelector('figcaption')?.textContent?.trim() ?? dom.getAttribute('title') ?? '',\n }\n}\n\nfunction playerHTML(target: EmbedTarget, title: string): string {\n const src = escapeHTML(target.src)\n if (target.kind === 'video')\n return `<video src=\"${src}\" controls preload=\"metadata\" style=\"aspect-ratio: ${ratioOf(target)}\"></video>`\n if (target.kind === 'audio') return `<audio src=\"${src}\" controls preload=\"metadata\"></audio>`\n const size = target.height\n ? `height: ${Number(target.height)}px`\n : `aspect-ratio: ${ratioOf(target)}`\n return `<iframe src=\"${src}\" title=\"${escapeHTML(title)}\" sandbox=\"${frameSandbox}\" allow=\"${frameAllow}\" loading=\"lazy\" referrerpolicy=\"strict-origin-when-cross-origin\" style=\"width: 100%; ${size}\"></iframe>`\n}\n\n/**\n * Embed blocks: a web page, a video or an audio file from a link, with a\n * caption. Only https links are embedded.\n */\nexport function embed(options: EmbedOptions = {}): EditorExtension {\n const resolve = (url: string) => {\n if (!httpsUrl(url)) return null\n return checkTarget((options.resolve ?? resolveEmbed)(url.trim()))\n }\n const View = defineComponent({\n name: 'DouEditorEmbedView',\n inheritAttrs: false,\n setup(_props, { attrs }) {\n return () => h(EmbedBlock, { ...attrs, resolve } as never)\n },\n })\n const block = defineBlock({\n type: 'embed',\n props: { src: '', caption: '' },\n content: 'none',\n component: View,\n menu: {\n label: (m) => m.types.embed,\n hint: (m) => m.hints.embed,\n icon: AppWindowIcon,\n keywords: ['embed', 'video', 'audio', 'iframe', 'qr', 'qianru', 'shipin', 'yinpin'],\n },\n spec: {\n parseDOM: [\n { tag: 'figure[data-embed]', priority: 60, getAttrs: (dom) => attrsOf(dom as HTMLElement) },\n { tag: 'iframe[src]', getAttrs: (dom) => attrsOf(dom as HTMLElement) },\n { tag: 'video', getAttrs: (dom) => attrsOf(dom as HTMLElement) },\n { tag: 'audio', getAttrs: (dom) => attrsOf(dom as HTMLElement) },\n ],\n // the link only: frames load in the view, not in what is copied\n toDOM: (node) =>\n [\n 'figure',\n {\n class: 'dou-editor__content dou-editor__content--embed',\n 'data-content-type': 'embed',\n 'data-embed': httpsUrl(String(node.attrs.src)) ? node.attrs.src : null,\n },\n ...(node.attrs.caption ? [['figcaption', node.attrs.caption]] : []),\n ] as never,\n },\n })\n return {\n ...block,\n output: {\n blocks: {\n embed: {\n html: (node, { children }) => {\n const link = String(node.props.src ?? '')\n const caption = String(node.props.caption ?? '')\n const target = resolve(link)\n if (!target) return children\n const figcaption = caption ? `<figcaption>${escapeHTML(caption)}</figcaption>` : ''\n return `<figure data-embed=\"${escapeHTML(link)}\">${playerHTML(target, caption || link)}${figcaption}</figure>${children}`\n },\n markdown: (node, { children }) => {\n const link = String(node.props.src ?? '')\n if (!resolve(link)) return children\n const address = /[\\s()<>]/.test(link) ? `<${link}>` : link\n const written = `[${escapeMarkdown(String(node.props.caption || link))}](${address})`\n return children ? `${written}\\n\\n${children}` : written\n },\n },\n },\n },\n }\n}\n\nexport { resolveEmbed, type EmbedResolver, type EmbedTarget }\n"],"mappings":"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"}
@@ -0,0 +1,79 @@
1
+ // src/extensions/embed/targets.ts
2
+ var videoTypes = [
3
+ "mp4",
4
+ "webm",
5
+ "ogv",
6
+ "mov",
7
+ "m4v"
8
+ ];
9
+ var audioTypes = [
10
+ "mp3",
11
+ "wav",
12
+ "oga",
13
+ "ogg",
14
+ "m4a",
15
+ "aac",
16
+ "flac",
17
+ "opus"
18
+ ];
19
+ /** The link as a URL, when it is an absolute https address. */
20
+ function httpsUrl(value) {
21
+ try {
22
+ const url = new URL(value.trim());
23
+ return url.protocol === "https:" ? url : null;
24
+ } catch {
25
+ return null;
26
+ }
27
+ }
28
+ /**
29
+ * The default: https links only. Media files play in the browser's player,
30
+ * any other page shows in a frame. Frames of the editor's own site are
31
+ * refused whatever the resolver (see `checkTarget`).
32
+ */
33
+ var resolveEmbed = (value) => {
34
+ const url = httpsUrl(value);
35
+ if (!url) return null;
36
+ const ext = /\.([a-z0-9]+)$/i.exec(url.pathname)?.[1]?.toLowerCase() ?? "";
37
+ if (videoTypes.includes(ext)) return {
38
+ kind: "video",
39
+ src: url.href
40
+ };
41
+ if (audioTypes.includes(ext)) return {
42
+ kind: "audio",
43
+ src: url.href
44
+ };
45
+ return {
46
+ kind: "frame",
47
+ src: url.href
48
+ };
49
+ };
50
+ /** What frames may do: run scripts, open links in a new tab, go full screen. */
51
+ var frameSandbox = "allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox allow-presentation allow-forms";
52
+ var frameAllow = "fullscreen; picture-in-picture; encrypted-media; clipboard-write";
53
+ /**
54
+ * A resolver's answer, checked: only https addresses get through, and no
55
+ * frame of the page's own site, which the sandbox would not keep apart
56
+ * (it allows scripts and the site's own storage). Without a page (on a
57
+ * server) the site is unknown: see the docs.
58
+ */
59
+ function checkTarget(target) {
60
+ if (!target || ![
61
+ "frame",
62
+ "video",
63
+ "audio"
64
+ ].includes(target.kind)) return null;
65
+ const url = httpsUrl(target.src);
66
+ if (!url) return null;
67
+ if (target.kind === "frame" && typeof location !== "undefined" && url.origin === location.origin) return null;
68
+ return {
69
+ ...target,
70
+ src: url.href
71
+ };
72
+ }
73
+ function ratioOf(target) {
74
+ const value = Number(target.aspectRatio);
75
+ return value > 0 ? value : 16 / 9;
76
+ }
77
+ export { checkTarget, frameAllow, frameSandbox, httpsUrl, ratioOf, resolveEmbed };
78
+
79
+ //# sourceMappingURL=targets.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"targets.js","names":[],"sources":["../../../src/extensions/embed/targets.ts"],"sourcesContent":["/** What an embed block shows for its link. */\nexport interface EmbedTarget {\n /** A web page in a frame, or the browser's own video or audio player. */\n kind: 'frame' | 'video' | 'audio'\n /** The address the frame or player loads; it may differ from the link, as for a player page. */\n src: string\n /** Width over height of a frame or video. Default `16 / 9`. */\n aspectRatio?: number\n /** A frame's fixed height in px, instead of `aspectRatio`. */\n height?: number\n}\n\n/** Turns a link into what the block shows, or `null` to refuse the link. */\nexport type EmbedResolver = (url: string) => EmbedTarget | null\n\nconst videoTypes = ['mp4', 'webm', 'ogv', 'mov', 'm4v']\nconst audioTypes = ['mp3', 'wav', 'oga', 'ogg', 'm4a', 'aac', 'flac', 'opus']\n\n/** The link as a URL, when it is an absolute https address. */\nexport function httpsUrl(value: string): URL | null {\n try {\n const url = new URL(value.trim())\n return url.protocol === 'https:' ? url : null\n } catch {\n return null\n }\n}\n\n/**\n * The default: https links only. Media files play in the browser's player,\n * any other page shows in a frame. Frames of the editor's own site are\n * refused whatever the resolver (see `checkTarget`).\n */\nexport const resolveEmbed: EmbedResolver = (value) => {\n const url = httpsUrl(value)\n if (!url) return null\n const ext = /\\.([a-z0-9]+)$/i.exec(url.pathname)?.[1]?.toLowerCase() ?? ''\n if (videoTypes.includes(ext)) return { kind: 'video', src: url.href }\n if (audioTypes.includes(ext)) return { kind: 'audio', src: url.href }\n return { kind: 'frame', src: url.href }\n}\n\n/** What frames may do: run scripts, open links in a new tab, go full screen. */\nexport const frameSandbox =\n 'allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox allow-presentation allow-forms'\nexport const frameAllow = 'fullscreen; picture-in-picture; encrypted-media; clipboard-write'\n\n/**\n * A resolver's answer, checked: only https addresses get through, and no\n * frame of the page's own site, which the sandbox would not keep apart\n * (it allows scripts and the site's own storage). Without a page (on a\n * server) the site is unknown: see the docs.\n */\nexport function checkTarget(target: EmbedTarget | null | undefined): EmbedTarget | null {\n if (!target || !['frame', 'video', 'audio'].includes(target.kind)) return null\n const url = httpsUrl(target.src)\n if (!url) return null\n if (target.kind === 'frame' && typeof location !== 'undefined' && url.origin === location.origin)\n return null\n return { ...target, src: url.href }\n}\n\nexport function ratioOf(target: EmbedTarget): number {\n const value = Number(target.aspectRatio)\n return value > 0 ? value : 16 / 9\n}\n"],"mappings":";AAeA,IAAM,aAAa;CAAC;CAAO;CAAQ;CAAO;CAAO;AAAK;AACtD,IAAM,aAAa;CAAC;CAAO;CAAO;CAAO;CAAO;CAAO;CAAO;CAAQ;AAAM;;AAG5E,SAAgB,SAAS,OAA2B;CAClD,IAAI;EACF,MAAM,MAAM,IAAI,IAAI,MAAM,KAAK,CAAC;EAChC,OAAO,IAAI,aAAa,WAAW,MAAM;CAC3C,QAAQ;EACN,OAAO;CACT;AACF;;;;;;AAOA,IAAa,gBAA+B,UAAU;CACpD,MAAM,MAAM,SAAS,KAAK;CAC1B,IAAI,CAAC,KAAK,OAAO;CACjB,MAAM,MAAM,kBAAkB,KAAK,IAAI,QAAQ,CAAC,GAAG,EAAE,EAAE,YAAY,KAAK;CACxE,IAAI,WAAW,SAAS,GAAG,GAAG,OAAO;EAAE,MAAM;EAAS,KAAK,IAAI;CAAK;CACpE,IAAI,WAAW,SAAS,GAAG,GAAG,OAAO;EAAE,MAAM;EAAS,KAAK,IAAI;CAAK;CACpE,OAAO;EAAE,MAAM;EAAS,KAAK,IAAI;CAAK;AACxC;;AAGA,IAAa,eACX;AACF,IAAa,aAAa;;;;;;;AAQ1B,SAAgB,YAAY,QAA4D;CACtF,IAAI,CAAC,UAAU,CAAC;EAAC;EAAS;EAAS;CAAO,CAAC,CAAC,SAAS,OAAO,IAAI,GAAG,OAAO;CAC1E,MAAM,MAAM,SAAS,OAAO,GAAG;CAC/B,IAAI,CAAC,KAAK,OAAO;CACjB,IAAI,OAAO,SAAS,WAAW,OAAO,aAAa,eAAe,IAAI,WAAW,SAAS,QACxF,OAAO;CACT,OAAO;EAAE,GAAG;EAAQ,KAAK,IAAI;CAAK;AACpC;AAEA,SAAgB,QAAQ,QAA6B;CACnD,MAAM,QAAQ,OAAO,OAAO,WAAW;CACvC,OAAO,QAAQ,IAAI,QAAQ,KAAK;AAClC"}
@@ -0,0 +1,153 @@
1
+ import { isSafeHref } from "../core/schema.js";
2
+ import { defineEditorExtension } from "../core/extension.js";
3
+ import { markInputRule } from "../core/input-rules.js";
4
+ import { BoldIcon, CodeIcon, ItalicIcon, StrikethroughIcon, UnderlineIcon } from "@dou-ui/icons";
5
+ import { toggleMark } from "prosemirror-commands";
6
+ // src/extensions/formatting.ts
7
+ /** Bold, italic, underline, strikethrough and inline code, with shortcuts and toolbar toggles. */
8
+ var formatting = defineEditorExtension({
9
+ name: "formatting",
10
+ marks: {
11
+ bold: {
12
+ parseDOM: [
13
+ { tag: "strong" },
14
+ {
15
+ tag: "b",
16
+ getAttrs: (dom) => dom.style.fontWeight !== "normal" && null
17
+ },
18
+ {
19
+ style: "font-weight",
20
+ getAttrs: (value) => /^(bold(er)?|[6-9]\d{2})$/.test(value) && null
21
+ }
22
+ ],
23
+ toDOM: () => ["strong", 0]
24
+ },
25
+ italic: {
26
+ parseDOM: [
27
+ { tag: "i" },
28
+ { tag: "em" },
29
+ { style: "font-style=italic" }
30
+ ],
31
+ toDOM: () => ["em", 0]
32
+ },
33
+ underline: {
34
+ parseDOM: [{ tag: "u" }, { style: "text-decoration=underline" }],
35
+ toDOM: () => ["u", 0]
36
+ },
37
+ strike: {
38
+ parseDOM: [
39
+ { tag: "s" },
40
+ { tag: "del" },
41
+ { style: "text-decoration=line-through" }
42
+ ],
43
+ toDOM: () => ["s", 0]
44
+ },
45
+ code: {
46
+ excludes: "_",
47
+ parseDOM: [{ tag: "code" }],
48
+ toDOM: () => [
49
+ "code",
50
+ { class: "dou-editor__inline-code" },
51
+ 0
52
+ ]
53
+ }
54
+ },
55
+ output: { marks: {
56
+ bold: {
57
+ html: ["<strong>", "</strong>"],
58
+ markdown: ["**", "**"]
59
+ },
60
+ italic: {
61
+ html: ["<em>", "</em>"],
62
+ markdown: ["*", "*"]
63
+ },
64
+ underline: {
65
+ html: ["<u>", "</u>"],
66
+ markdown: ["<u>", "</u>"]
67
+ },
68
+ strike: {
69
+ html: ["<s>", "</s>"],
70
+ markdown: ["~~", "~~"]
71
+ },
72
+ code: { html: ["<code>", "</code>"] }
73
+ } },
74
+ inputRules: () => [
75
+ markInputRule(/(?:^|[^*])(\*\*([^*\s](?:[^*]*[^*\s])?)\*\*)$/, "bold"),
76
+ markInputRule(/(?:^|[^*])(\*([^*\s](?:[^*]*[^*\s])?)\*)$/, "italic"),
77
+ markInputRule(/(?:^|[^~])(~~([^~\s](?:[^~]*[^~\s])?)~~)$/, "strike"),
78
+ markInputRule(/(?:^|[^`])(`([^`\s](?:[^`]*[^`\s])?)`)$/, "code")
79
+ ],
80
+ keymap: ({ schema }) => {
81
+ const toggle = (name) => toggleMark(schema.marks[name]);
82
+ return {
83
+ "mod+b": toggle("bold"),
84
+ "mod+i": toggle("italic"),
85
+ "mod+u": toggle("underline"),
86
+ "mod+shift+s": toggle("strike"),
87
+ "mod+e": toggle("code")
88
+ };
89
+ },
90
+ formats: [
91
+ {
92
+ mark: "bold",
93
+ label: (m) => m.bold,
94
+ icon: BoldIcon,
95
+ hotkey: "mod+b"
96
+ },
97
+ {
98
+ mark: "italic",
99
+ label: (m) => m.italic,
100
+ icon: ItalicIcon,
101
+ hotkey: "mod+i"
102
+ },
103
+ {
104
+ mark: "underline",
105
+ label: (m) => m.underline,
106
+ icon: UnderlineIcon,
107
+ hotkey: "mod+u"
108
+ },
109
+ {
110
+ mark: "strike",
111
+ label: (m) => m.strike,
112
+ icon: StrikethroughIcon,
113
+ hotkey: "mod+shift+s"
114
+ },
115
+ {
116
+ mark: "code",
117
+ label: (m) => m.code,
118
+ icon: CodeIcon,
119
+ hotkey: "mod+e"
120
+ }
121
+ ]
122
+ });
123
+ /**
124
+ * Links. Only web pages, mail, phone numbers and relative paths are kept;
125
+ * the formatting toolbar edits them, and `Mod+K` opens the editor for the
126
+ * selected text.
127
+ */
128
+ var link = defineEditorExtension({
129
+ name: "link",
130
+ marks: { link: {
131
+ attrs: { href: {} },
132
+ inclusive: false,
133
+ parseDOM: [{
134
+ tag: "a[href]",
135
+ getAttrs: (dom) => {
136
+ const href = dom.getAttribute("href") ?? "";
137
+ return isSafeHref(href) ? { href } : false;
138
+ }
139
+ }],
140
+ toDOM: (mark) => [
141
+ "a",
142
+ {
143
+ href: isSafeHref(mark.attrs.href) ? mark.attrs.href : null,
144
+ rel: "noopener noreferrer nofollow",
145
+ class: "dou-editor__link"
146
+ },
147
+ 0
148
+ ]
149
+ } }
150
+ });
151
+ export { formatting, link };
152
+
153
+ //# sourceMappingURL=formatting.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"formatting.js","names":[],"sources":["../../src/extensions/formatting.ts"],"sourcesContent":["import { BoldIcon, CodeIcon, ItalicIcon, StrikethroughIcon, UnderlineIcon } from '@dou-ui/icons'\nimport { toggleMark } from 'prosemirror-commands'\nimport type { Command } from 'prosemirror-state'\nimport { defineEditorExtension } from '../core/extension'\nimport { markInputRule } from '../core/input-rules'\nimport { isSafeHref } from '../core/schema'\n\n/** Bold, italic, underline, strikethrough and inline code, with shortcuts and toolbar toggles. */\nexport const formatting = defineEditorExtension({\n name: 'formatting',\n marks: {\n bold: {\n parseDOM: [\n { tag: 'strong' },\n { tag: 'b', getAttrs: (dom) => (dom as HTMLElement).style.fontWeight !== 'normal' && null },\n {\n style: 'font-weight',\n getAttrs: (value) => /^(bold(er)?|[6-9]\\d{2})$/.test(value as string) && null,\n },\n ],\n toDOM: () => ['strong', 0],\n },\n italic: {\n parseDOM: [{ tag: 'i' }, { tag: 'em' }, { style: 'font-style=italic' }],\n toDOM: () => ['em', 0],\n },\n underline: {\n parseDOM: [{ tag: 'u' }, { style: 'text-decoration=underline' }],\n toDOM: () => ['u', 0],\n },\n strike: {\n parseDOM: [{ tag: 's' }, { tag: 'del' }, { style: 'text-decoration=line-through' }],\n toDOM: () => ['s', 0],\n },\n code: {\n excludes: '_',\n parseDOM: [{ tag: 'code' }],\n toDOM: () => ['code', { class: 'dou-editor__inline-code' }, 0],\n },\n },\n output: {\n marks: {\n bold: { html: ['<strong>', '</strong>'], markdown: ['**', '**'] },\n italic: { html: ['<em>', '</em>'], markdown: ['*', '*'] },\n // Markdown has no underline; HTML is read back as one\n underline: { html: ['<u>', '</u>'], markdown: ['<u>', '</u>'] },\n strike: { html: ['<s>', '</s>'], markdown: ['~~', '~~'] },\n code: { html: ['<code>', '</code>'] },\n },\n },\n inputRules: () => [\n markInputRule(/(?:^|[^*])(\\*\\*([^*\\s](?:[^*]*[^*\\s])?)\\*\\*)$/, 'bold'),\n markInputRule(/(?:^|[^*])(\\*([^*\\s](?:[^*]*[^*\\s])?)\\*)$/, 'italic'),\n markInputRule(/(?:^|[^~])(~~([^~\\s](?:[^~]*[^~\\s])?)~~)$/, 'strike'),\n markInputRule(/(?:^|[^`])(`([^`\\s](?:[^`]*[^`\\s])?)`)$/, 'code'),\n ],\n keymap: ({ schema }) => {\n const toggle = (name: string): Command => toggleMark(schema.marks[name]!)\n return {\n 'mod+b': toggle('bold'),\n 'mod+i': toggle('italic'),\n 'mod+u': toggle('underline'),\n 'mod+shift+s': toggle('strike'),\n 'mod+e': toggle('code'),\n }\n },\n formats: [\n { mark: 'bold', label: (m) => m.bold, icon: BoldIcon, hotkey: 'mod+b' },\n { mark: 'italic', label: (m) => m.italic, icon: ItalicIcon, hotkey: 'mod+i' },\n { mark: 'underline', label: (m) => m.underline, icon: UnderlineIcon, hotkey: 'mod+u' },\n { mark: 'strike', label: (m) => m.strike, icon: StrikethroughIcon, hotkey: 'mod+shift+s' },\n { mark: 'code', label: (m) => m.code, icon: CodeIcon, hotkey: 'mod+e' },\n ],\n})\n\n/**\n * Links. Only web pages, mail, phone numbers and relative paths are kept;\n * the formatting toolbar edits them, and `Mod+K` opens the editor for the\n * selected text.\n */\nexport const link = defineEditorExtension({\n name: 'link',\n marks: {\n link: {\n attrs: { href: {} },\n inclusive: false,\n parseDOM: [\n {\n tag: 'a[href]',\n getAttrs: (dom) => {\n const href = (dom as HTMLElement).getAttribute('href') ?? ''\n return isSafeHref(href) ? { href } : false\n },\n },\n ],\n toDOM: (mark) => [\n 'a',\n {\n href: isSafeHref(mark.attrs.href as string) ? mark.attrs.href : null,\n rel: 'noopener noreferrer nofollow',\n class: 'dou-editor__link',\n },\n 0,\n ],\n },\n },\n})\n"],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import image_block_vue_vue_type_script_setup_true_lang_default from "./image-block.vue_vue_type_script_setup_true_lang.js";
2
+ // src/extensions/image/image-block.vue
3
+ var image_block_default = image_block_vue_vue_type_script_setup_true_lang_default;
4
+ export { image_block_default as default };
5
+
6
+ //# sourceMappingURL=image-block.js.map