mirror of https://github.com/halo-dev/halo
feat: add supports for copying attachment permalink (#3835)
#### What type of PR is this? /kind feature /area console /milestone 2.5.x #### What this PR does / why we need it: 添加复制附件链接的功能,支持三种格式:URL、HTML 格式、Markdown 格式。 #### Which issue(s) this PR fixes: Fixes #3801 #### Special notes for your reviewer: 测试方式: 1. 上传若干不同格式的附件。 2. 打开附件详情,检查列出的格式是否正确。 3. 检查复制链接的内容是否正确。 #### Does this PR introduce a user-facing change? ```release-note Console 端的附件详情支持复制链接的功能。 ```pull/3859/head^2
parent
814dc8921a
commit
7b8613049a
|
@ -498,7 +498,7 @@ core:
|
|||
size: 文件大小
|
||||
owner: 上传者
|
||||
creation_time: 上传时间
|
||||
permalink: 原始链接
|
||||
permalink: 链接
|
||||
preview:
|
||||
click_to_exit: 点击退出预览
|
||||
video_not_support: 当前浏览器不支持该视频播放
|
||||
|
|
|
@ -498,7 +498,7 @@ core:
|
|||
size: 文件大小
|
||||
owner: 上傳者
|
||||
creation_time: 上傳時間
|
||||
permalink: 原始連結
|
||||
permalink: 連結
|
||||
preview:
|
||||
click_to_exit: 點擊離開預覽
|
||||
video_not_support: 當前瀏覽器不支援該影片播放
|
||||
|
|
|
@ -15,16 +15,17 @@ import { isImage } from "@/utils/image";
|
|||
import { formatDatetime } from "@/utils/date";
|
||||
import { useFetchAttachmentGroup } from "../composables/use-attachment-group";
|
||||
import { useQuery } from "@tanstack/vue-query";
|
||||
import AttachmentPermalinkList from "./AttachmentPermalinkList.vue";
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
visible: boolean;
|
||||
attachment: Attachment | null;
|
||||
attachment: Attachment | undefined;
|
||||
mountToBody?: boolean;
|
||||
}>(),
|
||||
{
|
||||
visible: false,
|
||||
attachment: null,
|
||||
attachment: undefined,
|
||||
mountToBody: false,
|
||||
}
|
||||
);
|
||||
|
@ -68,7 +69,10 @@ const onVisibleChange = (visible: boolean) => {
|
|||
emit("update:visible", visible);
|
||||
if (!visible) {
|
||||
onlyPreview.value = false;
|
||||
emit("close");
|
||||
|
||||
setTimeout(() => {
|
||||
emit("close");
|
||||
}, 200);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
@ -187,9 +191,7 @@ const onVisibleChange = (visible: boolean) => {
|
|||
<VDescriptionItem
|
||||
:label="$t('core.attachment.detail_modal.fields.permalink')"
|
||||
>
|
||||
<a target="_blank" :href="attachment?.status?.permalink">
|
||||
{{ attachment?.status?.permalink }}
|
||||
</a>
|
||||
<AttachmentPermalinkList :attachment="attachment" />
|
||||
</VDescriptionItem>
|
||||
</VDescription>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
<script lang="ts" setup>
|
||||
import { VButton } from "@halo-dev/components";
|
||||
import type { Attachment } from "@halo-dev/api-client";
|
||||
import { useAttachmentPermalinkCopy } from "../composables/use-attachment";
|
||||
import { toRefs, computed } from "vue";
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
visible: boolean;
|
||||
attachment?: Attachment;
|
||||
mountToBody?: boolean;
|
||||
}>(),
|
||||
{
|
||||
visible: false,
|
||||
attachment: undefined,
|
||||
mountToBody: false,
|
||||
}
|
||||
);
|
||||
|
||||
const { attachment } = toRefs(props);
|
||||
|
||||
const { handleCopy, htmlText, markdownText } =
|
||||
useAttachmentPermalinkCopy(attachment);
|
||||
|
||||
const formats = computed(
|
||||
(): {
|
||||
label: string;
|
||||
key: "url" | "html" | "markdown";
|
||||
value?: string;
|
||||
}[] => {
|
||||
return [
|
||||
{
|
||||
label: "URL",
|
||||
key: "url",
|
||||
value: attachment?.value?.status?.permalink,
|
||||
},
|
||||
{
|
||||
label: "HTML",
|
||||
key: "html",
|
||||
value: htmlText.value,
|
||||
},
|
||||
{
|
||||
label: "Markdown",
|
||||
key: "markdown",
|
||||
value: markdownText.value,
|
||||
},
|
||||
];
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ul class="flex flex-col space-y-2">
|
||||
<li v-for="format in formats" :key="format.key">
|
||||
<div
|
||||
class="flex w-full cursor-pointer items-center justify-between space-x-3 rounded border p-3 hover:border-primary"
|
||||
>
|
||||
<div class="flex flex-1 flex-col space-y-2 text-xs text-gray-900">
|
||||
<span class="font-semibold">
|
||||
{{ format.label }}
|
||||
</span>
|
||||
<span class="break-all">
|
||||
{{ format.value }}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<VButton size="sm" @click="handleCopy(format.key)">
|
||||
{{ $t("core.common.buttons.copy") }}
|
||||
</VButton>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
|
@ -1,5 +1,5 @@
|
|||
import type { Attachment, Group, Policy, User } from "@halo-dev/api-client";
|
||||
import type { Ref } from "vue";
|
||||
import { computed, type Ref } from "vue";
|
||||
import { ref, watch } from "vue";
|
||||
import type { AttachmentLike } from "@halo-dev/console-shared";
|
||||
import { apiClient } from "@/utils/api-client";
|
||||
|
@ -7,6 +7,8 @@ import { Dialog, Toast } from "@halo-dev/components";
|
|||
import type { Content, Editor } from "@halo-dev/richtext-editor";
|
||||
import { useQuery } from "@tanstack/vue-query";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useClipboard } from "@vueuse/core";
|
||||
import { matchMediaType } from "@/utils/media-type";
|
||||
|
||||
interface useAttachmentControlReturn {
|
||||
attachments: Ref<Attachment[] | undefined>;
|
||||
|
@ -327,3 +329,71 @@ export function useAttachmentSelect(
|
|||
onAttachmentSelect,
|
||||
};
|
||||
}
|
||||
|
||||
export function useAttachmentPermalinkCopy(
|
||||
attachment: Ref<Attachment | undefined>
|
||||
) {
|
||||
const { copy } = useClipboard();
|
||||
const { t } = useI18n();
|
||||
|
||||
const mediaType = computed(() => {
|
||||
return attachment.value?.spec.mediaType;
|
||||
});
|
||||
|
||||
const isImage = computed(() => {
|
||||
return mediaType.value && matchMediaType(mediaType.value, "image/*");
|
||||
});
|
||||
|
||||
const isVideo = computed(() => {
|
||||
return mediaType.value && matchMediaType(mediaType.value, "video/*");
|
||||
});
|
||||
|
||||
const isAudio = computed(() => {
|
||||
return mediaType.value && matchMediaType(mediaType.value, "audio/*");
|
||||
});
|
||||
|
||||
const htmlText = computed(() => {
|
||||
const { permalink } = attachment.value?.status || {};
|
||||
const { displayName } = attachment.value?.spec || {};
|
||||
|
||||
if (isImage.value) {
|
||||
return `<img src="${permalink}" alt="${displayName}" />`;
|
||||
} else if (isVideo.value) {
|
||||
return `<video src="${permalink}"></video>`;
|
||||
} else if (isAudio.value) {
|
||||
return `<audio src="${permalink}"></audio>`;
|
||||
}
|
||||
return `<a href="${permalink}">${displayName}</a>`;
|
||||
});
|
||||
|
||||
const markdownText = computed(() => {
|
||||
const { permalink } = attachment.value?.status || {};
|
||||
const { displayName } = attachment.value?.spec || {};
|
||||
if (isImage.value) {
|
||||
return `![${displayName}](${permalink})`;
|
||||
}
|
||||
return `[${displayName}](${permalink})`;
|
||||
});
|
||||
|
||||
const handleCopy = (format: "markdown" | "html" | "url") => {
|
||||
const { permalink } = attachment.value?.status || {};
|
||||
|
||||
if (!permalink) return;
|
||||
|
||||
if (format === "url") {
|
||||
copy(permalink);
|
||||
} else if (format === "markdown") {
|
||||
copy(markdownText.value);
|
||||
} else if (format === "html") {
|
||||
copy(htmlText.value);
|
||||
}
|
||||
|
||||
Toast.success(t("core.common.toast.copy_success"));
|
||||
};
|
||||
|
||||
return {
|
||||
htmlText,
|
||||
markdownText,
|
||||
handleCopy,
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue