|
| 1 | +<script setup lang="ts"> |
| 2 | +import { formatDatetime, relativeTimeTo } from "@/utils/date"; |
| 3 | +import { |
| 4 | + consoleApiClient, |
| 5 | + coreApiClient, |
| 6 | + type ListedComment, |
| 7 | +} from "@halo-dev/api-client"; |
| 8 | +import { |
| 9 | + IconExternalLinkLine, |
| 10 | + Toast, |
| 11 | + VButton, |
| 12 | + VDescription, |
| 13 | + VDescriptionItem, |
| 14 | + VModal, |
| 15 | + VSpace, |
| 16 | +} from "@halo-dev/components"; |
| 17 | +import { useQueryClient } from "@tanstack/vue-query"; |
| 18 | +import { useUserAgent } from "@uc/modules/profile/tabs/composables/use-user-agent"; |
| 19 | +import { computed, ref, useTemplateRef } from "vue"; |
| 20 | +import { useI18n } from "vue-i18n"; |
| 21 | +import { useSubjectRef } from "../composables/use-subject-ref"; |
| 22 | +import OwnerButton from "./OwnerButton.vue"; |
| 23 | +import ReplyFormItems from "./ReplyFormItems.vue"; |
| 24 | +
|
| 25 | +const props = withDefaults( |
| 26 | + defineProps<{ |
| 27 | + comment: ListedComment; |
| 28 | + }>(), |
| 29 | + {} |
| 30 | +); |
| 31 | +
|
| 32 | +const queryClient = useQueryClient(); |
| 33 | +const { t } = useI18n(); |
| 34 | +
|
| 35 | +const emit = defineEmits<{ |
| 36 | + (e: "close"): void; |
| 37 | +}>(); |
| 38 | +
|
| 39 | +const modal = useTemplateRef<InstanceType<typeof VModal> | null>("modal"); |
| 40 | +
|
| 41 | +const { os, browser } = useUserAgent(props.comment.comment.spec.userAgent); |
| 42 | +
|
| 43 | +const creationTime = computed(() => { |
| 44 | + return ( |
| 45 | + props.comment?.comment.spec.creationTime || |
| 46 | + props.comment?.comment.metadata.creationTimestamp |
| 47 | + ); |
| 48 | +}); |
| 49 | +
|
| 50 | +const newReply = ref(""); |
| 51 | +
|
| 52 | +async function handleApprove() { |
| 53 | + if (!newReply.value) { |
| 54 | + await coreApiClient.content.comment.patchComment({ |
| 55 | + name: props.comment.comment.metadata.name, |
| 56 | + jsonPatchInner: [ |
| 57 | + { |
| 58 | + op: "add", |
| 59 | + path: "/spec/approved", |
| 60 | + value: true, |
| 61 | + }, |
| 62 | + { |
| 63 | + op: "add", |
| 64 | + path: "/spec/approvedTime", |
| 65 | + value: new Date().toISOString(), |
| 66 | + }, |
| 67 | + ], |
| 68 | + }); |
| 69 | + } else { |
| 70 | + await consoleApiClient.content.comment.createReply({ |
| 71 | + name: props.comment?.comment.metadata.name as string, |
| 72 | + replyRequest: { |
| 73 | + raw: newReply.value, |
| 74 | + content: newReply.value, |
| 75 | + allowNotification: true, |
| 76 | + quoteReply: undefined, |
| 77 | + }, |
| 78 | + }); |
| 79 | + } |
| 80 | + modal.value?.close(); |
| 81 | + queryClient.invalidateQueries({ queryKey: ["core:comments"] }); |
| 82 | + Toast.success(t("core.common.toast.operation_success")); |
| 83 | +} |
| 84 | +
|
| 85 | +const { subjectRefResult } = useSubjectRef(props.comment); |
| 86 | +
|
| 87 | +const websiteOfAnonymous = computed(() => { |
| 88 | + return props.comment.comment.spec.owner.annotations?.["website"]; |
| 89 | +}); |
| 90 | +</script> |
| 91 | +<template> |
| 92 | + <VModal |
| 93 | + ref="modal" |
| 94 | + :body-class="['!p-0']" |
| 95 | + :width="900" |
| 96 | + :title="$t('core.comment.comment_detail_modal.title')" |
| 97 | + mount-to-body |
| 98 | + :centered="false" |
| 99 | + @close="emit('close')" |
| 100 | + > |
| 101 | + <div> |
| 102 | + <VDescription> |
| 103 | + <VDescriptionItem :label="$t('core.comment.detail_modal.fields.owner')"> |
| 104 | + <div class="flex items-center gap-3"> |
| 105 | + <OwnerButton |
| 106 | + v-if="comment.comment.spec.owner.kind === 'User'" |
| 107 | + :owner="comment.comment.spec.owner" |
| 108 | + @click=" |
| 109 | + $router.push({ |
| 110 | + name: 'UserDetail', |
| 111 | + params: { name: comment.comment.spec.owner.name }, |
| 112 | + }) |
| 113 | + " |
| 114 | + /> |
| 115 | + <ul v-else class="space-y-1"> |
| 116 | + <li>{{ comment.comment.spec.owner.displayName }}</li> |
| 117 | + <li>{{ comment.comment.spec.owner.name }}</li> |
| 118 | + <li v-if="websiteOfAnonymous"> |
| 119 | + <a :href="websiteOfAnonymous" target="_blank">{{ |
| 120 | + websiteOfAnonymous |
| 121 | + }}</a> |
| 122 | + </li> |
| 123 | + </ul> |
| 124 | + </div> |
| 125 | + </VDescriptionItem> |
| 126 | + <VDescriptionItem label="IP"> |
| 127 | + {{ comment.comment.spec.ipAddress }} |
| 128 | + </VDescriptionItem> |
| 129 | + <VDescriptionItem |
| 130 | + :label="$t('core.comment.detail_modal.fields.user_agent')" |
| 131 | + > |
| 132 | + <span v-tooltip="comment.comment.spec.userAgent"> |
| 133 | + {{ os }} {{ browser }} |
| 134 | + </span> |
| 135 | + </VDescriptionItem> |
| 136 | + <VDescriptionItem |
| 137 | + :label="$t('core.comment.detail_modal.fields.creation_time')" |
| 138 | + > |
| 139 | + <span v-tooltip="formatDatetime(creationTime)"> |
| 140 | + {{ relativeTimeTo(creationTime) }} |
| 141 | + </span> |
| 142 | + </VDescriptionItem> |
| 143 | + <VDescriptionItem |
| 144 | + :label="$t('core.comment.detail_modal.fields.commented_on')" |
| 145 | + > |
| 146 | + <div class="flex items-center gap-2"> |
| 147 | + <RouterLink |
| 148 | + v-tooltip="`${subjectRefResult.label}`" |
| 149 | + :to="subjectRefResult.route || $route" |
| 150 | + class="inline-block text-sm hover:text-gray-600" |
| 151 | + > |
| 152 | + {{ subjectRefResult.title }} |
| 153 | + </RouterLink> |
| 154 | + <a |
| 155 | + v-if="subjectRefResult.externalUrl" |
| 156 | + :href="subjectRefResult.externalUrl" |
| 157 | + target="_blank" |
| 158 | + class="text-gray-600 hover:text-gray-900" |
| 159 | + > |
| 160 | + <IconExternalLinkLine class="h-3.5 w-3.5" /> |
| 161 | + </a> |
| 162 | + </div> |
| 163 | + </VDescriptionItem> |
| 164 | + <VDescriptionItem |
| 165 | + :label="$t('core.comment.comment_detail_modal.fields.content')" |
| 166 | + > |
| 167 | + <pre class="whitespace-pre-wrap break-words text-sm text-gray-900">{{ |
| 168 | + comment.comment.spec.content |
| 169 | + }}</pre> |
| 170 | + </VDescriptionItem> |
| 171 | + <VDescriptionItem |
| 172 | + v-if="!comment.comment.spec.approved" |
| 173 | + :label="$t('core.comment.detail_modal.fields.new_reply')" |
| 174 | + > |
| 175 | + <ReplyFormItems |
| 176 | + :required="false" |
| 177 | + :auto-focus="false" |
| 178 | + @update="newReply = $event" |
| 179 | + /> |
| 180 | + </VDescriptionItem> |
| 181 | + </VDescription> |
| 182 | + </div> |
| 183 | + <template #footer> |
| 184 | + <VSpace> |
| 185 | + <VButton |
| 186 | + v-if="!comment.comment.spec.approved" |
| 187 | + type="secondary" |
| 188 | + @click="handleApprove" |
| 189 | + > |
| 190 | + {{ |
| 191 | + newReply |
| 192 | + ? $t("core.comment.operations.reply_and_approve.button") |
| 193 | + : $t("core.comment.operations.approve.button") |
| 194 | + }} |
| 195 | + </VButton> |
| 196 | + <VButton @click="modal?.close()"> |
| 197 | + {{ $t("core.common.buttons.close") }} |
| 198 | + </VButton> |
| 199 | + </VSpace> |
| 200 | + </template> |
| 201 | + </VModal> |
| 202 | +</template> |
| 203 | + |
| 204 | +<style scoped> |
| 205 | +:deep(.description-item__content) { |
| 206 | + @apply lg:col-span-5; |
| 207 | +} |
| 208 | +</style> |
0 commit comments