mirror of https://github.com/halo-dev/halo
Add subject-based comment list modal (#7681)
parent
2bcfbbc371
commit
f5af5a1550
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts" setup>
|
||||
import UserFilterDropdown from "@/components/filter/UserFilterDropdown.vue";
|
||||
import type { ListedComment } from "@halo-dev/api-client";
|
||||
import { consoleApiClient, coreApiClient } from "@halo-dev/api-client";
|
||||
import { coreApiClient } from "@halo-dev/api-client";
|
||||
import {
|
||||
Dialog,
|
||||
IconMessage,
|
||||
|
@ -16,12 +16,12 @@ import {
|
|||
VPagination,
|
||||
VSpace,
|
||||
} from "@halo-dev/components";
|
||||
import { useQuery } from "@tanstack/vue-query";
|
||||
import { useRouteQuery } from "@vueuse/router";
|
||||
import { chunk } from "lodash-es";
|
||||
import { computed, ref, watch } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import CommentListItem from "./components/CommentListItem.vue";
|
||||
import useCommentsFetch from "./composables/use-comments-fetch";
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
|
@ -72,58 +72,21 @@ const page = useRouteQuery<number>("page", 1, {
|
|||
const size = useRouteQuery<number>("size", 20, {
|
||||
transform: Number,
|
||||
});
|
||||
const total = ref(0);
|
||||
|
||||
const {
|
||||
data: comments,
|
||||
isLoading,
|
||||
isFetching,
|
||||
refetch,
|
||||
} = useQuery<ListedComment[]>({
|
||||
queryKey: [
|
||||
} = useCommentsFetch(
|
||||
"core:comments",
|
||||
page,
|
||||
size,
|
||||
selectedApprovedStatus,
|
||||
selectedSort,
|
||||
selectedUser,
|
||||
keyword,
|
||||
],
|
||||
queryFn: async () => {
|
||||
const fieldSelectorMap: Record<string, string | boolean | undefined> = {
|
||||
"spec.approved": selectedApprovedStatus.value,
|
||||
};
|
||||
|
||||
const fieldSelector = Object.entries(fieldSelectorMap)
|
||||
.map(([key, value]) => {
|
||||
if (value !== undefined) {
|
||||
return `${key}=${value}`;
|
||||
}
|
||||
})
|
||||
.filter(Boolean) as string[];
|
||||
|
||||
const { data } = await consoleApiClient.content.comment.listComments({
|
||||
fieldSelector,
|
||||
page: page.value,
|
||||
size: size.value,
|
||||
sort: [selectedSort.value].filter(Boolean) as string[],
|
||||
keyword: keyword.value,
|
||||
ownerName: selectedUser.value,
|
||||
// TODO: email users are not supported at the moment.
|
||||
ownerKind: selectedUser.value ? "User" : undefined,
|
||||
});
|
||||
|
||||
total.value = data.total;
|
||||
|
||||
return data.items;
|
||||
},
|
||||
refetchInterval(data) {
|
||||
const hasDeletingData = data?.some(
|
||||
(comment) => !!comment.comment.metadata.deletionTimestamp
|
||||
keyword
|
||||
);
|
||||
return hasDeletingData ? 1000 : false;
|
||||
},
|
||||
});
|
||||
|
||||
// Selection
|
||||
const handleCheckAllChange = (e: Event) => {
|
||||
|
@ -131,7 +94,7 @@ const handleCheckAllChange = (e: Event) => {
|
|||
|
||||
if (checked) {
|
||||
selectedCommentNames.value =
|
||||
comments.value?.map((comment) => {
|
||||
comments.value?.items.map((comment) => {
|
||||
return comment.comment.metadata.name;
|
||||
}) || [];
|
||||
} else {
|
||||
|
@ -146,7 +109,7 @@ const isSelection = (comment: ListedComment) => {
|
|||
watch(
|
||||
() => selectedCommentNames.value,
|
||||
(newValue) => {
|
||||
checkAll.value = newValue.length === comments.value?.length;
|
||||
checkAll.value = newValue.length === comments.value?.items.length;
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -192,7 +155,7 @@ const handleApproveInBatch = async () => {
|
|||
cancelText: t("core.common.buttons.cancel"),
|
||||
onConfirm: async () => {
|
||||
try {
|
||||
const commentsToUpdate = comments.value?.filter((comment) => {
|
||||
const commentsToUpdate = comments.value?.items.filter((comment) => {
|
||||
return (
|
||||
selectedCommentNames.value.includes(
|
||||
comment.comment.metadata.name
|
||||
|
@ -370,7 +333,7 @@ const handleApproveInBatch = async () => {
|
|||
</div>
|
||||
</template>
|
||||
<VLoading v-if="isLoading" />
|
||||
<Transition v-else-if="!comments?.length" appear name="fade">
|
||||
<Transition v-else-if="!comments?.items.length" appear name="fade">
|
||||
<VEmpty
|
||||
:message="$t('core.comment.empty.message')"
|
||||
:title="$t('core.comment.empty.title')"
|
||||
|
@ -385,7 +348,7 @@ const handleApproveInBatch = async () => {
|
|||
<Transition v-else appear name="fade">
|
||||
<VEntityContainer>
|
||||
<CommentListItem
|
||||
v-for="comment in comments"
|
||||
v-for="comment in comments.items"
|
||||
:key="comment.comment.metadata.name"
|
||||
:comment="comment"
|
||||
:is-selected="isSelection(comment)"
|
||||
|
@ -409,9 +372,11 @@ const handleApproveInBatch = async () => {
|
|||
:page-label="$t('core.components.pagination.page_label')"
|
||||
:size-label="$t('core.components.pagination.size_label')"
|
||||
:total-label="
|
||||
$t('core.components.pagination.total_label', { total: total })
|
||||
$t('core.components.pagination.total_label', {
|
||||
total: comments?.total || 0,
|
||||
})
|
||||
"
|
||||
:total="total"
|
||||
:total="comments?.total || 0"
|
||||
:size-options="[20, 30, 50, 100]"
|
||||
/>
|
||||
</template>
|
||||
|
|
|
@ -275,7 +275,9 @@ const { data: contentProvider } = useContentProviderExtensionPoint();
|
|||
/>
|
||||
<VEntity :is-selected="isSelected">
|
||||
<template
|
||||
v-if="currentUserHasPermission(['system:comments:manage'])"
|
||||
v-if="
|
||||
currentUserHasPermission(['system:comments:manage']) && $slots.checkbox
|
||||
"
|
||||
#checkbox
|
||||
>
|
||||
<slot name="checkbox" />
|
||||
|
|
|
@ -0,0 +1,192 @@
|
|||
<script lang="ts" setup>
|
||||
import FilterCleanButton from "@/components/filter/FilterCleanButton.vue";
|
||||
import FilterDropdown from "@/components/filter/FilterDropdown.vue";
|
||||
import UserFilterDropdown from "@/components/filter/UserFilterDropdown.vue";
|
||||
import SearchInput from "@/components/input/SearchInput.vue";
|
||||
import HasPermission from "@/components/permission/HasPermission.vue";
|
||||
import {
|
||||
IconRefreshLine,
|
||||
VButton,
|
||||
VEmpty,
|
||||
VEntityContainer,
|
||||
VLoading,
|
||||
VPagination,
|
||||
VSpace,
|
||||
} from "@halo-dev/components";
|
||||
import { computed, ref, toRefs, watch } from "vue";
|
||||
import useCommentsFetch from "../composables/use-comments-fetch";
|
||||
import CommentListItem from "./CommentListItem.vue";
|
||||
|
||||
const props = defineProps<{
|
||||
subjectRefKey: string;
|
||||
}>();
|
||||
|
||||
const { subjectRefKey } = toRefs(props);
|
||||
|
||||
const selectedApprovedStatus = ref();
|
||||
const selectedSort = ref();
|
||||
const selectedUser = ref();
|
||||
const page = ref(1);
|
||||
const size = ref(20);
|
||||
const keyword = ref("");
|
||||
|
||||
const {
|
||||
data: comments,
|
||||
isLoading,
|
||||
isFetching,
|
||||
refetch,
|
||||
} = useCommentsFetch(
|
||||
"core:comments:with-subject",
|
||||
page,
|
||||
size,
|
||||
selectedApprovedStatus,
|
||||
selectedSort,
|
||||
selectedUser,
|
||||
keyword,
|
||||
subjectRefKey
|
||||
);
|
||||
|
||||
watch(
|
||||
() => [
|
||||
selectedApprovedStatus.value,
|
||||
selectedSort.value,
|
||||
selectedUser.value,
|
||||
keyword.value,
|
||||
],
|
||||
() => {
|
||||
page.value = 1;
|
||||
}
|
||||
);
|
||||
|
||||
const hasFilters = computed(() => {
|
||||
return (
|
||||
selectedApprovedStatus.value !== undefined ||
|
||||
selectedSort.value ||
|
||||
selectedUser.value
|
||||
);
|
||||
});
|
||||
|
||||
function handleClearFilters() {
|
||||
selectedApprovedStatus.value = undefined;
|
||||
selectedSort.value = undefined;
|
||||
selectedUser.value = undefined;
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div>
|
||||
<div class="mb-4 flex flex-wrap items-center justify-between gap-4">
|
||||
<SearchInput v-model="keyword" />
|
||||
<VSpace spacing="lg" class="flex-wrap">
|
||||
<FilterCleanButton v-if="hasFilters" @click="handleClearFilters" />
|
||||
<FilterDropdown
|
||||
v-model="selectedApprovedStatus"
|
||||
:label="$t('core.common.filters.labels.status')"
|
||||
:items="[
|
||||
{
|
||||
label: $t('core.common.filters.item_labels.all'),
|
||||
},
|
||||
{
|
||||
label: $t('core.comment.filters.status.items.approved'),
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
label: $t('core.comment.filters.status.items.pending_review'),
|
||||
value: false,
|
||||
},
|
||||
]"
|
||||
/>
|
||||
<HasPermission :permissions="['system:users:view']">
|
||||
<UserFilterDropdown
|
||||
v-model="selectedUser"
|
||||
:label="$t('core.comment.filters.owner.label')"
|
||||
/>
|
||||
</HasPermission>
|
||||
<FilterDropdown
|
||||
v-model="selectedSort"
|
||||
:label="$t('core.common.filters.labels.sort')"
|
||||
:items="[
|
||||
{
|
||||
label: $t('core.common.filters.item_labels.default'),
|
||||
},
|
||||
{
|
||||
label: $t('core.comment.filters.sort.items.last_reply_time_desc'),
|
||||
value: 'status.lastReplyTime,desc',
|
||||
},
|
||||
{
|
||||
label: $t('core.comment.filters.sort.items.last_reply_time_asc'),
|
||||
value: 'status.lastReplyTime,asc',
|
||||
},
|
||||
{
|
||||
label: $t('core.comment.filters.sort.items.reply_count_desc'),
|
||||
value: 'status.replyCount,desc',
|
||||
},
|
||||
{
|
||||
label: $t('core.comment.filters.sort.items.reply_count_asc'),
|
||||
value: 'status.replyCount,asc',
|
||||
},
|
||||
{
|
||||
label: $t('core.comment.filters.sort.items.create_time_desc'),
|
||||
value: 'metadata.creationTimestamp,desc',
|
||||
},
|
||||
{
|
||||
label: $t('core.comment.filters.sort.items.create_time_asc'),
|
||||
value: 'metadata.creationTimestamp,asc',
|
||||
},
|
||||
]"
|
||||
/>
|
||||
<div class="flex flex-row gap-2">
|
||||
<div
|
||||
class="group cursor-pointer rounded p-1 hover:bg-gray-200"
|
||||
@click="refetch()"
|
||||
>
|
||||
<IconRefreshLine
|
||||
v-tooltip="$t('core.common.buttons.refresh')"
|
||||
:class="{ 'animate-spin text-gray-900': isFetching }"
|
||||
class="h-4 w-4 text-gray-600 group-hover:text-gray-900"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</VSpace>
|
||||
</div>
|
||||
<VLoading v-if="isLoading" />
|
||||
<Transition v-else-if="!comments?.items.length" appear name="fade">
|
||||
<VEmpty
|
||||
:message="$t('core.comment.empty.message')"
|
||||
:title="$t('core.comment.empty.title')"
|
||||
>
|
||||
<template #actions>
|
||||
<VButton @click="refetch">
|
||||
{{ $t("core.common.buttons.refresh") }}
|
||||
</VButton>
|
||||
</template>
|
||||
</VEmpty>
|
||||
</Transition>
|
||||
<Transition v-else appear name="fade">
|
||||
<div class="overflow-hidden rounded-base border">
|
||||
<VEntityContainer>
|
||||
<CommentListItem
|
||||
v-for="comment in comments.items"
|
||||
:key="comment.comment.metadata.name"
|
||||
:comment="comment"
|
||||
>
|
||||
</CommentListItem>
|
||||
</VEntityContainer>
|
||||
</div>
|
||||
</Transition>
|
||||
<div class="mt-4">
|
||||
<VPagination
|
||||
v-model:page="page"
|
||||
v-model:size="size"
|
||||
:page-label="$t('core.components.pagination.page_label')"
|
||||
:size-label="$t('core.components.pagination.size_label')"
|
||||
:total-label="
|
||||
$t('core.components.pagination.total_label', {
|
||||
total: comments?.total || 0,
|
||||
})
|
||||
"
|
||||
:total="comments?.total || 0"
|
||||
:size-options="[20, 30, 50, 100]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,33 @@
|
|||
<script lang="ts" setup>
|
||||
import { VButton, VModal } from "@halo-dev/components";
|
||||
import { useTemplateRef } from "vue";
|
||||
import SubjectQueryCommentList from "./SubjectQueryCommentList.vue";
|
||||
|
||||
const props = defineProps<{
|
||||
subjectRefKey: string;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: "close"): void;
|
||||
}>();
|
||||
|
||||
const modal = useTemplateRef<InstanceType<typeof VModal> | null>("modal");
|
||||
</script>
|
||||
<template>
|
||||
<VModal
|
||||
ref="modal"
|
||||
:centered="false"
|
||||
:width="1400"
|
||||
:title="$t('core.comment.title')"
|
||||
:layer-closable="true"
|
||||
mount-to-body
|
||||
@close="emit('close')"
|
||||
>
|
||||
<SubjectQueryCommentList :subject-ref-key="props.subjectRefKey" />
|
||||
<template #footer>
|
||||
<VButton @click="modal?.close()">
|
||||
{{ $t("core.common.buttons.close") }}
|
||||
</VButton>
|
||||
</template>
|
||||
</VModal>
|
||||
</template>
|
|
@ -0,0 +1,50 @@
|
|||
import { consoleApiClient, type ListedCommentList } from "@halo-dev/api-client";
|
||||
import { useQuery } from "@tanstack/vue-query";
|
||||
import type { Ref } from "vue";
|
||||
|
||||
export default function useCommentsFetch(
|
||||
queryKey: string,
|
||||
page: Ref<number>,
|
||||
size: Ref<number>,
|
||||
approved: Ref<boolean | undefined>,
|
||||
sort: Ref<string | undefined>,
|
||||
user: Ref<string | undefined>,
|
||||
keyword: Ref<string | undefined>,
|
||||
subjectRefKey?: Ref<string | undefined>
|
||||
) {
|
||||
return useQuery<ListedCommentList>({
|
||||
queryKey: [queryKey, page, size, approved, sort, user, keyword],
|
||||
queryFn: async () => {
|
||||
const fieldSelectorMap: Record<string, string | boolean | undefined> = {
|
||||
"spec.approved": approved.value,
|
||||
"spec.subjectRef": subjectRefKey?.value,
|
||||
};
|
||||
|
||||
const fieldSelector = Object.entries(fieldSelectorMap)
|
||||
.map(([key, value]) => {
|
||||
if (value !== undefined) {
|
||||
return `${key}=${value}`;
|
||||
}
|
||||
})
|
||||
.filter(Boolean) as string[];
|
||||
|
||||
const { data } = await consoleApiClient.content.comment.listComments({
|
||||
fieldSelector,
|
||||
page: page.value,
|
||||
size: size.value,
|
||||
sort: [sort.value].filter(Boolean) as string[],
|
||||
keyword: keyword.value,
|
||||
ownerName: user.value,
|
||||
ownerKind: user.value ? "User" : undefined,
|
||||
});
|
||||
|
||||
return data;
|
||||
},
|
||||
refetchInterval(data) {
|
||||
const hasDeletingData = data?.items.some(
|
||||
(comment) => !!comment.comment.metadata.deletionTimestamp
|
||||
);
|
||||
return hasDeletingData ? 1000 : false;
|
||||
},
|
||||
});
|
||||
}
|
|
@ -3,8 +3,14 @@ import { IconMessage } from "@halo-dev/components";
|
|||
import { definePlugin } from "@halo-dev/console-shared";
|
||||
import { markRaw } from "vue";
|
||||
import CommentList from "./CommentList.vue";
|
||||
import SubjectQueryCommentList from "./components/SubjectQueryCommentList.vue";
|
||||
import SubjectQueryCommentListModal from "./components/SubjectQueryCommentListModal.vue";
|
||||
|
||||
export default definePlugin({
|
||||
components: {
|
||||
SubjectQueryCommentList,
|
||||
SubjectQueryCommentListModal,
|
||||
},
|
||||
routes: [
|
||||
{
|
||||
path: "/comments",
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<script lang="ts" setup>
|
||||
import { singlePageLabels } from "@/constants/labels";
|
||||
import SubjectQueryCommentListModal from "@console/modules/contents/comments/components/SubjectQueryCommentListModal.vue";
|
||||
import type { ListedSinglePage } from "@halo-dev/api-client";
|
||||
import {
|
||||
IconExternalLinkLine,
|
||||
|
@ -7,7 +8,8 @@ import {
|
|||
VSpace,
|
||||
VStatusDot,
|
||||
} from "@halo-dev/components";
|
||||
import { computed } from "vue";
|
||||
import { computed, ref } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
|
@ -16,6 +18,8 @@ const props = withDefaults(
|
|||
{}
|
||||
);
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const externalUrl = computed(() => {
|
||||
const { metadata, status } = props.singlePage.page;
|
||||
if (metadata.labels?.[singlePageLabels.PUBLISHED] === "true") {
|
||||
|
@ -23,6 +27,30 @@ const externalUrl = computed(() => {
|
|||
}
|
||||
return `/preview/singlepages/${metadata.name}`;
|
||||
});
|
||||
|
||||
const commentSubjectRefKey = `content.halo.run/SinglePage/${props.singlePage.page.metadata.name}`;
|
||||
const commentListVisible = ref(false);
|
||||
|
||||
const commentText = computed(() => {
|
||||
const { totalComment, approvedComment } = props.singlePage.stats || {};
|
||||
|
||||
let text = t("core.page.list.fields.comments", {
|
||||
comments: totalComment || 0,
|
||||
});
|
||||
|
||||
if (!totalComment || !approvedComment) {
|
||||
return text;
|
||||
}
|
||||
|
||||
const pendingComments = totalComment - approvedComment;
|
||||
|
||||
if (pendingComments > 0) {
|
||||
text += t("core.page.list.fields.comments-with-pending", {
|
||||
count: pendingComments,
|
||||
});
|
||||
}
|
||||
return text;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -66,15 +94,20 @@ const externalUrl = computed(() => {
|
|||
})
|
||||
}}
|
||||
</span>
|
||||
<span class="text-xs text-gray-500">
|
||||
{{
|
||||
$t("core.page.list.fields.comments", {
|
||||
comments: singlePage.stats.totalComment || 0,
|
||||
})
|
||||
}}
|
||||
<span
|
||||
class="cursor-pointer text-xs text-gray-500 hover:text-gray-900 hover:underline"
|
||||
@click="commentListVisible = true"
|
||||
>
|
||||
{{ commentText }}
|
||||
</span>
|
||||
</VSpace>
|
||||
</div>
|
||||
|
||||
<SubjectQueryCommentListModal
|
||||
v-if="commentListVisible"
|
||||
:subject-ref-key="commentSubjectRefKey"
|
||||
@close="commentListVisible = false"
|
||||
/>
|
||||
</template>
|
||||
</VEntityField>
|
||||
</template>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<script lang="ts" setup>
|
||||
import { postLabels } from "@/constants/labels";
|
||||
import SubjectQueryCommentListModal from "@console/modules/contents/comments/components/SubjectQueryCommentListModal.vue";
|
||||
import type { ListedPost } from "@halo-dev/api-client";
|
||||
import {
|
||||
IconExternalLinkLine,
|
||||
|
@ -7,7 +8,8 @@ import {
|
|||
VSpace,
|
||||
VStatusDot,
|
||||
} from "@halo-dev/components";
|
||||
import { computed } from "vue";
|
||||
import { computed, ref } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import PostTag from "../../tags/components/PostTag.vue";
|
||||
|
||||
const props = withDefaults(
|
||||
|
@ -17,6 +19,8 @@ const props = withDefaults(
|
|||
{}
|
||||
);
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const externalUrl = computed(() => {
|
||||
const { status, metadata } = props.post.post;
|
||||
if (metadata.labels?.[postLabels.PUBLISHED] === "true") {
|
||||
|
@ -24,6 +28,30 @@ const externalUrl = computed(() => {
|
|||
}
|
||||
return `/preview/posts/${metadata.name}`;
|
||||
});
|
||||
|
||||
const commentSubjectRefKey = `content.halo.run/Post/${props.post.post.metadata.name}`;
|
||||
const commentListVisible = ref(false);
|
||||
|
||||
const commentText = computed(() => {
|
||||
const { totalComment, approvedComment } = props.post.stats || {};
|
||||
|
||||
let text = t("core.post.list.fields.comments", {
|
||||
comments: totalComment || 0,
|
||||
});
|
||||
|
||||
if (!totalComment || !approvedComment) {
|
||||
return text;
|
||||
}
|
||||
|
||||
const pendingComments = totalComment - approvedComment;
|
||||
|
||||
if (pendingComments > 0) {
|
||||
text += t("core.post.list.fields.comments-with-pending", {
|
||||
count: pendingComments,
|
||||
});
|
||||
}
|
||||
return text;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -83,12 +111,11 @@ const externalUrl = computed(() => {
|
|||
})
|
||||
}}
|
||||
</span>
|
||||
<span class="text-xs text-gray-500">
|
||||
{{
|
||||
$t("core.post.list.fields.comments", {
|
||||
comments: post.stats.totalComment || 0,
|
||||
})
|
||||
}}
|
||||
<span
|
||||
class="cursor-pointer text-xs text-gray-500 hover:text-gray-900 hover:underline"
|
||||
@click="commentListVisible = true"
|
||||
>
|
||||
{{ commentText }}
|
||||
</span>
|
||||
<span v-if="post.post.spec.pinned" class="text-xs text-gray-500">
|
||||
{{ $t("core.post.list.fields.pinned") }}
|
||||
|
@ -103,6 +130,12 @@ const externalUrl = computed(() => {
|
|||
></PostTag>
|
||||
</VSpace>
|
||||
</div>
|
||||
|
||||
<SubjectQueryCommentListModal
|
||||
v-if="commentListVisible"
|
||||
:subject-ref-key="commentSubjectRefKey"
|
||||
@close="commentListVisible = false"
|
||||
/>
|
||||
</template>
|
||||
</VEntityField>
|
||||
</template>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<script lang="ts" setup>
|
||||
import { postLabels } from "@/constants/labels";
|
||||
import { formatDatetime, relativeTimeTo } from "@/utils/date";
|
||||
import SubjectQueryCommentListModal from "@console/modules/contents/comments/components/SubjectQueryCommentListModal.vue";
|
||||
import type { ListedPost } from "@halo-dev/api-client";
|
||||
import {
|
||||
IconExternalLinkLine,
|
||||
|
@ -9,7 +10,10 @@ import {
|
|||
VSpace,
|
||||
VTag,
|
||||
} from "@halo-dev/components";
|
||||
import { computed } from "vue";
|
||||
import { computed, ref } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const props = defineProps<{
|
||||
post: ListedPost;
|
||||
|
@ -33,6 +37,33 @@ const datetime = computed(() => {
|
|||
props.post.post.metadata.creationTimestamp
|
||||
);
|
||||
});
|
||||
|
||||
const commentSubjectRefKey = `content.halo.run/Post/${props.post.post.metadata.name}`;
|
||||
const commentListVisible = ref(false);
|
||||
|
||||
const commentText = computed(() => {
|
||||
const { totalComment, approvedComment } = props.post.stats || {};
|
||||
|
||||
let text = t("core.dashboard.widgets.presets.recent_published.comments", {
|
||||
comments: totalComment || 0,
|
||||
});
|
||||
|
||||
if (!totalComment || !approvedComment) {
|
||||
return text;
|
||||
}
|
||||
|
||||
const pendingComments = totalComment - approvedComment;
|
||||
|
||||
if (pendingComments > 0) {
|
||||
text += t(
|
||||
"core.dashboard.widgets.presets.recent_published.comments-with-pending",
|
||||
{
|
||||
count: pendingComments,
|
||||
}
|
||||
);
|
||||
}
|
||||
return text;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -54,14 +85,18 @@ const datetime = computed(() => {
|
|||
})
|
||||
}}
|
||||
</span>
|
||||
<span class="text-xs text-gray-500">
|
||||
{{
|
||||
$t("core.dashboard.widgets.presets.recent_published.comments", {
|
||||
comments: post.stats.totalComment || 0,
|
||||
})
|
||||
}}
|
||||
<span
|
||||
class="cursor-pointer text-xs text-gray-500 hover:text-gray-900 hover:underline"
|
||||
@click="commentListVisible = true"
|
||||
>
|
||||
{{ commentText }}
|
||||
</span>
|
||||
</VSpace>
|
||||
<SubjectQueryCommentListModal
|
||||
v-if="commentListVisible"
|
||||
:subject-ref-key="commentSubjectRefKey"
|
||||
@close="commentListVisible = false"
|
||||
/>
|
||||
</template>
|
||||
<template #extra>
|
||||
<VSpace>
|
||||
|
|
|
@ -33,6 +33,7 @@ core:
|
|||
recent_published:
|
||||
empty:
|
||||
title: No published posts
|
||||
comments-with-pending: " ({count} pending comments)"
|
||||
notification:
|
||||
title: Notifications
|
||||
empty:
|
||||
|
@ -116,6 +117,7 @@ core:
|
|||
fields:
|
||||
schedule_publish:
|
||||
tooltip: Schedule publish
|
||||
comments-with-pending: ({count} pending comments)
|
||||
settings:
|
||||
fields:
|
||||
publish_time:
|
||||
|
@ -175,6 +177,10 @@ core:
|
|||
fields:
|
||||
prevent_parent_post_cascade_query: Prevent parent category from including this category and its subcategories in cascade post queries
|
||||
hide_from_list: This category is hidden, This category and its subcategories, as well as its posts, will not be displayed in the front-end list. You need to actively visit the category archive page
|
||||
page:
|
||||
list:
|
||||
fields:
|
||||
comments-with-pending: " ({count} pending comments)"
|
||||
page_editor:
|
||||
actions:
|
||||
snapshots: Snapshots
|
||||
|
@ -375,10 +381,16 @@ core:
|
|||
operations:
|
||||
enable:
|
||||
title: Enable
|
||||
description: Are you sure you want to enable this user?
|
||||
description: Are you sure you want to enable this user? Once enabled, the user will be able to log back into the system.
|
||||
enable_in_batch:
|
||||
title: Enable
|
||||
description: Are you sure you want to enable the selected users? Once enabled, these users will be able to log back into the system.
|
||||
disable:
|
||||
title: Disable
|
||||
description: Are you sure you want to disable this user? This user will not be able to log in after being disabled
|
||||
description: Are you sure you want to disable this user? Once disabled, the user will no longer be able to log in.
|
||||
disable_in_batch:
|
||||
title: Disable
|
||||
description: Are you sure you want to disable the selected users? Once disabled, these users will no longer be able to log in.
|
||||
grant_permission_modal:
|
||||
roles_preview:
|
||||
all: The currently selected role contains all permissions
|
||||
|
|
|
@ -73,6 +73,7 @@ core:
|
|||
comments: "{comments} Comments"
|
||||
empty:
|
||||
title: No published posts
|
||||
comments-with-pending: " ({count} pending comments)"
|
||||
notification:
|
||||
title: Notifications
|
||||
empty:
|
||||
|
@ -238,6 +239,7 @@ core:
|
|||
pinned: Pinned
|
||||
schedule_publish:
|
||||
tooltip: Schedule publish
|
||||
comments-with-pending: " ({count} pending comments)"
|
||||
settings:
|
||||
title: Settings
|
||||
groups:
|
||||
|
@ -404,7 +406,10 @@ core:
|
|||
help: Theme adaptation is required to support
|
||||
description:
|
||||
label: Description
|
||||
help: "The description will be automatically added to the page's meta description tag for SEO; other display purposes require theme adaptation"
|
||||
help: >-
|
||||
The description will be automatically added to the page's meta
|
||||
description tag for SEO; other display purposes require theme
|
||||
adaptation
|
||||
prevent_parent_post_cascade_query:
|
||||
label: Prevent Parent Post Cascade Query
|
||||
help: >-
|
||||
|
@ -473,6 +478,7 @@ core:
|
|||
fields:
|
||||
visits: "{visits} Visits"
|
||||
comments: "{comments} Comments"
|
||||
comments-with-pending: " ({count} pending comments)"
|
||||
settings:
|
||||
title: Settings
|
||||
groups:
|
||||
|
@ -1143,19 +1149,23 @@ core:
|
|||
enable:
|
||||
title: Enable
|
||||
description: >-
|
||||
Are you sure you want to enable this user? Once enabled, the user will be able to log back into the system.
|
||||
Are you sure you want to enable this user? Once enabled, the user will
|
||||
be able to log back into the system.
|
||||
enable_in_batch:
|
||||
title: Enable
|
||||
description: >-
|
||||
Are you sure you want to enable the selected users? Once enabled, these users will be able to log back into the system.
|
||||
Are you sure you want to enable the selected users? Once enabled,
|
||||
these users will be able to log back into the system.
|
||||
disable:
|
||||
title: Disable
|
||||
description: >-
|
||||
Are you sure you want to disable this user? Once disabled, the user will no longer be able to log in.
|
||||
Are you sure you want to disable this user? Once disabled, the user
|
||||
will no longer be able to log in.
|
||||
disable_in_batch:
|
||||
title: Disable
|
||||
description: >-
|
||||
Are you sure you want to disable the selected users? Once disabled, these users will no longer be able to log in.
|
||||
Are you sure you want to disable the selected users? Once disabled,
|
||||
these users will no longer be able to log in.
|
||||
filters:
|
||||
role:
|
||||
label: Role
|
||||
|
|
|
@ -67,6 +67,7 @@ core:
|
|||
comments: 评论 {comments}
|
||||
empty:
|
||||
title: 暂无已发布文章
|
||||
comments-with-pending: " ({count} 条待审核)"
|
||||
notification:
|
||||
title: 通知
|
||||
empty:
|
||||
|
@ -224,6 +225,7 @@ core:
|
|||
pinned: 已置顶
|
||||
schedule_publish:
|
||||
tooltip: 定时发布
|
||||
comments-with-pending: ({count} 条待审核)
|
||||
settings:
|
||||
title: 文章设置
|
||||
groups:
|
||||
|
@ -451,6 +453,7 @@ core:
|
|||
fields:
|
||||
visits: 访问量 {visits}
|
||||
comments: 评论 {comments}
|
||||
comments-with-pending: ({count} 条待审核)
|
||||
settings:
|
||||
title: 页面设置
|
||||
groups:
|
||||
|
|
|
@ -67,6 +67,7 @@ core:
|
|||
comments: 留言 {comments}
|
||||
empty:
|
||||
title: 沒有已發布文章
|
||||
comments-with-pending: " ({count} 條待審核)"
|
||||
notification:
|
||||
title: 通知
|
||||
empty:
|
||||
|
@ -224,6 +225,7 @@ core:
|
|||
pinned: 已置頂
|
||||
schedule_publish:
|
||||
tooltip: 定時發佈
|
||||
comments-with-pending: ({count} 條待審核)
|
||||
settings:
|
||||
title: 文章設置
|
||||
groups:
|
||||
|
@ -436,6 +438,7 @@ core:
|
|||
fields:
|
||||
visits: 訪問量 {visits}
|
||||
comments: 留言 {comments}
|
||||
comments-with-pending: " ({count} 條待審核)"
|
||||
settings:
|
||||
title: 頁面設置
|
||||
groups:
|
||||
|
|
Loading…
Reference in New Issue