refactor: logic of publishing post (#5987)

#### What type of PR is this?

/area ui
/kind improvement
/milestone 2.16.x

#### What this PR does / why we need it:

重构文章的发布逻辑,以下是主要改动:

1. 如果文章未发布,点击文章设置的发布按钮时,会先保存文章。
2. 在文章列表的操作菜单中添加发布 / 取消发布的选项。
3. 重构文章设置中,发布 / 取消发布按钮的显示条件。
4. 优化文章设置对话框的显示条件,减少不必要的渲染开销和请求。

#### Special notes for your reviewer:

需要测试:

1. 文章正常新建和发布的逻辑。
2. 文章设置未来时间的发布逻辑。
3. 取消定时发布和已发布文章的逻辑。

#### Does this PR introduce a user-facing change?

```release-note
优化 Console 文章管理中的文章发布逻辑。
```
pull/5994/head^2
Ryan Wang 2024-05-27 16:26:58 +08:00 committed by GitHub
parent 6124ab9831
commit 54e088741e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 246 additions and 137 deletions

View File

@ -131,16 +131,14 @@ const {
return data.items; return data.items;
}, },
refetchInterval(data) { refetchInterval(data) {
const abnormalSinglePages = data?.filter((singlePage) => { const hasAbnormalSinglePage = data?.some((singlePage) => {
const { spec, metadata, status } = singlePage.page; const { spec, metadata } = singlePage.page;
return ( return (
spec.deleted || spec.deleted ||
(spec.publish && metadata.labels?.[singlePageLabels.PUBLISHED] !== spec.publish + ""
metadata.labels?.[singlePageLabels.PUBLISHED] !== "true") ||
(spec.releaseSnapshot === spec.headSnapshot && status?.inProgress)
); );
}); });
return abnormalSinglePages?.length ? 1000 : false; return hasAbnormalSinglePage ? 1000 : false;
}, },
}); });

View File

@ -67,8 +67,8 @@ const formState = ref<SinglePage>({
name: randomUUID(), name: randomUUID(),
}, },
}); });
const modal = ref(); const modal = ref<InstanceType<typeof VModal>>();
const saving = ref(false); const isSubmitting = ref(false);
const publishing = ref(false); const publishing = ref(false);
const publishCanceling = ref(false); const publishCanceling = ref(false);
const submitType = ref<"publish" | "save">(); const submitType = ref<"publish" | "save">();
@ -125,12 +125,12 @@ const handleSave = async () => {
if (props.onlyEmit) { if (props.onlyEmit) {
emit("saved", formState.value); emit("saved", formState.value);
modal.value.close(); modal.value?.close();
return; return;
} }
try { try {
saving.value = true; isSubmitting.value = true;
const { data } = isUpdateMode const { data } = isUpdateMode
? await singlePageUpdateMutate(formState.value) ? await singlePageUpdateMutate(formState.value)
@ -143,13 +143,13 @@ const handleSave = async () => {
formState.value = data; formState.value = data;
emit("saved", data); emit("saved", data);
modal.value.close(); modal.value?.close();
Toast.success(t("core.common.toast.save_success")); Toast.success(t("core.common.toast.save_success"));
} catch (error) { } catch (error) {
console.error("Failed to save single page", error); console.error("Failed to save single page", error);
} finally { } finally {
saving.value = false; isSubmitting.value = false;
} }
}; };
@ -170,7 +170,7 @@ const handlePublish = async () => {
if (props.onlyEmit) { if (props.onlyEmit) {
emit("published", formState.value); emit("published", formState.value);
modal.value.close(); modal.value?.close();
return; return;
} }
@ -195,7 +195,7 @@ const handlePublish = async () => {
emit("published", data); emit("published", data);
modal.value.close(); modal.value?.close();
Toast.success(t("core.common.toast.publish_success")); Toast.success(t("core.common.toast.publish_success"));
} catch (error) { } catch (error) {
@ -227,7 +227,7 @@ const handleUnpublish = async () => {
formState.value = data; formState.value = data;
modal.value.close(); modal.value?.close();
Toast.success(t("core.common.toast.cancel_publish_success")); Toast.success(t("core.common.toast.cancel_publish_success"));
} catch (error) { } catch (error) {
@ -457,10 +457,11 @@ const { handleGenerateSlug } = useSlugify(
</div> </div>
<template #footer> <template #footer>
<VSpace> <div class="flex items-center justify-between">
<template v-if="publishSupport"> <VSpace>
<VButton <VButton
v-if=" v-if="
publishSupport &&
formState.metadata.labels?.[singlePageLabels.PUBLISHED] !== 'true' formState.metadata.labels?.[singlePageLabels.PUBLISHED] !== 'true'
" "
:loading="publishing" :loading="publishing"
@ -470,21 +471,28 @@ const { handleGenerateSlug } = useSlugify(
{{ $t("core.common.buttons.publish") }} {{ $t("core.common.buttons.publish") }}
</VButton> </VButton>
<VButton <VButton
v-else :loading="isSubmitting"
:loading="publishCanceling" type="secondary"
type="danger" @click="handleSaveClick"
@click="handleUnpublish()"
> >
{{ $t("core.common.buttons.cancel_publish") }} {{ $t("core.common.buttons.save") }}
</VButton> </VButton>
</template> <VButton type="default" @click="modal?.close()">
<VButton :loading="saving" type="secondary" @click="handleSaveClick"> {{ $t("core.common.buttons.close") }}
{{ $t("core.common.buttons.save") }} </VButton>
</VSpace>
<VButton
v-if="
formState.metadata.labels?.[singlePageLabels.PUBLISHED] === 'true'
"
:loading="publishCanceling"
type="danger"
@click="handleUnpublish()"
>
{{ $t("core.common.buttons.cancel_publish") }}
</VButton> </VButton>
<VButton type="default" @click="modal.close()"> </div>
{{ $t("core.common.buttons.close") }}
</VButton>
</VSpace>
</template> </template>
</VModal> </VModal>
</template> </template>

View File

@ -342,7 +342,6 @@ const onSettingSaved = (post: Post) => {
} }
formState.value.post = post; formState.value.post = post;
settingModal.value = false;
if (!isUpdateMode.value) { if (!isUpdateMode.value) {
handleSave(); handleSave();
@ -351,7 +350,6 @@ const onSettingSaved = (post: Post) => {
const onSettingPublished = (post: Post) => { const onSettingPublished = (post: Post) => {
formState.value.post = post; formState.value.post = post;
settingModal.value = false;
handlePublish(); handlePublish();
}; };
@ -454,10 +452,11 @@ async function handleUploadImage(file: File, options?: AxiosRequestConfig) {
<template> <template>
<PostSettingModal <PostSettingModal
v-model:visible="settingModal" v-if="settingModal"
:post="formState.post" :post="formState.post"
:publish-support="!isUpdateMode" :publish-support="!isUpdateMode"
:only-emit="!isUpdateMode" :only-emit="!isUpdateMode"
@close="settingModal = false"
@saved="onSettingSaved" @saved="onSettingSaved"
@published="onSettingPublished" @published="onSettingPublished"
/> />

View File

@ -1,23 +1,24 @@
<script lang="ts" setup> <script lang="ts" setup>
import { import {
Dialog,
IconAddCircle, IconAddCircle,
IconArrowLeft, IconArrowLeft,
IconArrowRight, IconArrowRight,
IconBookRead, IconBookRead,
IconRefreshLine, IconRefreshLine,
Dialog, Toast,
VButton, VButton,
VCard, VCard,
VEmpty, VEmpty,
VLoading,
VPageHeader, VPageHeader,
VPagination, VPagination,
VSpace, VSpace,
VLoading,
Toast,
} from "@halo-dev/components"; } from "@halo-dev/components";
import PostSettingModal from "./components/PostSettingModal.vue"; import PostSettingModal from "./components/PostSettingModal.vue";
import { computed, ref, watch } from "vue"; import type { Ref } from "vue";
import type { Post, ListedPost } from "@halo-dev/api-client"; import { computed, provide, ref, watch } from "vue";
import type { ListedPost, Post } from "@halo-dev/api-client";
import { apiClient } from "@/utils/api-client"; import { apiClient } from "@/utils/api-client";
import { postLabels } from "@/constants/labels"; import { postLabels } from "@/constants/labels";
import { useQuery } from "@tanstack/vue-query"; import { useQuery } from "@tanstack/vue-query";
@ -27,8 +28,6 @@ import UserFilterDropdown from "@/components/filter/UserFilterDropdown.vue";
import CategoryFilterDropdown from "@/components/filter/CategoryFilterDropdown.vue"; import CategoryFilterDropdown from "@/components/filter/CategoryFilterDropdown.vue";
import TagFilterDropdown from "@/components/filter/TagFilterDropdown.vue"; import TagFilterDropdown from "@/components/filter/TagFilterDropdown.vue";
import PostListItem from "./components/PostListItem.vue"; import PostListItem from "./components/PostListItem.vue";
import { provide } from "vue";
import type { Ref } from "vue";
const { t } = useI18n(); const { t } = useI18n();
@ -152,18 +151,34 @@ const {
return data.items; return data.items;
}, },
refetchInterval: (data) => { refetchInterval: (data) => {
const abnormalPosts = data?.some((post) => { const hasDeletingPost = data?.some((post) => post.post.spec.deleted);
const { spec, metadata, status } = post.post;
if (hasDeletingPost) {
return 1000;
}
const hasPublishingPost = data?.some((post) => {
const { spec, metadata } = post.post;
return ( return (
spec.deleted || metadata.labels?.[postLabels.PUBLISHED] !== spec.publish + "" &&
(spec.publish && metadata.labels?.[postLabels.SCHEDULING_PUBLISH] !== "true"
metadata.labels?.[postLabels.PUBLISHED] !== "true" &&
metadata.labels?.[postLabels.SCHEDULING_PUBLISH] !== "true") ||
(spec.releaseSnapshot === spec.headSnapshot && status?.inProgress)
); );
}); });
return abnormalPosts ? 1000 : false; if (hasPublishingPost) {
return 1000;
}
const hasCancelingPublishPost = data?.some((post) => {
const { spec, metadata } = post.post;
return (
!spec.publish &&
(metadata.labels?.[postLabels.PUBLISHED] === "true" ||
metadata.labels?.[postLabels.SCHEDULING_PUBLISH] === "true")
);
});
return hasCancelingPublishPost ? 1000 : false;
}, },
}); });
@ -179,6 +194,7 @@ const handleOpenSettingModal = async (post: Post) => {
const onSettingModalClose = () => { const onSettingModalClose = () => {
selectedPost.value = undefined; selectedPost.value = undefined;
settingModal.value = false;
refetch(); refetch();
}; };
@ -274,7 +290,7 @@ watch(selectedPostNames, (newValue) => {
</script> </script>
<template> <template>
<PostSettingModal <PostSettingModal
v-model:visible="settingModal" v-if="settingModal"
:post="selectedPost" :post="selectedPost"
@close="onSettingModalClose" @close="onSettingModalClose"
> >

View File

@ -12,7 +12,7 @@ import { usePermission } from "@/utils/permission";
import { apiClient } from "@/utils/api-client"; import { apiClient } from "@/utils/api-client";
import { useQueryClient } from "@tanstack/vue-query"; import { useQueryClient } from "@tanstack/vue-query";
import type { Ref } from "vue"; import type { Ref } from "vue";
import { computed, toRefs, markRaw, ref, inject } from "vue"; import { computed, inject, markRaw, ref, toRefs } from "vue";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
import { useEntityFieldItemExtensionPoint } from "@console/composables/use-entity-extension-points"; import { useEntityFieldItemExtensionPoint } from "@console/composables/use-entity-extension-points";
import { useOperationItemExtensionPoint } from "@console/composables/use-operation-extension-points"; import { useOperationItemExtensionPoint } from "@console/composables/use-operation-extension-points";
@ -25,6 +25,7 @@ import PublishStatusField from "./entity-fields/PublishStatusField.vue";
import VisibleField from "./entity-fields/VisibleField.vue"; import VisibleField from "./entity-fields/VisibleField.vue";
import StatusDotField from "@/components/entity-fields/StatusDotField.vue"; import StatusDotField from "@/components/entity-fields/StatusDotField.vue";
import PublishTimeField from "./entity-fields/PublishTimeField.vue"; import PublishTimeField from "./entity-fields/PublishTimeField.vue";
import { postLabels } from "@/constants/labels";
const { currentUserHasPermission } = usePermission(); const { currentUserHasPermission } = usePermission();
const { t } = useI18n(); const { t } = useI18n();
@ -71,6 +72,26 @@ const { operationItems } = useOperationItemExtensionPoint<ListedPost>(
"post:list-item:operation:create", "post:list-item:operation:create",
post, post,
computed((): OperationItem<ListedPost>[] => [ computed((): OperationItem<ListedPost>[] => [
{
priority: 0,
component: markRaw(VDropdownItem),
label: t("core.common.buttons.publish"),
action: async () => {
await apiClient.post.publishPost({
name: props.post.post.metadata.name,
});
Toast.success(t("core.common.toast.publish_success"));
queryClient.invalidateQueries({
queryKey: ["posts"],
});
},
hidden:
props.post.post.metadata.labels?.[postLabels.PUBLISHED] == "true" ||
props.post.post.metadata.labels?.[postLabels.SCHEDULING_PUBLISH] ==
"true",
},
{ {
priority: 10, priority: 10,
component: markRaw(VDropdownItem), component: markRaw(VDropdownItem),
@ -102,6 +123,29 @@ const { operationItems } = useOperationItemExtensionPoint<ListedPost>(
props: { props: {
type: "danger", type: "danger",
}, },
label: t("core.common.buttons.cancel_publish"),
action: async () => {
await apiClient.post.unpublishPost({
name: props.post.post.metadata.name,
});
Toast.success(t("core.common.toast.cancel_publish_success"));
queryClient.invalidateQueries({
queryKey: ["posts"],
});
},
hidden:
props.post.post.metadata.labels?.[postLabels.PUBLISHED] !== "true" &&
props.post.post.metadata.labels?.[postLabels.SCHEDULING_PUBLISH] !==
"true",
},
{
priority: 50,
component: markRaw(VDropdownItem),
props: {
type: "danger",
},
label: t("core.common.buttons.delete"), label: t("core.common.buttons.delete"),
permissions: [], permissions: [],
action: handleDelete, action: handleDelete,

View File

@ -6,9 +6,8 @@ import {
VModal, VModal,
VSpace, VSpace,
} from "@halo-dev/components"; } from "@halo-dev/components";
import { computed, nextTick, ref, toRaw, watch } from "vue"; import { computed, nextTick, ref, watch } from "vue";
import type { Post } from "@halo-dev/api-client"; import type { Post } from "@halo-dev/api-client";
import { cloneDeep } from "lodash-es";
import { apiClient } from "@/utils/api-client"; import { apiClient } from "@/utils/api-client";
import { useThemeCustomTemplates } from "@console/modules/interface/themes/composables/use-theme"; import { useThemeCustomTemplates } from "@console/modules/interface/themes/composables/use-theme";
import { postLabels } from "@/constants/labels"; import { postLabels } from "@/constants/labels";
@ -20,8 +19,31 @@ import useSlugify from "@console/composables/use-slugify";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import { usePostUpdateMutate } from "../composables/use-post-update-mutate"; import { usePostUpdateMutate } from "../composables/use-post-update-mutate";
import { FormType } from "@/types/slug"; import { FormType } from "@/types/slug";
import { cloneDeep } from "lodash-es";
const initialFormState: Post = { const props = withDefaults(
defineProps<{
post?: Post;
publishSupport?: boolean;
onlyEmit?: boolean;
}>(),
{
post: undefined,
publishSupport: true,
onlyEmit: false,
}
);
const emit = defineEmits<{
(event: "close"): void;
(event: "saved", post: Post): void;
(event: "published", post: Post): void;
}>();
const { t } = useI18n();
const modal = ref<InstanceType<typeof VModal>>();
const formState = ref<Post>({
spec: { spec: {
title: "", title: "",
slug: "", slug: "",
@ -47,34 +69,8 @@ const initialFormState: Post = {
metadata: { metadata: {
name: randomUUID(), name: randomUUID(),
}, },
}; });
const isSubmitting = ref(false);
const props = withDefaults(
defineProps<{
visible: boolean;
post?: Post;
publishSupport?: boolean;
onlyEmit?: boolean;
}>(),
{
visible: false,
post: undefined,
publishSupport: true,
onlyEmit: false,
}
);
const emit = defineEmits<{
(event: "update:visible", visible: boolean): void;
(event: "close"): void;
(event: "saved", post: Post): void;
(event: "published", post: Post): void;
}>();
const { t } = useI18n();
const formState = ref<Post>(cloneDeep(initialFormState));
const saving = ref(false);
const publishing = ref(false); const publishing = ref(false);
const publishCanceling = ref(false); const publishCanceling = ref(false);
const submitType = ref<"publish" | "save">(); const submitType = ref<"publish" | "save">();
@ -84,13 +80,6 @@ const isUpdateMode = computed(() => {
return !!formState.value.metadata.creationTimestamp; return !!formState.value.metadata.creationTimestamp;
}); });
const handleVisibleChange = (visible: boolean) => {
emit("update:visible", visible);
if (!visible) {
emit("close");
}
};
const handleSubmit = () => { const handleSubmit = () => {
if (submitType.value === "publish") { if (submitType.value === "publish") {
handlePublish(); handlePublish();
@ -139,11 +128,12 @@ const handleSave = async () => {
if (props.onlyEmit) { if (props.onlyEmit) {
emit("saved", formState.value); emit("saved", formState.value);
modal.value?.close();
return; return;
} }
try { try {
saving.value = true; isSubmitting.value = true;
const { data } = isUpdateMode.value const { data } = isUpdateMode.value
? await postUpdateMutate(formState.value) ? await postUpdateMutate(formState.value)
@ -154,25 +144,28 @@ const handleSave = async () => {
formState.value = data; formState.value = data;
emit("saved", data); emit("saved", data);
handleVisibleChange(false); modal.value?.close();
Toast.success(t("core.common.toast.save_success")); Toast.success(t("core.common.toast.save_success"));
} catch (e) { } catch (e) {
console.error("Failed to save post", e); console.error("Failed to save post", e);
} finally { } finally {
saving.value = false; isSubmitting.value = false;
} }
}; };
const handlePublish = async () => { const handlePublish = async () => {
if (props.onlyEmit) { if (props.onlyEmit) {
emit("published", formState.value); emit("published", formState.value);
modal.value?.close();
return; return;
} }
try { try {
publishing.value = true; publishing.value = true;
await postUpdateMutate(formState.value);
const { data } = await apiClient.post.publishPost({ const { data } = await apiClient.post.publishPost({
name: formState.value.metadata.name, name: formState.value.metadata.name,
}); });
@ -181,7 +174,7 @@ const handlePublish = async () => {
emit("published", data); emit("published", data);
handleVisibleChange(false); modal.value?.close();
Toast.success(t("core.common.toast.publish_success")); Toast.success(t("core.common.toast.publish_success"));
} catch (e) { } catch (e) {
@ -199,7 +192,7 @@ const handleUnpublish = async () => {
name: formState.value.metadata.name, name: formState.value.metadata.name,
}); });
handleVisibleChange(false); modal.value?.close();
Toast.success(t("core.common.toast.cancel_publish_success")); Toast.success(t("core.common.toast.cancel_publish_success"));
} catch (e) { } catch (e) {
@ -214,7 +207,7 @@ watch(
() => props.post, () => props.post,
(value) => { (value) => {
if (value) { if (value) {
formState.value = toRaw(value); formState.value = cloneDeep(value);
publishTime.value = toDatetimeLocal(formState.value.spec.publishTime); publishTime.value = toDatetimeLocal(formState.value.spec.publishTime);
} }
}, },
@ -264,14 +257,37 @@ const { handleGenerateSlug } = useSlugify(
computed(() => !isUpdateMode.value), computed(() => !isUpdateMode.value),
FormType.POST FormType.POST
); );
// Buttons condition
const showPublishButton = computed(() => {
if (!props.publishSupport) {
return false;
}
const {
[postLabels.PUBLISHED]: published,
[postLabels.SCHEDULING_PUBLISH]: schedulingPublish,
} = formState.value.metadata.labels || {};
return published !== "true" && schedulingPublish !== "true";
});
const showCancelPublishButton = computed(() => {
const {
[postLabels.PUBLISHED]: published,
[postLabels.SCHEDULING_PUBLISH]: schedulingPublish,
} = formState.value.metadata.labels || {};
return published === "true" || schedulingPublish === "true";
});
</script> </script>
<template> <template>
<VModal <VModal
:visible="visible" ref="modal"
:width="700" :width="700"
:title="$t('core.post.settings.title')" :title="$t('core.post.settings.title')"
:centered="false" :centered="false"
@update:visible="handleVisibleChange" @close="emit('close')"
> >
<template #actions> <template #actions>
<slot name="actions"></slot> <slot name="actions"></slot>
@ -459,10 +475,10 @@ const { handleGenerateSlug } = useSlugify(
</div> </div>
<template #footer> <template #footer>
<VSpace> <div class="flex items-center justify-between">
<template v-if="publishSupport"> <VSpace>
<VButton <VButton
v-if="formState.metadata.labels?.[postLabels.PUBLISHED] !== 'true'" v-if="showPublishButton"
:loading="publishing" :loading="publishing"
type="secondary" type="secondary"
@click="handlePublishClick()" @click="handlePublishClick()"
@ -474,21 +490,26 @@ const { handleGenerateSlug } = useSlugify(
}} }}
</VButton> </VButton>
<VButton <VButton
v-else :loading="isSubmitting"
:loading="publishCanceling" type="secondary"
type="danger" @click="handleSaveClick()"
@click="handleUnpublish()"
> >
{{ $t("core.common.buttons.cancel_publish") }} {{ $t("core.common.buttons.save") }}
</VButton> </VButton>
</template> <VButton type="default" @click="modal?.close()">
<VButton :loading="saving" type="secondary" @click="handleSaveClick()"> {{ $t("core.common.buttons.close") }}
{{ $t("core.common.buttons.save") }} </VButton>
</VSpace>
<VButton
v-if="showCancelPublishButton"
:loading="publishCanceling"
type="danger"
@click="handleUnpublish()"
>
{{ $t("core.common.buttons.cancel_publish") }}
</VButton> </VButton>
<VButton type="default" @click="handleVisibleChange(false)"> </div>
{{ $t("core.common.buttons.close") }}
</VButton>
</VSpace>
</template> </template>
</VModal> </VModal>
</template> </template>

View File

@ -22,12 +22,11 @@ const publishStatus = computed(() => {
}); });
const isPublishing = computed(() => { const isPublishing = computed(() => {
const { spec, status, metadata } = props.post.post; const { spec, metadata } = props.post.post;
return ( return (
(spec.publish && spec.publish &&
metadata.labels?.[postLabels.PUBLISHED] !== "true" && metadata.labels?.[postLabels.PUBLISHED] !== "true" &&
metadata.labels?.[postLabels.SCHEDULING_PUBLISH] !== "true") || metadata.labels?.[postLabels.SCHEDULING_PUBLISH] !== "true"
(spec.releaseSnapshot === spec.headSnapshot && status?.inProgress)
); );
}); });
</script> </script>

View File

@ -15,8 +15,7 @@ import {
} from "@halo-dev/components"; } from "@halo-dev/components";
import PostListItem from "./components/PostListItem.vue"; import PostListItem from "./components/PostListItem.vue";
import { useRouteQuery } from "@vueuse/router"; import { useRouteQuery } from "@vueuse/router";
import { computed } from "vue"; import { computed, watch } from "vue";
import { watch } from "vue";
import { postLabels } from "@/constants/labels"; import { postLabels } from "@/constants/labels";
const page = useRouteQuery<number>("page", 1, { const page = useRouteQuery<number>("page", 1, {
@ -69,17 +68,34 @@ const {
size.value = data.size; size.value = data.size;
}, },
refetchInterval: (data) => { refetchInterval: (data) => {
const hasAbnormalPost = data?.items.some((post) => { const hasDeletingPosts = data?.items.some((post) => post.post.spec.deleted);
const { spec, metadata, status } = post.post;
if (hasDeletingPosts) {
return 1000;
}
const hasPublishingPost = data?.items.some((post) => {
const { spec, metadata } = post.post;
return ( return (
spec.deleted || metadata.labels?.[postLabels.PUBLISHED] !== spec.publish + "" &&
(metadata.labels?.[postLabels.PUBLISHED] !== spec.publish + "" && metadata.labels?.[postLabels.SCHEDULING_PUBLISH] !== "true"
metadata.labels?.[postLabels.SCHEDULING_PUBLISH] !== "true") ||
(spec.releaseSnapshot === spec.headSnapshot && status?.inProgress)
); );
}); });
return hasAbnormalPost ? 1000 : false; if (hasPublishingPost) {
return 1000;
}
const hasCancelingPublishPost = data?.items.some((post) => {
const { spec, metadata } = post.post;
return (
!spec.publish &&
(metadata.labels?.[postLabels.PUBLISHED] === "true" ||
metadata.labels?.[postLabels.SCHEDULING_PUBLISH] === "true")
);
});
return hasCancelingPublishPost ? 1000 : false;
}, },
}); });
</script> </script>

View File

@ -7,6 +7,7 @@ import {
IconTimerLine, IconTimerLine,
Toast, Toast,
VAvatar, VAvatar,
VDropdownDivider,
VDropdownItem, VDropdownItem,
VEntity, VEntity,
VEntityField, VEntityField,
@ -49,13 +50,20 @@ const publishStatus = computed(() => {
: t("core.post.filters.status.items.draft"); : t("core.post.filters.status.items.draft");
}); });
const isPublished = computed(() => {
const {
[postLabels.PUBLISHED]: published,
[postLabels.SCHEDULING_PUBLISH]: schedulingPublish,
} = props.post.post.metadata.labels || {};
return published !== "true" && schedulingPublish !== "true";
});
const isPublishing = computed(() => { const isPublishing = computed(() => {
const { spec, status, metadata } = props.post.post; const { spec, metadata } = props.post.post;
return ( return (
(spec.publish && spec.publish &&
metadata.labels?.[postLabels.PUBLISHED] !== "true" && metadata.labels?.[postLabels.PUBLISHED] !== "true" &&
metadata.labels?.[postLabels.SCHEDULING_PUBLISH] !== "true") || metadata.labels?.[postLabels.SCHEDULING_PUBLISH] !== "true"
(spec.releaseSnapshot === spec.headSnapshot && status?.inProgress)
); );
}); });
@ -226,6 +234,11 @@ function handleUnpublish() {
</VEntityField> </VEntityField>
</template> </template>
<template #dropdownItems> <template #dropdownItems>
<HasPermission v-if="isPublished" :permissions="['uc:posts:publish']">
<VDropdownItem @click="handlePublish">
{{ $t("core.common.buttons.publish") }}
</VDropdownItem>
</HasPermission>
<VDropdownItem <VDropdownItem
@click=" @click="
$router.push({ $router.push({
@ -236,14 +249,9 @@ function handleUnpublish() {
> >
{{ $t("core.common.buttons.edit") }} {{ $t("core.common.buttons.edit") }}
</VDropdownItem> </VDropdownItem>
<HasPermission :permissions="['uc:posts:publish']"> <HasPermission v-if="!isPublished" :permissions="['uc:posts:publish']">
<VDropdownItem <VDropdownDivider />
v-if="post.post.metadata.labels?.[postLabels.PUBLISHED] === 'false'" <VDropdownItem type="danger" @click="handleUnpublish">
@click="handlePublish"
>
{{ $t("core.common.buttons.publish") }}
</VDropdownItem>
<VDropdownItem v-else type="danger" @click="handleUnpublish">
{{ $t("core.common.buttons.cancel_publish") }} {{ $t("core.common.buttons.cancel_publish") }}
</VDropdownItem> </VDropdownItem>
</HasPermission> </HasPermission>