mirror of https://github.com/halo-dev/halo
feat: make field items of singlePage data list extendable (#7553)
Signed-off-by: Ryan Wang <i@ryanc.cc>pull/7558/head
parent
1bd6b5530e
commit
a91d072cdf
|
@ -1,33 +1,36 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import PostContributorList from "@/components/user/PostContributorList.vue";
|
import EntityFieldItems from "@/components/entity-fields/EntityFieldItems.vue";
|
||||||
import { singlePageLabels } from "@/constants/labels";
|
import StatusDotField from "@/components/entity-fields/StatusDotField.vue";
|
||||||
import { formatDatetime, relativeTimeTo } from "@/utils/date";
|
import EntityDropdownItems from "@/components/entity/EntityDropdownItems.vue";
|
||||||
import { usePermission } from "@/utils/permission";
|
import { usePermission } from "@/utils/permission";
|
||||||
import { generateThumbnailUrl } from "@/utils/thumbnail";
|
import { useEntityFieldItemExtensionPoint } from "@console/composables/use-entity-extension-points";
|
||||||
|
import { useOperationItemExtensionPoint } from "@console/composables/use-operation-extension-points";
|
||||||
import type { ListedSinglePage, SinglePage } from "@halo-dev/api-client";
|
import type { ListedSinglePage, SinglePage } from "@halo-dev/api-client";
|
||||||
import { coreApiClient } from "@halo-dev/api-client";
|
import { coreApiClient } from "@halo-dev/api-client";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
IconExternalLinkLine,
|
|
||||||
IconEye,
|
|
||||||
IconEyeOff,
|
|
||||||
Toast,
|
Toast,
|
||||||
VDropdownDivider,
|
VDropdownDivider,
|
||||||
VDropdownItem,
|
VDropdownItem,
|
||||||
VEntity,
|
VEntity,
|
||||||
VEntityField,
|
|
||||||
VSpace,
|
|
||||||
VStatusDot,
|
|
||||||
} from "@halo-dev/components";
|
} from "@halo-dev/components";
|
||||||
import { useMutation, useQueryClient } from "@tanstack/vue-query";
|
import { useQueryClient } from "@tanstack/vue-query";
|
||||||
|
import type { EntityFieldItem, OperationItem } from "packages/shared/dist";
|
||||||
import type { Ref } from "vue";
|
import type { Ref } from "vue";
|
||||||
import { computed, inject, ref } from "vue";
|
import { computed, inject, markRaw, ref, toRefs } from "vue";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import { RouterLink } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
|
import ContributorsField from "./entity-fields/ContributorsField.vue";
|
||||||
|
import CoverField from "./entity-fields/CoverField.vue";
|
||||||
|
import PublishStatusField from "./entity-fields/PublishStatusField.vue";
|
||||||
|
import PublishTimeField from "./entity-fields/PublishTimeField.vue";
|
||||||
|
import TitleField from "./entity-fields/TitleField.vue";
|
||||||
|
import VisibleField from "./entity-fields/VisibleField.vue";
|
||||||
|
|
||||||
const { currentUserHasPermission } = usePermission();
|
const { currentUserHasPermission } = usePermission();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
|
@ -39,59 +42,14 @@ const props = withDefaults(
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { singlePage } = toRefs(props);
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(event: "open-setting-modal", post: SinglePage): void;
|
(event: "open-setting-modal", singlePage: SinglePage): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const selectedPageNames = inject<Ref<string[]>>("selectedPageNames", ref([]));
|
const selectedPageNames = inject<Ref<string[]>>("selectedPageNames", ref([]));
|
||||||
|
|
||||||
const externalUrl = computed(() => {
|
|
||||||
const { metadata, status } = props.singlePage.page;
|
|
||||||
if (metadata.labels?.[singlePageLabels.PUBLISHED] === "true") {
|
|
||||||
return status?.permalink;
|
|
||||||
}
|
|
||||||
return `/preview/singlepages/${metadata.name}`;
|
|
||||||
});
|
|
||||||
|
|
||||||
const publishStatus = computed(() => {
|
|
||||||
const { labels } = props.singlePage.page.metadata;
|
|
||||||
return labels?.[singlePageLabels.PUBLISHED] === "true"
|
|
||||||
? t("core.page.filters.status.items.published")
|
|
||||||
: t("core.page.filters.status.items.draft");
|
|
||||||
});
|
|
||||||
|
|
||||||
const isPublishing = computed(() => {
|
|
||||||
const { spec, status, metadata } = props.singlePage.page;
|
|
||||||
return (
|
|
||||||
(spec.publish &&
|
|
||||||
metadata.labels?.[singlePageLabels.PUBLISHED] !== "true") ||
|
|
||||||
(spec.releaseSnapshot === spec.headSnapshot && status?.inProgress)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
const { mutate: changeVisibleMutation } = useMutation({
|
|
||||||
mutationFn: async (singlePage: SinglePage) => {
|
|
||||||
return await coreApiClient.content.singlePage.patchSinglePage({
|
|
||||||
name: singlePage.metadata.name,
|
|
||||||
jsonPatchInner: [
|
|
||||||
{
|
|
||||||
op: "add",
|
|
||||||
path: "/spec/visible",
|
|
||||||
value: singlePage.spec.visible === "PRIVATE" ? "PUBLIC" : "PRIVATE",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
},
|
|
||||||
retry: 3,
|
|
||||||
onSuccess: () => {
|
|
||||||
Toast.success(t("core.common.toast.operation_success"));
|
|
||||||
queryClient.invalidateQueries({ queryKey: ["singlePages"] });
|
|
||||||
},
|
|
||||||
onError: () => {
|
|
||||||
Toast.error(t("core.common.toast.operation_failed"));
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleDelete = async () => {
|
const handleDelete = async () => {
|
||||||
Dialog.warning({
|
Dialog.warning({
|
||||||
title: t("core.page.operations.delete.title"),
|
title: t("core.page.operations.delete.title"),
|
||||||
|
@ -117,6 +75,115 @@ const handleDelete = async () => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const { startFields, endFields } =
|
||||||
|
useEntityFieldItemExtensionPoint<ListedSinglePage>(
|
||||||
|
"single-page:list-item:field:create",
|
||||||
|
singlePage,
|
||||||
|
computed((): EntityFieldItem[] => [
|
||||||
|
{
|
||||||
|
priority: 10,
|
||||||
|
position: "start",
|
||||||
|
component: markRaw(CoverField),
|
||||||
|
hidden: !props.singlePage.page.spec.cover,
|
||||||
|
props: {
|
||||||
|
singlePage: props.singlePage,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
priority: 20,
|
||||||
|
position: "start",
|
||||||
|
component: markRaw(TitleField),
|
||||||
|
props: {
|
||||||
|
singlePage: props.singlePage,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
priority: 10,
|
||||||
|
position: "end",
|
||||||
|
component: markRaw(ContributorsField),
|
||||||
|
props: {
|
||||||
|
singlePage: props.singlePage,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
priority: 20,
|
||||||
|
position: "end",
|
||||||
|
component: markRaw(PublishStatusField),
|
||||||
|
props: {
|
||||||
|
singlePage: props.singlePage,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
priority: 30,
|
||||||
|
position: "end",
|
||||||
|
component: markRaw(VisibleField),
|
||||||
|
permissions: ["system:singlepages:manage"],
|
||||||
|
props: {
|
||||||
|
singlePage: props.singlePage,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
priority: 40,
|
||||||
|
position: "end",
|
||||||
|
component: markRaw(StatusDotField),
|
||||||
|
props: {
|
||||||
|
tooltip: t("core.common.status.deleting"),
|
||||||
|
state: "warning",
|
||||||
|
animate: true,
|
||||||
|
},
|
||||||
|
hidden: !props.singlePage?.page?.spec.deleted,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
priority: 50,
|
||||||
|
position: "end",
|
||||||
|
component: markRaw(PublishTimeField),
|
||||||
|
hidden: !props.singlePage.page.spec.publishTime,
|
||||||
|
props: {
|
||||||
|
singlePage: props.singlePage,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
const { operationItems } = useOperationItemExtensionPoint<ListedSinglePage>(
|
||||||
|
"single-page:list-item:operation:create",
|
||||||
|
singlePage,
|
||||||
|
computed((): OperationItem<ListedSinglePage>[] => [
|
||||||
|
{
|
||||||
|
priority: 0,
|
||||||
|
component: markRaw(VDropdownItem),
|
||||||
|
label: t("core.common.buttons.edit"),
|
||||||
|
action: async () => {
|
||||||
|
router.push({
|
||||||
|
name: "SinglePageEditor",
|
||||||
|
query: { name: props.singlePage.page.metadata.name },
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
priority: 10,
|
||||||
|
component: markRaw(VDropdownItem),
|
||||||
|
label: t("core.common.buttons.setting"),
|
||||||
|
action: () => {
|
||||||
|
emit("open-setting-modal", props.singlePage.page);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
priority: 20,
|
||||||
|
component: markRaw(VDropdownDivider),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
priority: 30,
|
||||||
|
component: markRaw(VDropdownItem),
|
||||||
|
props: {
|
||||||
|
type: "danger",
|
||||||
|
},
|
||||||
|
label: t("core.common.buttons.delete"),
|
||||||
|
action: handleDelete,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -132,137 +199,19 @@ const handleDelete = async () => {
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<template #start>
|
<template #start>
|
||||||
<VEntityField v-if="singlePage.page.spec.cover">
|
<EntityFieldItems :fields="startFields" />
|
||||||
<template #description>
|
|
||||||
<div class="aspect-h-2 rounded-md overflow-hidden aspect-w-3 w-20">
|
|
||||||
<img
|
|
||||||
class="object-cover w-full h-full"
|
|
||||||
:src="generateThumbnailUrl(singlePage.page.spec.cover, 's')"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</VEntityField>
|
|
||||||
<VEntityField
|
|
||||||
:title="singlePage.page.spec.title"
|
|
||||||
:route="{
|
|
||||||
name: 'SinglePageEditor',
|
|
||||||
query: { name: singlePage.page.metadata.name },
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<template #extra>
|
|
||||||
<VSpace>
|
|
||||||
<RouterLink
|
|
||||||
v-if="singlePage.page.status?.inProgress"
|
|
||||||
v-tooltip="$t('core.common.tooltips.unpublished_content_tip')"
|
|
||||||
:to="{
|
|
||||||
name: 'SinglePageEditor',
|
|
||||||
query: { name: singlePage.page.metadata.name },
|
|
||||||
}"
|
|
||||||
class="flex items-center"
|
|
||||||
>
|
|
||||||
<VStatusDot state="success" animate />
|
|
||||||
</RouterLink>
|
|
||||||
<a
|
|
||||||
target="_blank"
|
|
||||||
:href="externalUrl"
|
|
||||||
class="hidden text-gray-600 transition-all hover:text-gray-900 group-hover:inline-block"
|
|
||||||
>
|
|
||||||
<IconExternalLinkLine class="h-3.5 w-3.5" />
|
|
||||||
</a>
|
|
||||||
</VSpace>
|
|
||||||
</template>
|
|
||||||
<template #description>
|
|
||||||
<div class="flex w-full flex-col gap-1">
|
|
||||||
<VSpace class="w-full">
|
|
||||||
<span class="text-xs text-gray-500">
|
|
||||||
{{
|
|
||||||
$t("core.page.list.fields.visits", {
|
|
||||||
visits: singlePage.stats.visit || 0,
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
</span>
|
|
||||||
<span class="text-xs text-gray-500">
|
|
||||||
{{
|
|
||||||
$t("core.page.list.fields.comments", {
|
|
||||||
comments: singlePage.stats.totalComment || 0,
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
</span>
|
|
||||||
</VSpace>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</VEntityField>
|
|
||||||
</template>
|
</template>
|
||||||
<template #end>
|
<template #end>
|
||||||
<VEntityField>
|
<EntityFieldItems :fields="endFields" />
|
||||||
<template #description>
|
|
||||||
<PostContributorList
|
|
||||||
:owner="singlePage.owner"
|
|
||||||
:contributors="singlePage.contributors"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</VEntityField>
|
|
||||||
<VEntityField :description="publishStatus">
|
|
||||||
<template v-if="isPublishing" #description>
|
|
||||||
<VStatusDot :text="$t('core.common.tooltips.publishing')" animate />
|
|
||||||
</template>
|
|
||||||
</VEntityField>
|
|
||||||
<HasPermission :permissions="['system:singlepages:manage']">
|
|
||||||
<VEntityField>
|
|
||||||
<template #description>
|
|
||||||
<IconEye
|
|
||||||
v-if="singlePage.page.spec.visible === 'PUBLIC'"
|
|
||||||
v-tooltip="$t('core.page.filters.visible.items.public')"
|
|
||||||
class="cursor-pointer text-sm transition-all hover:text-blue-600"
|
|
||||||
@click="changeVisibleMutation(singlePage.page)"
|
|
||||||
/>
|
|
||||||
<IconEyeOff
|
|
||||||
v-if="singlePage.page.spec.visible === 'PRIVATE'"
|
|
||||||
v-tooltip="$t('core.page.filters.visible.items.private')"
|
|
||||||
class="cursor-pointer text-sm transition-all hover:text-blue-600"
|
|
||||||
@click="changeVisibleMutation(singlePage.page)"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</VEntityField>
|
|
||||||
</HasPermission>
|
|
||||||
|
|
||||||
<VEntityField v-if="singlePage?.page?.spec.deleted">
|
|
||||||
<template #description>
|
|
||||||
<VStatusDot
|
|
||||||
v-tooltip="$t('core.common.status.deleting')"
|
|
||||||
state="warning"
|
|
||||||
animate
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</VEntityField>
|
|
||||||
<VEntityField
|
|
||||||
v-if="singlePage.page.spec.publishTime"
|
|
||||||
v-tooltip="formatDatetime(singlePage.page.spec.publishTime)"
|
|
||||||
:description="relativeTimeTo(singlePage.page.spec.publishTime)"
|
|
||||||
>
|
|
||||||
</VEntityField>
|
|
||||||
</template>
|
</template>
|
||||||
<template
|
<template
|
||||||
v-if="currentUserHasPermission(['system:singlepages:manage'])"
|
v-if="currentUserHasPermission(['system:singlepages:manage'])"
|
||||||
#dropdownItems
|
#dropdownItems
|
||||||
>
|
>
|
||||||
<VDropdownItem
|
<EntityDropdownItems
|
||||||
@click="
|
:dropdown-items="operationItems"
|
||||||
$router.push({
|
:item="singlePage"
|
||||||
name: 'SinglePageEditor',
|
/>
|
||||||
query: { name: singlePage.page.metadata.name },
|
|
||||||
})
|
|
||||||
"
|
|
||||||
>
|
|
||||||
{{ $t("core.common.buttons.edit") }}
|
|
||||||
</VDropdownItem>
|
|
||||||
<VDropdownItem @click="emit('open-setting-modal', singlePage.page)">
|
|
||||||
{{ $t("core.common.buttons.setting") }}
|
|
||||||
</VDropdownItem>
|
|
||||||
<VDropdownDivider />
|
|
||||||
<VDropdownItem type="danger" @click="handleDelete">
|
|
||||||
{{ $t("core.common.buttons.delete") }}
|
|
||||||
</VDropdownItem>
|
|
||||||
</template>
|
</template>
|
||||||
</VEntity>
|
</VEntity>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import PostContributorList from "@/components/user/PostContributorList.vue";
|
||||||
|
import type { ListedSinglePage } from "@halo-dev/api-client";
|
||||||
|
import { VEntityField } from "@halo-dev/components";
|
||||||
|
|
||||||
|
withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
singlePage: ListedSinglePage;
|
||||||
|
}>(),
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<VEntityField>
|
||||||
|
<template #description>
|
||||||
|
<PostContributorList
|
||||||
|
:owner="singlePage.owner"
|
||||||
|
:contributors="singlePage.contributors"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</VEntityField>
|
||||||
|
</template>
|
|
@ -0,0 +1,25 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { generateThumbnailUrl } from "@/utils/thumbnail";
|
||||||
|
import type { ListedSinglePage } from "@halo-dev/api-client";
|
||||||
|
import { VEntityField } from "@halo-dev/components";
|
||||||
|
|
||||||
|
withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
singlePage: ListedSinglePage;
|
||||||
|
}>(),
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<VEntityField v-if="singlePage.page.spec.cover">
|
||||||
|
<template #description>
|
||||||
|
<div class="aspect-h-2 rounded-md overflow-hidden aspect-w-3 w-20">
|
||||||
|
<img
|
||||||
|
class="object-cover w-full h-full"
|
||||||
|
:src="generateThumbnailUrl(singlePage.page.spec.cover, 's')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</VEntityField>
|
||||||
|
</template>
|
|
@ -0,0 +1,40 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { singlePageLabels } from "@/constants/labels";
|
||||||
|
import type { ListedSinglePage } from "@halo-dev/api-client";
|
||||||
|
import { VEntityField, VStatusDot } from "@halo-dev/components";
|
||||||
|
import { computed } from "vue";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
singlePage: ListedSinglePage;
|
||||||
|
}>(),
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
|
||||||
|
const publishStatus = computed(() => {
|
||||||
|
const { labels } = props.singlePage.page.metadata;
|
||||||
|
return labels?.[singlePageLabels.PUBLISHED] === "true"
|
||||||
|
? t("core.page.filters.status.items.published")
|
||||||
|
: t("core.page.filters.status.items.draft");
|
||||||
|
});
|
||||||
|
|
||||||
|
const isPublishing = computed(() => {
|
||||||
|
const { spec, status, metadata } = props.singlePage.page;
|
||||||
|
return (
|
||||||
|
(spec.publish &&
|
||||||
|
metadata.labels?.[singlePageLabels.PUBLISHED] !== "true") ||
|
||||||
|
(spec.releaseSnapshot === spec.headSnapshot && status?.inProgress)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<VEntityField :description="publishStatus">
|
||||||
|
<template v-if="isPublishing" #description>
|
||||||
|
<VStatusDot :text="$t('core.common.tooltips.publishing')" animate />
|
||||||
|
</template>
|
||||||
|
</VEntityField>
|
||||||
|
</template>
|
|
@ -0,0 +1,21 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { formatDatetime, relativeTimeTo } from "@/utils/date";
|
||||||
|
import type { ListedSinglePage } from "@halo-dev/api-client";
|
||||||
|
import { VEntityField } from "@halo-dev/components";
|
||||||
|
|
||||||
|
withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
singlePage: ListedSinglePage;
|
||||||
|
}>(),
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<VEntityField
|
||||||
|
v-if="singlePage.page.spec.publishTime"
|
||||||
|
v-tooltip="formatDatetime(singlePage.page.spec.publishTime)"
|
||||||
|
:description="relativeTimeTo(singlePage.page.spec.publishTime)"
|
||||||
|
>
|
||||||
|
</VEntityField>
|
||||||
|
</template>
|
|
@ -0,0 +1,79 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { singlePageLabels } from "@/constants/labels";
|
||||||
|
import type { ListedSinglePage } from "@halo-dev/api-client";
|
||||||
|
import {
|
||||||
|
IconExternalLinkLine,
|
||||||
|
VEntityField,
|
||||||
|
VSpace,
|
||||||
|
VStatusDot,
|
||||||
|
} from "@halo-dev/components";
|
||||||
|
import { computed } from "vue";
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
singlePage: ListedSinglePage;
|
||||||
|
}>(),
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
|
||||||
|
const externalUrl = computed(() => {
|
||||||
|
const { metadata, status } = props.singlePage.page;
|
||||||
|
if (metadata.labels?.[singlePageLabels.PUBLISHED] === "true") {
|
||||||
|
return status?.permalink;
|
||||||
|
}
|
||||||
|
return `/preview/singlepages/${metadata.name}`;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<VEntityField
|
||||||
|
:title="singlePage.page.spec.title"
|
||||||
|
:route="{
|
||||||
|
name: 'SinglePageEditor',
|
||||||
|
query: { name: singlePage.page.metadata.name },
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<template #extra>
|
||||||
|
<VSpace>
|
||||||
|
<RouterLink
|
||||||
|
v-if="singlePage.page.status?.inProgress"
|
||||||
|
v-tooltip="$t('core.common.tooltips.unpublished_content_tip')"
|
||||||
|
:to="{
|
||||||
|
name: 'SinglePageEditor',
|
||||||
|
query: { name: singlePage.page.metadata.name },
|
||||||
|
}"
|
||||||
|
class="flex items-center"
|
||||||
|
>
|
||||||
|
<VStatusDot state="success" animate />
|
||||||
|
</RouterLink>
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
:href="externalUrl"
|
||||||
|
class="hidden text-gray-600 transition-all hover:text-gray-900 group-hover:inline-block"
|
||||||
|
>
|
||||||
|
<IconExternalLinkLine class="h-3.5 w-3.5" />
|
||||||
|
</a>
|
||||||
|
</VSpace>
|
||||||
|
</template>
|
||||||
|
<template #description>
|
||||||
|
<div class="flex w-full flex-col gap-1">
|
||||||
|
<VSpace class="w-full">
|
||||||
|
<span class="text-xs text-gray-500">
|
||||||
|
{{
|
||||||
|
$t("core.page.list.fields.visits", {
|
||||||
|
visits: singlePage.stats.visit || 0,
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
|
<span class="text-xs text-gray-500">
|
||||||
|
{{
|
||||||
|
$t("core.page.list.fields.comments", {
|
||||||
|
comments: singlePage.stats.totalComment || 0,
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
|
</VSpace>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</VEntityField>
|
||||||
|
</template>
|
|
@ -0,0 +1,59 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { ListedSinglePage, SinglePage } from "@halo-dev/api-client";
|
||||||
|
import { coreApiClient } from "@halo-dev/api-client";
|
||||||
|
import { IconEye, IconEyeOff, Toast, VEntityField } from "@halo-dev/components";
|
||||||
|
import { useMutation, useQueryClient } from "@tanstack/vue-query";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
singlePage: ListedSinglePage;
|
||||||
|
}>(),
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
|
||||||
|
const { mutate: changeVisibleMutation } = useMutation({
|
||||||
|
mutationFn: async (singlePage: SinglePage) => {
|
||||||
|
return await coreApiClient.content.singlePage.patchSinglePage({
|
||||||
|
name: singlePage.metadata.name,
|
||||||
|
jsonPatchInner: [
|
||||||
|
{
|
||||||
|
op: "add",
|
||||||
|
path: "/spec/visible",
|
||||||
|
value: singlePage.spec.visible === "PRIVATE" ? "PUBLIC" : "PRIVATE",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
},
|
||||||
|
retry: 3,
|
||||||
|
onSuccess: () => {
|
||||||
|
Toast.success(t("core.common.toast.operation_success"));
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["singlePages"] });
|
||||||
|
},
|
||||||
|
onError: () => {
|
||||||
|
Toast.error(t("core.common.toast.operation_failed"));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<VEntityField>
|
||||||
|
<template #description>
|
||||||
|
<IconEye
|
||||||
|
v-if="singlePage.page.spec.visible === 'PUBLIC'"
|
||||||
|
v-tooltip="$t('core.page.filters.visible.items.public')"
|
||||||
|
class="cursor-pointer text-sm transition-all hover:text-blue-600"
|
||||||
|
@click="changeVisibleMutation(singlePage.page)"
|
||||||
|
/>
|
||||||
|
<IconEyeOff
|
||||||
|
v-if="singlePage.page.spec.visible === 'PRIVATE'"
|
||||||
|
v-tooltip="$t('core.page.filters.visible.items.private')"
|
||||||
|
class="cursor-pointer text-sm transition-all hover:text-blue-600"
|
||||||
|
@click="changeVisibleMutation(singlePage.page)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</VEntityField>
|
||||||
|
</template>
|
|
@ -13,6 +13,7 @@ import type {
|
||||||
Theme,
|
Theme,
|
||||||
ListedComment,
|
ListedComment,
|
||||||
ListedReply,
|
ListedReply,
|
||||||
|
ListedSinglePage,
|
||||||
} from "@halo-dev/api-client";
|
} from "@halo-dev/api-client";
|
||||||
import type { AnyExtension } from "@halo-dev/richtext-editor";
|
import type { AnyExtension } from "@halo-dev/richtext-editor";
|
||||||
import type { Component, Ref } from "vue";
|
import type { Component, Ref } from "vue";
|
||||||
|
@ -59,6 +60,10 @@ export interface ExtensionPoint {
|
||||||
post: Ref<ListedPost>
|
post: Ref<ListedPost>
|
||||||
) => OperationItem<ListedPost>[];
|
) => OperationItem<ListedPost>[];
|
||||||
|
|
||||||
|
"single-page:list-item:operation:create"?: (
|
||||||
|
singlePage: Ref<ListedSinglePage>
|
||||||
|
) => OperationItem<ListedSinglePage>[];
|
||||||
|
|
||||||
"comment:list-item:operation:create"?: (
|
"comment:list-item:operation:create"?: (
|
||||||
comment: Ref<ListedComment>
|
comment: Ref<ListedComment>
|
||||||
) => OperationItem<ListedComment>[];
|
) => OperationItem<ListedComment>[];
|
||||||
|
@ -83,6 +88,10 @@ export interface ExtensionPoint {
|
||||||
|
|
||||||
"post:list-item:field:create"?: (post: Ref<ListedPost>) => EntityFieldItem[];
|
"post:list-item:field:create"?: (post: Ref<ListedPost>) => EntityFieldItem[];
|
||||||
|
|
||||||
|
"single-page:list-item:field:create"?: (
|
||||||
|
singlePage: Ref<ListedSinglePage>
|
||||||
|
) => EntityFieldItem[];
|
||||||
|
|
||||||
"theme:list:tabs:create"?: () => ThemeListTab[] | Promise<ThemeListTab[]>;
|
"theme:list:tabs:create"?: () => ThemeListTab[] | Promise<ThemeListTab[]>;
|
||||||
|
|
||||||
"theme:list-item:operation:create"?: (
|
"theme:list-item:operation:create"?: (
|
||||||
|
|
Loading…
Reference in New Issue