mirror of https://github.com/halo-dev/halo
refactor: logic of post and singlepage data filtering (#4193)
#### What type of PR is this? /area console /kind improvement /milestone 2.8.x #### What this PR does / why we need it: 重构文章和页面数据管理的筛选条件逻辑以及 UI。 <img width="1646" alt="image" src="https://github.com/halo-dev/halo/assets/21301288/154c5588-91c0-448d-aa39-d75e90bce6ca"> Ref https://github.com/halo-dev/halo/pull/4182 Ref https://github.com/halo-dev/halo/issues/4181 #### Special notes for your reviewer: 需要测试: 1. 测试文章的筛选条件包括关键词筛选功能是否正常。 2. 测试页面的筛选条件包括关键词筛选功能是否正常。 #### Does this PR introduce a user-facing change? ```release-note 重构 Console 端文章数据列表的筛选项 UI 和逻辑。 ```pull/4195/head^2
parent
f622b1787c
commit
119f352145
|
@ -1,6 +1,11 @@
|
|||
<script lang="ts" setup>
|
||||
import type { Category } from "@halo-dev/api-client";
|
||||
import { VEntity, VEntityField, VDropdown } from "@halo-dev/components";
|
||||
import {
|
||||
VEntity,
|
||||
VEntityField,
|
||||
VDropdown,
|
||||
IconArrowDown,
|
||||
} from "@halo-dev/components";
|
||||
import { setFocus } from "@/formkit/utils/focus";
|
||||
import { computed, ref, watch } from "vue";
|
||||
import Fuse from "fuse.js";
|
||||
|
@ -8,16 +13,16 @@ import { usePostCategory } from "@/modules/contents/posts/categories/composables
|
|||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
selected?: Category;
|
||||
label: string;
|
||||
modelValue?: string;
|
||||
}>(),
|
||||
{
|
||||
selected: undefined,
|
||||
modelValue: undefined,
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: "update:selected", category?: Category): void;
|
||||
(event: "select", category?: Category): void;
|
||||
(event: "update:modelValue", value?: string): void;
|
||||
}>();
|
||||
|
||||
const { categories } = usePostCategory();
|
||||
|
@ -25,24 +30,18 @@ const { categories } = usePostCategory();
|
|||
const dropdown = ref();
|
||||
|
||||
const handleSelect = (category: Category) => {
|
||||
if (
|
||||
props.selected &&
|
||||
category.metadata.name === props.selected.metadata.name
|
||||
) {
|
||||
emit("update:selected", undefined);
|
||||
emit("select", undefined);
|
||||
return;
|
||||
if (category.metadata.name === props.modelValue) {
|
||||
emit("update:modelValue", undefined);
|
||||
} else {
|
||||
emit("update:modelValue", category.metadata.name);
|
||||
}
|
||||
|
||||
emit("update:selected", category);
|
||||
emit("select", category);
|
||||
|
||||
dropdown.value.hide();
|
||||
};
|
||||
|
||||
function onDropdownShow() {
|
||||
setTimeout(() => {
|
||||
setFocus("categoryDropdownSelectorInput");
|
||||
setFocus("categoryFilterDropdownInput");
|
||||
}, 200);
|
||||
}
|
||||
|
||||
|
@ -72,16 +71,35 @@ const searchResults = computed(() => {
|
|||
|
||||
return fuse?.search(keyword.value).map((item) => item.item);
|
||||
});
|
||||
|
||||
const selectedCategory = computed(() => {
|
||||
return categories.value?.find(
|
||||
(category) => category.metadata.name === props.modelValue
|
||||
);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VDropdown ref="dropdown" :classes="['!p-0']" @show="onDropdownShow">
|
||||
<slot />
|
||||
<div
|
||||
class="flex cursor-pointer select-none items-center text-sm text-gray-700 hover:text-black"
|
||||
:class="{ 'font-semibold text-gray-700': modelValue !== undefined }"
|
||||
>
|
||||
<span v-if="!selectedCategory" class="mr-0.5">
|
||||
{{ label }}
|
||||
</span>
|
||||
<span v-else class="mr-0.5">
|
||||
{{ label }}:{{ selectedCategory.spec.displayName }}
|
||||
</span>
|
||||
<span>
|
||||
<IconArrowDown />
|
||||
</span>
|
||||
</div>
|
||||
<template #popper>
|
||||
<div class="h-96 w-80">
|
||||
<div class="border-b border-b-gray-100 bg-white p-4">
|
||||
<FormKit
|
||||
id="categoryDropdownSelectorInput"
|
||||
id="categoryFilterDropdownInput"
|
||||
v-model="keyword"
|
||||
:placeholder="$t('core.common.placeholder.search')"
|
||||
type="text"
|
||||
|
@ -97,11 +115,7 @@ const searchResults = computed(() => {
|
|||
:key="index"
|
||||
@click="handleSelect(category)"
|
||||
>
|
||||
<VEntity
|
||||
:is-selected="
|
||||
selected?.metadata.name === category.metadata.name
|
||||
"
|
||||
>
|
||||
<VEntity :is-selected="modelValue === category.metadata.name">
|
||||
<template #start>
|
||||
<VEntityField
|
||||
:title="category.spec.displayName"
|
|
@ -48,7 +48,7 @@ function handleSelect(item: {
|
|||
<span v-if="!selectedItem" class="mr-0.5">
|
||||
{{ label }}
|
||||
</span>
|
||||
<span v-else> {{ label }}:{{ selectedItem.label }} </span>
|
||||
<span v-else class="mr-0.5"> {{ label }}:{{ selectedItem.label }} </span>
|
||||
<span>
|
||||
<IconArrowDown />
|
||||
</span>
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
<script lang="ts" setup>
|
||||
import type { Tag } from "@halo-dev/api-client";
|
||||
import { VEntity, VEntityField, VDropdown } from "@halo-dev/components";
|
||||
import {
|
||||
VEntity,
|
||||
VEntityField,
|
||||
VDropdown,
|
||||
IconArrowDown,
|
||||
} from "@halo-dev/components";
|
||||
import { setFocus } from "@/formkit/utils/focus";
|
||||
import { computed, ref, watch } from "vue";
|
||||
import Fuse from "fuse.js";
|
||||
|
@ -9,16 +14,16 @@ import PostTag from "@/modules/contents/posts/tags/components/PostTag.vue";
|
|||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
selected?: Tag;
|
||||
label: string;
|
||||
modelValue?: string;
|
||||
}>(),
|
||||
{
|
||||
selected: undefined,
|
||||
modelValue: undefined,
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: "update:selected", tag?: Tag): void;
|
||||
(event: "select", tag?: Tag): void;
|
||||
(event: "update:modelValue", value?: string): void;
|
||||
}>();
|
||||
|
||||
const { tags } = usePostTag();
|
||||
|
@ -26,21 +31,18 @@ const { tags } = usePostTag();
|
|||
const dropdown = ref();
|
||||
|
||||
const handleSelect = (tag: Tag) => {
|
||||
if (props.selected && tag.metadata.name === props.selected.metadata.name) {
|
||||
emit("update:selected", undefined);
|
||||
emit("select", undefined);
|
||||
return;
|
||||
if (tag.metadata.name === props.modelValue) {
|
||||
emit("update:modelValue", undefined);
|
||||
} else {
|
||||
emit("update:modelValue", tag.metadata.name);
|
||||
}
|
||||
|
||||
emit("update:selected", tag);
|
||||
emit("select", tag);
|
||||
|
||||
dropdown.value.hide();
|
||||
};
|
||||
|
||||
function onDropdownShow() {
|
||||
setTimeout(() => {
|
||||
setFocus("tagDropdownSelectorInput");
|
||||
setFocus("tagFilterDropdownInput");
|
||||
}, 200);
|
||||
}
|
||||
|
||||
|
@ -70,16 +72,33 @@ const searchResults = computed(() => {
|
|||
|
||||
return fuse?.search(keyword.value).map((item) => item.item);
|
||||
});
|
||||
|
||||
const selectedTag = computed(() => {
|
||||
return tags.value?.find((tag) => tag.metadata.name === props.modelValue);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VDropdown ref="dropdown" :classes="['!p-0']" @show="onDropdownShow">
|
||||
<slot />
|
||||
<div
|
||||
class="flex cursor-pointer select-none items-center text-sm text-gray-700 hover:text-black"
|
||||
:class="{ 'font-semibold text-gray-700': modelValue !== undefined }"
|
||||
>
|
||||
<span v-if="!selectedTag" class="mr-0.5">
|
||||
{{ label }}
|
||||
</span>
|
||||
<span v-else class="mr-0.5">
|
||||
{{ label }}:{{ selectedTag.spec.displayName }}
|
||||
</span>
|
||||
<span>
|
||||
<IconArrowDown />
|
||||
</span>
|
||||
</div>
|
||||
<template #popper>
|
||||
<div class="h-96 w-80">
|
||||
<div class="border-b border-b-gray-100 bg-white p-4">
|
||||
<FormKit
|
||||
id="tagDropdownSelectorInput"
|
||||
id="tagFilterDropdownInput"
|
||||
v-model="keyword"
|
||||
:placeholder="$t('core.common.placeholder.search')"
|
||||
type="text"
|
||||
|
@ -95,9 +114,7 @@ const searchResults = computed(() => {
|
|||
:key="index"
|
||||
@click="handleSelect(tag)"
|
||||
>
|
||||
<VEntity
|
||||
:is-selected="selected?.metadata.name === tag.metadata.name"
|
||||
>
|
||||
<VEntity :is-selected="modelValue === tag.metadata.name">
|
||||
<template #start>
|
||||
<VEntityField :description="tag.status?.permalink">
|
||||
<template #title>
|
|
@ -2,6 +2,7 @@
|
|||
import type { User } from "@halo-dev/api-client";
|
||||
import { useUserFetch } from "@/modules/system/users/composables/use-user";
|
||||
import {
|
||||
IconArrowDown,
|
||||
VAvatar,
|
||||
VDropdown,
|
||||
VEntity,
|
||||
|
@ -13,16 +14,16 @@ import Fuse from "fuse.js";
|
|||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
selected?: User;
|
||||
label: string;
|
||||
modelValue?: string;
|
||||
}>(),
|
||||
{
|
||||
selected: undefined,
|
||||
modelValue: undefined,
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: "update:selected", user?: User): void;
|
||||
(event: "select", user?: User): void;
|
||||
(event: "update:modelValue", value?: string): void;
|
||||
}>();
|
||||
|
||||
const { users, handleFetchUsers } = useUserFetch();
|
||||
|
@ -30,22 +31,19 @@ const { users, handleFetchUsers } = useUserFetch();
|
|||
const dropdown = ref();
|
||||
|
||||
const handleSelect = (user: User) => {
|
||||
if (props.selected && user.metadata.name === props.selected.metadata.name) {
|
||||
emit("update:selected", undefined);
|
||||
emit("select", undefined);
|
||||
return;
|
||||
if (user.metadata.name === props.modelValue) {
|
||||
emit("update:modelValue", undefined);
|
||||
} else {
|
||||
emit("update:modelValue", user.metadata.name);
|
||||
}
|
||||
|
||||
emit("update:selected", user);
|
||||
emit("select", user);
|
||||
|
||||
dropdown.value.hide();
|
||||
};
|
||||
|
||||
function onDropdownShow() {
|
||||
handleFetchUsers();
|
||||
setTimeout(() => {
|
||||
setFocus("userDropdownSelectorInput");
|
||||
setFocus("userFilterDropdownInput");
|
||||
}, 200);
|
||||
}
|
||||
|
||||
|
@ -72,16 +70,33 @@ const searchResults = computed(() => {
|
|||
|
||||
return fuse?.search(keyword.value).map((item) => item.item);
|
||||
});
|
||||
|
||||
const selectedUser = computed(() => {
|
||||
return users.value.find((user) => user.metadata.name === props.modelValue);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VDropdown ref="dropdown" :classes="['!p-0']" @show="onDropdownShow">
|
||||
<slot />
|
||||
<div
|
||||
class="flex cursor-pointer select-none items-center text-sm text-gray-700 hover:text-black"
|
||||
:class="{ 'font-semibold text-gray-700': modelValue !== undefined }"
|
||||
>
|
||||
<span v-if="!selectedUser" class="mr-0.5">
|
||||
{{ label }}
|
||||
</span>
|
||||
<span v-else class="mr-0.5">
|
||||
{{ label }}:{{ selectedUser.spec.displayName }}
|
||||
</span>
|
||||
<span>
|
||||
<IconArrowDown />
|
||||
</span>
|
||||
</div>
|
||||
<template #popper>
|
||||
<div class="h-96 w-80">
|
||||
<div class="border-b border-b-gray-100 bg-white p-4">
|
||||
<FormKit
|
||||
id="userDropdownSelectorInput"
|
||||
id="userFilterDropdownInput"
|
||||
v-model="keyword"
|
||||
:placeholder="$t('core.common.placeholder.search')"
|
||||
type="text"
|
||||
|
@ -97,9 +112,7 @@ const searchResults = computed(() => {
|
|||
:key="index"
|
||||
@click="handleSelect(user)"
|
||||
>
|
||||
<VEntity
|
||||
:is-selected="selected?.metadata.name === user.metadata.name"
|
||||
>
|
||||
<VEntity :is-selected="modelValue === user.metadata.name">
|
||||
<template #start>
|
||||
<VEntityField>
|
||||
<template #description>
|
|
@ -154,14 +154,12 @@ core:
|
|||
filters:
|
||||
status:
|
||||
items:
|
||||
all: All
|
||||
published: Published
|
||||
draft: Draft
|
||||
visible:
|
||||
label: Visible
|
||||
result: "Visible: {visible}"
|
||||
items:
|
||||
all: All
|
||||
public: Public
|
||||
private: Private
|
||||
category:
|
||||
|
@ -324,14 +322,12 @@ core:
|
|||
filters:
|
||||
status:
|
||||
items:
|
||||
all: All
|
||||
published: Published
|
||||
draft: Draft
|
||||
visible:
|
||||
label: Visible
|
||||
result: "Visible: {visible}"
|
||||
items:
|
||||
all: All
|
||||
public: Public
|
||||
private: Private
|
||||
author:
|
||||
|
|
|
@ -154,14 +154,12 @@ core:
|
|||
filters:
|
||||
status:
|
||||
items:
|
||||
all: 全部
|
||||
published: 已发布
|
||||
draft: 未发布
|
||||
visible:
|
||||
label: 可见性
|
||||
result: "可见性:{visible}"
|
||||
items:
|
||||
all: 全部
|
||||
public: 公开
|
||||
private: 私有
|
||||
category:
|
||||
|
@ -324,14 +322,12 @@ core:
|
|||
filters:
|
||||
status:
|
||||
items:
|
||||
all: 全部
|
||||
published: 已发布
|
||||
draft: 未发布
|
||||
visible:
|
||||
label: 可见性
|
||||
result: "可见性:{visible}"
|
||||
items:
|
||||
all: 全部
|
||||
public: 公开
|
||||
private: 私有
|
||||
author:
|
||||
|
|
|
@ -154,14 +154,12 @@ core:
|
|||
filters:
|
||||
status:
|
||||
items:
|
||||
all: 全部
|
||||
published: 已發布
|
||||
draft: 未發布
|
||||
visible:
|
||||
label: 可見性
|
||||
result: "可見性:{visible}"
|
||||
items:
|
||||
all: 全部
|
||||
public: 公開
|
||||
private: 私有
|
||||
category:
|
||||
|
@ -324,14 +322,12 @@ core:
|
|||
filters:
|
||||
status:
|
||||
items:
|
||||
all: 全部
|
||||
published: 已發布
|
||||
draft: 未發布
|
||||
visible:
|
||||
label: 可見性
|
||||
result: "可見性:{visible}"
|
||||
items:
|
||||
all: 全部
|
||||
public: 公開
|
||||
private: 私有
|
||||
author:
|
||||
|
|
|
@ -25,13 +25,12 @@ import {
|
|||
VDropdownItem,
|
||||
} from "@halo-dev/components";
|
||||
import LazyImage from "@/components/image/LazyImage.vue";
|
||||
import UserDropdownSelector from "@/components/dropdown-selector/UserDropdownSelector.vue";
|
||||
import AttachmentDetailModal from "./components/AttachmentDetailModal.vue";
|
||||
import AttachmentUploadModal from "./components/AttachmentUploadModal.vue";
|
||||
import AttachmentPoliciesModal from "./components/AttachmentPoliciesModal.vue";
|
||||
import AttachmentGroupList from "./components/AttachmentGroupList.vue";
|
||||
import { computed, onMounted, ref, watch } from "vue";
|
||||
import type { Attachment, Group, Policy, User } from "@halo-dev/api-client";
|
||||
import type { Attachment, Group, Policy } from "@halo-dev/api-client";
|
||||
import { formatDatetime } from "@/utils/date";
|
||||
import prettyBytes from "pretty-bytes";
|
||||
import { useFetchAttachmentPolicy } from "./composables/use-attachment-policy";
|
||||
|
@ -46,6 +45,7 @@ import { usePermission } from "@/utils/permission";
|
|||
import FilterTag from "@/components/filter/FilterTag.vue";
|
||||
import { getNode } from "@formkit/core";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import UserFilterDropdown from "@/components/filter/UserFilterDropdown.vue";
|
||||
|
||||
const { currentUserHasPermission } = usePermission();
|
||||
const { t } = useI18n();
|
||||
|
@ -85,7 +85,7 @@ const SortItems: SortItem[] = [
|
|||
];
|
||||
|
||||
const selectedPolicy = ref<Policy>();
|
||||
const selectedUser = ref<User>();
|
||||
const selectedUser = ref();
|
||||
const selectedSortItem = ref<SortItem>();
|
||||
const selectedSortItemValue = computed(() => {
|
||||
return selectedSortItem.value?.value;
|
||||
|
@ -96,11 +96,6 @@ function handleSelectPolicy(policy: Policy | undefined) {
|
|||
page.value = 1;
|
||||
}
|
||||
|
||||
function handleSelectUser(user: User | undefined) {
|
||||
selectedUser.value = user;
|
||||
page.value = 1;
|
||||
}
|
||||
|
||||
function handleSortItemChange(sortItem?: SortItem) {
|
||||
selectedSortItem.value = sortItem;
|
||||
page.value = 1;
|
||||
|
@ -386,17 +381,6 @@ onMounted(() => {
|
|||
}}
|
||||
</FilterTag>
|
||||
|
||||
<FilterTag
|
||||
v-if="selectedUser"
|
||||
@close="handleSelectUser(undefined)"
|
||||
>
|
||||
{{
|
||||
$t("core.attachment.filters.owner.result", {
|
||||
owner: selectedUser.spec.displayName,
|
||||
})
|
||||
}}
|
||||
</FilterTag>
|
||||
|
||||
<FilterTag
|
||||
v-if="selectedSortItem"
|
||||
@click="handleSortItemChange()"
|
||||
|
@ -464,21 +448,11 @@ onMounted(() => {
|
|||
</VDropdownItem>
|
||||
</template>
|
||||
</VDropdown>
|
||||
<UserDropdownSelector
|
||||
v-model:selected="selectedUser"
|
||||
@select="handleSelectUser"
|
||||
>
|
||||
<div
|
||||
class="flex cursor-pointer select-none items-center text-sm text-gray-700 hover:text-black"
|
||||
>
|
||||
<span class="mr-0.5">
|
||||
{{ $t("core.attachment.filters.owner.label") }}
|
||||
</span>
|
||||
<span>
|
||||
<IconArrowDown />
|
||||
</span>
|
||||
</div>
|
||||
</UserDropdownSelector>
|
||||
|
||||
<UserFilterDropdown
|
||||
v-model="selectedUser"
|
||||
:label="$t('core.attachment.filters.owner.label')"
|
||||
/>
|
||||
<!-- TODO: add filter by ref support -->
|
||||
<VDropdown v-if="false">
|
||||
<div
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import type { Attachment, Group, Policy, User } from "@halo-dev/api-client";
|
||||
import type { Attachment, Group, Policy } from "@halo-dev/api-client";
|
||||
import { computed, type Ref } from "vue";
|
||||
import { ref, watch } from "vue";
|
||||
import type { AttachmentLike } from "@halo-dev/console-shared";
|
||||
|
@ -36,7 +36,7 @@ interface useAttachmentSelectReturn {
|
|||
export function useAttachmentControl(filterOptions: {
|
||||
policy?: Ref<Policy | undefined>;
|
||||
group?: Ref<Group | undefined>;
|
||||
user?: Ref<User | undefined>;
|
||||
user?: Ref<string | undefined>;
|
||||
keyword?: Ref<string | undefined>;
|
||||
sort?: Ref<string | undefined>;
|
||||
page: Ref<number>;
|
||||
|
@ -62,7 +62,7 @@ export function useAttachmentControl(filterOptions: {
|
|||
displayName: keyword?.value,
|
||||
group: group?.value?.metadata.name,
|
||||
ungrouped: group?.value?.metadata.name === "ungrouped",
|
||||
uploadedBy: user?.value?.metadata.name,
|
||||
uploadedBy: user?.value,
|
||||
page: page?.value,
|
||||
size: size?.value,
|
||||
sort: [sort?.value as string].filter(Boolean),
|
||||
|
|
|
@ -16,14 +16,14 @@ import {
|
|||
Toast,
|
||||
} from "@halo-dev/components";
|
||||
import CommentListItem from "./components/CommentListItem.vue";
|
||||
import UserDropdownSelector from "@/components/dropdown-selector/UserDropdownSelector.vue";
|
||||
import type { ListedComment, User } from "@halo-dev/api-client";
|
||||
import type { ListedComment } from "@halo-dev/api-client";
|
||||
import { computed, ref, watch } from "vue";
|
||||
import { apiClient } from "@/utils/api-client";
|
||||
import FilterTag from "@/components/filter/FilterTag.vue";
|
||||
import { getNode } from "@formkit/core";
|
||||
import { useQuery } from "@tanstack/vue-query";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import UserFilterDropdown from "@/components/filter/UserFilterDropdown.vue";
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
|
@ -87,7 +87,7 @@ const selectedApprovedFilterItem = ref<{ label: string; value?: boolean }>(
|
|||
|
||||
const selectedSortItem = ref<SortItem>();
|
||||
|
||||
const selectedUser = ref<User>();
|
||||
const selectedUser = ref();
|
||||
|
||||
const handleApprovedFilterItemChange = (filterItem: {
|
||||
label: string;
|
||||
|
@ -104,11 +104,6 @@ const handleSortItemChange = (sortItem: SortItem) => {
|
|||
page.value = 1;
|
||||
};
|
||||
|
||||
function handleSelectUser(user: User | undefined) {
|
||||
selectedUser.value = user;
|
||||
page.value = 1;
|
||||
}
|
||||
|
||||
function handleKeywordChange() {
|
||||
const keywordNode = getNode("keywordInput");
|
||||
if (keywordNode) {
|
||||
|
@ -165,7 +160,7 @@ const {
|
|||
approved: selectedApprovedFilterItem.value.value,
|
||||
sort: [selectedSortItem.value?.sort].filter(Boolean) as string[],
|
||||
keyword: keyword.value,
|
||||
ownerName: selectedUser.value?.metadata.name,
|
||||
ownerName: selectedUser.value,
|
||||
});
|
||||
|
||||
total.value = data.total;
|
||||
|
@ -345,17 +340,6 @@ const handleApproveInBatch = async () => {
|
|||
}}
|
||||
</FilterTag>
|
||||
|
||||
<FilterTag
|
||||
v-if="selectedUser"
|
||||
@close="handleSelectUser(undefined)"
|
||||
>
|
||||
{{
|
||||
$t("core.comment.filters.owner.result", {
|
||||
owner: selectedUser.spec.displayName,
|
||||
})
|
||||
}}
|
||||
</FilterTag>
|
||||
|
||||
<FilterTag
|
||||
v-if="selectedSortItem"
|
||||
@close="handleSortItemChange(SortItems[0])"
|
||||
|
@ -411,21 +395,10 @@ const handleApproveInBatch = async () => {
|
|||
</VDropdownItem>
|
||||
</template>
|
||||
</VDropdown>
|
||||
<UserDropdownSelector
|
||||
v-model:selected="selectedUser"
|
||||
@select="handleSelectUser"
|
||||
>
|
||||
<div
|
||||
class="flex cursor-pointer select-none items-center text-sm text-gray-700 hover:text-black"
|
||||
>
|
||||
<span class="mr-0.5">
|
||||
{{ $t("core.comment.filters.owner.label") }}
|
||||
</span>
|
||||
<span>
|
||||
<IconArrowDown />
|
||||
</span>
|
||||
</div>
|
||||
</UserDropdownSelector>
|
||||
<UserFilterDropdown
|
||||
v-model="selectedUser"
|
||||
:label="$t('core.comment.filters.owner.label')"
|
||||
/>
|
||||
<VDropdown>
|
||||
<div
|
||||
class="flex cursor-pointer select-none items-center text-sm text-gray-700 hover:text-black"
|
||||
|
|
|
@ -25,8 +25,6 @@ import { formatDatetime } from "@/utils/date";
|
|||
import { RouterLink } from "vue-router";
|
||||
import cloneDeep from "lodash.clonedeep";
|
||||
import { usePermission } from "@/utils/permission";
|
||||
import { getNode } from "@formkit/core";
|
||||
import FilterTag from "@/components/filter/FilterTag.vue";
|
||||
import { useQuery } from "@tanstack/vue-query";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
|
@ -199,19 +197,12 @@ watch(selectedPageNames, (newValue) => {
|
|||
checkedAll.value = newValue.length === singlePages.value?.length;
|
||||
});
|
||||
|
||||
// Filters
|
||||
function handleKeywordChange() {
|
||||
const keywordNode = getNode("keywordInput");
|
||||
if (keywordNode) {
|
||||
keyword.value = keywordNode._value as string;
|
||||
watch(
|
||||
() => keyword.value,
|
||||
() => {
|
||||
page.value = 1;
|
||||
}
|
||||
page.value = 1;
|
||||
}
|
||||
|
||||
function handleClearKeyword() {
|
||||
keyword.value = "";
|
||||
page.value = 1;
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -256,28 +247,7 @@ function handleClearKeyword() {
|
|||
/>
|
||||
</div>
|
||||
<div class="flex w-full flex-1 items-center sm:w-auto">
|
||||
<div
|
||||
v-if="!selectedPageNames.length"
|
||||
class="flex items-center gap-2"
|
||||
>
|
||||
<FormKit
|
||||
id="keywordInput"
|
||||
outer-class="!p-0"
|
||||
:placeholder="$t('core.common.placeholder.search')"
|
||||
type="text"
|
||||
name="keyword"
|
||||
:model-value="keyword"
|
||||
@keyup.enter="handleKeywordChange"
|
||||
></FormKit>
|
||||
|
||||
<FilterTag v-if="keyword" @close="handleClearKeyword()">
|
||||
{{
|
||||
$t("core.common.filters.results.keyword", {
|
||||
keyword: keyword,
|
||||
})
|
||||
}}
|
||||
</FilterTag>
|
||||
</div>
|
||||
<SearchInput v-if="!selectedPageNames.length" v-model="keyword" />
|
||||
<VSpace v-else>
|
||||
<VButton type="danger" @click="handleDeletePermanentlyInBatch">
|
||||
{{ $t("core.common.buttons.delete_permanently") }}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
<script lang="ts" setup>
|
||||
import {
|
||||
IconArrowDown,
|
||||
IconArrowLeft,
|
||||
IconArrowRight,
|
||||
IconEye,
|
||||
|
@ -22,24 +21,21 @@ import {
|
|||
VLoading,
|
||||
VPageHeader,
|
||||
Toast,
|
||||
VDropdown,
|
||||
VDropdownItem,
|
||||
VDropdownDivider,
|
||||
} from "@halo-dev/components";
|
||||
import SinglePageSettingModal from "./components/SinglePageSettingModal.vue";
|
||||
import UserDropdownSelector from "@/components/dropdown-selector/UserDropdownSelector.vue";
|
||||
import { computed, ref, watch } from "vue";
|
||||
import type { ListedSinglePage, SinglePage, User } from "@halo-dev/api-client";
|
||||
import type { ListedSinglePage, SinglePage } from "@halo-dev/api-client";
|
||||
import { apiClient } from "@/utils/api-client";
|
||||
import { formatDatetime } from "@/utils/date";
|
||||
import { RouterLink } from "vue-router";
|
||||
import cloneDeep from "lodash.clonedeep";
|
||||
import { usePermission } from "@/utils/permission";
|
||||
import { singlePageLabels } from "@/constants/labels";
|
||||
import FilterTag from "@/components/filter/FilterTag.vue";
|
||||
import { getNode } from "@formkit/core";
|
||||
import { useMutation, useQuery } from "@tanstack/vue-query";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import UserFilterDropdown from "@/components/filter/UserFilterDropdown.vue";
|
||||
|
||||
const { currentUserHasPermission } = usePermission();
|
||||
const { t } = useI18n();
|
||||
|
@ -50,126 +46,39 @@ const selectedPageNames = ref<string[]>([]);
|
|||
const checkedAll = ref(false);
|
||||
|
||||
// Filters
|
||||
interface VisibleItem {
|
||||
label: string;
|
||||
value?: "PUBLIC" | "INTERNAL" | "PRIVATE";
|
||||
}
|
||||
|
||||
interface PublishStatusItem {
|
||||
label: string;
|
||||
value?: boolean;
|
||||
}
|
||||
|
||||
interface SortItem {
|
||||
label: string;
|
||||
sort: string;
|
||||
}
|
||||
|
||||
const VisibleItems: VisibleItem[] = [
|
||||
{
|
||||
label: t("core.page.filters.visible.items.all"),
|
||||
value: undefined,
|
||||
},
|
||||
{
|
||||
label: t("core.page.filters.visible.items.public"),
|
||||
value: "PUBLIC",
|
||||
},
|
||||
{
|
||||
label: t("core.page.filters.visible.items.private"),
|
||||
value: "PRIVATE",
|
||||
},
|
||||
];
|
||||
|
||||
const PublishStatusItems: PublishStatusItem[] = [
|
||||
{
|
||||
label: t("core.page.filters.status.items.all"),
|
||||
value: undefined,
|
||||
},
|
||||
{
|
||||
label: t("core.page.filters.status.items.published"),
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
label: t("core.page.filters.status.items.draft"),
|
||||
value: false,
|
||||
},
|
||||
];
|
||||
|
||||
const SortItems: SortItem[] = [
|
||||
{
|
||||
label: t("core.page.filters.sort.items.publish_time_desc"),
|
||||
sort: "publishTime,desc",
|
||||
},
|
||||
{
|
||||
label: t("core.page.filters.sort.items.publish_time_asc"),
|
||||
sort: "publishTime,asc",
|
||||
},
|
||||
{
|
||||
label: t("core.page.filters.sort.items.create_time_desc"),
|
||||
sort: "creationTimestamp,desc",
|
||||
},
|
||||
{
|
||||
label: t("core.page.filters.sort.items.create_time_asc"),
|
||||
sort: "creationTimestamp,asc",
|
||||
},
|
||||
];
|
||||
|
||||
const selectedContributor = ref<User>();
|
||||
const selectedVisibleItem = ref<VisibleItem>(VisibleItems[0]);
|
||||
const selectedPublishStatusItem = ref<PublishStatusItem>(PublishStatusItems[0]);
|
||||
const selectedSortItem = ref<SortItem>();
|
||||
const selectedContributor = ref();
|
||||
const selectedVisible = ref();
|
||||
const selectedPublishStatus = ref();
|
||||
const selectedSortValue = ref();
|
||||
const keyword = ref("");
|
||||
|
||||
function handleVisibleItemChange(visibleItem: VisibleItem) {
|
||||
selectedVisibleItem.value = visibleItem;
|
||||
page.value = 1;
|
||||
}
|
||||
|
||||
const handleSelectUser = (user?: User) => {
|
||||
selectedContributor.value = user;
|
||||
page.value = 1;
|
||||
};
|
||||
|
||||
function handlePublishStatusItemChange(publishStatusItem: PublishStatusItem) {
|
||||
selectedPublishStatusItem.value = publishStatusItem;
|
||||
page.value = 1;
|
||||
}
|
||||
|
||||
function handleSortItemChange(sortItem?: SortItem) {
|
||||
selectedSortItem.value = sortItem;
|
||||
page.value = 1;
|
||||
}
|
||||
|
||||
function handleKeywordChange() {
|
||||
const keywordNode = getNode("keywordInput");
|
||||
if (keywordNode) {
|
||||
keyword.value = keywordNode._value as string;
|
||||
watch(
|
||||
() => [
|
||||
selectedContributor.value,
|
||||
selectedVisible.value,
|
||||
selectedPublishStatus.value,
|
||||
selectedSortValue.value,
|
||||
keyword.value,
|
||||
],
|
||||
() => {
|
||||
page.value = 1;
|
||||
}
|
||||
page.value = 1;
|
||||
}
|
||||
|
||||
function handleClearKeyword() {
|
||||
keyword.value = "";
|
||||
page.value = 1;
|
||||
}
|
||||
);
|
||||
|
||||
const hasFilters = computed(() => {
|
||||
return (
|
||||
selectedContributor.value ||
|
||||
selectedVisibleItem.value.value ||
|
||||
selectedPublishStatusItem.value.value !== undefined ||
|
||||
selectedSortItem.value ||
|
||||
keyword.value
|
||||
selectedVisible.value ||
|
||||
selectedPublishStatus.value !== undefined ||
|
||||
selectedSortValue.value
|
||||
);
|
||||
});
|
||||
|
||||
function handleClearFilters() {
|
||||
selectedContributor.value = undefined;
|
||||
selectedVisibleItem.value = VisibleItems[0];
|
||||
selectedPublishStatusItem.value = PublishStatusItems[0];
|
||||
selectedSortItem.value = undefined;
|
||||
keyword.value = "";
|
||||
page.value = 1;
|
||||
selectedVisible.value = undefined;
|
||||
selectedPublishStatus.value = undefined;
|
||||
selectedSortValue.value = undefined;
|
||||
}
|
||||
|
||||
const page = ref(1);
|
||||
|
@ -187,11 +96,11 @@ const {
|
|||
queryKey: [
|
||||
"singlePages",
|
||||
selectedContributor,
|
||||
selectedPublishStatusItem,
|
||||
selectedPublishStatus,
|
||||
page,
|
||||
size,
|
||||
selectedVisibleItem,
|
||||
selectedSortItem,
|
||||
selectedVisible,
|
||||
selectedSortValue,
|
||||
keyword,
|
||||
],
|
||||
queryFn: async () => {
|
||||
|
@ -199,12 +108,12 @@ const {
|
|||
const labelSelector: string[] = ["content.halo.run/deleted=false"];
|
||||
|
||||
if (selectedContributor.value) {
|
||||
contributors = [selectedContributor.value.metadata.name];
|
||||
contributors = [selectedContributor.value];
|
||||
}
|
||||
|
||||
if (selectedPublishStatusItem.value.value !== undefined) {
|
||||
if (selectedPublishStatus.value !== undefined) {
|
||||
labelSelector.push(
|
||||
`${singlePageLabels.PUBLISHED}=${selectedPublishStatusItem.value.value}`
|
||||
`${singlePageLabels.PUBLISHED}=${selectedPublishStatus.value}`
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -212,8 +121,8 @@ const {
|
|||
labelSelector,
|
||||
page: page.value,
|
||||
size: size.value,
|
||||
visible: selectedVisibleItem.value.value,
|
||||
sort: [selectedSortItem.value?.sort].filter(Boolean) as string[],
|
||||
visible: selectedVisible.value,
|
||||
sort: [selectedSortValue.value].filter(Boolean) as string[],
|
||||
keyword: keyword.value,
|
||||
contributor: contributors,
|
||||
});
|
||||
|
@ -497,77 +406,7 @@ const getExternalUrl = (singlePage: SinglePage) => {
|
|||
/>
|
||||
</div>
|
||||
<div class="flex w-full flex-1 items-center sm:w-auto">
|
||||
<div
|
||||
v-if="!selectedPageNames.length"
|
||||
class="flex items-center gap-2"
|
||||
>
|
||||
<FormKit
|
||||
id="keywordInput"
|
||||
outer-class="!p-0"
|
||||
:placeholder="$t('core.common.placeholder.search')"
|
||||
type="text"
|
||||
name="keyword"
|
||||
:model-value="keyword"
|
||||
@keyup.enter="handleKeywordChange"
|
||||
></FormKit>
|
||||
|
||||
<FilterTag v-if="keyword" @close="handleClearKeyword()">
|
||||
{{
|
||||
$t("core.common.filters.results.keyword", {
|
||||
keyword: keyword,
|
||||
})
|
||||
}}
|
||||
</FilterTag>
|
||||
|
||||
<FilterTag
|
||||
v-if="selectedPublishStatusItem.value !== undefined"
|
||||
@close="handlePublishStatusItemChange(PublishStatusItems[0])"
|
||||
>
|
||||
{{
|
||||
$t("core.common.filters.results.status", {
|
||||
status: selectedPublishStatusItem.label,
|
||||
})
|
||||
}}
|
||||
</FilterTag>
|
||||
|
||||
<FilterTag
|
||||
v-if="selectedVisibleItem.value"
|
||||
@close="handleVisibleItemChange(VisibleItems[0])"
|
||||
>
|
||||
{{
|
||||
$t("core.page.filters.visible.result", {
|
||||
visible: selectedVisibleItem.label,
|
||||
})
|
||||
}}
|
||||
</FilterTag>
|
||||
|
||||
<FilterTag
|
||||
v-if="selectedContributor"
|
||||
@close="handleSelectUser()"
|
||||
>
|
||||
{{
|
||||
$t("core.page.filters.author.result", {
|
||||
author: selectedContributor.spec.displayName,
|
||||
})
|
||||
}}
|
||||
</FilterTag>
|
||||
|
||||
<FilterTag
|
||||
v-if="selectedSortItem"
|
||||
@close="handleSortItemChange()"
|
||||
>
|
||||
{{
|
||||
$t("core.common.filters.results.sort", {
|
||||
sort: selectedSortItem.label,
|
||||
})
|
||||
}}
|
||||
</FilterTag>
|
||||
|
||||
<FilterCleanButton
|
||||
v-if="hasFilters"
|
||||
@click="handleClearFilters"
|
||||
/>
|
||||
</div>
|
||||
<SearchInput v-if="!selectedPageNames.length" v-model="keyword" />
|
||||
<VSpace v-else>
|
||||
<VButton type="danger" @click="handleDeleteInBatch">
|
||||
{{ $t("core.common.buttons.delete") }}
|
||||
|
@ -576,89 +415,77 @@ const getExternalUrl = (singlePage: SinglePage) => {
|
|||
</div>
|
||||
<div class="mt-4 flex sm:mt-0">
|
||||
<VSpace spacing="lg">
|
||||
<VDropdown>
|
||||
<div
|
||||
class="flex cursor-pointer select-none items-center text-sm text-gray-700 hover:text-black"
|
||||
>
|
||||
<span class="mr-0.5">
|
||||
{{ $t("core.common.filters.labels.status") }}
|
||||
</span>
|
||||
<span>
|
||||
<IconArrowDown />
|
||||
</span>
|
||||
</div>
|
||||
<template #popper>
|
||||
<VDropdownItem
|
||||
v-for="(filterItem, index) in PublishStatusItems"
|
||||
:key="index"
|
||||
:selected="
|
||||
filterItem.value === selectedPublishStatusItem.value
|
||||
"
|
||||
@click="handlePublishStatusItemChange(filterItem)"
|
||||
>
|
||||
{{ filterItem.label }}
|
||||
</VDropdownItem>
|
||||
</template>
|
||||
</VDropdown>
|
||||
<VDropdown>
|
||||
<div
|
||||
class="flex cursor-pointer select-none items-center text-sm text-gray-700 hover:text-black"
|
||||
>
|
||||
<span class="mr-0.5">
|
||||
{{ $t("core.page.filters.visible.label") }}
|
||||
</span>
|
||||
<span>
|
||||
<IconArrowDown />
|
||||
</span>
|
||||
</div>
|
||||
<template #popper>
|
||||
<VDropdownItem
|
||||
v-for="(filterItem, index) in VisibleItems"
|
||||
:key="index"
|
||||
:selected="filterItem.value === selectedVisibleItem.value"
|
||||
@click="handleVisibleItemChange(filterItem)"
|
||||
>
|
||||
{{ filterItem.label }}
|
||||
</VDropdownItem>
|
||||
</template>
|
||||
</VDropdown>
|
||||
<UserDropdownSelector
|
||||
v-model:selected="selectedContributor"
|
||||
@select="handleSelectUser"
|
||||
>
|
||||
<div
|
||||
class="flex cursor-pointer select-none items-center text-sm text-gray-700 hover:text-black"
|
||||
>
|
||||
<span class="mr-0.5">
|
||||
{{ $t("core.page.filters.author.label") }}
|
||||
</span>
|
||||
<span>
|
||||
<IconArrowDown />
|
||||
</span>
|
||||
</div>
|
||||
</UserDropdownSelector>
|
||||
<VDropdown>
|
||||
<div
|
||||
class="flex cursor-pointer select-none items-center text-sm text-gray-700 hover:text-black"
|
||||
>
|
||||
<span class="mr-0.5">
|
||||
{{ $t("core.common.filters.labels.sort") }}
|
||||
</span>
|
||||
<span>
|
||||
<IconArrowDown />
|
||||
</span>
|
||||
</div>
|
||||
<template #popper>
|
||||
<VDropdownItem
|
||||
v-for="(sortItem, index) in SortItems"
|
||||
:key="index"
|
||||
:selected="sortItem.sort === selectedSortItem?.sort"
|
||||
@click="handleSortItemChange(sortItem)"
|
||||
>
|
||||
{{ sortItem.label }}
|
||||
</VDropdownItem>
|
||||
</template>
|
||||
</VDropdown>
|
||||
<FilterCleanButton
|
||||
v-if="hasFilters"
|
||||
@click="handleClearFilters"
|
||||
/>
|
||||
<FilterDropdown
|
||||
v-model="selectedPublishStatus"
|
||||
:label="$t('core.common.filters.labels.status')"
|
||||
:items="[
|
||||
{
|
||||
label: t('core.common.filters.item_labels.all'),
|
||||
value: undefined,
|
||||
},
|
||||
{
|
||||
label: t('core.page.filters.status.items.published'),
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
label: t('core.page.filters.status.items.draft'),
|
||||
value: false,
|
||||
},
|
||||
]"
|
||||
/>
|
||||
<FilterDropdown
|
||||
v-model="selectedVisible"
|
||||
:label="$t('core.page.filters.visible.label')"
|
||||
:items="[
|
||||
{
|
||||
label: t('core.common.filters.item_labels.all'),
|
||||
value: undefined,
|
||||
},
|
||||
{
|
||||
label: t('core.page.filters.visible.items.public'),
|
||||
value: 'PUBLIC',
|
||||
},
|
||||
{
|
||||
label: t('core.page.filters.visible.items.private'),
|
||||
value: 'PRIVATE',
|
||||
},
|
||||
]"
|
||||
/>
|
||||
<UserFilterDropdown
|
||||
v-model="selectedContributor"
|
||||
:label="$t('core.page.filters.author.label')"
|
||||
/>
|
||||
<FilterDropdown
|
||||
v-model="selectedSortValue"
|
||||
:label="$t('core.common.filters.labels.sort')"
|
||||
:items="[
|
||||
{
|
||||
label: t('core.common.filters.item_labels.default'),
|
||||
},
|
||||
{
|
||||
label: t(
|
||||
'core.page.filters.sort.items.publish_time_desc'
|
||||
),
|
||||
value: 'publishTime,desc',
|
||||
},
|
||||
{
|
||||
label: t('core.page.filters.sort.items.publish_time_asc'),
|
||||
value: 'publishTime,asc',
|
||||
},
|
||||
{
|
||||
label: t('core.page.filters.sort.items.create_time_desc'),
|
||||
value: 'creationTimestamp,desc',
|
||||
},
|
||||
{
|
||||
label: t('core.page.filters.sort.items.create_time_asc'),
|
||||
value: 'creationTimestamp,asc',
|
||||
},
|
||||
]"
|
||||
/>
|
||||
<div class="flex flex-row gap-2">
|
||||
<div
|
||||
class="group cursor-pointer rounded p-1 hover:bg-gray-200"
|
||||
|
|
|
@ -25,8 +25,6 @@ import { apiClient } from "@/utils/api-client";
|
|||
import { formatDatetime } from "@/utils/date";
|
||||
import { usePermission } from "@/utils/permission";
|
||||
import cloneDeep from "lodash.clonedeep";
|
||||
import { getNode } from "@formkit/core";
|
||||
import FilterTag from "@/components/filter/FilterTag.vue";
|
||||
import { useQuery } from "@tanstack/vue-query";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
|
@ -191,18 +189,12 @@ watch(selectedPostNames, (newValue) => {
|
|||
checkedAll.value = newValue.length === posts.value?.length;
|
||||
});
|
||||
|
||||
function handleKeywordChange() {
|
||||
const keywordNode = getNode("keywordInput");
|
||||
if (keywordNode) {
|
||||
keyword.value = keywordNode._value as string;
|
||||
watch(
|
||||
() => keyword.value,
|
||||
() => {
|
||||
page.value = 1;
|
||||
}
|
||||
page.value = 1;
|
||||
}
|
||||
|
||||
function handleClearKeyword() {
|
||||
keyword.value = "";
|
||||
page.value = 1;
|
||||
}
|
||||
);
|
||||
</script>
|
||||
<template>
|
||||
<VPageHeader :title="$t('core.deleted_post.title')">
|
||||
|
@ -247,28 +239,7 @@ function handleClearKeyword() {
|
|||
/>
|
||||
</div>
|
||||
<div class="flex w-full flex-1 items-center sm:w-auto">
|
||||
<div
|
||||
v-if="!selectedPostNames.length"
|
||||
class="flex items-center gap-2"
|
||||
>
|
||||
<FormKit
|
||||
id="keywordInput"
|
||||
outer-class="!p-0"
|
||||
:placeholder="$t('core.common.placeholder.search')"
|
||||
type="text"
|
||||
name="keyword"
|
||||
:model-value="keyword"
|
||||
@keyup.enter="handleKeywordChange"
|
||||
></FormKit>
|
||||
|
||||
<FilterTag v-if="keyword" @close="handleClearKeyword()">
|
||||
{{
|
||||
$t("core.common.filters.results.keyword", {
|
||||
keyword: keyword,
|
||||
})
|
||||
}}
|
||||
</FilterTag>
|
||||
</div>
|
||||
<SearchInput v-if="!selectedPostNames.length" v-model="keyword" />
|
||||
<VSpace v-else>
|
||||
<VButton type="danger" @click="handleDeletePermanentlyInBatch">
|
||||
{{ $t("core.common.buttons.delete_permanently") }}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
<script lang="ts" setup>
|
||||
import {
|
||||
IconAddCircle,
|
||||
IconArrowDown,
|
||||
IconArrowLeft,
|
||||
IconArrowRight,
|
||||
IconBookRead,
|
||||
|
@ -23,30 +22,21 @@ import {
|
|||
VLoading,
|
||||
Toast,
|
||||
VDropdownItem,
|
||||
VDropdown,
|
||||
VDropdownDivider,
|
||||
} from "@halo-dev/components";
|
||||
import UserDropdownSelector from "@/components/dropdown-selector/UserDropdownSelector.vue";
|
||||
import CategoryDropdownSelector from "@/components/dropdown-selector/CategoryDropdownSelector.vue";
|
||||
import PostSettingModal from "./components/PostSettingModal.vue";
|
||||
import PostTag from "../posts/tags/components/PostTag.vue";
|
||||
import { computed, ref, watch } from "vue";
|
||||
import type {
|
||||
User,
|
||||
Category,
|
||||
Post,
|
||||
Tag,
|
||||
ListedPost,
|
||||
} from "@halo-dev/api-client";
|
||||
import type { Post, ListedPost } from "@halo-dev/api-client";
|
||||
import { apiClient } from "@/utils/api-client";
|
||||
import { formatDatetime } from "@/utils/date";
|
||||
import { usePermission } from "@/utils/permission";
|
||||
import { postLabels } from "@/constants/labels";
|
||||
import FilterTag from "@/components/filter/FilterTag.vue";
|
||||
import { getNode } from "@formkit/core";
|
||||
import TagDropdownSelector from "@/components/dropdown-selector/TagDropdownSelector.vue";
|
||||
import { useMutation, useQuery } from "@tanstack/vue-query";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import UserFilterDropdown from "@/components/filter/UserFilterDropdown.vue";
|
||||
import CategoryFilterDropdown from "@/components/filter/CategoryFilterDropdown.vue";
|
||||
import TagFilterDropdown from "@/components/filter/TagFilterDropdown.vue";
|
||||
|
||||
const { currentUserHasPermission } = usePermission();
|
||||
const { t } = useI18n();
|
||||
|
@ -57,141 +47,46 @@ const checkedAll = ref(false);
|
|||
const selectedPostNames = ref<string[]>([]);
|
||||
|
||||
// Filters
|
||||
interface VisibleItem {
|
||||
label: string;
|
||||
value?: "PUBLIC" | "INTERNAL" | "PRIVATE";
|
||||
}
|
||||
|
||||
interface PublishStatusItem {
|
||||
label: string;
|
||||
value?: boolean;
|
||||
}
|
||||
|
||||
interface SortItem {
|
||||
label: string;
|
||||
sort: string;
|
||||
}
|
||||
|
||||
const VisibleItems: VisibleItem[] = [
|
||||
{
|
||||
label: t("core.post.filters.visible.items.all"),
|
||||
value: undefined,
|
||||
},
|
||||
{
|
||||
label: t("core.post.filters.visible.items.public"),
|
||||
value: "PUBLIC",
|
||||
},
|
||||
{
|
||||
label: t("core.post.filters.visible.items.private"),
|
||||
value: "PRIVATE",
|
||||
},
|
||||
];
|
||||
|
||||
const PublishStatusItems: PublishStatusItem[] = [
|
||||
{
|
||||
label: t("core.post.filters.status.items.all"),
|
||||
value: undefined,
|
||||
},
|
||||
{
|
||||
label: t("core.post.filters.status.items.published"),
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
label: t("core.post.filters.status.items.draft"),
|
||||
value: false,
|
||||
},
|
||||
];
|
||||
|
||||
const SortItems: SortItem[] = [
|
||||
{
|
||||
label: t("core.post.filters.sort.items.publish_time_desc"),
|
||||
sort: "publishTime,desc",
|
||||
},
|
||||
{
|
||||
label: t("core.post.filters.sort.items.publish_time_asc"),
|
||||
sort: "publishTime,asc",
|
||||
},
|
||||
{
|
||||
label: t("core.post.filters.sort.items.create_time_desc"),
|
||||
sort: "creationTimestamp,desc",
|
||||
},
|
||||
{
|
||||
label: t("core.post.filters.sort.items.create_time_asc"),
|
||||
sort: "creationTimestamp,asc",
|
||||
},
|
||||
];
|
||||
|
||||
const selectedVisibleItem = ref<VisibleItem>(VisibleItems[0]);
|
||||
const selectedPublishStatusItem = ref<PublishStatusItem>(PublishStatusItems[0]);
|
||||
const selectedSortItem = ref<SortItem>();
|
||||
const selectedCategory = ref<Category>();
|
||||
const selectedTag = ref<Tag>();
|
||||
const selectedContributor = ref<User>();
|
||||
const selectedVisible = ref();
|
||||
const selectedPublishStatus = ref();
|
||||
const selectedSort = ref();
|
||||
const selectedCategory = ref();
|
||||
const selectedTag = ref();
|
||||
const selectedContributor = ref();
|
||||
const keyword = ref("");
|
||||
|
||||
function handleVisibleItemChange(visibleItem: VisibleItem) {
|
||||
selectedVisibleItem.value = visibleItem;
|
||||
page.value = 1;
|
||||
}
|
||||
|
||||
function handlePublishStatusItemChange(publishStatusItem: PublishStatusItem) {
|
||||
selectedPublishStatusItem.value = publishStatusItem;
|
||||
page.value = 1;
|
||||
}
|
||||
|
||||
function handleSortItemChange(sortItem?: SortItem) {
|
||||
selectedSortItem.value = sortItem;
|
||||
page.value = 1;
|
||||
}
|
||||
|
||||
function handleCategoryChange(category?: Category) {
|
||||
selectedCategory.value = category;
|
||||
page.value = 1;
|
||||
}
|
||||
|
||||
function handleTagChange(tag?: Tag) {
|
||||
selectedTag.value = tag;
|
||||
page.value = 1;
|
||||
}
|
||||
|
||||
function handleContributorChange(user?: User) {
|
||||
selectedContributor.value = user;
|
||||
page.value = 1;
|
||||
}
|
||||
|
||||
function handleKeywordChange() {
|
||||
const keywordNode = getNode("keywordInput");
|
||||
if (keywordNode) {
|
||||
keyword.value = keywordNode._value as string;
|
||||
watch(
|
||||
() => [
|
||||
selectedVisible.value,
|
||||
selectedPublishStatus.value,
|
||||
selectedSort.value,
|
||||
selectedCategory.value,
|
||||
selectedTag.value,
|
||||
selectedContributor.value,
|
||||
keyword.value,
|
||||
],
|
||||
() => {
|
||||
page.value = 1;
|
||||
}
|
||||
page.value = 1;
|
||||
}
|
||||
|
||||
function handleClearKeyword() {
|
||||
keyword.value = "";
|
||||
page.value = 1;
|
||||
}
|
||||
);
|
||||
|
||||
function handleClearFilters() {
|
||||
selectedVisibleItem.value = VisibleItems[0];
|
||||
selectedPublishStatusItem.value = PublishStatusItems[0];
|
||||
selectedSortItem.value = undefined;
|
||||
selectedVisible.value = undefined;
|
||||
selectedPublishStatus.value = undefined;
|
||||
selectedSort.value = undefined;
|
||||
selectedCategory.value = undefined;
|
||||
selectedTag.value = undefined;
|
||||
selectedContributor.value = undefined;
|
||||
keyword.value = "";
|
||||
page.value = 1;
|
||||
}
|
||||
|
||||
const hasFilters = computed(() => {
|
||||
return (
|
||||
selectedVisibleItem.value.value ||
|
||||
selectedPublishStatusItem.value.value !== undefined ||
|
||||
selectedSortItem.value ||
|
||||
selectedVisible.value ||
|
||||
selectedPublishStatus.value !== undefined ||
|
||||
selectedSort.value ||
|
||||
selectedCategory.value ||
|
||||
selectedTag.value ||
|
||||
selectedContributor.value ||
|
||||
keyword.value
|
||||
selectedContributor.value
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -214,9 +109,9 @@ const {
|
|||
selectedCategory,
|
||||
selectedTag,
|
||||
selectedContributor,
|
||||
selectedPublishStatusItem,
|
||||
selectedVisibleItem,
|
||||
selectedSortItem,
|
||||
selectedPublishStatus,
|
||||
selectedVisible,
|
||||
selectedSort,
|
||||
keyword,
|
||||
],
|
||||
queryFn: async () => {
|
||||
|
@ -226,20 +121,20 @@ const {
|
|||
const labelSelector: string[] = ["content.halo.run/deleted=false"];
|
||||
|
||||
if (selectedCategory.value) {
|
||||
categories = [selectedCategory.value.metadata.name];
|
||||
categories = [selectedCategory.value];
|
||||
}
|
||||
|
||||
if (selectedTag.value) {
|
||||
tags = [selectedTag.value.metadata.name];
|
||||
tags = [selectedTag.value];
|
||||
}
|
||||
|
||||
if (selectedContributor.value) {
|
||||
contributors = [selectedContributor.value.metadata.name];
|
||||
contributors = [selectedContributor.value];
|
||||
}
|
||||
|
||||
if (selectedPublishStatusItem.value.value !== undefined) {
|
||||
if (selectedPublishStatus.value !== undefined) {
|
||||
labelSelector.push(
|
||||
`${postLabels.PUBLISHED}=${selectedPublishStatusItem.value.value}`
|
||||
`${postLabels.PUBLISHED}=${selectedPublishStatus.value}`
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -247,8 +142,8 @@ const {
|
|||
labelSelector,
|
||||
page: page.value,
|
||||
size: size.value,
|
||||
visible: selectedVisibleItem.value?.value,
|
||||
sort: [selectedSortItem.value?.sort].filter(Boolean) as string[],
|
||||
visible: selectedVisible.value,
|
||||
sort: [selectedSort.value?.sort].filter(Boolean) as string[],
|
||||
keyword: keyword.value,
|
||||
category: categories,
|
||||
tag: tags,
|
||||
|
@ -511,96 +406,7 @@ const getExternalUrl = (post: Post) => {
|
|||
/>
|
||||
</div>
|
||||
<div class="flex w-full flex-1 items-center sm:w-auto">
|
||||
<div
|
||||
v-if="!selectedPostNames.length"
|
||||
class="flex items-center gap-2"
|
||||
>
|
||||
<FormKit
|
||||
id="keywordInput"
|
||||
outer-class="!p-0"
|
||||
:placeholder="$t('core.common.placeholder.search')"
|
||||
type="text"
|
||||
name="keyword"
|
||||
:model-value="keyword"
|
||||
@keyup.enter="handleKeywordChange"
|
||||
></FormKit>
|
||||
|
||||
<FilterTag v-if="keyword" @close="handleClearKeyword()">
|
||||
{{
|
||||
$t("core.common.filters.results.keyword", {
|
||||
keyword: keyword,
|
||||
})
|
||||
}}
|
||||
</FilterTag>
|
||||
|
||||
<FilterTag
|
||||
v-if="selectedPublishStatusItem.value !== undefined"
|
||||
@close="handlePublishStatusItemChange(PublishStatusItems[0])"
|
||||
>
|
||||
{{
|
||||
$t("core.common.filters.results.status", {
|
||||
status: selectedPublishStatusItem.label,
|
||||
})
|
||||
}}
|
||||
</FilterTag>
|
||||
|
||||
<FilterTag
|
||||
v-if="selectedVisibleItem.value"
|
||||
@close="handleVisibleItemChange(VisibleItems[0])"
|
||||
>
|
||||
{{
|
||||
$t("core.post.filters.visible.result", {
|
||||
visible: selectedVisibleItem.label,
|
||||
})
|
||||
}}
|
||||
</FilterTag>
|
||||
|
||||
<FilterTag
|
||||
v-if="selectedCategory"
|
||||
@close="handleCategoryChange()"
|
||||
>
|
||||
{{
|
||||
$t("core.post.filters.category.result", {
|
||||
category: selectedCategory.spec.displayName,
|
||||
})
|
||||
}}
|
||||
</FilterTag>
|
||||
|
||||
<FilterTag v-if="selectedTag" @click="handleTagChange()">
|
||||
{{
|
||||
$t("core.post.filters.tag.result", {
|
||||
tag: selectedTag.spec.displayName,
|
||||
})
|
||||
}}
|
||||
</FilterTag>
|
||||
|
||||
<FilterTag
|
||||
v-if="selectedContributor"
|
||||
@close="handleContributorChange()"
|
||||
>
|
||||
{{
|
||||
$t("core.post.filters.author.result", {
|
||||
author: selectedContributor.spec.displayName,
|
||||
})
|
||||
}}
|
||||
</FilterTag>
|
||||
|
||||
<FilterTag
|
||||
v-if="selectedSortItem"
|
||||
@close="handleSortItemChange()"
|
||||
>
|
||||
{{
|
||||
$t("core.common.filters.results.sort", {
|
||||
sort: selectedSortItem.label,
|
||||
})
|
||||
}}
|
||||
</FilterTag>
|
||||
|
||||
<FilterCleanButton
|
||||
v-if="hasFilters"
|
||||
@click="handleClearFilters"
|
||||
/>
|
||||
</div>
|
||||
<SearchInput v-if="!selectedPostNames.length" v-model="keyword" />
|
||||
<VSpace v-else>
|
||||
<VButton type="danger" @click="handleDeleteInBatch">
|
||||
{{ $t("core.common.buttons.delete") }}
|
||||
|
@ -609,119 +415,85 @@ const getExternalUrl = (post: Post) => {
|
|||
</div>
|
||||
<div class="mt-4 flex sm:mt-0">
|
||||
<VSpace spacing="lg">
|
||||
<VDropdown>
|
||||
<div
|
||||
class="flex cursor-pointer select-none items-center text-sm text-gray-700 hover:text-black"
|
||||
>
|
||||
<span class="mr-0.5">
|
||||
{{ $t("core.common.filters.labels.status") }}
|
||||
</span>
|
||||
<span>
|
||||
<IconArrowDown />
|
||||
</span>
|
||||
</div>
|
||||
<template #popper>
|
||||
<VDropdownItem
|
||||
v-for="(filterItem, index) in PublishStatusItems"
|
||||
:key="index"
|
||||
:selected="
|
||||
filterItem.value === selectedPublishStatusItem.value
|
||||
"
|
||||
@click="handlePublishStatusItemChange(filterItem)"
|
||||
>
|
||||
{{ filterItem.label }}
|
||||
</VDropdownItem>
|
||||
</template>
|
||||
</VDropdown>
|
||||
<VDropdown>
|
||||
<div
|
||||
class="flex cursor-pointer select-none items-center text-sm text-gray-700 hover:text-black"
|
||||
>
|
||||
<span class="mr-0.5">
|
||||
{{ $t("core.post.filters.visible.label") }}
|
||||
</span>
|
||||
<span>
|
||||
<IconArrowDown />
|
||||
</span>
|
||||
</div>
|
||||
<template #popper>
|
||||
<VDropdownItem
|
||||
v-for="(filterItem, index) in VisibleItems"
|
||||
:key="index"
|
||||
:selected="filterItem.value === selectedVisibleItem.value"
|
||||
@click="handleVisibleItemChange(filterItem)"
|
||||
>
|
||||
{{ filterItem.label }}
|
||||
</VDropdownItem>
|
||||
</template>
|
||||
</VDropdown>
|
||||
<CategoryDropdownSelector
|
||||
v-model:selected="selectedCategory"
|
||||
@select="handleCategoryChange"
|
||||
>
|
||||
<div
|
||||
class="flex cursor-pointer select-none items-center text-sm text-gray-700 hover:text-black"
|
||||
>
|
||||
<span class="mr-0.5">
|
||||
{{ $t("core.post.filters.category.label") }}
|
||||
</span>
|
||||
<span>
|
||||
<IconArrowDown />
|
||||
</span>
|
||||
</div>
|
||||
</CategoryDropdownSelector>
|
||||
<TagDropdownSelector
|
||||
v-model:selected="selectedTag"
|
||||
@select="handleTagChange"
|
||||
>
|
||||
<div
|
||||
class="flex cursor-pointer select-none items-center text-sm text-gray-700 hover:text-black"
|
||||
>
|
||||
<span class="mr-0.5">
|
||||
{{ $t("core.post.filters.tag.label") }}
|
||||
</span>
|
||||
<span>
|
||||
<IconArrowDown />
|
||||
</span>
|
||||
</div>
|
||||
</TagDropdownSelector>
|
||||
<UserDropdownSelector
|
||||
v-model:selected="selectedContributor"
|
||||
@select="handleContributorChange"
|
||||
>
|
||||
<div
|
||||
class="flex cursor-pointer select-none items-center text-sm text-gray-700 hover:text-black"
|
||||
>
|
||||
<span class="mr-0.5">
|
||||
{{ $t("core.post.filters.author.label") }}
|
||||
</span>
|
||||
<span>
|
||||
<IconArrowDown />
|
||||
</span>
|
||||
</div>
|
||||
</UserDropdownSelector>
|
||||
<VDropdown>
|
||||
<div
|
||||
class="flex cursor-pointer select-none items-center text-sm text-gray-700 hover:text-black"
|
||||
>
|
||||
<span class="mr-0.5">
|
||||
{{ $t("core.common.filters.labels.sort") }}
|
||||
</span>
|
||||
<span>
|
||||
<IconArrowDown />
|
||||
</span>
|
||||
</div>
|
||||
<template #popper>
|
||||
<VDropdownItem
|
||||
v-for="(sortItem, index) in SortItems"
|
||||
:key="index"
|
||||
:selected="sortItem.sort === selectedSortItem?.sort"
|
||||
@click="handleSortItemChange(sortItem)"
|
||||
>
|
||||
{{ sortItem.label }}
|
||||
</VDropdownItem>
|
||||
</template>
|
||||
</VDropdown>
|
||||
<FilterCleanButton
|
||||
v-if="hasFilters"
|
||||
@click="handleClearFilters"
|
||||
/>
|
||||
<FilterDropdown
|
||||
v-model="selectedPublishStatus"
|
||||
:label="$t('core.common.filters.labels.status')"
|
||||
:items="[
|
||||
{
|
||||
label: t('core.common.filters.item_labels.all'),
|
||||
value: undefined,
|
||||
},
|
||||
{
|
||||
label: t('core.post.filters.status.items.published'),
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
label: t('core.post.filters.status.items.draft'),
|
||||
value: false,
|
||||
},
|
||||
]"
|
||||
/>
|
||||
<FilterDropdown
|
||||
v-model="selectedVisible"
|
||||
:label="$t('core.post.filters.visible.label')"
|
||||
:items="[
|
||||
{
|
||||
label: t('core.common.filters.item_labels.all'),
|
||||
value: undefined,
|
||||
},
|
||||
{
|
||||
label: t('core.post.filters.visible.items.public'),
|
||||
value: 'PUBLIC',
|
||||
},
|
||||
{
|
||||
label: t('core.post.filters.visible.items.private'),
|
||||
value: 'PRIVATE',
|
||||
},
|
||||
]"
|
||||
/>
|
||||
<CategoryFilterDropdown
|
||||
v-model="selectedCategory"
|
||||
:label="$t('core.post.filters.category.label')"
|
||||
/>
|
||||
<TagFilterDropdown
|
||||
v-model="selectedTag"
|
||||
:label="$t('core.post.filters.tag.label')"
|
||||
/>
|
||||
<UserFilterDropdown
|
||||
v-model="selectedContributor"
|
||||
:label="$t('core.post.filters.author.label')"
|
||||
/>
|
||||
<FilterDropdown
|
||||
v-model="selectedSort"
|
||||
:label="$t('core.common.filters.labels.sort')"
|
||||
:items="[
|
||||
{
|
||||
label: t('core.common.filters.item_labels.default'),
|
||||
},
|
||||
{
|
||||
label: t(
|
||||
'core.post.filters.sort.items.publish_time_desc'
|
||||
),
|
||||
value: 'publishTime,desc',
|
||||
},
|
||||
{
|
||||
label: t('core.post.filters.sort.items.publish_time_asc'),
|
||||
value: 'publishTime,asc',
|
||||
},
|
||||
{
|
||||
label: t('core.post.filters.sort.items.create_time_desc'),
|
||||
value: 'creationTimestamp,desc',
|
||||
},
|
||||
{
|
||||
label: t('core.post.filters.sort.items.create_time_asc'),
|
||||
value: 'creationTimestamp,asc',
|
||||
},
|
||||
]"
|
||||
/>
|
||||
<div class="flex flex-row gap-2">
|
||||
<div
|
||||
class="group cursor-pointer rounded p-1 hover:bg-gray-200"
|
||||
|
|
Loading…
Reference in New Issue