mirror of https://github.com/halo-dev/halo
Optimize comment notification template to support rich text rendering (#7683)
#### What type of PR is this? /area core /area ui /milestone 2.21.x /kind feature #### What this PR does / why we need it: Optimize comment notification template to support rich text rendering #### Does this PR introduce a user-facing change? ```release-note None ```pull/7685/head
parent
eddcb5bc38
commit
2bcfbbc371
|
@ -25,7 +25,7 @@ spec:
|
||||||
<a th:href="${postUrl}" target="_blank" th:text="|《${postTitle}》|"></a>
|
<a th:href="${postUrl}" target="_blank" th:text="|《${postTitle}》|"></a>
|
||||||
<span>,以下是评论的具体内容:</span>
|
<span>,以下是评论的具体内容:</span>
|
||||||
</p>
|
</p>
|
||||||
<pre class="content" th:text="${content}"></pre>
|
<div class="content" th:utext="${content}"></div>
|
||||||
</div>
|
</div>
|
||||||
<div></div>
|
<div></div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -58,7 +58,7 @@ spec:
|
||||||
<a th:href="${pageUrl}" target="_blank" th:text="|《${pageTitle}》|"></a>
|
<a th:href="${pageUrl}" target="_blank" th:text="|《${pageTitle}》|"></a>
|
||||||
<span>,以下是评论的具体内容:</span>
|
<span>,以下是评论的具体内容:</span>
|
||||||
</p>
|
</p>
|
||||||
<pre class="content" th:text="${content}"></pre>
|
<div class="content" th:utext="${content}"></div>
|
||||||
</div>
|
</div>
|
||||||
<div></div>
|
<div></div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -87,15 +87,18 @@ spec:
|
||||||
</div>
|
</div>
|
||||||
<div class="body">
|
<div class="body">
|
||||||
<p>
|
<p>
|
||||||
<span th:text="${replier}"></span> 在评论
|
<span th:text="${replier}"></span> 在
|
||||||
<a
|
<a
|
||||||
th:href="${commentSubjectUrl}"
|
th:href="${commentSubjectUrl}"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
th:text="|”${isQuoteReply ? quoteContent : commentContent}”|"
|
th:text="|《${commentSubjectTitle}》|"
|
||||||
></a>
|
></a>
|
||||||
<span>中回复了你,以下是回复的具体内容:</span>
|
<span>中回复了你。</span>
|
||||||
</p>
|
</p>
|
||||||
<pre class="content" th:text="${content}"></pre>
|
<p>你的评论:</p>
|
||||||
|
<div class="content" th:utext="${isQuoteReply ? quoteContent : commentContent}"></div>
|
||||||
|
<p>回复的内容:</p>
|
||||||
|
<div class="content" th:utext="${content}"></div>
|
||||||
</div>
|
</div>
|
||||||
<div></div>
|
<div></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -14,6 +14,7 @@ import {
|
||||||
} from "@halo-dev/components";
|
} from "@halo-dev/components";
|
||||||
import { useQuery } from "@tanstack/vue-query";
|
import { useQuery } from "@tanstack/vue-query";
|
||||||
import { OverlayScrollbarsComponent } from "overlayscrollbars-vue";
|
import { OverlayScrollbarsComponent } from "overlayscrollbars-vue";
|
||||||
|
import sanitize from "sanitize-html";
|
||||||
|
|
||||||
const { currentUser } = useUserStore();
|
const { currentUser } = useUserStore();
|
||||||
|
|
||||||
|
@ -81,7 +82,12 @@ function handleRouteToNotification(notification: Notification) {
|
||||||
<template #start>
|
<template #start>
|
||||||
<VEntityField
|
<VEntityField
|
||||||
:title="notification.spec?.title"
|
:title="notification.spec?.title"
|
||||||
:description="notification.spec?.rawContent"
|
:description="
|
||||||
|
sanitize(notification.spec?.htmlContent || '', {
|
||||||
|
allowedTags: [],
|
||||||
|
allowedAttributes: {},
|
||||||
|
})
|
||||||
|
"
|
||||||
@click="handleRouteToNotification(notification)"
|
@click="handleRouteToNotification(notification)"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -231,7 +231,9 @@ function handleMarkAllAsRead() {
|
||||||
</Transition>
|
</Transition>
|
||||||
</OverlayScrollbarsComponent>
|
</OverlayScrollbarsComponent>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-span-12 sm:col-span-6 lg:col-span-7 xl:col-span-9">
|
<div
|
||||||
|
class="col-span-12 overflow-auto sm:col-span-6 lg:col-span-7 xl:col-span-9"
|
||||||
|
>
|
||||||
<NotificationContent :notification="selectedNotification" />
|
<NotificationContent :notification="selectedNotification" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { Notification } from "@halo-dev/api-client";
|
import type { Notification } from "@halo-dev/api-client";
|
||||||
import { OverlayScrollbarsComponent } from "overlayscrollbars-vue";
|
import { OverlayScrollbarsComponent } from "overlayscrollbars-vue";
|
||||||
|
import sanitize from "sanitize-html";
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
|
@ -10,34 +11,28 @@ const props = withDefaults(
|
||||||
{ notification: undefined }
|
{ notification: undefined }
|
||||||
);
|
);
|
||||||
|
|
||||||
const htmlContent = computed(() => {
|
const content = computed(() => {
|
||||||
const styles = `
|
return sanitize(props.notification?.spec?.htmlContent || "");
|
||||||
<style>
|
|
||||||
html {
|
|
||||||
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
|
||||||
font-size: 0.875rem;
|
|
||||||
line-height: 1.25rem;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
`;
|
|
||||||
|
|
||||||
if (!props.notification?.spec?.htmlContent) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
return styles + props.notification?.spec?.htmlContent;
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="h-full w-full overflow-auto">
|
<OverlayScrollbarsComponent
|
||||||
<OverlayScrollbarsComponent
|
element="div"
|
||||||
element="div"
|
:options="{ scrollbars: { autoHide: 'scroll' } }"
|
||||||
:options="{ scrollbars: { autoHide: 'scroll' } }"
|
class="h-full w-full"
|
||||||
class="h-full w-full"
|
defer
|
||||||
defer
|
>
|
||||||
>
|
<div class="markdown-body h-full w-full p-2 text-sm" v-html="content"></div>
|
||||||
<iframe class="h-full w-full p-2" :srcdoc="htmlContent"></iframe>
|
</OverlayScrollbarsComponent>
|
||||||
</OverlayScrollbarsComponent>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.markdown-body :deep(ul) {
|
||||||
|
list-style: disc !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-body :deep(ol) {
|
||||||
|
list-style: decimal !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -5,7 +5,8 @@ import type { Notification } from "@halo-dev/api-client";
|
||||||
import { ucApiClient } from "@halo-dev/api-client";
|
import { ucApiClient } from "@halo-dev/api-client";
|
||||||
import { Dialog, Toast, VStatusDot } from "@halo-dev/components";
|
import { Dialog, Toast, VStatusDot } from "@halo-dev/components";
|
||||||
import { useMutation, useQueryClient } from "@tanstack/vue-query";
|
import { useMutation, useQueryClient } from "@tanstack/vue-query";
|
||||||
import { ref, watch } from "vue";
|
import sanitize from "sanitize-html";
|
||||||
|
import { computed, ref, watch } from "vue";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
|
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
@ -74,6 +75,14 @@ watch(
|
||||||
immediate: true,
|
immediate: true,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const content = computed(() => {
|
||||||
|
// Clean html tags
|
||||||
|
return sanitize(props.notification.spec?.htmlContent || "", {
|
||||||
|
allowedTags: [],
|
||||||
|
allowedAttributes: {},
|
||||||
|
});
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
|
@ -99,10 +108,10 @@ watch(
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="notification.spec?.rawContent"
|
v-if="notification.spec?.htmlContent"
|
||||||
class="truncate text-xs text-gray-600"
|
class="line-clamp-1 text-xs text-gray-600"
|
||||||
>
|
>
|
||||||
{{ notification.spec.rawContent }}
|
{{ content }}
|
||||||
</div>
|
</div>
|
||||||
<div class="flex h-6 items-end justify-between">
|
<div class="flex h-6 items-end justify-between">
|
||||||
<div class="text-xs text-gray-600">
|
<div class="text-xs text-gray-600">
|
||||||
|
|
Loading…
Reference in New Issue