Skip to content

Commit

Permalink
pref: refactor default editor audio block upload logic (#5421)
Browse files Browse the repository at this point in the history
#### What type of PR is this?

/kind improvement
/area ui
/area editor

#### What this PR does / why we need it:

重构默认编辑器音频组件。使其支持上传、取消上传、从附件库选择、替换等功能。

#### How to test it?

直接拖动、复制或选择文件上传一个音频,查看是否显示上传进度条,取消、重试功能是否正常

#### Which issue(s) this PR fixes:

Fixes #5239 

#### Does this PR introduce a user-facing change?
```release-note
重构编辑器音频组件的上传逻辑,增加选择文件上传、上传进度条、取消、重试等机制。
```
  • Loading branch information
LIlGG committed Mar 14, 2024
1 parent e704e09 commit 820c103
Show file tree
Hide file tree
Showing 6 changed files with 272 additions and 4 deletions.
7 changes: 6 additions & 1 deletion ui/src/components/editor/DefaultEditor.vue
Expand Up @@ -53,6 +53,7 @@ import {
} from "@halo-dev/richtext-editor";
// ui custom extension
import {
UiExtensionAudio,
UiExtensionImage,
UiExtensionUpload,
UiExtensionVideo,
Expand Down Expand Up @@ -272,7 +273,11 @@ onMounted(() => {
uploadVideo: props.uploadImage,
})
: ExtensionVideo,
ExtensionAudio,
currentUserHasPermission(["uc:attachments:manage"])
? UiExtensionAudio.configure({
uploadAudio: props.uploadImage,
})
: ExtensionAudio,
ExtensionCharacterCount,
ExtensionFontSize,
ExtensionColor,
Expand Down
4 changes: 2 additions & 2 deletions ui/src/components/editor/components/EditorLinkObtain.vue
@@ -1,6 +1,6 @@
<script setup lang="ts">
import { VButton, VSpace, VDropdown } from "@halo-dev/components";
import type { Editor } from "@halo-dev/richtext-editor";
import type { CoreEditor } from "@halo-dev/richtext-editor";
import { useFileDialog } from "@vueuse/core";
import type { AttachmentLike } from "@halo-dev/console-shared";
import { getAttachmentUrl, type AttachmentAttr } from "../utils/attachment";
Expand All @@ -13,7 +13,7 @@ import type { AxiosRequestConfig } from "axios";
const props = withDefaults(
defineProps<{
editor: Editor;
editor: CoreEditor;
accept: string;
uploadedFile: File;
uploadToAttachmentFile: (
Expand Down
196 changes: 196 additions & 0 deletions ui/src/components/editor/extensions/audio/AudioView.vue
@@ -0,0 +1,196 @@
<script lang="ts" setup>
import type { NodeViewProps } from "@halo-dev/richtext-editor";
import { NodeViewWrapper } from "@halo-dev/richtext-editor";
import { computed, ref } from "vue";
import type { AttachmentAttr } from "../../utils/attachment";
import RiFileMusicLine from "~icons/ri/file-music-line";
import { EditorLinkObtain } from "../../components";
import { VButton } from "@halo-dev/components";
const props = defineProps<NodeViewProps>();
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;
});
const initialization = computed(() => {
return !src.value;
});
const editorLinkObtain = ref();
const handleSetExternalLink = (attachment: AttachmentAttr) => {
props.updateAttributes({
src: attachment.url,
});
};
const resetUpload = () => {
if (props.getPos()) {
props.updateAttributes({
width: undefined,
height: undefined,
file: undefined,
});
}
};
const handleUploadRetry = () => {
editorLinkObtain.value?.reset();
};
const handleUploadAbort = () => {
editorLinkObtain.value?.abort();
};
const handleResetInit = () => {
editorLinkObtain.value?.reset();
props.updateAttributes({
src: "",
file: undefined,
});
};
</script>

<template>
<node-view-wrapper as="div" class="inline-block w-full">
<div
class="relative inline-block h-full max-w-full overflow-hidden rounded-md text-center transition-all"
:class="{
'rounded ring-2': selected,
}"
:style="{
width: initialization ? '100%' : node.attrs.width,
}"
>
<div v-if="src" class="group relative">
<audio
class="m-0 rounded-md"
controls
:src="src"
:autoplay="autoplay"
:loop="loop"
preload="metadata"
:style="{
width: node.attrs.width,
height: node.attrs.height,
}"
></audio>
<div
v-if="src"
class="absolute left-0 top-0 hidden h-1/4 w-full cursor-pointer justify-end bg-gradient-to-b from-gray-300 to-transparent p-2 ease-in-out group-hover:flex"
>
<VButton size="xs" type="secondary" @click="handleResetInit">
{{
$t(
"core.components.default_editor.extensions.upload.operations.replace.button"
)
}}
</VButton>
</div>
</div>
<div v-show="!src" class="relative w-full">
<EditorLinkObtain
ref="editorLinkObtain"
class="!h-40 w-full"
:accept="'audio/*'"
:editor="editor"
:upload-to-attachment-file="extension.options.uploadAudio"
:uploaded-file="node?.attrs.file"
@set-external-link="handleSetExternalLink"
@on-upload-finish="resetUpload"
@on-upload-abort="resetUpload"
>
<template #icon>
<div
class="flex h-14 w-14 items-center justify-center rounded-full bg-primary/20"
>
<RiFileMusicLine class="text-xl text-primary" />
</div>
</template>
<template #uploading="{ progress }">
<div class="absolute top-0 h-full w-full bg-black bg-opacity-20">
<div class="absolute top-[50%] w-full space-y-2 text-white">
<div class="px-10">
<div
class="relative h-4 w-full overflow-hidden rounded-full bg-gray-200"
>
<div
class="h-full bg-primary"
:style="{
width: `${progress || 0}%`,
}"
></div>
<div
class="absolute left-[50%] top-0 -translate-x-[50%] text-xs leading-4 text-white"
>
{{
progress
? `${progress}%`
: `${$t(
"core.components.default_editor.extensions.upload.loading"
)}...`
}}
</div>
</div>
</div>
<div
class="inline-block cursor-pointer text-sm hover:opacity-70"
@click="handleUploadAbort"
>
{{ $t("core.common.buttons.cancel") }}
</div>
</div>
</div>
</template>
<template #error>
<div class="absolute top-0 h-full w-full bg-black bg-opacity-20">
<div class="absolute top-[50%] w-full space-y-2 text-white">
<div class="px-10">
<div
class="relative h-4 w-full overflow-hidden rounded-full bg-gray-200"
>
<div class="h-full w-full bg-red-600"></div>
<div
class="absolute left-[50%] top-0 -translate-x-[50%] text-xs leading-4 text-white"
>
{{
$t(
"core.components.default_editor.extensions.upload.error"
)
}}
</div>
</div>
</div>
<div
class="inline-block cursor-pointer text-sm hover:opacity-70"
@click="handleUploadRetry"
>
{{
$t(
"core.components.default_editor.extensions.upload.click_retry"
)
}}
</div>
</div>
</div>
</template>
</EditorLinkObtain>
</div>
</div>
</node-view-wrapper>
</template>
42 changes: 42 additions & 0 deletions ui/src/components/editor/extensions/audio/index.ts
@@ -0,0 +1,42 @@
import { ExtensionAudio, VueNodeViewRenderer } from "@halo-dev/richtext-editor";
import type { AxiosRequestConfig } from "axios";
import type { Attachment } from "@halo-dev/api-client";
import AudioView from "./AudioView.vue";

interface UiAudioOptions {
uploadAudio?: (
file: File,
options?: AxiosRequestConfig
) => Promise<Attachment>;
}

const Audio = ExtensionAudio.extend<UiAudioOptions>({
addOptions() {
const { parent } = this;
return {
...parent?.(),
uploadAudio: undefined,
};
},

addAttributes() {
return {
...this.parent?.(),
file: {
default: null,
renderHTML() {
return {};
},
parseHTML() {
return null;
},
},
};
},

addNodeView() {
return VueNodeViewRenderer(AudioView);
},
});

export default Audio;
8 changes: 7 additions & 1 deletion ui/src/components/editor/extensions/index.ts
@@ -1,4 +1,10 @@
import UiExtensionImage from "./image";
import UiExtensionVideo from "./video";
import UiExtensionUpload from "./upload";
export { UiExtensionImage, UiExtensionUpload, UiExtensionVideo };
import UiExtensionAudio from "./audio";
export {
UiExtensionImage,
UiExtensionUpload,
UiExtensionVideo,
UiExtensionAudio,
};
19 changes: 19 additions & 0 deletions ui/src/components/editor/utils/upload.ts
Expand Up @@ -3,6 +3,7 @@ import { CoreEditor } from "@halo-dev/richtext-editor";
import type { Attachment } from "@halo-dev/api-client";
import Image from "../extensions/image";
import ExtensionVideo from "../extensions/video";
import ExtensionAudio from "../extensions/audio";
import type { AxiosRequestConfig } from "axios";

export interface FileProps {
Expand Down Expand Up @@ -31,6 +32,11 @@ export const handleFileEvent = ({ file, editor }: FileProps) => {
return true;
}

if (file.type.startsWith("audio/")) {
uploadAudio({ file, editor });
return true;
}

return true;
};

Expand Down Expand Up @@ -60,6 +66,19 @@ export const uploadVideo = ({ file, editor }: FileProps) => {
editor.view.dispatch(editor.view.state.tr.replaceSelectionWith(node));
};

/**
* Uploads an audio file and inserts it into the editor.
*
* @param {FileProps} { file, editor } - File to be uploaded and the editor instance
*/
export const uploadAudio = ({ file, editor }: FileProps) => {
const { view } = editor;
const node = view.props.state.schema.nodes[ExtensionAudio.name].create({
file: file,
});
editor.view.dispatch(editor.view.state.tr.replaceSelectionWith(node));
};

export interface UploadFetchResponse {
controller: AbortController;
onUploadProgress: (progress: number) => void;
Expand Down

0 comments on commit 820c103

Please sign in to comment.