mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-14 13:25:12 +00:00
Merge pull request #739 from toeverything/feat/datacenter-published-search
feat: add nav bar to the public page
This commit is contained in:
@@ -8,17 +8,17 @@ import React, {
|
||||
import { SearchIcon } from '@blocksuite/icons';
|
||||
import { StyledInputContent, StyledLabel } from './style';
|
||||
import { Command } from 'cmdk';
|
||||
import { useAppState } from '@/providers/app-state-provider';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
export const Input = (props: {
|
||||
query: string;
|
||||
setQuery: Dispatch<SetStateAction<string>>;
|
||||
setLoading: Dispatch<SetStateAction<boolean>>;
|
||||
isPublic: boolean;
|
||||
publishWorkspaceName: string | undefined;
|
||||
}) => {
|
||||
const [isComposition, setIsComposition] = useState(false);
|
||||
const [inputValue, setInputValue] = useState('');
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const { currentWorkspace } = useAppState();
|
||||
const { t } = useTranslation();
|
||||
useEffect(() => {
|
||||
inputRef.current?.addEventListener(
|
||||
@@ -78,9 +78,9 @@ export const Input = (props: {
|
||||
}
|
||||
}}
|
||||
placeholder={
|
||||
currentWorkspace?.isPublish
|
||||
props.isPublic
|
||||
? t('Quick search placeholder2', {
|
||||
workspace: currentWorkspace?.blocksuiteWorkspace?.meta.name,
|
||||
workspace: props.publishWorkspaceName,
|
||||
})
|
||||
: t('Quick search placeholder')
|
||||
}
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
import { Command } from 'cmdk';
|
||||
import { StyledListItem, StyledNotFound } from './style';
|
||||
import { PaperIcon, EdgelessIcon } from '@blocksuite/icons';
|
||||
import { Dispatch, SetStateAction, useEffect, useState } from 'react';
|
||||
import { useAppState, PageMeta } from '@/providers/app-state-provider';
|
||||
import { useRouter } from 'next/router';
|
||||
import { NoResultSVG } from './NoResultSVG';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import usePageHelper from '@/hooks/use-page-helper';
|
||||
import { Workspace } from '@blocksuite/store';
|
||||
|
||||
export const PublishedResults = (props: {
|
||||
query: string;
|
||||
loading: boolean;
|
||||
setLoading: Dispatch<SetStateAction<boolean>>;
|
||||
setPublishWorkspaceName: Dispatch<SetStateAction<string>>;
|
||||
onClose: () => void;
|
||||
}) => {
|
||||
const [workspace, setWorkspace] = useState<Workspace>();
|
||||
const { query, loading, setLoading, onClose, setPublishWorkspaceName } =
|
||||
props;
|
||||
const { search } = usePageHelper();
|
||||
const [results, setResults] = useState(new Map<string, string | undefined>());
|
||||
const { dataCenter } = useAppState();
|
||||
const router = useRouter();
|
||||
const [pageList, setPageList] = useState<PageMeta[]>([]);
|
||||
useEffect(() => {
|
||||
dataCenter
|
||||
.loadPublicWorkspace(router.query.workspaceId as string)
|
||||
.then(data => {
|
||||
setPageList(data.blocksuiteWorkspace?.meta.pageMetas as PageMeta[]);
|
||||
if (data.blocksuiteWorkspace) {
|
||||
setWorkspace(data.blocksuiteWorkspace);
|
||||
setPublishWorkspaceName(data.blocksuiteWorkspace.meta.name);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
router.push('/404');
|
||||
});
|
||||
}, [router, dataCenter, setPublishWorkspaceName]);
|
||||
const { t } = useTranslation();
|
||||
useEffect(() => {
|
||||
setResults(search(query, workspace));
|
||||
setLoading(false);
|
||||
//Save the Map<BlockId, PageId> obtained from the search as state
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [query, setResults, setLoading]);
|
||||
const pageIds = [...results.values()];
|
||||
const resultsPageMeta = pageList.filter(
|
||||
page => pageIds.indexOf(page.id) > -1 && !page.trash
|
||||
);
|
||||
|
||||
return loading ? null : (
|
||||
<>
|
||||
{query ? (
|
||||
resultsPageMeta.length ? (
|
||||
<Command.Group
|
||||
heading={t('Find results', { number: resultsPageMeta.length })}
|
||||
>
|
||||
{resultsPageMeta.map(result => {
|
||||
return (
|
||||
<Command.Item
|
||||
key={result.id}
|
||||
onSelect={() => {
|
||||
router.push(
|
||||
`/public-workspace/${router.query.workspaceId}/${result.id}`
|
||||
);
|
||||
onClose();
|
||||
}}
|
||||
value={result.id}
|
||||
>
|
||||
<StyledListItem>
|
||||
{result.mode === 'edgeless' ? (
|
||||
<EdgelessIcon />
|
||||
) : (
|
||||
<PaperIcon />
|
||||
)}
|
||||
<span>{result.title}</span>
|
||||
</StyledListItem>
|
||||
</Command.Item>
|
||||
);
|
||||
})}
|
||||
</Command.Group>
|
||||
) : (
|
||||
<StyledNotFound>
|
||||
<span>{t('Find 0 result')}</span>
|
||||
<NoResultSVG />
|
||||
</StyledNotFound>
|
||||
)
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -13,7 +13,8 @@ import { Command } from 'cmdk';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useModal } from '@/providers/GlobalModalProvider';
|
||||
import { getUaHelper } from '@/utils';
|
||||
import { useAppState } from '@/providers/app-state-provider';
|
||||
import { useRouter } from 'next/router';
|
||||
import { PublishedResults } from './PublishedResults';
|
||||
type TransitionsModalProps = {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
@@ -22,10 +23,11 @@ const isMac = () => {
|
||||
return getUaHelper().isMacOs;
|
||||
};
|
||||
export const QuickSearch = ({ open, onClose }: TransitionsModalProps) => {
|
||||
const { currentWorkspace } = useAppState();
|
||||
|
||||
const router = useRouter();
|
||||
const [query, setQuery] = useState('');
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [isPublic, setIsPublic] = useState(false);
|
||||
const [publishWorkspaceName, setPublishWorkspaceName] = useState('');
|
||||
const [showCreatePage, setShowCreatePage] = useState(true);
|
||||
const { triggerQuickSearchModal } = useModal();
|
||||
|
||||
@@ -51,6 +53,14 @@ export const QuickSearch = ({ open, onClose }: TransitionsModalProps) => {
|
||||
document.removeEventListener('keydown', down, { capture: true });
|
||||
}, [open, triggerQuickSearchModal]);
|
||||
|
||||
useEffect(() => {
|
||||
if (router.pathname.startsWith('/public-workspace')) {
|
||||
return setIsPublic(true);
|
||||
} else {
|
||||
return setIsPublic(false);
|
||||
}
|
||||
}, [router]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open={open}
|
||||
@@ -81,28 +91,44 @@ export const QuickSearch = ({ open, onClose }: TransitionsModalProps) => {
|
||||
}}
|
||||
>
|
||||
<StyledModalHeader>
|
||||
<Input query={query} setQuery={setQuery} setLoading={setLoading} />
|
||||
<Input
|
||||
query={query}
|
||||
setQuery={setQuery}
|
||||
setLoading={setLoading}
|
||||
isPublic={isPublic}
|
||||
publishWorkspaceName={publishWorkspaceName}
|
||||
/>
|
||||
<StyledShortcut>{isMac() ? '⌘ + K' : 'Ctrl + K'}</StyledShortcut>
|
||||
</StyledModalHeader>
|
||||
<StyledModalDivider />
|
||||
<Command.List>
|
||||
<StyledContent>
|
||||
<Results
|
||||
query={query}
|
||||
loading={loading}
|
||||
setLoading={setLoading}
|
||||
setShowCreatePage={setShowCreatePage}
|
||||
/>
|
||||
{!isPublic ? (
|
||||
<Results
|
||||
query={query}
|
||||
loading={loading}
|
||||
setLoading={setLoading}
|
||||
setShowCreatePage={setShowCreatePage}
|
||||
/>
|
||||
) : (
|
||||
<PublishedResults
|
||||
query={query}
|
||||
loading={loading}
|
||||
setLoading={setLoading}
|
||||
onClose={onClose}
|
||||
setPublishWorkspaceName={setPublishWorkspaceName}
|
||||
/>
|
||||
)}
|
||||
</StyledContent>
|
||||
{currentWorkspace?.published ? (
|
||||
<></>
|
||||
) : showCreatePage ? (
|
||||
<>
|
||||
<StyledModalDivider />
|
||||
<StyledModalFooter>
|
||||
<Footer query={query} />
|
||||
</StyledModalFooter>
|
||||
</>
|
||||
{!isPublic ? (
|
||||
showCreatePage ? (
|
||||
<>
|
||||
<StyledModalDivider />
|
||||
<StyledModalFooter>
|
||||
<Footer query={query} />
|
||||
</StyledModalFooter>
|
||||
</>
|
||||
) : null
|
||||
) : null}
|
||||
</Command.List>
|
||||
</Command>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { uuidv4 } from '@blocksuite/store';
|
||||
import { uuidv4, Workspace } from '@blocksuite/store';
|
||||
import { QueryContent } from '@blocksuite/store/dist/workspace/search';
|
||||
import { PageMeta, useAppState } from '@/providers/app-state-provider';
|
||||
import { EditorContainer } from '@blocksuite/editor';
|
||||
@@ -20,7 +20,10 @@ export type EditorHandlers = {
|
||||
toggleDeletePage: (pageId: string) => Promise<boolean>;
|
||||
toggleFavoritePage: (pageId: string) => Promise<boolean>;
|
||||
permanentlyDeletePage: (pageId: string) => void;
|
||||
search: (query: QueryContent) => Map<string, string | undefined>;
|
||||
search: (
|
||||
query: QueryContent,
|
||||
workspace?: Workspace
|
||||
) => Map<string, string | undefined>;
|
||||
// changeEditorMode: (pageId: string) => void;
|
||||
changePageMode: (
|
||||
pageId: string,
|
||||
@@ -83,9 +86,16 @@ export const usePageHelper = (): EditorHandlers => {
|
||||
});
|
||||
return trash;
|
||||
},
|
||||
search: (query: QueryContent) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
return currentWorkspace!.blocksuiteWorkspace!.search(query);
|
||||
search: (query: QueryContent, workspace?: Workspace) => {
|
||||
if (workspace) {
|
||||
return workspace.search(query);
|
||||
}
|
||||
if (currentWorkspace) {
|
||||
if (currentWorkspace.blocksuiteWorkspace) {
|
||||
return currentWorkspace.blocksuiteWorkspace.search(query);
|
||||
}
|
||||
}
|
||||
return new Map();
|
||||
},
|
||||
changePageMode: async (pageId, mode) => {
|
||||
const pageMeta = getPageMeta(currentWorkspace, pageId);
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
import { ReactElement, useEffect, useState } from 'react';
|
||||
import { useAppState } from '@/providers/app-state-provider';
|
||||
import type { NextPageWithLayout } from '../..//_app';
|
||||
import { styled } from '@/styles';
|
||||
import { displayFlex, styled } from '@/styles';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { useRouter } from 'next/router';
|
||||
import { Page as PageStore, Workspace } from '@blocksuite/store';
|
||||
import { PageLoading } from '@/components/loading';
|
||||
import { Breadcrumbs } from '@/ui/breadcrumbs';
|
||||
import { IconButton } from '@/ui/button';
|
||||
import NextLink from 'next/link';
|
||||
import { PaperIcon, SearchIcon } from '@blocksuite/icons';
|
||||
import { WorkspaceUnitAvatar } from '@/components/workspace-avatar';
|
||||
import { useModal } from '@/providers/GlobalModalProvider';
|
||||
|
||||
const DynamicBlocksuite = dynamic(() => import('@/components/editor'), {
|
||||
ssr: false,
|
||||
@@ -16,12 +22,16 @@ const Page: NextPageWithLayout = () => {
|
||||
const { dataCenter } = useAppState();
|
||||
const router = useRouter();
|
||||
const [loaded, setLoaded] = useState(false);
|
||||
const [workspaceName, setWorkspaceName] = useState('');
|
||||
const [pageTitle, setPageTitle] = useState('');
|
||||
const { triggerQuickSearchModal } = useModal();
|
||||
|
||||
useEffect(() => {
|
||||
dataCenter
|
||||
.loadPublicWorkspace(router.query.workspaceId as string)
|
||||
.then(data => {
|
||||
if (data && data.blocksuiteWorkspace) {
|
||||
setWorkspaceName(data.blocksuiteWorkspace?.meta.name as string);
|
||||
if (data.blocksuiteWorkspace) {
|
||||
setWorkspace(data.blocksuiteWorkspace);
|
||||
if (
|
||||
router.query.pageId &&
|
||||
@@ -32,6 +42,7 @@ const Page: NextPageWithLayout = () => {
|
||||
const page = data.blocksuiteWorkspace.getPage(
|
||||
router.query.pageId as string
|
||||
);
|
||||
page && setPageTitle(page.meta.title);
|
||||
page && setPage(page);
|
||||
} else {
|
||||
router.push('/404');
|
||||
@@ -46,6 +57,30 @@ const Page: NextPageWithLayout = () => {
|
||||
<>
|
||||
{!loaded && <PageLoading />}
|
||||
<PageContainer>
|
||||
<NavContainer>
|
||||
<Breadcrumbs>
|
||||
<StyledBreadcrumbs
|
||||
href={`/public-workspace/${router.query.workspaceId}`}
|
||||
>
|
||||
<WorkspaceUnitAvatar size={24} name={workspaceName} />
|
||||
<span>{workspaceName}</span>
|
||||
</StyledBreadcrumbs>
|
||||
<StyledBreadcrumbs
|
||||
href={`/public-workspace/${router.query.workspaceId}/${router.query.pageId}`}
|
||||
>
|
||||
<PaperIcon fontSize={24} />
|
||||
<span>{pageTitle ? pageTitle : 'Untitled'}</span>
|
||||
</StyledBreadcrumbs>
|
||||
</Breadcrumbs>
|
||||
<SearchButton
|
||||
onClick={() => {
|
||||
triggerQuickSearchModal();
|
||||
}}
|
||||
>
|
||||
<SearchIcon />
|
||||
</SearchButton>
|
||||
</NavContainer>
|
||||
|
||||
{workspace && page && (
|
||||
<DynamicBlocksuite
|
||||
page={page}
|
||||
@@ -69,9 +104,43 @@ export default Page;
|
||||
|
||||
export const PageContainer = styled.div(({ theme }) => {
|
||||
return {
|
||||
height: 'calc(100vh)',
|
||||
padding: '78px 72px',
|
||||
height: '100vh',
|
||||
overflowY: 'auto',
|
||||
backgroundColor: theme.colors.pageBackground,
|
||||
};
|
||||
});
|
||||
export const NavContainer = styled.div(({ theme }) => {
|
||||
return {
|
||||
width: '100vw',
|
||||
padding: '0 12px',
|
||||
height: '60px',
|
||||
...displayFlex('start', 'center'),
|
||||
backgroundColor: theme.colors.pageBackground,
|
||||
};
|
||||
});
|
||||
export const StyledBreadcrumbs = styled(NextLink)(({ theme }) => {
|
||||
return {
|
||||
flex: 1,
|
||||
...displayFlex('center', 'center'),
|
||||
paddingLeft: '12px',
|
||||
span: {
|
||||
padding: '0 12px',
|
||||
fontSize: theme.font.base,
|
||||
lineHeight: theme.font.lineHeightBase,
|
||||
},
|
||||
':hover': { color: theme.colors.primaryColor },
|
||||
transition: 'all .15s',
|
||||
':visited': {
|
||||
color: theme.colors.popoverColor,
|
||||
':hover': { color: theme.colors.primaryColor },
|
||||
},
|
||||
};
|
||||
});
|
||||
export const SearchButton = styled(IconButton)(({ theme }) => {
|
||||
return {
|
||||
color: theme.colors.iconColor,
|
||||
fontSize: '24px',
|
||||
marginLeft: 'auto',
|
||||
padding: '0 24px',
|
||||
};
|
||||
});
|
||||
|
||||
@@ -2,16 +2,28 @@ import { PageList } from '@/components/page-list';
|
||||
import { ReactElement, useEffect, useState } from 'react';
|
||||
import { PageMeta, useAppState } from '@/providers/app-state-provider';
|
||||
import { useRouter } from 'next/router';
|
||||
import { PageContainer } from './[pageId]';
|
||||
import {
|
||||
PageContainer,
|
||||
NavContainer,
|
||||
StyledBreadcrumbs,
|
||||
SearchButton,
|
||||
} from './[pageId]';
|
||||
import { Breadcrumbs } from '@/ui/breadcrumbs';
|
||||
import { WorkspaceUnitAvatar } from '@/components/workspace-avatar';
|
||||
import { SearchIcon } from '@blocksuite/icons';
|
||||
import { useModal } from '@/providers/GlobalModalProvider';
|
||||
const All = () => {
|
||||
const { dataCenter } = useAppState();
|
||||
const router = useRouter();
|
||||
const [pageList, setPageList] = useState<PageMeta[]>([]);
|
||||
const [workspaceName, setWorkspaceName] = useState('');
|
||||
const { triggerQuickSearchModal } = useModal();
|
||||
useEffect(() => {
|
||||
dataCenter
|
||||
.loadPublicWorkspace(router.query.workspaceId as string)
|
||||
.then(data => {
|
||||
setPageList(data.blocksuiteWorkspace?.meta.pageMetas as PageMeta[]);
|
||||
setWorkspaceName(data.blocksuiteWorkspace?.meta.name as string);
|
||||
})
|
||||
.catch(() => {
|
||||
router.push('/404');
|
||||
@@ -20,6 +32,23 @@ const All = () => {
|
||||
|
||||
return (
|
||||
<PageContainer>
|
||||
<NavContainer>
|
||||
<Breadcrumbs>
|
||||
<StyledBreadcrumbs
|
||||
href={`/public-workspace/${router.query.workspaceId}`}
|
||||
>
|
||||
<WorkspaceUnitAvatar size={24} name={workspaceName} />
|
||||
<span>{workspaceName}</span>
|
||||
</StyledBreadcrumbs>
|
||||
</Breadcrumbs>
|
||||
<SearchButton
|
||||
onClick={() => {
|
||||
triggerQuickSearchModal();
|
||||
}}
|
||||
>
|
||||
<SearchIcon />
|
||||
</SearchButton>
|
||||
</NavContainer>
|
||||
<PageList
|
||||
pageList={pageList.filter(p => !p.trash)}
|
||||
showFavoriteTag={false}
|
||||
|
||||
8
packages/app/src/ui/breadcrumbs/index.ts
Normal file
8
packages/app/src/ui/breadcrumbs/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import MuiBreadcrumbs from '@mui/material/Breadcrumbs';
|
||||
import { styled } from '@/styles';
|
||||
|
||||
export const Breadcrumbs = styled(MuiBreadcrumbs)(({ theme }) => {
|
||||
return {
|
||||
color: theme.colors.popoverColor,
|
||||
};
|
||||
});
|
||||
Reference in New Issue
Block a user