2022-11-02 09:48:23 +00:00
|
|
|
<script lang="ts" setup>
|
2024-06-27 10:10:55 +00:00
|
|
|
import PostContributorList from "@/components/user/PostContributorList.vue";
|
2024-06-26 10:42:50 +00:00
|
|
|
import { formatDatetime } from "@/utils/date";
|
|
|
|
import { usePermission } from "@/utils/permission";
|
|
|
|
import type { ListedPost, Post } from "@halo-dev/api-client";
|
|
|
|
import { consoleApiClient, coreApiClient } from "@halo-dev/api-client";
|
2022-11-02 09:48:23 +00:00
|
|
|
import {
|
2024-06-26 10:42:50 +00:00
|
|
|
Dialog,
|
2022-11-02 09:48:23 +00:00
|
|
|
IconAddCircle,
|
|
|
|
IconDeleteBin,
|
|
|
|
IconRefreshLine,
|
2024-06-26 10:42:50 +00:00
|
|
|
Toast,
|
2022-11-02 09:48:23 +00:00
|
|
|
VButton,
|
|
|
|
VCard,
|
2024-06-26 10:42:50 +00:00
|
|
|
VDropdownItem,
|
2022-11-02 09:48:23 +00:00
|
|
|
VEmpty,
|
2024-06-26 10:42:50 +00:00
|
|
|
VEntity,
|
|
|
|
VEntityField,
|
|
|
|
VLoading,
|
2022-11-02 09:48:23 +00:00
|
|
|
VPageHeader,
|
|
|
|
VPagination,
|
|
|
|
VSpace,
|
|
|
|
VStatusDot,
|
|
|
|
} from "@halo-dev/components";
|
2023-02-24 04:10:13 +00:00
|
|
|
import { useQuery } from "@tanstack/vue-query";
|
2024-06-26 10:42:50 +00:00
|
|
|
import { cloneDeep } from "lodash-es";
|
|
|
|
import { ref, watch } from "vue";
|
2023-03-23 08:54:33 +00:00
|
|
|
import { useI18n } from "vue-i18n";
|
2024-06-26 10:42:50 +00:00
|
|
|
import PostTag from "./tags/components/PostTag.vue";
|
2022-11-02 09:48:23 +00:00
|
|
|
|
|
|
|
const { currentUserHasPermission } = usePermission();
|
2023-03-23 08:54:33 +00:00
|
|
|
const { t } = useI18n();
|
2022-11-02 09:48:23 +00:00
|
|
|
|
|
|
|
const checkedAll = ref(false);
|
|
|
|
const selectedPostNames = ref<string[]>([]);
|
|
|
|
const keyword = ref("");
|
|
|
|
|
2023-02-24 04:10:13 +00:00
|
|
|
const page = ref(1);
|
|
|
|
const size = ref(20);
|
|
|
|
const total = ref(0);
|
|
|
|
|
|
|
|
const {
|
|
|
|
data: posts,
|
|
|
|
isLoading,
|
|
|
|
isFetching,
|
|
|
|
refetch,
|
|
|
|
} = useQuery<ListedPost[]>({
|
|
|
|
queryKey: ["deleted-posts", page, size, keyword],
|
|
|
|
queryFn: async () => {
|
2024-06-25 04:31:44 +00:00
|
|
|
const { data } = await consoleApiClient.content.post.listPosts({
|
2022-11-02 09:48:23 +00:00
|
|
|
labelSelector: [`content.halo.run/deleted=true`],
|
2023-02-24 04:10:13 +00:00
|
|
|
page: page.value,
|
|
|
|
size: size.value,
|
2022-11-02 09:48:23 +00:00
|
|
|
keyword: keyword.value,
|
|
|
|
});
|
|
|
|
|
2023-02-24 04:10:13 +00:00
|
|
|
total.value = data.total;
|
|
|
|
|
|
|
|
return data.items;
|
|
|
|
},
|
|
|
|
refetchInterval: (data) => {
|
2024-01-25 04:17:12 +00:00
|
|
|
const deletingPosts = data?.some(
|
2022-11-02 09:48:23 +00:00
|
|
|
(post) =>
|
|
|
|
!!post.post.metadata.deletionTimestamp || !post.post.spec.deleted
|
|
|
|
);
|
2024-01-25 04:17:12 +00:00
|
|
|
return deletingPosts ? 1000 : false;
|
2023-02-24 04:10:13 +00:00
|
|
|
},
|
2022-11-02 09:48:23 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
const checkSelection = (post: Post) => {
|
|
|
|
return selectedPostNames.value.includes(post.metadata.name);
|
|
|
|
};
|
|
|
|
|
|
|
|
const handleCheckAllChange = (e: Event) => {
|
|
|
|
const { checked } = e.target as HTMLInputElement;
|
|
|
|
|
|
|
|
if (checked) {
|
|
|
|
selectedPostNames.value =
|
2023-02-24 04:10:13 +00:00
|
|
|
posts.value?.map((post) => {
|
2022-11-02 09:48:23 +00:00
|
|
|
return post.post.metadata.name;
|
|
|
|
}) || [];
|
|
|
|
} else {
|
|
|
|
selectedPostNames.value = [];
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const handleDeletePermanently = async (post: Post) => {
|
|
|
|
Dialog.warning({
|
2023-03-23 08:54:33 +00:00
|
|
|
title: t("core.deleted_post.operations.delete.title"),
|
|
|
|
description: t("core.deleted_post.operations.delete.description"),
|
2022-11-02 09:48:23 +00:00
|
|
|
confirmType: "danger",
|
2023-03-23 08:54:33 +00:00
|
|
|
confirmText: t("core.common.buttons.confirm"),
|
|
|
|
cancelText: t("core.common.buttons.cancel"),
|
2022-11-02 09:48:23 +00:00
|
|
|
onConfirm: async () => {
|
2024-06-25 04:31:44 +00:00
|
|
|
await coreApiClient.content.post.deletePost({
|
2022-11-02 09:48:23 +00:00
|
|
|
name: post.metadata.name,
|
|
|
|
});
|
2023-02-24 04:10:13 +00:00
|
|
|
await refetch();
|
2022-12-20 11:04:29 +00:00
|
|
|
|
2023-03-23 08:54:33 +00:00
|
|
|
Toast.success(t("core.common.toast.delete_success"));
|
2022-11-02 09:48:23 +00:00
|
|
|
},
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
const handleDeletePermanentlyInBatch = async () => {
|
|
|
|
Dialog.warning({
|
2023-03-23 08:54:33 +00:00
|
|
|
title: t("core.deleted_post.operations.delete_in_batch.title"),
|
|
|
|
description: t("core.deleted_post.operations.delete_in_batch.description"),
|
2022-11-02 09:48:23 +00:00
|
|
|
confirmType: "danger",
|
2023-03-23 08:54:33 +00:00
|
|
|
confirmText: t("core.common.buttons.confirm"),
|
|
|
|
cancelText: t("core.common.buttons.cancel"),
|
2022-11-02 09:48:23 +00:00
|
|
|
onConfirm: async () => {
|
|
|
|
await Promise.all(
|
|
|
|
selectedPostNames.value.map((name) => {
|
2024-06-25 04:31:44 +00:00
|
|
|
return coreApiClient.content.post.deletePost({
|
2022-11-02 09:48:23 +00:00
|
|
|
name,
|
|
|
|
});
|
|
|
|
})
|
|
|
|
);
|
2023-02-24 04:10:13 +00:00
|
|
|
await refetch();
|
2022-11-02 09:48:23 +00:00
|
|
|
selectedPostNames.value = [];
|
2022-12-20 11:04:29 +00:00
|
|
|
|
2023-03-23 08:54:33 +00:00
|
|
|
Toast.success(t("core.common.toast.delete_success"));
|
2022-11-02 09:48:23 +00:00
|
|
|
},
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
const handleRecovery = async (post: Post) => {
|
|
|
|
Dialog.warning({
|
2023-03-23 08:54:33 +00:00
|
|
|
title: t("core.deleted_post.operations.recovery.title"),
|
|
|
|
description: t("core.deleted_post.operations.recovery.description"),
|
|
|
|
confirmText: t("core.common.buttons.confirm"),
|
|
|
|
cancelText: t("core.common.buttons.cancel"),
|
2022-11-02 09:48:23 +00:00
|
|
|
onConfirm: async () => {
|
|
|
|
const postToUpdate = cloneDeep(post);
|
|
|
|
postToUpdate.spec.deleted = false;
|
2024-06-25 04:31:44 +00:00
|
|
|
await coreApiClient.content.post.updatePost({
|
2022-11-02 09:48:23 +00:00
|
|
|
name: postToUpdate.metadata.name,
|
|
|
|
post: postToUpdate,
|
|
|
|
});
|
2023-02-24 04:10:13 +00:00
|
|
|
|
|
|
|
await refetch();
|
2022-12-20 11:04:29 +00:00
|
|
|
|
2023-03-23 08:54:33 +00:00
|
|
|
Toast.success(t("core.common.toast.recovery_success"));
|
2022-11-02 09:48:23 +00:00
|
|
|
},
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
const handleRecoveryInBatch = async () => {
|
|
|
|
Dialog.warning({
|
2023-03-23 08:54:33 +00:00
|
|
|
title: t("core.deleted_post.operations.recovery_in_batch.title"),
|
|
|
|
description: t(
|
|
|
|
"core.deleted_post.operations.recovery_in_batch.description"
|
|
|
|
),
|
|
|
|
confirmText: t("core.common.buttons.confirm"),
|
|
|
|
cancelText: t("core.common.buttons.cancel"),
|
2022-11-02 09:48:23 +00:00
|
|
|
onConfirm: async () => {
|
|
|
|
await Promise.all(
|
|
|
|
selectedPostNames.value.map((name) => {
|
2023-02-24 04:10:13 +00:00
|
|
|
const post = posts.value?.find(
|
2022-11-02 09:48:23 +00:00
|
|
|
(item) => item.post.metadata.name === name
|
|
|
|
)?.post;
|
|
|
|
|
|
|
|
if (!post) {
|
|
|
|
return Promise.resolve();
|
|
|
|
}
|
|
|
|
|
2024-06-25 04:31:44 +00:00
|
|
|
return coreApiClient.content.post.updatePost({
|
2022-11-02 09:48:23 +00:00
|
|
|
name: post.metadata.name,
|
2023-02-24 04:10:13 +00:00
|
|
|
post: {
|
|
|
|
...post,
|
|
|
|
spec: {
|
|
|
|
...post.spec,
|
|
|
|
deleted: false,
|
|
|
|
},
|
|
|
|
},
|
2022-11-02 09:48:23 +00:00
|
|
|
});
|
|
|
|
})
|
|
|
|
);
|
2023-02-24 04:10:13 +00:00
|
|
|
await refetch();
|
2022-11-02 09:48:23 +00:00
|
|
|
selectedPostNames.value = [];
|
2022-12-20 11:04:29 +00:00
|
|
|
|
2023-03-23 08:54:33 +00:00
|
|
|
Toast.success(t("core.common.toast.recovery_success"));
|
2022-11-02 09:48:23 +00:00
|
|
|
},
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
watch(selectedPostNames, (newValue) => {
|
2023-02-24 04:10:13 +00:00
|
|
|
checkedAll.value = newValue.length === posts.value?.length;
|
2022-11-02 09:48:23 +00:00
|
|
|
});
|
2022-11-23 04:59:28 +00:00
|
|
|
|
2023-07-11 07:15:10 +00:00
|
|
|
watch(
|
|
|
|
() => keyword.value,
|
|
|
|
() => {
|
|
|
|
page.value = 1;
|
2022-11-23 04:59:28 +00:00
|
|
|
}
|
2023-07-11 07:15:10 +00:00
|
|
|
);
|
2022-11-02 09:48:23 +00:00
|
|
|
</script>
|
|
|
|
<template>
|
2023-03-23 08:54:33 +00:00
|
|
|
<VPageHeader :title="$t('core.deleted_post.title')">
|
2022-11-02 09:48:23 +00:00
|
|
|
<template #icon>
|
|
|
|
<IconDeleteBin class="mr-2 self-center text-green-600" />
|
|
|
|
</template>
|
|
|
|
<template #actions>
|
|
|
|
<VSpace>
|
2023-03-23 08:54:33 +00:00
|
|
|
<VButton :route="{ name: 'Posts' }" size="sm">
|
|
|
|
{{ $t("core.common.buttons.back") }}
|
|
|
|
</VButton>
|
2022-11-02 09:48:23 +00:00
|
|
|
<VButton
|
|
|
|
v-permission="['system:posts:manage']"
|
|
|
|
:route="{ name: 'PostEditor' }"
|
|
|
|
type="secondary"
|
|
|
|
>
|
|
|
|
<template #icon>
|
|
|
|
<IconAddCircle class="h-full w-full" />
|
|
|
|
</template>
|
2023-03-23 08:54:33 +00:00
|
|
|
{{ $t("core.common.buttons.new") }}
|
2022-11-02 09:48:23 +00:00
|
|
|
</VButton>
|
|
|
|
</VSpace>
|
|
|
|
</template>
|
|
|
|
</VPageHeader>
|
|
|
|
|
|
|
|
<div class="m-0 md:m-4">
|
|
|
|
<VCard :body-class="['!p-0']">
|
|
|
|
<template #header>
|
|
|
|
<div class="block w-full bg-gray-50 px-4 py-3">
|
|
|
|
<div
|
2023-09-14 16:14:14 +00:00
|
|
|
class="relative flex flex-col flex-wrap items-start gap-4 sm:flex-row sm:items-center"
|
2022-11-02 09:48:23 +00:00
|
|
|
>
|
|
|
|
<div
|
|
|
|
v-permission="['system:posts:manage']"
|
2023-09-14 16:14:14 +00:00
|
|
|
class="hidden items-center sm:flex"
|
2022-11-02 09:48:23 +00:00
|
|
|
>
|
|
|
|
<input
|
|
|
|
v-model="checkedAll"
|
|
|
|
type="checkbox"
|
|
|
|
@change="handleCheckAllChange"
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
<div class="flex w-full flex-1 items-center sm:w-auto">
|
2023-07-11 07:15:10 +00:00
|
|
|
<SearchInput v-if="!selectedPostNames.length" v-model="keyword" />
|
2022-11-02 09:48:23 +00:00
|
|
|
<VSpace v-else>
|
|
|
|
<VButton type="danger" @click="handleDeletePermanentlyInBatch">
|
2023-03-23 08:54:33 +00:00
|
|
|
{{ $t("core.common.buttons.delete_permanently") }}
|
2022-11-02 09:48:23 +00:00
|
|
|
</VButton>
|
|
|
|
<VButton type="default" @click="handleRecoveryInBatch">
|
2023-03-23 08:54:33 +00:00
|
|
|
{{ $t("core.common.buttons.recovery") }}
|
2022-11-02 09:48:23 +00:00
|
|
|
</VButton>
|
|
|
|
</VSpace>
|
|
|
|
</div>
|
2023-09-14 16:14:14 +00:00
|
|
|
<VSpace spacing="lg" class="flex-wrap">
|
|
|
|
<div class="flex flex-row gap-2">
|
|
|
|
<div
|
|
|
|
class="group cursor-pointer rounded p-1 hover:bg-gray-200"
|
|
|
|
@click="refetch()"
|
|
|
|
>
|
|
|
|
<IconRefreshLine
|
|
|
|
:class="{ 'animate-spin text-gray-900': isFetching }"
|
|
|
|
class="h-4 w-4 text-gray-600 group-hover:text-gray-900"
|
|
|
|
/>
|
2022-11-02 09:48:23 +00:00
|
|
|
</div>
|
2023-09-14 16:14:14 +00:00
|
|
|
</div>
|
|
|
|
</VSpace>
|
2022-11-02 09:48:23 +00:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
|
2023-02-24 04:10:13 +00:00
|
|
|
<VLoading v-if="isLoading" />
|
2022-11-02 09:48:23 +00:00
|
|
|
|
2023-02-24 04:10:13 +00:00
|
|
|
<Transition v-else-if="!posts?.length" appear name="fade">
|
2022-11-24 03:46:10 +00:00
|
|
|
<VEmpty
|
2023-03-23 08:54:33 +00:00
|
|
|
:message="$t('core.deleted_post.empty.message')"
|
|
|
|
:title="$t('core.deleted_post.empty.title')"
|
2022-11-24 03:46:10 +00:00
|
|
|
>
|
|
|
|
<template #actions>
|
|
|
|
<VSpace>
|
2023-03-23 08:54:33 +00:00
|
|
|
<VButton @click="refetch">
|
|
|
|
{{ $t("core.common.buttons.refresh") }}
|
|
|
|
</VButton>
|
2022-11-24 03:46:10 +00:00
|
|
|
<VButton :route="{ name: 'Posts' }" type="primary">
|
2023-03-23 08:54:33 +00:00
|
|
|
{{ $t("core.common.buttons.back") }}
|
2022-11-24 03:46:10 +00:00
|
|
|
</VButton>
|
|
|
|
</VSpace>
|
|
|
|
</template>
|
|
|
|
</VEmpty>
|
|
|
|
</Transition>
|
|
|
|
|
|
|
|
<Transition v-else appear name="fade">
|
|
|
|
<ul
|
|
|
|
class="box-border h-full w-full divide-y divide-gray-100"
|
|
|
|
role="list"
|
|
|
|
>
|
2023-02-24 04:10:13 +00:00
|
|
|
<li v-for="(post, index) in posts" :key="index">
|
2022-11-24 03:46:10 +00:00
|
|
|
<VEntity :is-selected="checkSelection(post.post)">
|
|
|
|
<template
|
|
|
|
v-if="currentUserHasPermission(['system:posts:manage'])"
|
|
|
|
#checkbox
|
|
|
|
>
|
|
|
|
<input
|
|
|
|
v-model="selectedPostNames"
|
|
|
|
:value="post.post.metadata.name"
|
|
|
|
name="post-checkbox"
|
|
|
|
type="checkbox"
|
|
|
|
/>
|
|
|
|
</template>
|
|
|
|
<template #start>
|
2022-12-22 08:42:30 +00:00
|
|
|
<VEntityField :title="post.post.spec.title" width="27rem">
|
2022-11-24 03:46:10 +00:00
|
|
|
<template #description>
|
2023-02-21 03:46:10 +00:00
|
|
|
<div class="flex flex-col gap-1.5">
|
|
|
|
<VSpace class="flex-wrap !gap-y-1">
|
|
|
|
<p
|
|
|
|
v-if="post.categories.length"
|
|
|
|
class="inline-flex flex-wrap gap-1 text-xs text-gray-500"
|
2022-11-24 03:46:10 +00:00
|
|
|
>
|
2023-03-23 08:54:33 +00:00
|
|
|
{{ $t("core.post.list.fields.categories") }}
|
|
|
|
<span
|
2023-02-21 03:46:10 +00:00
|
|
|
v-for="(category, categoryIndex) in post.categories"
|
|
|
|
:key="categoryIndex"
|
|
|
|
class="cursor-pointer hover:text-gray-900"
|
|
|
|
>
|
|
|
|
{{ category.spec.displayName }}
|
|
|
|
</span>
|
|
|
|
</p>
|
|
|
|
<span class="text-xs text-gray-500">
|
2023-03-23 08:54:33 +00:00
|
|
|
{{
|
|
|
|
$t("core.post.list.fields.visits", {
|
|
|
|
visits: post.stats.visit,
|
|
|
|
})
|
|
|
|
}}
|
2023-02-21 03:46:10 +00:00
|
|
|
</span>
|
|
|
|
<span class="text-xs text-gray-500">
|
2023-03-23 08:54:33 +00:00
|
|
|
{{
|
|
|
|
$t("core.post.list.fields.comments", {
|
|
|
|
comments: post.stats.totalComment || 0,
|
|
|
|
})
|
|
|
|
}}
|
2022-11-24 03:46:10 +00:00
|
|
|
</span>
|
2023-02-21 03:46:10 +00:00
|
|
|
</VSpace>
|
|
|
|
<VSpace v-if="post.tags.length" class="flex-wrap">
|
|
|
|
<PostTag
|
|
|
|
v-for="(tag, tagIndex) in post.tags"
|
|
|
|
:key="tagIndex"
|
|
|
|
:tag="tag"
|
|
|
|
route
|
|
|
|
></PostTag>
|
|
|
|
</VSpace>
|
|
|
|
</div>
|
2022-11-24 03:46:10 +00:00
|
|
|
</template>
|
|
|
|
</VEntityField>
|
|
|
|
</template>
|
|
|
|
<template #end>
|
|
|
|
<VEntityField>
|
|
|
|
<template #description>
|
2024-06-27 10:10:55 +00:00
|
|
|
<PostContributorList
|
|
|
|
:owner="post.owner"
|
|
|
|
:contributors="post.contributors"
|
|
|
|
/>
|
2022-11-24 03:46:10 +00:00
|
|
|
</template>
|
|
|
|
</VEntityField>
|
|
|
|
<VEntityField v-if="!post?.post?.spec.deleted">
|
|
|
|
<template #description>
|
2023-03-23 08:54:33 +00:00
|
|
|
<VStatusDot
|
|
|
|
v-tooltip="$t('core.common.tooltips.recovering')"
|
|
|
|
state="success"
|
|
|
|
animate
|
|
|
|
/>
|
2022-11-24 03:46:10 +00:00
|
|
|
</template>
|
|
|
|
</VEntityField>
|
|
|
|
<VEntityField v-if="post?.post?.metadata.deletionTimestamp">
|
|
|
|
<template #description>
|
2023-03-23 08:54:33 +00:00
|
|
|
<VStatusDot
|
|
|
|
v-tooltip="$t('core.common.status.deleting')"
|
|
|
|
state="warning"
|
|
|
|
animate
|
|
|
|
/>
|
2022-11-24 03:46:10 +00:00
|
|
|
</template>
|
|
|
|
</VEntityField>
|
|
|
|
<VEntityField>
|
|
|
|
<template #description>
|
|
|
|
<span class="truncate text-xs tabular-nums text-gray-500">
|
|
|
|
{{ formatDatetime(post.post.spec.publishTime) }}
|
2022-11-02 09:48:23 +00:00
|
|
|
</span>
|
2022-11-24 03:46:10 +00:00
|
|
|
</template>
|
|
|
|
</VEntityField>
|
|
|
|
</template>
|
|
|
|
<template
|
|
|
|
v-if="currentUserHasPermission(['system:posts:manage'])"
|
|
|
|
#dropdownItems
|
2022-11-02 09:48:23 +00:00
|
|
|
>
|
2023-03-27 08:06:13 +00:00
|
|
|
<VDropdownItem
|
2022-11-24 03:46:10 +00:00
|
|
|
type="danger"
|
|
|
|
@click="handleDeletePermanently(post.post)"
|
|
|
|
>
|
2023-03-23 08:54:33 +00:00
|
|
|
{{ $t("core.common.buttons.delete_permanently") }}
|
2023-03-27 08:06:13 +00:00
|
|
|
</VDropdownItem>
|
|
|
|
<VDropdownItem @click="handleRecovery(post.post)">
|
2023-03-23 08:54:33 +00:00
|
|
|
{{ $t("core.common.buttons.recovery") }}
|
2023-03-27 08:06:13 +00:00
|
|
|
</VDropdownItem>
|
2022-11-24 03:46:10 +00:00
|
|
|
</template>
|
|
|
|
</VEntity>
|
|
|
|
</li>
|
|
|
|
</ul>
|
|
|
|
</Transition>
|
2022-11-02 09:48:23 +00:00
|
|
|
|
|
|
|
<template #footer>
|
2023-07-28 03:15:08 +00:00
|
|
|
<VPagination
|
|
|
|
v-model:page="page"
|
|
|
|
v-model:size="size"
|
|
|
|
:page-label="$t('core.components.pagination.page_label')"
|
|
|
|
:size-label="$t('core.components.pagination.size_label')"
|
|
|
|
:total-label="
|
|
|
|
$t('core.components.pagination.total_label', { total: total })
|
|
|
|
"
|
|
|
|
:total="total"
|
|
|
|
:size-options="[20, 30, 50, 100]"
|
|
|
|
/>
|
2022-11-02 09:48:23 +00:00
|
|
|
</template>
|
|
|
|
</VCard>
|
|
|
|
</div>
|
|
|
|
</template>
|