fix: support publish pages sorted by publish time (#14)

* fix: support publish pages sorted by publish time

* fix: add publish pages skeleton
This commit is contained in:
Kilu.He
2025-01-13 14:57:37 +08:00
committed by GitHub
parent 50d33e7de4
commit 7ff3344fc9
5 changed files with 96 additions and 65 deletions

View File

@@ -846,6 +846,9 @@ export interface View {
last_viewed_at?: string;
created_at?: string;
database_relations?: DatabaseRelations;
publisher_email?: string;
publish_name?: string;
publish_timestamp?: string;
}
export interface Invitation {

View File

@@ -4,10 +4,11 @@ import { flattenViews } from '@/components/_shared/outline/utils';
import { useAppHandlers, useUserWorkspaceInfo } from '@/components/app/app.hooks';
import HomePageSetting from '@/components/app/publish-manage/HomePageSetting';
import PublishedPages from '@/components/app/publish-manage/PublishedPages';
import PublishPagesSkeleton from '@/components/app/publish-manage/PublishPagesSkeleton';
import UpdateNamespace from '@/components/app/publish-manage/UpdateNamespace';
import { useCurrentUser, useService } from '@/components/main/app.hooks';
import { openUrl } from '@/utils/url';
import { Button, Divider, IconButton, Tooltip } from '@mui/material';
import { Button, CircularProgress, Divider, IconButton, Tooltip } from '@mui/material';
import React, { useCallback, useEffect, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { ReactComponent as EditIcon } from '@/assets/edit.svg';
@@ -64,8 +65,14 @@ export function PublishManage ({
setLoading(true);
try {
const outline = await service.getPublishOutline(namespace);
setPublishViews(flattenViews(outline).filter(item => item.is_published));
setPublishViews(flattenViews(outline).filter(item => item.is_published).sort((a, b) => {
if (!a.publish_timestamp || !b.publish_timestamp) {
return 0;
}
return new Date(b.publish_timestamp).getTime() - new Date(a.publish_timestamp).getTime();
}));
// eslint-disable-next-line
} catch (e: any) {
console.error(e);
@@ -261,7 +268,10 @@ export function PublishManage ({
</div>
</div>
<div className={'text-base mt-4 px-1 font-medium'}>{t('settings.sites.publishedPage.title')}</div>
<div className={'text-base flex items-center gap-2 mt-4 px-1 font-medium'}>
{t('settings.sites.publishedPage.title')}
{loading && <CircularProgress size={14} />}
</div>
<div className={'text-text-caption px-1 text-xs'}>{t('settings.sites.publishedPage.description')}</div>
<Divider className={'mb-2'} />
<div className={'w-full px-1 flex text-sm font-medium items-center gap-4'}>
@@ -273,13 +283,14 @@ export function PublishManage ({
</div>
<Divider className={'mb-1'} />
<PublishedPages
loading={loading}
publishViews={publishViews}
onPublish={handlePublish}
onClose={onClose}
onUnPublish={handleUnpublish}
/>
{loading && !publishViews.length ? <PublishPagesSkeleton /> :
<PublishedPages
publishViews={publishViews}
onPublish={handlePublish}
onClose={onClose}
onUnPublish={handleUnpublish}
namespace={namespace}
/>}
{updateOpen && <UpdateNamespace
namespace={namespace}

View File

@@ -0,0 +1,33 @@
import { Skeleton } from '@mui/material';
import React from 'react';
function PublishPagesSkeleton () {
return (
<>
{
Array.from({ length: 5 }).map((_, index) => (
<div
key={index}
className={'w-full px-1 flex text-sm font-medium items-center gap-4'}
>
{
Array.from({ length: 3 }).map((_, i) => (
<div
key={i}
className={'flex-1'}
>
<Skeleton
height={24}
variant={'text'}
width={100}
/>
</div>
))
}
</div>))
}
</>
);
}
export default PublishPagesSkeleton;

View File

@@ -4,12 +4,12 @@ import { Popover } from '@/components/_shared/popover';
import PageIcon from '@/components/_shared/view-icon/PageIcon';
import { useAppHandlers, useUserWorkspaceInfo } from '@/components/app/app.hooks';
import { PublishNameSetting } from '@/components/app/publish-manage/PublishNameSetting';
import { useCurrentUser, useService } from '@/components/main/app.hooks';
import { useCurrentUser } from '@/components/main/app.hooks';
import { copyTextToClipboard } from '@/utils/copy';
import { openUrl } from '@/utils/url';
import { Button, CircularProgress, IconButton, Tooltip } from '@mui/material';
import dayjs from 'dayjs';
import React, { useCallback, useEffect, useMemo } from 'react';
import React, { useEffect, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { ReactComponent as MoreIcon } from '@/assets/more.svg';
import { ReactComponent as GlobalIcon } from '@/assets/publish.svg';
@@ -17,49 +17,31 @@ import { ReactComponent as CopyIcon } from '@/assets/copy.svg';
import { ReactComponent as TrashIcon } from '@/assets/trash.svg';
import { ReactComponent as SettingIcon } from '@/assets/settings.svg';
function PublishedPageItem ({ onClose, view, onUnPublish, onPublish }: {
function PublishedPageItem ({ namespace, onClose, view, onUnPublish, onPublish }: {
view: View,
onClose?: () => void;
onUnPublish: (viewId: string) => Promise<void>;
onPublish: (view: View, publishName: string) => Promise<void>
namespace: string;
}) {
const { t } = useTranslation();
const [openSetting, setOpenSetting] = React.useState<boolean>(false);
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
const [publishInfo, setPublishInfo] = React.useState<{
namespace: string,
publishName: string,
publisherEmail: string
publishedAt: string
} | undefined>(undefined);
const [publishName, setPublishName] = React.useState<string>(view.publish_name || '');
const toView = useAppHandlers().toView;
const service = useService();
const [unPublishLoading, setUnPublishLoading] = React.useState<boolean>(false);
const userWorkspaceInfo = useUserWorkspaceInfo();
const currentUser = useCurrentUser();
const isOwner = userWorkspaceInfo?.selectedWorkspace?.owner?.uid.toString() === currentUser?.uid.toString();
const isPublisher = publishInfo?.publisherEmail === currentUser?.email;
const getPublishInfo = useCallback(async (viewId: string) => {
if (!service) return;
try {
const res = await service?.getPublishInfo(viewId);
setPublishInfo(res);
return res;
} catch (e) {
console.error(e);
}
}, [service]);
const url = useMemo(() => {
return `${window.origin}/${publishInfo?.namespace}/${publishInfo?.publishName}`;
}, [publishInfo]);
const isPublisher = view?.publisher_email === currentUser?.email;
useEffect(() => {
void getPublishInfo(view.view_id);
}, [getPublishInfo, view.view_id]);
setPublishName(view.publish_name || '');
}, [view.publish_name]);
const url = useMemo(() => {
return `${window.origin}/${namespace}/${publishName}`;
}, [namespace, publishName]);
const actions = useMemo(() => {
return [
@@ -156,7 +138,7 @@ function PublishedPageItem ({ onClose, view, onUnPublish, onPublish }: {
<Tooltip
disableInteractive={true}
title={<div className={'whitespace-pre-wrap break-words'}>
{`Open Page in New Tab \n${publishInfo?.publishName || ''}`}
{`Open Page in New Tab \n${publishName || ''}`}
</div>}
>
<Button
@@ -169,13 +151,13 @@ function PublishedPageItem ({ onClose, view, onUnPublish, onPublish }: {
className={'w-full p-1 px-2 justify-start overflow-hidden'}
>
<span className={'truncate'}>
{publishInfo?.publishName}
{publishName}
</span>
</Button>
</Tooltip>
</div>
<div className={'flex-1 overflow-hidden flex gap-2 justify-between'}>
{dayjs(publishInfo?.publishedAt).format('MMM D, YYYY')}
{view?.publish_timestamp ? dayjs(view.publish_timestamp).format('MMM D, YYYY') : ''}
<IconButton
onClick={(e) => {
setAnchorEl(e.currentTarget);
@@ -204,27 +186,30 @@ function PublishedPageItem ({ onClose, view, onUnPublish, onPublish }: {
onClick={action.onClick}
size={'small'}
className={'justify-start'}
startIcon={<action.IconComponent className={'w-4 h-4'} />}
startIcon={<action.IconComponent
size={14}
className={'w-4 h-4'}
/>}
color={'inherit'}
>{action.label}</Button>
</Tooltip>;
})}
</div>
</Popover>
{openSetting && publishInfo && <PublishNameSetting
{openSetting && <PublishNameSetting
onUnPublish={() => {
return onUnPublish(view.view_id);
}}
onPublish={async (publishName: string) => {
await onPublish(view, publishName);
void getPublishInfo(view.view_id);
setPublishName(publishName);
}}
onClose={() => {
setOpenSetting(false);
}}
url={url}
open={openSetting}
defaultName={publishInfo.publishName}
defaultName={publishName}
/>}
</div>
);

View File

@@ -1,7 +1,7 @@
import { View } from '@/application/types';
import PublishedPageItem from '@/components/app/publish-manage/PublishedPageItem';
import { CircularProgress, Divider } from '@mui/material';
import { Divider } from '@mui/material';
import React from 'react';
@@ -9,33 +9,32 @@ function PublishedPages ({
publishViews,
onUnPublish,
onPublish,
loading,
onClose,
namespace,
}: {
publishViews: View[];
loading: boolean;
onUnPublish: (viewId: string) => Promise<void>;
onPublish: (view: View, publishName: string) => Promise<void>;
onClose?: () => void
onClose?: () => void;
namespace: string;
}) {
return (
<div className={'flex flex-col gap-2'}>
{loading ? <div className={'flex justify-center w-full items-center'}>
<CircularProgress size={20} /></div>
: publishViews.map((view, index) => {
return <React.StrictMode key={view.view_id}>
<PublishedPageItem
onClose={onClose}
view={view}
onUnPublish={onUnPublish}
onPublish={onPublish}
/>
{index !== publishViews.length - 1 && <Divider />}
{publishViews.map((view, index) => {
return <React.StrictMode key={view.view_id}>
<PublishedPageItem
namespace={namespace}
onClose={onClose}
view={view}
onUnPublish={onUnPublish}
onPublish={onPublish}
/>
{index !== publishViews.length - 1 && <Divider />}
</React.StrictMode>;
})}
</React.StrictMode>;
})}
</div>
);