Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

pref: refactor default editor audio block upload logic #5421

Merged
merged 4 commits into from Mar 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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