This repository has been archived by the owner on Nov 29, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add supports for video and audio (#4)
支持添加音视频内容。 Fixes halo-dev/halo#2684 <img width="863" alt="image" src="https://user-images.githubusercontent.com/21301288/215723575-ec961dad-af71-4d21-851b-a929d6023d6e.png"> <img width="847" alt="image" src="https://user-images.githubusercontent.com/21301288/215723637-c717bb02-61a6-4113-9c3b-b7e5b1fc3d52.png"> /kind feature ```release-note 支持添加音视频内容 ```
- Loading branch information
Showing
7 changed files
with
665 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
<script lang="ts" setup> | ||
import type { Node as ProseMirrorNode } from "prosemirror-model"; | ||
import type { Decoration } from "prosemirror-view"; | ||
import { Editor, NodeViewWrapper, Node } from "@tiptap/vue-3"; | ||
import { computed } from "vue"; | ||
import BlockCard from "@/components/block/BlockCard.vue"; | ||
import BlockActionButton from "@/components/block/BlockActionButton.vue"; | ||
import BlockActionSeparator from "@/components/block/BlockActionSeparator.vue"; | ||
import MdiLinkVariant from "~icons/mdi/link-variant"; | ||
import MdiPlayCircle from "~icons/mdi/play-circle"; | ||
import MdiPlayCircleOutline from "~icons/mdi/play-circle-outline"; | ||
import MdiMotionPlayOutline from "~icons/mdi/motion-play-outline"; | ||
import MdiMotionPlay from "~icons/mdi/motion-play"; | ||
const props = defineProps<{ | ||
editor: Editor; | ||
node: ProseMirrorNode; | ||
decorations: Decoration[]; | ||
selected: boolean; | ||
extension: Node<any, any>; | ||
getPos: () => number; | ||
updateAttributes: (attributes: Record<string, any>) => void; | ||
deleteNode: () => void; | ||
}>(); | ||
const src = computed({ | ||
get: () => { | ||
return props.node?.attrs.src; | ||
}, | ||
set: (src: string) => { | ||
props.updateAttributes({ src: src }); | ||
}, | ||
}); | ||
const autoplay = computed(() => { | ||
return props.node.attrs.autoplay; | ||
}); | ||
const loop = computed(() => { | ||
return props.node.attrs.loop; | ||
}); | ||
function handleSetFocus() { | ||
props.editor.commands.setNodeSelection(props.getPos()); | ||
} | ||
function handleToggleAutoplay() { | ||
props.updateAttributes({ | ||
autoplay: props.node.attrs.autoplay ? null : true, | ||
}); | ||
props.editor.chain().focus().setNodeSelection(props.getPos()).run(); | ||
} | ||
function handleToggleLoop() { | ||
props.updateAttributes({ | ||
loop: props.node.attrs.loop ? null : true, | ||
}); | ||
props.editor.chain().focus().setNodeSelection(props.getPos()).run(); | ||
} | ||
function handleOpenLink() { | ||
window.open(src.value, "_blank"); | ||
} | ||
</script> | ||
|
||
<template> | ||
<node-view-wrapper as="div"> | ||
<block-card | ||
:editor="editor" | ||
:get-pos="getPos" | ||
:delete-node="deleteNode" | ||
:selected="selected" | ||
> | ||
<template #content> | ||
<div | ||
class="inline-block overflow-hidden transition-all text-center relative h-full w-full" | ||
> | ||
<div class="py-1.5"> | ||
<input | ||
v-model.lazy="src" | ||
class="block px-2 w-full py-1.5 text-sm text-gray-900 border border-gray-300 rounded-md bg-gray-50 focus:ring-blue-500 focus:border-blue-500" | ||
placeholder="输入链接,按回车确定" | ||
tabindex="-1" | ||
@focus="handleSetFocus" | ||
/> | ||
</div> | ||
<audio | ||
v-if="src" | ||
controls | ||
:autoplay="autoplay" | ||
:loop="loop" | ||
:src="node!.attrs.src" | ||
@mouseenter="handleSetFocus" | ||
></audio> | ||
</div> | ||
</template> | ||
<template #actions> | ||
<BlockActionButton | ||
:selected="autoplay" | ||
:tooltip="`${autoplay ? '关闭自动播放' : '开启自动播放'}`" | ||
@click="handleToggleAutoplay" | ||
> | ||
<template #icon> | ||
<MdiPlayCircle v-if="autoplay" /> | ||
<MdiPlayCircleOutline v-else /> | ||
</template> | ||
</BlockActionButton> | ||
|
||
<BlockActionButton | ||
:selected="loop" | ||
:tooltip="`${loop ? '关闭循环播放' : '开启循环播放'}`" | ||
@click="handleToggleLoop" | ||
> | ||
<template #icon> | ||
<MdiMotionPlay v-if="loop" /> | ||
<MdiMotionPlayOutline v-else /> | ||
</template> | ||
</BlockActionButton> | ||
|
||
<BlockActionSeparator /> | ||
|
||
<BlockActionButton tooltip="打开链接" @click="handleOpenLink"> | ||
<template #icon> | ||
<MdiLinkVariant /> | ||
</template> | ||
</BlockActionButton> | ||
</template> | ||
</block-card> | ||
</node-view-wrapper> | ||
</template> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
import { mergeAttributes, Node, nodeInputRule } from "@tiptap/core"; | ||
import { VueNodeViewRenderer } from "@tiptap/vue-3"; | ||
import AudioView from "./AudioView.vue"; | ||
|
||
declare module "@tiptap/core" { | ||
interface Commands<ReturnType> { | ||
audio: { | ||
setAudio: (options: { src: string }) => ReturnType; | ||
}; | ||
} | ||
} | ||
|
||
const Audio = Node.create({ | ||
name: "audio", | ||
|
||
inline() { | ||
return true; | ||
}, | ||
|
||
group() { | ||
return "inline"; | ||
}, | ||
|
||
addAttributes() { | ||
return { | ||
...this.parent?.(), | ||
src: { | ||
default: null, | ||
parseHTML: (element) => { | ||
return element.getAttribute("src"); | ||
}, | ||
}, | ||
autoplay: { | ||
default: null, | ||
parseHTML: (element) => { | ||
return element.getAttribute("autoplay"); | ||
}, | ||
renderHTML: (attributes) => { | ||
return { | ||
autoplay: attributes.autoplay, | ||
}; | ||
}, | ||
}, | ||
controls: { | ||
default: true, | ||
parseHTML: (element) => { | ||
return element.getAttribute("controls"); | ||
}, | ||
renderHTML: (attributes) => { | ||
return { | ||
controls: attributes.controls, | ||
}; | ||
}, | ||
}, | ||
loop: { | ||
default: null, | ||
parseHTML: (element) => { | ||
return element.getAttribute("loop"); | ||
}, | ||
renderHTML: (attributes) => { | ||
return { | ||
loop: attributes.loop, | ||
}; | ||
}, | ||
}, | ||
}; | ||
}, | ||
|
||
parseHTML() { | ||
return [ | ||
{ | ||
tag: "audio", | ||
}, | ||
]; | ||
}, | ||
|
||
renderHTML({ HTMLAttributes }) { | ||
return [ | ||
"audio", | ||
mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), | ||
]; | ||
}, | ||
|
||
addCommands() { | ||
return { | ||
setAudio: | ||
(options) => | ||
({ commands }) => { | ||
return commands.insertContent({ | ||
type: this.name, | ||
attrs: options, | ||
}); | ||
}, | ||
}; | ||
}, | ||
|
||
addInputRules() { | ||
return [ | ||
nodeInputRule({ | ||
find: /^\$audio\$$/, | ||
type: this.type, | ||
getAttributes: () => { | ||
return { width: "100%" }; | ||
}, | ||
}), | ||
]; | ||
}, | ||
|
||
addNodeView() { | ||
return VueNodeViewRenderer(AudioView); | ||
}, | ||
}); | ||
|
||
export default Audio; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.