mirror of
https://github.com/AppFlowy-IO/AppFlowy-Web.git
synced 2026-03-13 10:00:26 +08:00
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:
@@ -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 {
|
||||
|
||||
@@ -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}
|
||||
|
||||
33
src/components/app/publish-manage/PublishPagesSkeleton.tsx
Normal file
33
src/components/app/publish-manage/PublishPagesSkeleton.tsx
Normal 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;
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user