@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.
- package/LICENSE +21 -0
- package/README.md +10 -2
- package/dist/catalog.js +18 -0
- package/dist/catalog.js.map +1 -0
- package/dist/code/code-editor-block.js +6 -0
- package/dist/code/code-editor-block.js.map +1 -0
- package/dist/code/code-editor-block.vue_vue_type_script_setup_true_lang.js +116 -0
- package/dist/code/code-editor-block.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/code/index.js +69 -0
- package/dist/code/index.js.map +1 -0
- package/dist/code/languages.js +51 -0
- package/dist/code/languages.js.map +1 -0
- package/dist/code/view.js +255 -0
- package/dist/code/view.js.map +1 -0
- package/dist/collaboration/binding.js +381 -0
- package/dist/collaboration/binding.js.map +1 -0
- package/dist/collaboration/cursors.js +150 -0
- package/dist/collaboration/cursors.js.map +1 -0
- package/dist/collaboration/index.js +108 -0
- package/dist/collaboration/index.js.map +1 -0
- package/dist/collaboration/order.js +88 -0
- package/dist/collaboration/order.js.map +1 -0
- package/dist/collaboration/table.js +234 -0
- package/dist/collaboration/table.js.map +1 -0
- package/dist/collaboration/text.js +91 -0
- package/dist/collaboration/text.js.map +1 -0
- package/dist/components/block-handle.js +6 -0
- package/dist/components/block-handle.js.map +1 -0
- package/dist/components/block-handle.vue_vue_type_script_setup_true_lang.js +474 -0
- package/dist/components/block-handle.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/bubble-toolbar.js +6 -0
- package/dist/components/bubble-toolbar.js.map +1 -0
- package/dist/components/bubble-toolbar.vue_vue_type_script_setup_true_lang.js +381 -0
- package/dist/components/bubble-toolbar.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/comment-button.js +6 -0
- package/dist/components/comment-button.js.map +1 -0
- package/dist/components/comment-button.vue_vue_type_script_setup_true_lang.js +83 -0
- package/dist/components/comment-button.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/comment-card.js +6 -0
- package/dist/components/comment-card.js.map +1 -0
- package/dist/components/comment-card.vue_vue_type_script_setup_true_lang.js +104 -0
- package/dist/components/comment-card.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/menu-escape.js +25 -0
- package/dist/components/menu-escape.js.map +1 -0
- package/dist/components/search-bar.js +6 -0
- package/dist/components/search-bar.js.map +1 -0
- package/dist/components/search-bar.vue_vue_type_script_setup_true_lang.js +192 -0
- package/dist/components/search-bar.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/slash-menu.js +6 -0
- package/dist/components/slash-menu.js.map +1 -0
- package/dist/components/slash-menu.vue_vue_type_script_setup_true_lang.js +164 -0
- package/dist/components/slash-menu.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/core/align.js +94 -0
- package/dist/core/align.js.map +1 -0
- package/dist/core/block-selection.js +155 -0
- package/dist/core/block-selection.js.map +1 -0
- package/dist/core/blocks.js +132 -0
- package/dist/core/blocks.js.map +1 -0
- package/dist/core/changes.js +85 -0
- package/dist/core/changes.js.map +1 -0
- package/dist/core/colors.js +16 -0
- package/dist/core/colors.js.map +1 -0
- package/dist/core/commands.js +379 -0
- package/dist/core/commands.js.map +1 -0
- package/dist/core/comments.js +186 -0
- package/dist/core/comments.js.map +1 -0
- package/dist/core/compare.js +106 -0
- package/dist/core/compare.js.map +1 -0
- package/dist/core/convert.js +28 -0
- package/dist/core/convert.js.map +1 -0
- package/dist/core/diff.js +141 -0
- package/dist/core/diff.js.map +1 -0
- package/dist/core/editor.js +138 -0
- package/dist/core/editor.js.map +1 -0
- package/dist/core/extension.js +99 -0
- package/dist/core/extension.js.map +1 -0
- package/dist/core/file-drop.js +56 -0
- package/dist/core/file-drop.js.map +1 -0
- package/dist/core/highlight.js +120 -0
- package/dist/core/highlight.js.map +1 -0
- package/dist/core/input-rules.js +61 -0
- package/dist/core/input-rules.js.map +1 -0
- package/dist/core/keymap.js +38 -0
- package/dist/core/keymap.js.map +1 -0
- package/dist/core/model.js +196 -0
- package/dist/core/model.js.map +1 -0
- package/dist/core/output.js +263 -0
- package/dist/core/output.js.map +1 -0
- package/dist/core/plugins.js +102 -0
- package/dist/core/plugins.js.map +1 -0
- package/dist/core/prepare.js +102 -0
- package/dist/core/prepare.js.map +1 -0
- package/dist/core/schema.js +158 -0
- package/dist/core/schema.js.map +1 -0
- package/dist/core/search.js +197 -0
- package/dist/core/search.js.map +1 -0
- package/dist/core/selection.js +16 -0
- package/dist/core/selection.js.map +1 -0
- package/dist/core/slash.js +83 -0
- package/dist/core/slash.js.map +1 -0
- package/dist/core/transaction.js +311 -0
- package/dist/core/transaction.js.map +1 -0
- package/dist/define-block.js +177 -0
- package/dist/define-block.js.map +1 -0
- package/dist/document.js +157 -0
- package/dist/document.js.map +1 -0
- package/dist/dom.js +48 -0
- package/dist/dom.js.map +1 -0
- package/dist/editor.js +33 -0
- package/dist/editor.js.map +1 -0
- package/dist/editor.vue_vue_type_script_setup_true_lang.js +564 -0
- package/dist/editor.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/editor2.js +6 -0
- package/dist/editor2.js.map +1 -0
- package/dist/extensions/align/align-menu.js +6 -0
- package/dist/extensions/align/align-menu.js.map +1 -0
- package/dist/extensions/align/align-menu.vue_vue_type_script_setup_true_lang.js +82 -0
- package/dist/extensions/align/align-menu.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/extensions/align/align-sub.js +6 -0
- package/dist/extensions/align/align-sub.js.map +1 -0
- package/dist/extensions/align/align-sub.vue_vue_type_script_setup_true_lang.js +57 -0
- package/dist/extensions/align/align-sub.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/extensions/align/commands.js +40 -0
- package/dist/extensions/align/commands.js.map +1 -0
- package/dist/extensions/align/index.js +27 -0
- package/dist/extensions/align/index.js.map +1 -0
- package/dist/extensions/attachment/attachment-block.js +6 -0
- package/dist/extensions/attachment/attachment-block.js.map +1 -0
- package/dist/extensions/attachment/attachment-block.vue_vue_type_script_setup_true_lang.js +119 -0
- package/dist/extensions/attachment/attachment-block.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/extensions/attachment/files.js +66 -0
- package/dist/extensions/attachment/files.js.map +1 -0
- package/dist/extensions/attachment/index.js +132 -0
- package/dist/extensions/attachment/index.js.map +1 -0
- package/dist/extensions/blocks.js +170 -0
- package/dist/extensions/blocks.js.map +1 -0
- package/dist/extensions/callout/callout-block.js +6 -0
- package/dist/extensions/callout/callout-block.js.map +1 -0
- package/dist/extensions/callout/callout-block.vue_vue_type_script_setup_true_lang.js +71 -0
- package/dist/extensions/callout/callout-block.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/extensions/callout/html.js +61 -0
- package/dist/extensions/callout/html.js.map +1 -0
- package/dist/extensions/callout/index.js +52 -0
- package/dist/extensions/callout/index.js.map +1 -0
- package/dist/extensions/callout/tones.js +12 -0
- package/dist/extensions/callout/tones.js.map +1 -0
- package/dist/extensions/code-block/code-block-view.js +6 -0
- package/dist/extensions/code-block/code-block-view.js.map +1 -0
- package/dist/extensions/code-block/code-block-view.vue_vue_type_script_setup_true_lang.js +87 -0
- package/dist/extensions/code-block/code-block-view.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/extensions/code-block/index.js +98 -0
- package/dist/extensions/code-block/index.js.map +1 -0
- package/dist/extensions/code-block/languages.js +116 -0
- package/dist/extensions/code-block/languages.js.map +1 -0
- package/dist/extensions/colors/color-menu.js +6 -0
- package/dist/extensions/colors/color-menu.js.map +1 -0
- package/dist/extensions/colors/color-menu.vue_vue_type_script_setup_true_lang.js +113 -0
- package/dist/extensions/colors/color-menu.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/extensions/colors/index.js +49 -0
- package/dist/extensions/colors/index.js.map +1 -0
- package/dist/extensions/colors/palette.js +26 -0
- package/dist/extensions/colors/palette.js.map +1 -0
- package/dist/extensions/editor-kit.js +35 -0
- package/dist/extensions/editor-kit.js.map +1 -0
- package/dist/extensions/embed/embed-block.js +6 -0
- package/dist/extensions/embed/embed-block.js.map +1 -0
- package/dist/extensions/embed/embed-block.vue_vue_type_script_setup_true_lang.js +210 -0
- package/dist/extensions/embed/embed-block.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/extensions/embed/index.js +118 -0
- package/dist/extensions/embed/index.js.map +1 -0
- package/dist/extensions/embed/targets.js +79 -0
- package/dist/extensions/embed/targets.js.map +1 -0
- package/dist/extensions/formatting.js +153 -0
- package/dist/extensions/formatting.js.map +1 -0
- package/dist/extensions/image/image-block.js +6 -0
- package/dist/extensions/image/image-block.js.map +1 -0
- package/dist/extensions/image/image-block.vue_vue_type_script_setup_true_lang.js +206 -0
- package/dist/extensions/image/image-block.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/extensions/image/index.js +125 -0
- package/dist/extensions/image/index.js.map +1 -0
- package/dist/extensions/image/uploads.js +96 -0
- package/dist/extensions/image/uploads.js.map +1 -0
- package/dist/extensions/list-html.js +58 -0
- package/dist/extensions/list-html.js.map +1 -0
- package/dist/extensions/lists.js +182 -0
- package/dist/extensions/lists.js.map +1 -0
- package/dist/extensions/mention/index.js +56 -0
- package/dist/extensions/mention/index.js.map +1 -0
- package/dist/extensions/mention/mention-menu.js +6 -0
- package/dist/extensions/mention/mention-menu.js.map +1 -0
- package/dist/extensions/mention/mention-menu.vue_vue_type_script_setup_true_lang.js +132 -0
- package/dist/extensions/mention/mention-menu.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/extensions/mention/state.js +113 -0
- package/dist/extensions/mention/state.js.map +1 -0
- package/dist/extensions/table/commands.js +356 -0
- package/dist/extensions/table/commands.js.map +1 -0
- package/dist/extensions/table/html.js +134 -0
- package/dist/extensions/table/html.js.map +1 -0
- package/dist/extensions/table/index.js +56 -0
- package/dist/extensions/table/index.js.map +1 -0
- package/dist/extensions/table/plugin.js +81 -0
- package/dist/extensions/table/plugin.js.map +1 -0
- package/dist/extensions/table/schema.js +258 -0
- package/dist/extensions/table/schema.js.map +1 -0
- package/dist/extensions/table/table-controls.js +6 -0
- package/dist/extensions/table/table-controls.js.map +1 -0
- package/dist/extensions/table/table-controls.vue_vue_type_script_setup_true_lang.js +698 -0
- package/dist/extensions/table/table-controls.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/extensions/table/view.js +19 -0
- package/dist/extensions/table/view.js.map +1 -0
- package/dist/extensions/toggle/index.js +87 -0
- package/dist/extensions/toggle/index.js.map +1 -0
- package/dist/extensions/toggle/state.js +120 -0
- package/dist/extensions/toggle/state.js.map +1 -0
- package/dist/extensions/toggle/toggle-block.js +6 -0
- package/dist/extensions/toggle/toggle-block.js.map +1 -0
- package/dist/extensions/toggle/toggle-block.vue_vue_type_script_setup_true_lang.js +42 -0
- package/dist/extensions/toggle/toggle-block.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/index.js +49 -0
- package/dist/index.js.map +1 -0
- package/dist/locale.js +522 -0
- package/dist/locale.js.map +1 -0
- package/dist/markdown/index.js +24 -0
- package/dist/markdown/index.js.map +1 -0
- package/dist/style.css +1 -0
- package/dist/style.d.ts +1 -0
- package/dist/style.js +8 -0
- package/dist/types/catalog.d.ts +8 -0
- package/dist/types/code/code-editor-block.vue.d.ts +17 -0
- package/dist/types/code/index.d.ts +18 -0
- package/dist/types/code/languages.d.ts +7 -0
- package/dist/types/code/types.d.ts +37 -0
- package/dist/types/code/view.d.ts +12 -0
- package/dist/types/collaboration/binding.d.ts +32 -0
- package/dist/types/collaboration/cursors.d.ts +26 -0
- package/dist/types/collaboration/index.d.ts +47 -0
- package/dist/types/collaboration/order.d.ts +20 -0
- package/dist/types/collaboration/table.d.ts +25 -0
- package/dist/types/collaboration/text.d.ts +9 -0
- package/dist/types/components/block-handle.vue.d.ts +3 -0
- package/dist/types/components/bubble-toolbar.vue.d.ts +7 -0
- package/dist/types/components/comment-button.vue.d.ts +3 -0
- package/dist/types/components/comment-card.vue.d.ts +3 -0
- package/dist/types/components/menu-escape.d.ts +7 -0
- package/dist/types/components/search-bar.vue.d.ts +22 -0
- package/dist/types/components/slash-menu.vue.d.ts +11 -0
- package/dist/types/core/align.d.ts +16 -0
- package/dist/types/core/block-selection.d.ts +22 -0
- package/dist/types/core/blocks.d.ts +52 -0
- package/dist/types/core/changes.d.ts +14 -0
- package/dist/types/core/colors.d.ts +4 -0
- package/dist/types/core/commands.d.ts +61 -0
- package/dist/types/core/comments.d.ts +44 -0
- package/dist/types/core/compare.d.ts +10 -0
- package/dist/types/core/convert.d.ts +12 -0
- package/dist/types/core/diff.d.ts +49 -0
- package/dist/types/core/editor.d.ts +64 -0
- package/dist/types/core/extension.d.ts +167 -0
- package/dist/types/core/file-drop.d.ts +19 -0
- package/dist/types/core/highlight.d.ts +32 -0
- package/dist/types/core/input-rules.d.ts +21 -0
- package/dist/types/core/keymap.d.ts +8 -0
- package/dist/types/core/model.d.ts +158 -0
- package/dist/types/core/output.d.ts +64 -0
- package/dist/types/core/plugins.d.ts +24 -0
- package/dist/types/core/prepare.d.ts +23 -0
- package/dist/types/core/schema.d.ts +38 -0
- package/dist/types/core/search.d.ts +83 -0
- package/dist/types/core/selection.d.ts +8 -0
- package/dist/types/core/slash.d.ts +21 -0
- package/dist/types/core/transaction.d.ts +97 -0
- package/dist/types/define-block.d.ts +66 -0
- package/dist/types/document.d.ts +46 -0
- package/dist/types/dom.d.ts +17 -0
- package/dist/types/editor.d.ts +252 -0
- package/dist/types/editor.vue.d.ts +210 -0
- package/dist/types/extensions/align/align-menu.vue.d.ts +3 -0
- package/dist/types/extensions/align/align-sub.vue.d.ts +7 -0
- package/dist/types/extensions/align/commands.d.ts +13 -0
- package/dist/types/extensions/align/index.d.ts +7 -0
- package/dist/types/extensions/attachment/attachment-block.vue.d.ts +17 -0
- package/dist/types/extensions/attachment/files.d.ts +6 -0
- package/dist/types/extensions/attachment/index.d.ts +20 -0
- package/dist/types/extensions/blocks.d.ts +8 -0
- package/dist/types/extensions/callout/callout-block.vue.d.ts +14 -0
- package/dist/types/extensions/callout/html.d.ts +4 -0
- package/dist/types/extensions/callout/index.d.ts +3 -0
- package/dist/types/extensions/callout/tones.d.ts +8 -0
- package/dist/types/extensions/code-block/code-block-view.vue.d.ts +16 -0
- package/dist/types/extensions/code-block/index.d.ts +19 -0
- package/dist/types/extensions/code-block/languages.d.ts +9 -0
- package/dist/types/extensions/colors/color-menu.vue.d.ts +3 -0
- package/dist/types/extensions/colors/index.d.ts +7 -0
- package/dist/types/extensions/colors/palette.d.ts +7 -0
- package/dist/types/extensions/editor-kit.d.ts +37 -0
- package/dist/types/extensions/embed/embed-block.vue.d.ts +16 -0
- package/dist/types/extensions/embed/index.d.ts +18 -0
- package/dist/types/extensions/embed/targets.d.ts +32 -0
- package/dist/types/extensions/formatting.d.ts +8 -0
- package/dist/types/extensions/image/image-block.vue.d.ts +16 -0
- package/dist/types/extensions/image/index.d.ts +16 -0
- package/dist/types/extensions/image/uploads.d.ts +35 -0
- package/dist/types/extensions/list-html.d.ts +11 -0
- package/dist/types/extensions/lists.d.ts +6 -0
- package/dist/types/extensions/mention/index.d.ts +11 -0
- package/dist/types/extensions/mention/mention-menu.vue.d.ts +7 -0
- package/dist/types/extensions/mention/state.d.ts +28 -0
- package/dist/types/extensions/table/commands.d.ts +69 -0
- package/dist/types/extensions/table/html.d.ts +4 -0
- package/dist/types/extensions/table/index.d.ts +8 -0
- package/dist/types/extensions/table/plugin.d.ts +4 -0
- package/dist/types/extensions/table/schema.d.ts +23 -0
- package/dist/types/extensions/table/table-controls.vue.d.ts +3 -0
- package/dist/types/extensions/table/view.d.ts +13 -0
- package/dist/types/extensions/toggle/index.d.ts +7 -0
- package/dist/types/extensions/toggle/state.d.ts +17 -0
- package/dist/types/extensions/toggle/toggle-block.vue.d.ts +14 -0
- package/dist/types/index.d.ts +266 -0
- package/dist/types/locale.d.ts +195 -0
- package/dist/types/markdown/index.d.ts +14 -0
- package/dist/types/use-comments.d.ts +17 -0
- package/dist/use-comments.js +141 -0
- package/dist/use-comments.js.map +1 -0
- 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
|