mirror of https://github.com/halo-dev/halo
feat: add support for setting an owner for posts (#6178)
#### What type of PR is this? /area ui /kind feature /milestone 2.17.x #### What this PR does / why we need it: 支持手动为文章设置作者。 <img width="734" alt="image" src="https://github.com/halo-dev/halo/assets/21301288/b91b1754-4f50-4333-8478-6735d5846847"> #### Which issue(s) this PR fixes: Fixes https://github.com/halo-dev/halo/issues/5720 #### Special notes for your reviewer: 需要测试: 1. 打开任意文章的设置,在高级中设置作者并保存,观察是否成功。 2. 选择一批文章,点击批量设置,设置一个作者,观察是否设置成功。 #### Does this PR introduce a user-facing change? ```release-note 支持手动为文章设置作者。 ```pull/6185/head
parent
9d478eecf9
commit
bb0a3bc467
|
@ -1,4 +1,5 @@
|
|||
<script lang="ts" setup>
|
||||
import PostContributorList from "@/components/user/PostContributorList.vue";
|
||||
import { formatDatetime } from "@/utils/date";
|
||||
import { usePermission } from "@/utils/permission";
|
||||
import type { ListedSinglePage, SinglePage } from "@halo-dev/api-client";
|
||||
|
@ -25,7 +26,6 @@ import { useQuery } from "@tanstack/vue-query";
|
|||
import { cloneDeep } from "lodash-es";
|
||||
import { ref, watch } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import ContributorList from "../_components/ContributorList.vue";
|
||||
|
||||
const { currentUserHasPermission } = usePermission();
|
||||
const { t } = useI18n();
|
||||
|
@ -327,7 +327,10 @@ watch(
|
|||
<template #end>
|
||||
<VEntityField>
|
||||
<template #description>
|
||||
<ContributorList :contributors="singlePage.contributors" />
|
||||
<PostContributorList
|
||||
:owner="singlePage.owner"
|
||||
:contributors="singlePage.contributors"
|
||||
/>
|
||||
</template>
|
||||
</VEntityField>
|
||||
<VEntityField v-if="!singlePage?.page?.spec.deleted">
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<script lang="ts" setup>
|
||||
import PostContributorList from "@/components/user/PostContributorList.vue";
|
||||
import { singlePageLabels } from "@/constants/labels";
|
||||
import { formatDatetime } from "@/utils/date";
|
||||
import { usePermission } from "@/utils/permission";
|
||||
|
@ -23,7 +24,6 @@ import type { Ref } from "vue";
|
|||
import { computed, inject, ref } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { RouterLink } from "vue-router";
|
||||
import ContributorList from "../../_components/ContributorList.vue";
|
||||
|
||||
const { currentUserHasPermission } = usePermission();
|
||||
const { t } = useI18n();
|
||||
|
@ -184,7 +184,10 @@ const handleDelete = async () => {
|
|||
<template #end>
|
||||
<VEntityField>
|
||||
<template #description>
|
||||
<ContributorList :contributors="singlePage.contributors" />
|
||||
<PostContributorList
|
||||
:owner="singlePage.owner"
|
||||
:contributors="singlePage.contributors"
|
||||
/>
|
||||
</template>
|
||||
</VEntityField>
|
||||
<VEntityField :description="publishStatus">
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<script lang="ts" setup>
|
||||
import PostContributorList from "@/components/user/PostContributorList.vue";
|
||||
import { formatDatetime } from "@/utils/date";
|
||||
import { usePermission } from "@/utils/permission";
|
||||
import type { ListedPost, Post } from "@halo-dev/api-client";
|
||||
|
@ -25,7 +26,6 @@ import { useQuery } from "@tanstack/vue-query";
|
|||
import { cloneDeep } from "lodash-es";
|
||||
import { ref, watch } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import ContributorList from "../_components/ContributorList.vue";
|
||||
import PostTag from "./tags/components/PostTag.vue";
|
||||
|
||||
const { currentUserHasPermission } = usePermission();
|
||||
|
@ -351,7 +351,10 @@ watch(
|
|||
<template #end>
|
||||
<VEntityField>
|
||||
<template #description>
|
||||
<ContributorList :contributors="post.contributors" />
|
||||
<PostContributorList
|
||||
:owner="post.owner"
|
||||
:contributors="post.contributors"
|
||||
/>
|
||||
</template>
|
||||
</VEntityField>
|
||||
<VEntityField v-if="!post?.post?.spec.deleted">
|
||||
|
|
|
@ -22,6 +22,10 @@ interface FormData {
|
|||
names?: string[];
|
||||
op: ArrayPatchOp;
|
||||
};
|
||||
owner: {
|
||||
enabled: boolean;
|
||||
value: string;
|
||||
};
|
||||
visible: {
|
||||
enabled: boolean;
|
||||
value: "PUBLIC" | "PRIVATE";
|
||||
|
@ -73,6 +77,14 @@ const { mutate, isLoading } = useMutation({
|
|||
});
|
||||
}
|
||||
|
||||
if (data.owner.enabled) {
|
||||
jsonPatchInner.push({
|
||||
op: "add",
|
||||
path: "/spec/owner",
|
||||
value: data.owner.value,
|
||||
});
|
||||
}
|
||||
|
||||
if (data.visible.enabled) {
|
||||
jsonPatchInner.push({
|
||||
op: "add",
|
||||
|
@ -235,6 +247,25 @@ function onSubmit(data: FormData) {
|
|||
validation="required"
|
||||
></FormKit>
|
||||
</FormKit>
|
||||
<FormKit
|
||||
v-slot="{ value }"
|
||||
type="group"
|
||||
name="owner"
|
||||
:label="$t('core.post.batch_setting_modal.fields.owner_group')"
|
||||
>
|
||||
<FormKit
|
||||
:value="false"
|
||||
:label="$t('core.post.batch_setting_modal.fields.common.enabled')"
|
||||
type="checkbox"
|
||||
name="enabled"
|
||||
></FormKit>
|
||||
<FormKit
|
||||
v-if="value?.enabled"
|
||||
:label="$t('core.post.batch_setting_modal.fields.owner_value')"
|
||||
name="value"
|
||||
type="userSelect"
|
||||
></FormKit>
|
||||
</FormKit>
|
||||
<FormKit
|
||||
v-slot="{ value }"
|
||||
type="group"
|
||||
|
|
|
@ -391,6 +391,11 @@ const showCancelPublishButton = computed(() => {
|
|||
</div>
|
||||
</div>
|
||||
<div class="mt-5 divide-y divide-gray-100 md:col-span-3 md:mt-0">
|
||||
<FormKit
|
||||
v-model="formState.spec.owner"
|
||||
:label="$t('core.post.settings.fields.owner.label')"
|
||||
type="userSelect"
|
||||
></FormKit>
|
||||
<FormKit
|
||||
v-model="formState.spec.allowComment"
|
||||
:options="[
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts" setup>
|
||||
import ContributorList from "@console/modules/contents/_components/ContributorList.vue";
|
||||
import PostContributorList from "@/components/user/PostContributorList.vue";
|
||||
import type { ListedPost } from "@halo-dev/api-client";
|
||||
import { VEntityField } from "@halo-dev/components";
|
||||
|
||||
|
@ -14,7 +14,10 @@ withDefaults(
|
|||
<template>
|
||||
<VEntityField>
|
||||
<template #description>
|
||||
<ContributorList :contributors="post.contributors" />
|
||||
<PostContributorList
|
||||
:owner="post.owner"
|
||||
:contributors="post.contributors"
|
||||
/>
|
||||
</template>
|
||||
</VEntityField>
|
||||
</template>
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
|
||||
export {};
|
||||
|
||||
import type { CoreMenuGroupId } from "@halo-dev/console-shared";
|
||||
import type { FormKitInputs } from "@formkit/inputs";
|
||||
import type { CoreMenuGroupId } from "@halo-dev/console-shared";
|
||||
|
||||
import "vue-router";
|
||||
|
||||
|
@ -124,5 +124,10 @@ declare module "@formkit/inputs" {
|
|||
type: "code";
|
||||
value?: string;
|
||||
};
|
||||
|
||||
userSelect: {
|
||||
type: "userSelect";
|
||||
value?: string;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,20 +2,35 @@
|
|||
import { usePermission } from "@/utils/permission";
|
||||
import type { Contributor } from "@halo-dev/api-client";
|
||||
import { VAvatar, VAvatarGroup } from "@halo-dev/components";
|
||||
import { computed } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
withDefaults(
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
allowViewUserDetail?: boolean;
|
||||
owner?: Contributor;
|
||||
contributors: Contributor[];
|
||||
}>(),
|
||||
{}
|
||||
{
|
||||
allowViewUserDetail: true,
|
||||
owner: undefined,
|
||||
}
|
||||
);
|
||||
|
||||
const router = useRouter();
|
||||
const { currentUserHasPermission } = usePermission();
|
||||
|
||||
const contributorsWithoutOwner = computed(() =>
|
||||
props.contributors.filter(
|
||||
(contributor) => contributor.name !== props.owner?.name
|
||||
)
|
||||
);
|
||||
|
||||
function handleRouteToUserDetail(contributor: Contributor) {
|
||||
if (!currentUserHasPermission(["system:users:view"])) {
|
||||
if (
|
||||
!currentUserHasPermission(["system:users:view"]) ||
|
||||
!props.allowViewUserDetail
|
||||
) {
|
||||
return;
|
||||
}
|
||||
router.push({
|
||||
|
@ -28,7 +43,14 @@ function handleRouteToUserDetail(contributor: Contributor) {
|
|||
<template>
|
||||
<VAvatarGroup size="xs" circle>
|
||||
<VAvatar
|
||||
v-for="contributor in contributors"
|
||||
v-if="owner"
|
||||
v-tooltip="owner?.displayName"
|
||||
:src="owner.avatar"
|
||||
:alt="owner.displayName"
|
||||
@click="handleRouteToUserDetail(owner)"
|
||||
/>
|
||||
<VAvatar
|
||||
v-for="contributor in contributorsWithoutOwner"
|
||||
:key="contributor.name"
|
||||
v-tooltip="contributor.displayName"
|
||||
:src="contributor.avatar"
|
|
@ -25,6 +25,7 @@ import { tagSelect } from "./inputs/tag-select";
|
|||
import { verificationForm } from "./inputs/verify-form";
|
||||
import theme from "./theme";
|
||||
|
||||
import { userSelect } from "./inputs/user-select";
|
||||
import autoScrollToErrors from "./plugins/auto-scroll-to-errors";
|
||||
import passwordPreventAutocomplete from "./plugins/password-prevent-autocomplete";
|
||||
import radioAlt from "./plugins/radio-alt";
|
||||
|
@ -65,6 +66,7 @@ const config: DefaultConfigOptions = {
|
|||
tagCheckbox,
|
||||
tagSelect,
|
||||
verificationForm,
|
||||
userSelect,
|
||||
},
|
||||
locales: { zh, en },
|
||||
locale: "zh",
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
// TODO: This is a temporary approach.
|
||||
// We will provide searchable user selection components in the future.
|
||||
|
||||
import type { FormKitNode, FormKitTypeDefinition } from "@formkit/core";
|
||||
import { defaultIcon, select, selects } from "@formkit/inputs";
|
||||
import { coreApiClient } from "@halo-dev/api-client";
|
||||
|
||||
function optionsHandler(node: FormKitNode) {
|
||||
node.on("created", async () => {
|
||||
const { data } = await coreApiClient.user.listUser();
|
||||
|
||||
node.props.options = data.items.map((user) => {
|
||||
return {
|
||||
value: user.metadata.name,
|
||||
label: user.spec.displayName,
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export const userSelect: FormKitTypeDefinition = {
|
||||
...select,
|
||||
props: ["placeholder"],
|
||||
forceTypeProp: "select",
|
||||
features: [optionsHandler, selects, defaultIcon("select", "select")],
|
||||
};
|
|
@ -279,6 +279,8 @@ core:
|
|||
label: Template
|
||||
cover:
|
||||
label: Cover
|
||||
owner:
|
||||
label: Owner
|
||||
tag:
|
||||
filters:
|
||||
sort:
|
||||
|
@ -306,6 +308,8 @@ core:
|
|||
visible_value: "Select visible option "
|
||||
allow_comment_group: " Allow comment"
|
||||
allow_comment_value: Choose whether to allow comments
|
||||
owner_group: Owner
|
||||
owner_value: Select owner
|
||||
deleted_post:
|
||||
title: Deleted Posts
|
||||
empty:
|
||||
|
|
|
@ -267,6 +267,8 @@ core:
|
|||
label: 自定义模板
|
||||
cover:
|
||||
label: 封面图
|
||||
owner:
|
||||
label: 作者
|
||||
tag:
|
||||
filters:
|
||||
sort:
|
||||
|
@ -294,6 +296,8 @@ core:
|
|||
visible_value: 选择可见性
|
||||
allow_comment_group: 允许评论
|
||||
allow_comment_value: 选择是否允许评论
|
||||
owner_group: 作者
|
||||
owner_value: 设置作者
|
||||
deleted_post:
|
||||
title: 文章回收站
|
||||
empty:
|
||||
|
|
|
@ -267,6 +267,8 @@ core:
|
|||
label: 自定義模板
|
||||
cover:
|
||||
label: 封面圖
|
||||
owner:
|
||||
label: 作者
|
||||
batch_setting_modal:
|
||||
title: 文章批量設置
|
||||
fields:
|
||||
|
@ -286,6 +288,8 @@ core:
|
|||
visible_value: 選擇可見性
|
||||
allow_comment_group: 允許評論
|
||||
allow_comment_value: 選擇是否允許評論
|
||||
owner_group: 作者
|
||||
owner_value: 設置作者
|
||||
deleted_post:
|
||||
title: 文章回收站
|
||||
empty:
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<script lang="ts" setup>
|
||||
import StatusDotField from "@/components/entity-fields/StatusDotField.vue";
|
||||
import HasPermission from "@/components/permission/HasPermission.vue";
|
||||
import PostContributorList from "@/components/user/PostContributorList.vue";
|
||||
import { postLabels } from "@/constants/labels";
|
||||
import { formatDatetime } from "@/utils/date";
|
||||
import PostTag from "@console/modules/contents/posts/tags/components/PostTag.vue";
|
||||
|
@ -13,8 +14,6 @@ import {
|
|||
IconEyeOff,
|
||||
IconTimerLine,
|
||||
Toast,
|
||||
VAvatar,
|
||||
VAvatarGroup,
|
||||
VDropdownDivider,
|
||||
VDropdownItem,
|
||||
VEntity,
|
||||
|
@ -180,15 +179,11 @@ function handleUnpublish() {
|
|||
<template #end>
|
||||
<VEntityField>
|
||||
<template #description>
|
||||
<VAvatarGroup size="xs" circle>
|
||||
<VAvatar
|
||||
v-for="{ name, avatar, displayName } in post.contributors"
|
||||
:key="name"
|
||||
v-tooltip="displayName"
|
||||
:src="avatar"
|
||||
:alt="displayName"
|
||||
></VAvatar>
|
||||
</VAvatarGroup>
|
||||
<PostContributorList
|
||||
:owner="post.owner"
|
||||
:contributors="post.contributors"
|
||||
:allow-view-user-detail="false"
|
||||
/>
|
||||
</template>
|
||||
</VEntityField>
|
||||
<VEntityField :description="publishStatus">
|
||||
|
|
Loading…
Reference in New Issue