mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-08-22 20:41:50 +08:00
refactor: remove unused code (#2484)
This commit is contained in:
@@ -81,7 +81,7 @@ export const LocalPlugin: WorkspaceAdapter<WorkspaceFlavour.LOCAL> = {
|
|||||||
Provider: ({ children }) => {
|
Provider: ({ children }) => {
|
||||||
return <>{children}</>;
|
return <>{children}</>;
|
||||||
},
|
},
|
||||||
PageDetail: ({ currentWorkspace, currentPageId }) => {
|
PageDetail: ({ currentWorkspace, currentPageId, onLoadEditor }) => {
|
||||||
const page = currentWorkspace.blockSuiteWorkspace.getPage(currentPageId);
|
const page = currentWorkspace.blockSuiteWorkspace.getPage(currentPageId);
|
||||||
if (!page) {
|
if (!page) {
|
||||||
throw new PageNotFoundError(
|
throw new PageNotFoundError(
|
||||||
@@ -94,6 +94,7 @@ export const LocalPlugin: WorkspaceAdapter<WorkspaceFlavour.LOCAL> = {
|
|||||||
<PageDetailEditor
|
<PageDetailEditor
|
||||||
pageId={currentPageId}
|
pageId={currentPageId}
|
||||||
onInit={initPage}
|
onInit={initPage}
|
||||||
|
onLoad={onLoadEditor}
|
||||||
workspace={currentWorkspace}
|
workspace={currentWorkspace}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ const Editor: React.FC<{
|
|||||||
globalThis.page = page;
|
globalThis.page = page;
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
globalThis.editor = editor;
|
globalThis.editor = editor;
|
||||||
|
return () => void 0;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
if (!page) {
|
if (!page) {
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
export * from './pinboard-menu';
|
|
||||||
export * from './pinboard-render/';
|
|
||||||
@@ -1,133 +0,0 @@
|
|||||||
import type { PureMenuProps } from '@affine/component';
|
|
||||||
import { Input, PureMenu, TreeView } from '@affine/component';
|
|
||||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
|
||||||
import { RemoveIcon, SearchIcon } from '@blocksuite/icons';
|
|
||||||
import type { PageMeta } from '@blocksuite/store';
|
|
||||||
import React, { useCallback, useMemo, useState } from 'react';
|
|
||||||
|
|
||||||
import { useReferenceLinkHelper } from '../../../../hooks/affine/use-reference-link-helper';
|
|
||||||
import { usePinboardData } from '../../../../hooks/use-pinboard-data';
|
|
||||||
import { usePinboardHandler } from '../../../../hooks/use-pinboard-handler';
|
|
||||||
import type { BlockSuiteWorkspace } from '../../../../shared';
|
|
||||||
import { toast } from '../../../../utils';
|
|
||||||
import { PinboardRender } from '../pinboard-render/';
|
|
||||||
import {
|
|
||||||
StyledMenuContent,
|
|
||||||
StyledMenuFooter,
|
|
||||||
StyledMenuSubTitle,
|
|
||||||
StyledPinboard,
|
|
||||||
StyledSearchContainer,
|
|
||||||
} from '../styles';
|
|
||||||
import { SearchContent } from './search-content';
|
|
||||||
|
|
||||||
export interface PinboardMenuProps extends PureMenuProps {
|
|
||||||
metas: PageMeta[];
|
|
||||||
currentMeta: PageMeta;
|
|
||||||
blockSuiteWorkspace: BlockSuiteWorkspace;
|
|
||||||
showRemovePinboard?: boolean;
|
|
||||||
onPinboardClick?: (p: { dragId: string; dropId: string }) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const PinboardMenu = ({
|
|
||||||
metas: propsMetas,
|
|
||||||
currentMeta,
|
|
||||||
blockSuiteWorkspace,
|
|
||||||
showRemovePinboard = false,
|
|
||||||
onPinboardClick,
|
|
||||||
...pureMenuProps
|
|
||||||
}: PinboardMenuProps) => {
|
|
||||||
const metas = useMemo(
|
|
||||||
() => propsMetas.filter(m => m.id !== currentMeta.id),
|
|
||||||
[currentMeta.id, propsMetas]
|
|
||||||
);
|
|
||||||
const t = useAFFiNEI18N();
|
|
||||||
const [query, setQuery] = useState('');
|
|
||||||
const isSearching = query.length > 0;
|
|
||||||
|
|
||||||
const searchResult = metas.filter(
|
|
||||||
meta => !meta.trash && meta.title.includes(query)
|
|
||||||
);
|
|
||||||
const { removeReferenceLink } = useReferenceLinkHelper(blockSuiteWorkspace);
|
|
||||||
|
|
||||||
const { dropPin } = usePinboardHandler({
|
|
||||||
blockSuiteWorkspace,
|
|
||||||
metas,
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleClick = useCallback(
|
|
||||||
(dropId: string) => {
|
|
||||||
const targetTitle = metas.find(m => m.id === dropId)?.title;
|
|
||||||
|
|
||||||
dropPin(currentMeta.id, dropId, {
|
|
||||||
bottomLine: false,
|
|
||||||
topLine: false,
|
|
||||||
internal: true,
|
|
||||||
});
|
|
||||||
onPinboardClick?.({ dragId: currentMeta.id, dropId });
|
|
||||||
toast(`Moved "${currentMeta.title}" to "${targetTitle}"`);
|
|
||||||
},
|
|
||||||
[currentMeta.id, currentMeta.title, dropPin, metas, onPinboardClick]
|
|
||||||
);
|
|
||||||
|
|
||||||
const { data } = usePinboardData({
|
|
||||||
metas,
|
|
||||||
pinboardRender: PinboardRender,
|
|
||||||
blockSuiteWorkspace,
|
|
||||||
onClick: (e, node) => {
|
|
||||||
handleClick(node.id);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<PureMenu
|
|
||||||
width={320}
|
|
||||||
height={480}
|
|
||||||
{...pureMenuProps}
|
|
||||||
data-testid="pinboard-menu"
|
|
||||||
>
|
|
||||||
<StyledSearchContainer>
|
|
||||||
<label>
|
|
||||||
<SearchIcon />
|
|
||||||
</label>
|
|
||||||
<Input
|
|
||||||
value={query}
|
|
||||||
onChange={setQuery}
|
|
||||||
placeholder={t['Move page to']()}
|
|
||||||
height={32}
|
|
||||||
noBorder={true}
|
|
||||||
onClick={e => e.stopPropagation()}
|
|
||||||
data-testid="pinboard-menu-search"
|
|
||||||
/>
|
|
||||||
</StyledSearchContainer>
|
|
||||||
|
|
||||||
<StyledMenuContent>
|
|
||||||
{isSearching && (
|
|
||||||
<SearchContent results={searchResult} onClick={handleClick} />
|
|
||||||
)}
|
|
||||||
{!isSearching && (
|
|
||||||
<>
|
|
||||||
<StyledMenuSubTitle>Suggested</StyledMenuSubTitle>
|
|
||||||
<TreeView data={data} indent={16} enableDnd={false} />
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</StyledMenuContent>
|
|
||||||
|
|
||||||
{showRemovePinboard && (
|
|
||||||
<StyledMenuFooter>
|
|
||||||
<StyledPinboard
|
|
||||||
data-testid={'remove-from-pinboard-button'}
|
|
||||||
onClick={() => {
|
|
||||||
removeReferenceLink(currentMeta.id);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<RemoveIcon />
|
|
||||||
{t['Remove from Pivots']()}
|
|
||||||
</StyledPinboard>
|
|
||||||
<p>{t['RFP']()}</p>
|
|
||||||
</StyledMenuFooter>
|
|
||||||
)}
|
|
||||||
</PureMenu>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default PinboardMenu;
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
import { FlexWrapper } from '@affine/component';
|
|
||||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
|
||||||
import { EdgelessIcon, PageIcon } from '@blocksuite/icons';
|
|
||||||
import type { PageMeta } from '@blocksuite/store';
|
|
||||||
import { useAtomValue } from 'jotai';
|
|
||||||
import Image from 'next/legacy/image';
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
import { workspacePreferredModeAtom } from '../../../../atoms';
|
|
||||||
import { StyledMenuSubTitle, StyledPinboard } from '../styles';
|
|
||||||
|
|
||||||
export const SearchContent = ({
|
|
||||||
results,
|
|
||||||
onClick,
|
|
||||||
}: {
|
|
||||||
results: PageMeta[];
|
|
||||||
onClick?: (dropId: string) => void;
|
|
||||||
}) => {
|
|
||||||
const t = useAFFiNEI18N();
|
|
||||||
const record = useAtomValue(workspacePreferredModeAtom);
|
|
||||||
|
|
||||||
if (results.length) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<StyledMenuSubTitle>
|
|
||||||
{t['Find results']({ number: `${results.length}` })}
|
|
||||||
</StyledMenuSubTitle>
|
|
||||||
{results.map(meta => {
|
|
||||||
return (
|
|
||||||
<StyledPinboard
|
|
||||||
key={meta.id}
|
|
||||||
onClick={() => {
|
|
||||||
onClick?.(meta.id);
|
|
||||||
}}
|
|
||||||
data-testid="pinboard-search-result"
|
|
||||||
>
|
|
||||||
{record[meta.id] === 'edgeless' ? <EdgelessIcon /> : <PageIcon />}
|
|
||||||
{meta.title}
|
|
||||||
</StyledPinboard>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<StyledMenuSubTitle>{t['Find 0 result']()}</StyledMenuSubTitle>
|
|
||||||
<FlexWrapper
|
|
||||||
alignItems="center"
|
|
||||||
justifyContent="center"
|
|
||||||
style={{ marginTop: 20 }}
|
|
||||||
>
|
|
||||||
<Image
|
|
||||||
src="/imgs/no-result.svg"
|
|
||||||
alt="no result"
|
|
||||||
width={150}
|
|
||||||
height={150}
|
|
||||||
/>
|
|
||||||
</FlexWrapper>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
import { PlusIcon } from '@blocksuite/icons';
|
|
||||||
|
|
||||||
import { StyledOperationButton } from '../styles';
|
|
||||||
import type { OperationButtonProps } from './operation-button';
|
|
||||||
|
|
||||||
export const AddButton = ({
|
|
||||||
onAdd,
|
|
||||||
visible,
|
|
||||||
}: Pick<OperationButtonProps, 'onAdd' | 'visible'>) => {
|
|
||||||
return (
|
|
||||||
<StyledOperationButton
|
|
||||||
visible={visible}
|
|
||||||
size="small"
|
|
||||||
onClick={e => {
|
|
||||||
e.stopPropagation();
|
|
||||||
onAdd();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<PlusIcon />
|
|
||||||
</StyledOperationButton>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
|
||||||
|
|
||||||
import { StyledPinboard } from '../styles';
|
|
||||||
|
|
||||||
export const EmptyItem = () => {
|
|
||||||
const t = useAFFiNEI18N();
|
|
||||||
return (
|
|
||||||
<StyledPinboard disable={true} textWrap={true}>
|
|
||||||
{t['Organize pages to build knowledge']()}
|
|
||||||
</StyledPinboard>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default EmptyItem;
|
|
||||||
@@ -1,137 +0,0 @@
|
|||||||
import { Input } from '@affine/component';
|
|
||||||
import {
|
|
||||||
ArrowDownSmallIcon,
|
|
||||||
EdgelessIcon,
|
|
||||||
LevelIcon,
|
|
||||||
PageIcon,
|
|
||||||
PinboardIcon,
|
|
||||||
} from '@blocksuite/icons';
|
|
||||||
import { usePageMetaHelper } from '@toeverything/hooks/use-block-suite-page-meta';
|
|
||||||
import { useAtomValue } from 'jotai';
|
|
||||||
import { useRouter } from 'next/router';
|
|
||||||
import { useMemo, useState } from 'react';
|
|
||||||
|
|
||||||
import { workspacePreferredModeAtom } from '../../../../atoms';
|
|
||||||
import type { PinboardNode } from '../../../../hooks/use-pinboard-data';
|
|
||||||
import { StyledCollapsedButton, StyledPinboard } from '../styles';
|
|
||||||
import { AddButton } from './add-button';
|
|
||||||
import EmptyItem from './empty-item';
|
|
||||||
import { OperationButton } from './operation-button';
|
|
||||||
|
|
||||||
const getIcon = (type: 'root' | 'edgeless' | 'page') => {
|
|
||||||
switch (type) {
|
|
||||||
case 'root':
|
|
||||||
return <PinboardIcon className="mode-icon" />;
|
|
||||||
case 'edgeless':
|
|
||||||
return <EdgelessIcon className="mode-icon" />;
|
|
||||||
default:
|
|
||||||
return <PageIcon className="mode-icon" />;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const PinboardRender: PinboardNode['render'] = (
|
|
||||||
node,
|
|
||||||
{
|
|
||||||
isOver,
|
|
||||||
onAdd,
|
|
||||||
onDelete,
|
|
||||||
collapsed,
|
|
||||||
setCollapsed,
|
|
||||||
isSelected,
|
|
||||||
disableCollapse,
|
|
||||||
},
|
|
||||||
renderProps
|
|
||||||
) => {
|
|
||||||
const {
|
|
||||||
onClick,
|
|
||||||
showOperationButton = false,
|
|
||||||
currentMeta,
|
|
||||||
metas = [],
|
|
||||||
blockSuiteWorkspace,
|
|
||||||
asPath,
|
|
||||||
} = renderProps!;
|
|
||||||
const record = useAtomValue(workspacePreferredModeAtom);
|
|
||||||
const { setPageTitle } = usePageMetaHelper(blockSuiteWorkspace);
|
|
||||||
|
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
const [isHover, setIsHover] = useState(false);
|
|
||||||
const [showRename, setShowRename] = useState(false);
|
|
||||||
|
|
||||||
const active = router.query.pageId === node.id;
|
|
||||||
const isRoot = !!currentMeta.isRootPinboard;
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<StyledPinboard
|
|
||||||
data-testid={`pinboard-${node.id}`}
|
|
||||||
onClick={e => {
|
|
||||||
onClick?.(e, node);
|
|
||||||
}}
|
|
||||||
onMouseEnter={() => setIsHover(true)}
|
|
||||||
onMouseLeave={() => setIsHover(false)}
|
|
||||||
isOver={isOver || isSelected}
|
|
||||||
active={active}
|
|
||||||
disableCollapse={!!disableCollapse}
|
|
||||||
>
|
|
||||||
{!disableCollapse && (
|
|
||||||
<StyledCollapsedButton
|
|
||||||
collapse={collapsed}
|
|
||||||
show={!!node.children?.length}
|
|
||||||
onClick={e => {
|
|
||||||
e.stopPropagation();
|
|
||||||
setCollapsed(node.id, !collapsed);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ArrowDownSmallIcon />
|
|
||||||
</StyledCollapsedButton>
|
|
||||||
)}
|
|
||||||
{asPath && !isRoot ? <LevelIcon className="path-icon" /> : null}
|
|
||||||
{getIcon(isRoot ? 'root' : record[node.id])}
|
|
||||||
{showRename ? (
|
|
||||||
<Input
|
|
||||||
data-testid={`pinboard-input-${node.id}`}
|
|
||||||
value={currentMeta.title || ''}
|
|
||||||
placeholder="Untitled"
|
|
||||||
onClick={e => e.stopPropagation()}
|
|
||||||
height={32}
|
|
||||||
onBlur={() => {
|
|
||||||
setShowRename(false);
|
|
||||||
}}
|
|
||||||
onChange={value => {
|
|
||||||
// FIXME: setPageTitle would make input blur, and can't input the Chinese character
|
|
||||||
setPageTitle(node.id, value);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<span>{isRoot ? 'Pinboard' : currentMeta.title || 'Untitled'}</span>
|
|
||||||
)}
|
|
||||||
{showOperationButton && <AddButton onAdd={onAdd} visible={isHover} />}
|
|
||||||
|
|
||||||
{showOperationButton && (
|
|
||||||
<OperationButton
|
|
||||||
isRoot={isRoot}
|
|
||||||
onAdd={onAdd}
|
|
||||||
onDelete={onDelete}
|
|
||||||
metas={metas}
|
|
||||||
currentMeta={currentMeta!}
|
|
||||||
blockSuiteWorkspace={blockSuiteWorkspace!}
|
|
||||||
visible={isHover}
|
|
||||||
onMenuClose={() => setIsHover(false)}
|
|
||||||
onRename={() => {
|
|
||||||
setShowRename(true);
|
|
||||||
setIsHover(false);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</StyledPinboard>
|
|
||||||
|
|
||||||
{useMemo(
|
|
||||||
() =>
|
|
||||||
isRoot &&
|
|
||||||
!metas.find(m => (currentMeta.subpageIds ?? []).includes(m.id)),
|
|
||||||
[currentMeta.subpageIds, isRoot, metas]
|
|
||||||
) && <EmptyItem />}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
export default PinboardRender;
|
|
||||||
@@ -1,170 +0,0 @@
|
|||||||
import { MenuItem, MuiClickAwayListener, PureMenu } from '@affine/component';
|
|
||||||
import { CopyLink, MoveToTrash } from '@affine/component/page-list';
|
|
||||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
|
||||||
import {
|
|
||||||
MoreVerticalIcon,
|
|
||||||
MoveToIcon,
|
|
||||||
PenIcon,
|
|
||||||
PlusIcon,
|
|
||||||
} from '@blocksuite/icons';
|
|
||||||
import type { PageMeta } from '@blocksuite/store';
|
|
||||||
import { baseTheme } from '@toeverything/theme';
|
|
||||||
import { useMemo, useRef, useState } from 'react';
|
|
||||||
|
|
||||||
import { useBlockSuiteMetaHelper } from '../../../../hooks/affine/use-block-suite-meta-helper';
|
|
||||||
import type { BlockSuiteWorkspace } from '../../../../shared';
|
|
||||||
import { toast } from '../../../../utils';
|
|
||||||
import { PinboardMenu } from '../pinboard-menu/';
|
|
||||||
import { StyledOperationButton } from '../styles';
|
|
||||||
|
|
||||||
export type OperationButtonProps = {
|
|
||||||
isRoot: boolean;
|
|
||||||
onAdd: () => void;
|
|
||||||
onDelete: () => void;
|
|
||||||
metas: PageMeta[];
|
|
||||||
currentMeta: PageMeta;
|
|
||||||
blockSuiteWorkspace: BlockSuiteWorkspace;
|
|
||||||
visible: boolean;
|
|
||||||
onRename?: () => void;
|
|
||||||
onMenuClose?: () => void;
|
|
||||||
};
|
|
||||||
export const OperationButton = ({
|
|
||||||
isRoot,
|
|
||||||
onAdd,
|
|
||||||
onDelete,
|
|
||||||
metas,
|
|
||||||
currentMeta,
|
|
||||||
blockSuiteWorkspace,
|
|
||||||
visible,
|
|
||||||
onMenuClose,
|
|
||||||
onRename,
|
|
||||||
}: OperationButtonProps) => {
|
|
||||||
const t = useAFFiNEI18N();
|
|
||||||
|
|
||||||
const timer = useRef<ReturnType<typeof setTimeout>>();
|
|
||||||
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
|
||||||
const [operationMenuOpen, setOperationMenuOpen] = useState(false);
|
|
||||||
const [pinboardMenuOpen, setPinboardMenuOpen] = useState(false);
|
|
||||||
const [confirmModalOpen, setConfirmModalOpen] = useState(false);
|
|
||||||
const menuIndex = useMemo(() => parseInt(baseTheme.zIndexModal) + 1, []);
|
|
||||||
const { removeToTrash } = useBlockSuiteMetaHelper(blockSuiteWorkspace);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<MuiClickAwayListener
|
|
||||||
onClickAway={() => {
|
|
||||||
setOperationMenuOpen(false);
|
|
||||||
setPinboardMenuOpen(false);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
style={{ display: 'flex' }}
|
|
||||||
onClick={e => {
|
|
||||||
e.stopPropagation();
|
|
||||||
}}
|
|
||||||
onMouseLeave={() => {
|
|
||||||
timer.current = setTimeout(() => {
|
|
||||||
setOperationMenuOpen(false);
|
|
||||||
setPinboardMenuOpen(false);
|
|
||||||
}, 150);
|
|
||||||
}}
|
|
||||||
onMouseEnter={() => {
|
|
||||||
clearTimeout(timer.current);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<StyledOperationButton
|
|
||||||
data-testid="pinboard-operation-button"
|
|
||||||
ref={ref => setAnchorEl(ref)}
|
|
||||||
size="small"
|
|
||||||
onClick={() => {
|
|
||||||
setOperationMenuOpen(!operationMenuOpen);
|
|
||||||
}}
|
|
||||||
visible={visible}
|
|
||||||
>
|
|
||||||
<MoreVerticalIcon />
|
|
||||||
</StyledOperationButton>
|
|
||||||
|
|
||||||
<PureMenu
|
|
||||||
data-testid="pinboard-operation-menu"
|
|
||||||
width={256}
|
|
||||||
anchorEl={anchorEl}
|
|
||||||
open={operationMenuOpen}
|
|
||||||
placement="bottom"
|
|
||||||
zIndex={menuIndex}
|
|
||||||
>
|
|
||||||
<MenuItem
|
|
||||||
data-testid="pinboard-operation-add"
|
|
||||||
onClick={() => {
|
|
||||||
onAdd();
|
|
||||||
setOperationMenuOpen(false);
|
|
||||||
onMenuClose?.();
|
|
||||||
}}
|
|
||||||
icon={<PlusIcon />}
|
|
||||||
>
|
|
||||||
{t['Add a subpage inside']()}
|
|
||||||
</MenuItem>
|
|
||||||
{!isRoot && (
|
|
||||||
<MenuItem
|
|
||||||
data-testid="pinboard-operation-move-to"
|
|
||||||
onClick={() => {
|
|
||||||
setOperationMenuOpen(false);
|
|
||||||
setPinboardMenuOpen(true);
|
|
||||||
}}
|
|
||||||
icon={<MoveToIcon />}
|
|
||||||
>
|
|
||||||
{t['Move to']()}
|
|
||||||
</MenuItem>
|
|
||||||
)}
|
|
||||||
{!isRoot && (
|
|
||||||
<MenuItem
|
|
||||||
data-testid="pinboard-operation-rename"
|
|
||||||
onClick={() => {
|
|
||||||
onRename?.();
|
|
||||||
setOperationMenuOpen(false);
|
|
||||||
onMenuClose?.();
|
|
||||||
}}
|
|
||||||
icon={<PenIcon />}
|
|
||||||
>
|
|
||||||
{t['Rename']()}
|
|
||||||
</MenuItem>
|
|
||||||
)}
|
|
||||||
{!isRoot && (
|
|
||||||
<MoveToTrash
|
|
||||||
testId="pinboard-operation-move-to-trash"
|
|
||||||
onItemClick={() => {
|
|
||||||
setOperationMenuOpen(false);
|
|
||||||
setConfirmModalOpen(true);
|
|
||||||
onMenuClose?.();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<CopyLink />
|
|
||||||
</PureMenu>
|
|
||||||
|
|
||||||
<PinboardMenu
|
|
||||||
anchorEl={anchorEl}
|
|
||||||
open={pinboardMenuOpen}
|
|
||||||
placement="bottom"
|
|
||||||
zIndex={menuIndex}
|
|
||||||
metas={metas}
|
|
||||||
currentMeta={currentMeta}
|
|
||||||
blockSuiteWorkspace={blockSuiteWorkspace}
|
|
||||||
showRemovePinboard={true}
|
|
||||||
/>
|
|
||||||
<MoveToTrash.ConfirmModal
|
|
||||||
open={confirmModalOpen}
|
|
||||||
title={currentMeta.title}
|
|
||||||
onConfirm={() => {
|
|
||||||
toast(t['Moved to Trash']());
|
|
||||||
removeToTrash(currentMeta.id);
|
|
||||||
onDelete();
|
|
||||||
}}
|
|
||||||
onCancel={() => {
|
|
||||||
setConfirmModalOpen(false);
|
|
||||||
}}
|
|
||||||
confirmButtonTestId="move-to-trash-confirm"
|
|
||||||
cancelButtonTestId="move-to-trash-cancel"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</MuiClickAwayListener>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,148 +0,0 @@
|
|||||||
import {
|
|
||||||
displayFlex,
|
|
||||||
IconButton,
|
|
||||||
styled,
|
|
||||||
textEllipsis,
|
|
||||||
} from '@affine/component';
|
|
||||||
|
|
||||||
export const StyledCollapsedButton = styled('button')<{
|
|
||||||
collapse: boolean;
|
|
||||||
show?: boolean;
|
|
||||||
}>(({ collapse, show = true }) => {
|
|
||||||
return {
|
|
||||||
width: '16px',
|
|
||||||
height: '100%',
|
|
||||||
...displayFlex('center', 'center'),
|
|
||||||
fontSize: '16px',
|
|
||||||
position: 'absolute',
|
|
||||||
left: '0',
|
|
||||||
top: '0',
|
|
||||||
bottom: '0',
|
|
||||||
margin: 'auto',
|
|
||||||
color: 'var(--affine-icon-color)',
|
|
||||||
opacity: '.6',
|
|
||||||
transition: 'opacity .15s ease-in-out',
|
|
||||||
display: show ? 'flex' : 'none',
|
|
||||||
svg: {
|
|
||||||
transform: `rotate(${collapse ? '-90' : '0'}deg)`,
|
|
||||||
},
|
|
||||||
':hover': {
|
|
||||||
opacity: '1',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
export const StyledPinboard = styled('div')<{
|
|
||||||
disable?: boolean;
|
|
||||||
active?: boolean;
|
|
||||||
isOver?: boolean;
|
|
||||||
disableCollapse?: boolean;
|
|
||||||
textWrap?: boolean;
|
|
||||||
}>(
|
|
||||||
({
|
|
||||||
disableCollapse,
|
|
||||||
disable = false,
|
|
||||||
active = false,
|
|
||||||
isOver,
|
|
||||||
textWrap = false,
|
|
||||||
}) => {
|
|
||||||
return {
|
|
||||||
width: '100%',
|
|
||||||
lineHeight: '1.5',
|
|
||||||
minHeight: '32px',
|
|
||||||
borderRadius: '8px',
|
|
||||||
...displayFlex('flex-start', 'center'),
|
|
||||||
padding: disableCollapse ? '0 5px' : '0 2px 0 16px',
|
|
||||||
position: 'relative',
|
|
||||||
color: disable
|
|
||||||
? 'var(--affine-text-disable-color)'
|
|
||||||
: active
|
|
||||||
? 'var(--affine-primary-color)'
|
|
||||||
: 'var(--affine-text-primary-color)',
|
|
||||||
cursor: disable ? 'not-allowed' : 'pointer',
|
|
||||||
background: isOver ? 'rgba(118, 95, 254, 0.06)' : '',
|
|
||||||
fontSize: 'var(--affine-font-base)',
|
|
||||||
userSelect: 'none',
|
|
||||||
...(textWrap
|
|
||||||
? {
|
|
||||||
wordBreak: 'break-word',
|
|
||||||
whiteSpace: 'pre-wrap',
|
|
||||||
}
|
|
||||||
: {}),
|
|
||||||
|
|
||||||
span: {
|
|
||||||
flexGrow: '1',
|
|
||||||
textAlign: 'left',
|
|
||||||
...textEllipsis(1),
|
|
||||||
},
|
|
||||||
'.path-icon': {
|
|
||||||
fontSize: '16px',
|
|
||||||
transform: 'translateY(-4px)',
|
|
||||||
},
|
|
||||||
'.mode-icon': {
|
|
||||||
fontSize: '20px',
|
|
||||||
marginRight: '8px',
|
|
||||||
flexShrink: '0',
|
|
||||||
color: active
|
|
||||||
? 'var(--affine-primary-color)'
|
|
||||||
: 'var(--affine-icon-color)',
|
|
||||||
},
|
|
||||||
|
|
||||||
':hover': {
|
|
||||||
backgroundColor: disable ? '' : 'var(--affine-hover-color)',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
export const StyledOperationButton = styled(IconButton, {
|
|
||||||
shouldForwardProp: prop => {
|
|
||||||
return !['visible'].includes(prop as string);
|
|
||||||
},
|
|
||||||
})<{ visible: boolean }>(({ visible }) => {
|
|
||||||
return {
|
|
||||||
visibility: visible ? 'visible' : 'hidden',
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
export const StyledSearchContainer = styled('div')(() => {
|
|
||||||
return {
|
|
||||||
width: 'calc(100% - 24px)',
|
|
||||||
margin: '0 auto',
|
|
||||||
...displayFlex('flex-start', 'center'),
|
|
||||||
borderBottom: '1px solid var(--affine-border-color)',
|
|
||||||
label: {
|
|
||||||
color: 'var(--affine-icon-color)',
|
|
||||||
fontSize: '20px',
|
|
||||||
height: '20px',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
export const StyledMenuContent = styled('div')(() => {
|
|
||||||
return {
|
|
||||||
height: '266px',
|
|
||||||
overflow: 'auto',
|
|
||||||
};
|
|
||||||
});
|
|
||||||
export const StyledMenuSubTitle = styled('div')(() => {
|
|
||||||
return {
|
|
||||||
color: 'var(--affine-text-secondary-color)',
|
|
||||||
lineHeight: '36px',
|
|
||||||
padding: '0 12px',
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
export const StyledMenuFooter = styled('div')(() => {
|
|
||||||
return {
|
|
||||||
width: 'calc(100% - 24px)',
|
|
||||||
margin: '0 auto',
|
|
||||||
borderTop: '1px solid var(--affine-border-color)',
|
|
||||||
padding: '6px 0',
|
|
||||||
|
|
||||||
p: {
|
|
||||||
paddingLeft: '44px',
|
|
||||||
color: 'var(--affine-text-secondary-color)',
|
|
||||||
fontSize: '14px',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
+6
-8
@@ -108,14 +108,12 @@ const PageMenu = () => {
|
|||||||
{mode === 'page' ? t['Edgeless']() : t['Page']()}
|
{mode === 'page' ? t['Edgeless']() : t['Page']()}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<Export />
|
<Export />
|
||||||
{!pageMeta.isRootPinboard && (
|
<MoveToTrash
|
||||||
<MoveToTrash
|
testId="editor-option-menu-delete"
|
||||||
testId="editor-option-menu-delete"
|
onItemClick={() => {
|
||||||
onItemClick={() => {
|
setOpenConfirm(true);
|
||||||
setOpenConfirm(true);
|
}}
|
||||||
}}
|
/>
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<div className={styles.horizontalDividerContainer}>
|
<div className={styles.horizontalDividerContainer}>
|
||||||
<div className={styles.horizontalDivider} />
|
<div className={styles.horizontalDivider} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export type PageDetailEditorProps = {
|
|||||||
workspace: AffineOfficialWorkspace;
|
workspace: AffineOfficialWorkspace;
|
||||||
pageId: string;
|
pageId: string;
|
||||||
onInit: (page: Page, editor: Readonly<EditorContainer>) => void;
|
onInit: (page: Page, editor: Readonly<EditorContainer>) => void;
|
||||||
onLoad?: (page: Page, editor: EditorContainer) => void;
|
onLoad?: (page: Page, editor: EditorContainer) => () => void;
|
||||||
header?: React.ReactNode;
|
header?: React.ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -85,7 +85,10 @@ export const PageDetailEditor: React.FC<PageDetailEditorProps> = ({
|
|||||||
updatedDate: Date.now(),
|
updatedDate: Date.now(),
|
||||||
});
|
});
|
||||||
localStorage.setItem('last_page_id', page.id);
|
localStorage.setItem('last_page_id', page.id);
|
||||||
onLoad?.(page, editor);
|
if (onLoad) {
|
||||||
|
return onLoad(page, editor);
|
||||||
|
}
|
||||||
|
return () => {};
|
||||||
},
|
},
|
||||||
[onLoad, setEditor]
|
[onLoad, setEditor]
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,166 +0,0 @@
|
|||||||
import { IconButton, Tooltip, TreeView } from '@affine/component';
|
|
||||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
|
||||||
import {
|
|
||||||
ArrowRightSmallIcon,
|
|
||||||
CollapseIcon,
|
|
||||||
ExpandIcon,
|
|
||||||
MoreHorizontalIcon,
|
|
||||||
} from '@blocksuite/icons';
|
|
||||||
import type { PageMeta } from '@blocksuite/store';
|
|
||||||
import { useBlockSuitePageMeta } from '@toeverything/hooks/use-block-suite-page-meta';
|
|
||||||
import { useRouter } from 'next/router';
|
|
||||||
import type { MouseEvent } from 'react';
|
|
||||||
import { Fragment, useCallback, useMemo, useState } from 'react';
|
|
||||||
|
|
||||||
import type { PinboardNode } from '../../../../hooks/use-pinboard-data';
|
|
||||||
import { usePinboardData } from '../../../../hooks/use-pinboard-data';
|
|
||||||
import { useRouterHelper } from '../../../../hooks/use-router-helper';
|
|
||||||
import type { BlockSuiteWorkspace } from '../../../../shared';
|
|
||||||
import { PinboardRender } from '../../../affine/pinboard';
|
|
||||||
import {
|
|
||||||
StyledNavigationPathContainer,
|
|
||||||
StyledNavPathExtendContainer,
|
|
||||||
StyledNavPathLink,
|
|
||||||
} from './styles';
|
|
||||||
import { calcHowManyPathShouldBeShown, findPath } from './utils';
|
|
||||||
|
|
||||||
export const NavigationPath = ({
|
|
||||||
blockSuiteWorkspace,
|
|
||||||
pageId: propsPageId,
|
|
||||||
onJumpToPage,
|
|
||||||
}: {
|
|
||||||
blockSuiteWorkspace: BlockSuiteWorkspace;
|
|
||||||
pageId?: string;
|
|
||||||
onJumpToPage?: (pageId: string) => void;
|
|
||||||
}) => {
|
|
||||||
const metas = useBlockSuitePageMeta(blockSuiteWorkspace);
|
|
||||||
const router = useRouter();
|
|
||||||
const t = useAFFiNEI18N();
|
|
||||||
|
|
||||||
const [openExtend, setOpenExtend] = useState(false);
|
|
||||||
const pageId = propsPageId ?? router.query.pageId;
|
|
||||||
const { jumpToPage } = useRouterHelper(router);
|
|
||||||
const pathData = useMemo(() => {
|
|
||||||
const meta = metas.find(m => m.id === pageId);
|
|
||||||
const path = meta ? findPath(metas, meta) : [];
|
|
||||||
|
|
||||||
const actualPath = calcHowManyPathShouldBeShown(path);
|
|
||||||
return {
|
|
||||||
hasEllipsis: path.length !== actualPath.length,
|
|
||||||
path: actualPath,
|
|
||||||
};
|
|
||||||
}, [metas, pageId]);
|
|
||||||
|
|
||||||
if (pathData.path.length < 2) {
|
|
||||||
// Means there is no parent page
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<StyledNavigationPathContainer data-testid="navigation-path">
|
|
||||||
{openExtend ? (
|
|
||||||
<span>{t['Navigation Path']()}</span>
|
|
||||||
) : (
|
|
||||||
pathData.path.map((meta, index) => {
|
|
||||||
const isLast = index === pathData.path.length - 1;
|
|
||||||
const showEllipsis = pathData.hasEllipsis && index === 1;
|
|
||||||
return (
|
|
||||||
<Fragment key={meta.id}>
|
|
||||||
{showEllipsis && (
|
|
||||||
<>
|
|
||||||
<IconButton
|
|
||||||
size="small"
|
|
||||||
onClick={() => setOpenExtend(true)}
|
|
||||||
>
|
|
||||||
<MoreHorizontalIcon />
|
|
||||||
</IconButton>
|
|
||||||
<ArrowRightSmallIcon className="path-arrow" />
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
<StyledNavPathLink
|
|
||||||
data-testid="navigation-path-link"
|
|
||||||
active={isLast}
|
|
||||||
onClick={() => {
|
|
||||||
if (isLast) return;
|
|
||||||
jumpToPage(blockSuiteWorkspace.id, meta.id);
|
|
||||||
onJumpToPage?.(meta.id);
|
|
||||||
}}
|
|
||||||
title={meta.title}
|
|
||||||
>
|
|
||||||
{meta.title}
|
|
||||||
</StyledNavPathLink>
|
|
||||||
{!isLast && <ArrowRightSmallIcon className="path-arrow" />}
|
|
||||||
</Fragment>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
)}
|
|
||||||
<Tooltip
|
|
||||||
content={
|
|
||||||
openExtend
|
|
||||||
? t['Back to Quick Search']()
|
|
||||||
: t['View Navigation Path']()
|
|
||||||
}
|
|
||||||
placement="top"
|
|
||||||
disablePortal={true}
|
|
||||||
>
|
|
||||||
<IconButton
|
|
||||||
data-testid="navigation-path-expand-btn"
|
|
||||||
size="small"
|
|
||||||
className="collapse-btn"
|
|
||||||
onClick={() => {
|
|
||||||
setOpenExtend(!openExtend);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{openExtend ? <CollapseIcon /> : <ExpandIcon />}
|
|
||||||
</IconButton>
|
|
||||||
</Tooltip>
|
|
||||||
</StyledNavigationPathContainer>
|
|
||||||
<NavigationPathExtendPanel
|
|
||||||
open={openExtend}
|
|
||||||
blockSuiteWorkspace={blockSuiteWorkspace}
|
|
||||||
metas={metas}
|
|
||||||
onJumpToPage={onJumpToPage}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const NavigationPathExtendPanel = ({
|
|
||||||
open,
|
|
||||||
metas,
|
|
||||||
blockSuiteWorkspace,
|
|
||||||
onJumpToPage,
|
|
||||||
}: {
|
|
||||||
open: boolean;
|
|
||||||
metas: PageMeta[];
|
|
||||||
blockSuiteWorkspace: BlockSuiteWorkspace;
|
|
||||||
onJumpToPage?: (pageId: string) => void;
|
|
||||||
}) => {
|
|
||||||
const router = useRouter();
|
|
||||||
const { jumpToPage } = useRouterHelper(router);
|
|
||||||
|
|
||||||
const handlePinboardClick = useCallback(
|
|
||||||
(e: MouseEvent<HTMLDivElement>, node: PinboardNode) => {
|
|
||||||
jumpToPage(blockSuiteWorkspace.id, node.id);
|
|
||||||
onJumpToPage?.(node.id);
|
|
||||||
},
|
|
||||||
[blockSuiteWorkspace.id, jumpToPage, onJumpToPage]
|
|
||||||
);
|
|
||||||
|
|
||||||
const { data } = usePinboardData({
|
|
||||||
metas,
|
|
||||||
pinboardRender: PinboardRender,
|
|
||||||
blockSuiteWorkspace: blockSuiteWorkspace,
|
|
||||||
onClick: handlePinboardClick,
|
|
||||||
asPath: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<StyledNavPathExtendContainer
|
|
||||||
show={open}
|
|
||||||
data-testid="navigation-path-expand-panel"
|
|
||||||
>
|
|
||||||
<TreeView data={data} indent={10} disableCollapse={true} />
|
|
||||||
</StyledNavPathExtendContainer>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
import { displayFlex, styled, textEllipsis } from '@affine/component';
|
|
||||||
|
|
||||||
export const StyledNavigationPathContainer = styled('div')(() => {
|
|
||||||
return {
|
|
||||||
height: '46px',
|
|
||||||
...displayFlex('flex-start', 'center'),
|
|
||||||
background: 'var(--affine-background-secondary-color)',
|
|
||||||
padding: '0 40px 0 20px',
|
|
||||||
position: 'relative',
|
|
||||||
fontSize: 'var(--affine-font-sm)',
|
|
||||||
zIndex: 2,
|
|
||||||
'.collapse-btn': {
|
|
||||||
position: 'absolute',
|
|
||||||
right: '12px',
|
|
||||||
top: 0,
|
|
||||||
bottom: 0,
|
|
||||||
margin: 'auto',
|
|
||||||
},
|
|
||||||
'.path-arrow': {
|
|
||||||
fontSize: '16px',
|
|
||||||
color: 'var(--affine-icon-color)',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
export const StyledNavPathLink = styled('div')<{ active?: boolean }>(
|
|
||||||
({ active }) => {
|
|
||||||
return {
|
|
||||||
color: active
|
|
||||||
? 'var(--affine-text-primary-color)'
|
|
||||||
: 'var(--affine-text-secondary-color)',
|
|
||||||
cursor: active ? 'auto' : 'pointer',
|
|
||||||
maxWidth: '160px',
|
|
||||||
...textEllipsis(1),
|
|
||||||
padding: '0 4px',
|
|
||||||
transition: 'background .15s',
|
|
||||||
':hover': active
|
|
||||||
? {}
|
|
||||||
: {
|
|
||||||
background: 'var(--affine-hover-color)',
|
|
||||||
borderRadius: '4px',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
export const StyledNavPathExtendContainer = styled('div')<{ show: boolean }>(
|
|
||||||
({ show }) => {
|
|
||||||
return {
|
|
||||||
position: 'absolute',
|
|
||||||
left: '0',
|
|
||||||
top: show ? '0' : '-100%',
|
|
||||||
zIndex: '1',
|
|
||||||
height: '100%',
|
|
||||||
width: '100%',
|
|
||||||
background: 'var(--affine-background-secondary-color)',
|
|
||||||
transition: 'top .15s',
|
|
||||||
fontSize: 'var(--affine-font-sm)',
|
|
||||||
color: 'var(--affine-text-secondary-color)',
|
|
||||||
padding: '46px 12px 0 15px',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
import type { PageMeta } from '@blocksuite/store';
|
|
||||||
|
|
||||||
export function findPath(metas: PageMeta[], meta: PageMeta): PageMeta[] {
|
|
||||||
function helper(group: PageMeta[]): PageMeta[] {
|
|
||||||
const last = group[group.length - 1];
|
|
||||||
const parent = metas.find(m => (m.subpageIds ?? []).includes(last.id));
|
|
||||||
if (parent) {
|
|
||||||
return helper([...group, parent]);
|
|
||||||
}
|
|
||||||
return group;
|
|
||||||
}
|
|
||||||
|
|
||||||
return helper([meta]).reverse();
|
|
||||||
}
|
|
||||||
|
|
||||||
function getPathItemWidth(content: string) {
|
|
||||||
// padding is 8px, arrow is 16px, and each char is 10px
|
|
||||||
// the max width is 160px
|
|
||||||
const charWidth = 10;
|
|
||||||
const w = content.length * charWidth + 8 + 16;
|
|
||||||
return w > 160 ? 160 : w;
|
|
||||||
}
|
|
||||||
|
|
||||||
// XXX: this is a static way to calculate the path width, not get the real width
|
|
||||||
export function calcHowManyPathShouldBeShown(path: PageMeta[]): PageMeta[] {
|
|
||||||
if (path.length === 0) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
const first = path[0];
|
|
||||||
const last = path[path.length - 1];
|
|
||||||
// 20 is the ellipsis icon width
|
|
||||||
const maxWidth = 550 - 20;
|
|
||||||
if (first.id === last.id) {
|
|
||||||
return [first];
|
|
||||||
}
|
|
||||||
|
|
||||||
function getMiddlePath(restWidth: number, restPath: PageMeta[]): PageMeta[] {
|
|
||||||
if (restPath.length === 0) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
const last = restPath[restPath.length - 1];
|
|
||||||
const w = getPathItemWidth(last.title);
|
|
||||||
if (restWidth - w > 80) {
|
|
||||||
return [
|
|
||||||
...getMiddlePath(restWidth - w, restPath.slice(0, restPath.length - 1)),
|
|
||||||
last,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
first,
|
|
||||||
...getMiddlePath(
|
|
||||||
maxWidth - getPathItemWidth(first.title),
|
|
||||||
path.slice(1, -1)
|
|
||||||
),
|
|
||||||
last,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
@@ -1,25 +1,5 @@
|
|||||||
import type { Page } from '@blocksuite/store';
|
|
||||||
|
|
||||||
import type { AllWorkspace } from '../../../shared';
|
import type { AllWorkspace } from '../../../shared';
|
||||||
|
|
||||||
export type FavoriteListProps = {
|
export type FavoriteListProps = {
|
||||||
currentWorkspace: AllWorkspace;
|
currentWorkspace: AllWorkspace;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type WorkSpaceSliderBarProps = {
|
|
||||||
isPublicWorkspace: boolean;
|
|
||||||
onOpenQuickSearchModal: () => void;
|
|
||||||
onOpenWorkspaceListModal: () => void;
|
|
||||||
currentWorkspace: AllWorkspace | null;
|
|
||||||
currentPageId: string | null;
|
|
||||||
openPage: (pageId: string) => void;
|
|
||||||
createPage: () => Page;
|
|
||||||
currentPath: string;
|
|
||||||
paths: {
|
|
||||||
all: (workspaceId: string) => string;
|
|
||||||
favorite: (workspaceId: string) => string;
|
|
||||||
trash: (workspaceId: string) => string;
|
|
||||||
setting: (workspaceId: string) => string;
|
|
||||||
shared: (workspaceId: string) => string;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,64 +0,0 @@
|
|||||||
import { TreeView } from '@affine/component';
|
|
||||||
import { useBlockSuitePageMeta } from '@toeverything/hooks/use-block-suite-page-meta';
|
|
||||||
import type { MouseEvent } from 'react';
|
|
||||||
import { useCallback } from 'react';
|
|
||||||
|
|
||||||
import type { PinboardNode } from '../../../hooks/use-pinboard-data';
|
|
||||||
import { usePinboardData } from '../../../hooks/use-pinboard-data';
|
|
||||||
import { usePinboardHandler } from '../../../hooks/use-pinboard-handler';
|
|
||||||
import type { BlockSuiteWorkspace } from '../../../shared';
|
|
||||||
import { PinboardRender } from '../../affine/pinboard';
|
|
||||||
|
|
||||||
export type PinboardProps = {
|
|
||||||
blockSuiteWorkspace: BlockSuiteWorkspace;
|
|
||||||
openPage: (pageId: string) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated
|
|
||||||
*/
|
|
||||||
export const Pinboard = ({ blockSuiteWorkspace, openPage }: PinboardProps) => {
|
|
||||||
const allMetas = useBlockSuitePageMeta(blockSuiteWorkspace);
|
|
||||||
const handlePinboardClick = useCallback(
|
|
||||||
(e: MouseEvent<HTMLDivElement>, node: PinboardNode) => {
|
|
||||||
openPage(node.id);
|
|
||||||
},
|
|
||||||
[openPage]
|
|
||||||
);
|
|
||||||
const onAdd = useCallback(
|
|
||||||
(id: string) => {
|
|
||||||
openPage(id);
|
|
||||||
},
|
|
||||||
[openPage]
|
|
||||||
);
|
|
||||||
|
|
||||||
const { data } = usePinboardData({
|
|
||||||
metas: allMetas,
|
|
||||||
pinboardRender: PinboardRender,
|
|
||||||
blockSuiteWorkspace: blockSuiteWorkspace,
|
|
||||||
onClick: handlePinboardClick,
|
|
||||||
showOperationButton: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const { addPin, deletePin, dropPin } = usePinboardHandler({
|
|
||||||
blockSuiteWorkspace: blockSuiteWorkspace,
|
|
||||||
metas: allMetas,
|
|
||||||
onAdd,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!data.length) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<div data-testid="sidebar-pinboard-container">
|
|
||||||
<TreeView
|
|
||||||
data={data}
|
|
||||||
onAdd={addPin}
|
|
||||||
onDelete={deletePin}
|
|
||||||
onDrop={dropPin}
|
|
||||||
indent={16}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
export default Pinboard;
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
import { useAtomValue } from 'jotai';
|
|
||||||
import { useEffect } from 'react';
|
|
||||||
|
|
||||||
import { currentEditorAtom } from '../../atoms';
|
|
||||||
|
|
||||||
export function useReferenceLinkEffect(props?: {
|
|
||||||
pageLinkClicked?: (params: { pageId: string }) => void;
|
|
||||||
subpageLinked?: (params: { pageId: string }) => void;
|
|
||||||
subpageUnlinked?: (params: { pageId: string }) => void;
|
|
||||||
}) {
|
|
||||||
const { pageLinkClicked, subpageLinked, subpageUnlinked } = props ?? {};
|
|
||||||
const editor = useAtomValue(currentEditorAtom);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!editor) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const linkClickedDisposable = editor.slots.pageLinkClicked.on(
|
|
||||||
({ pageId }) => {
|
|
||||||
pageLinkClicked?.({ pageId });
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// const subpageLinkedDisposable = editor.slots.subpageLinked.on(
|
|
||||||
// ({ pageId }) => {
|
|
||||||
// subpageLinked?.({ pageId });
|
|
||||||
// }
|
|
||||||
// );
|
|
||||||
// const subpageUnlinkedDisposable = editor.slots.subpageUnlinked.on(
|
|
||||||
// ({ pageId }) => {
|
|
||||||
// subpageUnlinked?.({ pageId });
|
|
||||||
// }
|
|
||||||
// );
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
linkClickedDisposable.dispose();
|
|
||||||
// subpageLinkedDisposable.dispose();
|
|
||||||
// subpageUnlinkedDisposable.dispose();
|
|
||||||
};
|
|
||||||
}, [editor, pageLinkClicked, subpageLinked, subpageUnlinked]);
|
|
||||||
}
|
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
import type { Node } from '@affine/component';
|
|
||||||
import type { PageMeta } from '@blocksuite/store';
|
|
||||||
import type { MouseEvent } from 'react';
|
|
||||||
import { useMemo } from 'react';
|
|
||||||
|
|
||||||
import type { BlockSuiteWorkspace } from '../shared';
|
|
||||||
|
|
||||||
export type RenderProps = {
|
|
||||||
blockSuiteWorkspace: BlockSuiteWorkspace;
|
|
||||||
onClick?: (e: MouseEvent<HTMLDivElement>, node: PinboardNode) => void;
|
|
||||||
showOperationButton?: boolean;
|
|
||||||
// If true, the node will be rendered with path icon at start
|
|
||||||
asPath?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type NodeRenderProps = RenderProps & {
|
|
||||||
metas: PageMeta[];
|
|
||||||
currentMeta: PageMeta;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type PinboardNode = Node<NodeRenderProps>;
|
|
||||||
|
|
||||||
function flattenToTree(
|
|
||||||
metas: PageMeta[],
|
|
||||||
pinboardRender: PinboardNode['render'],
|
|
||||||
renderProps: RenderProps
|
|
||||||
): PinboardNode[] {
|
|
||||||
const rootMeta = metas.find(meta => meta.isRootPinboard);
|
|
||||||
const helper = (internalMetas: PageMeta[]): PinboardNode[] => {
|
|
||||||
return internalMetas.reduce<PinboardNode[]>(
|
|
||||||
(returnedMetas, internalMeta) => {
|
|
||||||
const { subpageIds = [] } = internalMeta;
|
|
||||||
const childrenMetas = subpageIds
|
|
||||||
.map(id => metas.find(m => m.id === id)!)
|
|
||||||
.filter(m => m);
|
|
||||||
// @ts-ignore
|
|
||||||
const returnedMeta: PinboardNode = {
|
|
||||||
...internalMeta,
|
|
||||||
children: helper(childrenMetas),
|
|
||||||
render: (node, props) =>
|
|
||||||
pinboardRender(node, props, {
|
|
||||||
...renderProps,
|
|
||||||
currentMeta: internalMeta,
|
|
||||||
metas,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
returnedMetas.push(returnedMeta);
|
|
||||||
return returnedMetas;
|
|
||||||
},
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
};
|
|
||||||
// Unreachable code, we have removed the root pinboard
|
|
||||||
// @ts-expect-error
|
|
||||||
return helper(rootMeta ? [{ ...rootMeta, renderTopLine: false }] : []);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function usePinboardData({
|
|
||||||
metas,
|
|
||||||
pinboardRender,
|
|
||||||
blockSuiteWorkspace,
|
|
||||||
onClick,
|
|
||||||
showOperationButton,
|
|
||||||
asPath,
|
|
||||||
}: {
|
|
||||||
metas: PageMeta[];
|
|
||||||
pinboardRender: PinboardNode['render'];
|
|
||||||
} & RenderProps) {
|
|
||||||
const data = useMemo(
|
|
||||||
() =>
|
|
||||||
flattenToTree(metas, pinboardRender, {
|
|
||||||
blockSuiteWorkspace,
|
|
||||||
onClick,
|
|
||||||
showOperationButton,
|
|
||||||
asPath,
|
|
||||||
}),
|
|
||||||
[
|
|
||||||
asPath,
|
|
||||||
blockSuiteWorkspace,
|
|
||||||
metas,
|
|
||||||
onClick,
|
|
||||||
pinboardRender,
|
|
||||||
showOperationButton,
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
data,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default usePinboardData;
|
|
||||||
@@ -1,138 +0,0 @@
|
|||||||
import type { TreeViewProps } from '@affine/component';
|
|
||||||
import { DebugLogger } from '@affine/debug';
|
|
||||||
import type { PageMeta } from '@blocksuite/store';
|
|
||||||
import { nanoid } from '@blocksuite/store';
|
|
||||||
import { usePageMetaHelper } from '@toeverything/hooks/use-block-suite-page-meta';
|
|
||||||
import { useBlockSuiteWorkspaceHelper } from '@toeverything/hooks/use-block-suite-workspace-helper';
|
|
||||||
import { useCallback, useMemo } from 'react';
|
|
||||||
|
|
||||||
import type { BlockSuiteWorkspace } from '../shared';
|
|
||||||
import { useBlockSuiteMetaHelper } from './affine/use-block-suite-meta-helper';
|
|
||||||
import { useReferenceLinkHelper } from './affine/use-reference-link-helper';
|
|
||||||
import type { NodeRenderProps } from './use-pinboard-data';
|
|
||||||
|
|
||||||
const logger = new DebugLogger('pinboard');
|
|
||||||
|
|
||||||
function findRootIds(metas: PageMeta[], id: string): string[] {
|
|
||||||
const parentMeta = metas.find(m => m.subpageIds?.includes(id));
|
|
||||||
if (!parentMeta) {
|
|
||||||
return [id];
|
|
||||||
}
|
|
||||||
return [parentMeta.id, ...findRootIds(metas, parentMeta.id)];
|
|
||||||
}
|
|
||||||
export function usePinboardHandler({
|
|
||||||
blockSuiteWorkspace,
|
|
||||||
metas: propsMetas,
|
|
||||||
onAdd,
|
|
||||||
onDelete,
|
|
||||||
onDrop,
|
|
||||||
}: {
|
|
||||||
blockSuiteWorkspace: BlockSuiteWorkspace;
|
|
||||||
metas?: PageMeta[];
|
|
||||||
onAdd?: (addedId: string, parentId: string) => void;
|
|
||||||
onDelete?: TreeViewProps<NodeRenderProps>['onDelete'];
|
|
||||||
onDrop?: TreeViewProps<NodeRenderProps>['onDrop'];
|
|
||||||
}) {
|
|
||||||
const metas = useMemo(
|
|
||||||
() => propsMetas || blockSuiteWorkspace.meta.pageMetas || [],
|
|
||||||
[blockSuiteWorkspace.meta.pageMetas, propsMetas]
|
|
||||||
);
|
|
||||||
const { createPage } = useBlockSuiteWorkspaceHelper(blockSuiteWorkspace);
|
|
||||||
const { setPageMeta } = usePageMetaHelper(blockSuiteWorkspace);
|
|
||||||
const { removeToTrash: removeToTrashHelper } =
|
|
||||||
useBlockSuiteMetaHelper(blockSuiteWorkspace);
|
|
||||||
const { addReferenceLink, removeReferenceLink } =
|
|
||||||
useReferenceLinkHelper(blockSuiteWorkspace);
|
|
||||||
|
|
||||||
const addPin = useCallback(
|
|
||||||
(parentId: string) => {
|
|
||||||
const id = nanoid();
|
|
||||||
createPage(id);
|
|
||||||
onAdd?.(id, parentId);
|
|
||||||
addReferenceLink(parentId, id);
|
|
||||||
},
|
|
||||||
[addReferenceLink, createPage, onAdd]
|
|
||||||
);
|
|
||||||
|
|
||||||
const deletePin = useCallback(
|
|
||||||
(deleteId: string) => {
|
|
||||||
removeToTrashHelper(deleteId);
|
|
||||||
onDelete?.(deleteId);
|
|
||||||
},
|
|
||||||
[removeToTrashHelper, onDelete]
|
|
||||||
);
|
|
||||||
|
|
||||||
const dropPin = useCallback(
|
|
||||||
(
|
|
||||||
dragId: string,
|
|
||||||
dropId: string,
|
|
||||||
position: {
|
|
||||||
topLine: boolean;
|
|
||||||
bottomLine: boolean;
|
|
||||||
internal: boolean;
|
|
||||||
}
|
|
||||||
) => {
|
|
||||||
if (dragId === dropId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const dropRootIds = findRootIds(metas, dropId);
|
|
||||||
if (dropRootIds.includes(dragId)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
logger.info('handleDrop', {
|
|
||||||
dragId,
|
|
||||||
dropId,
|
|
||||||
position,
|
|
||||||
metas,
|
|
||||||
});
|
|
||||||
|
|
||||||
const { topLine, bottomLine } = position;
|
|
||||||
|
|
||||||
const dragParentMeta = metas.find(meta =>
|
|
||||||
meta.subpageIds?.includes(dragId)
|
|
||||||
);
|
|
||||||
if (bottomLine || topLine) {
|
|
||||||
const insertOffset = bottomLine ? 1 : 0;
|
|
||||||
|
|
||||||
const dropParentMeta = metas.find(m => m.subpageIds?.includes(dropId));
|
|
||||||
if (dropParentMeta?.id === dragParentMeta?.id) {
|
|
||||||
// same parent, resort node
|
|
||||||
const newSubpageIds = [...(dragParentMeta?.subpageIds ?? [])];
|
|
||||||
const deleteIndex = newSubpageIds.findIndex(id => id === dragId);
|
|
||||||
newSubpageIds.splice(deleteIndex, 1);
|
|
||||||
const insertIndex =
|
|
||||||
newSubpageIds.findIndex(id => id === dropId) + insertOffset;
|
|
||||||
newSubpageIds.splice(insertIndex, 0, dragId);
|
|
||||||
dragParentMeta &&
|
|
||||||
setPageMeta(dragParentMeta.id, {
|
|
||||||
subpageIds: newSubpageIds,
|
|
||||||
});
|
|
||||||
return onDrop?.(dragId, dropId, position);
|
|
||||||
}
|
|
||||||
// Old parent will delete drag node, new parent will be added
|
|
||||||
removeReferenceLink(dragId);
|
|
||||||
dropParentMeta && addReferenceLink(dropParentMeta.id, dragId);
|
|
||||||
return onDrop?.(dragId, dropId, position);
|
|
||||||
}
|
|
||||||
|
|
||||||
// drop into the node
|
|
||||||
if (dragParentMeta && dragParentMeta.id === dropId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (dragParentMeta) {
|
|
||||||
removeReferenceLink(dragId);
|
|
||||||
}
|
|
||||||
const dropMeta = metas.find(meta => meta.id === dropId)!;
|
|
||||||
addReferenceLink(dropMeta.id, dragId);
|
|
||||||
},
|
|
||||||
[addReferenceLink, metas, onDrop, removeReferenceLink, setPageMeta]
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
dropPin,
|
|
||||||
addPin,
|
|
||||||
deletePin,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default usePinboardHandler;
|
|
||||||
@@ -287,11 +287,6 @@ export const WorkspaceLayoutInner: FC<PropsWithChildren> = ({ children }) => {
|
|||||||
void jumpToPage(currentWorkspace.id, pageId);
|
void jumpToPage(currentWorkspace.id, pageId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// fixme: pinboard has been removed,
|
|
||||||
// the related code should be removed in the future.
|
|
||||||
// no matter the workspace is empty, ensure the root pinboard exists
|
|
||||||
// ensureRootPinboard(currentWorkspace.blockSuiteWorkspace);
|
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { useAtom, useAtomValue } from 'jotai';
|
|||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import type { ReactElement } from 'react';
|
import type { ReactElement } from 'react';
|
||||||
import { Suspense, useCallback, useEffect } from 'react';
|
import { Suspense, useEffect } from 'react';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
publicPageBlockSuiteAtom,
|
publicPageBlockSuiteAtom,
|
||||||
@@ -19,7 +19,6 @@ import {
|
|||||||
import { PageDetailEditor } from '../../../components/page-detail-editor';
|
import { PageDetailEditor } from '../../../components/page-detail-editor';
|
||||||
import { WorkspaceAvatar } from '../../../components/pure/footer';
|
import { WorkspaceAvatar } from '../../../components/pure/footer';
|
||||||
import { PageLoading } from '../../../components/pure/loading';
|
import { PageLoading } from '../../../components/pure/loading';
|
||||||
import { useReferenceLinkEffect } from '../../../hooks/affine/use-reference-link-effect';
|
|
||||||
import { useRouterHelper } from '../../../hooks/use-router-helper';
|
import { useRouterHelper } from '../../../hooks/use-router-helper';
|
||||||
import {
|
import {
|
||||||
PublicQuickSearch,
|
PublicQuickSearch,
|
||||||
@@ -64,14 +63,6 @@ const PublicWorkspaceDetailPageInner = (): ReactElement => {
|
|||||||
}
|
}
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { openPage } = useRouterHelper(router);
|
const { openPage } = useRouterHelper(router);
|
||||||
useReferenceLinkEffect({
|
|
||||||
pageLinkClicked: useCallback(
|
|
||||||
({ pageId }: { pageId: string }) => {
|
|
||||||
return openPage(blockSuiteWorkspace.id, pageId);
|
|
||||||
},
|
|
||||||
[blockSuiteWorkspace.id, openPage]
|
|
||||||
),
|
|
||||||
});
|
|
||||||
const t = useAFFiNEI18N();
|
const t = useAFFiNEI18N();
|
||||||
const [name] = useBlockSuiteWorkspaceName(blockSuiteWorkspace);
|
const [name] = useBlockSuiteWorkspaceName(blockSuiteWorkspace);
|
||||||
const [avatar] = useBlockSuiteWorkspaceAvatarUrl(blockSuiteWorkspace);
|
const [avatar] = useBlockSuiteWorkspaceAvatarUrl(blockSuiteWorkspace);
|
||||||
@@ -86,6 +77,12 @@ const PublicWorkspaceDetailPageInner = (): ReactElement => {
|
|||||||
onLoad={(_, editor) => {
|
onLoad={(_, editor) => {
|
||||||
const { page } = editor;
|
const { page } = editor;
|
||||||
page.awarenessStore.setReadonly(page, true);
|
page.awarenessStore.setReadonly(page, true);
|
||||||
|
const dispose = editor.slots.pageLinkClicked.on(({ pageId }) => {
|
||||||
|
return openPage(blockSuiteWorkspace.id, pageId);
|
||||||
|
});
|
||||||
|
return () => {
|
||||||
|
dispose.dispose();
|
||||||
|
};
|
||||||
}}
|
}}
|
||||||
onInit={initPage}
|
onInit={initPage}
|
||||||
header={
|
header={
|
||||||
|
|||||||
@@ -4,11 +4,9 @@ import { config } from '@affine/env';
|
|||||||
import { Unreachable } from '@affine/env/constant';
|
import { Unreachable } from '@affine/env/constant';
|
||||||
import { rootCurrentPageIdAtom } from '@affine/workspace/atom';
|
import { rootCurrentPageIdAtom } from '@affine/workspace/atom';
|
||||||
import { WorkspaceFlavour } from '@affine/workspace/type';
|
import { WorkspaceFlavour } from '@affine/workspace/type';
|
||||||
|
import type { EditorContainer } from '@blocksuite/editor';
|
||||||
|
import type { Page } from '@blocksuite/store';
|
||||||
import { assertExists } from '@blocksuite/store';
|
import { assertExists } from '@blocksuite/store';
|
||||||
import {
|
|
||||||
useBlockSuitePageMeta,
|
|
||||||
usePageMetaHelper,
|
|
||||||
} from '@toeverything/hooks/use-block-suite-page-meta';
|
|
||||||
import { useBlockSuiteWorkspacePage } from '@toeverything/hooks/use-block-suite-workspace-page';
|
import { useBlockSuiteWorkspacePage } from '@toeverything/hooks/use-block-suite-workspace-page';
|
||||||
import { useAtomValue } from 'jotai';
|
import { useAtomValue } from 'jotai';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
@@ -17,9 +15,7 @@ import { useCallback, useEffect } from 'react';
|
|||||||
|
|
||||||
import { WorkspaceAdapters } from '../../../adapters/workspace';
|
import { WorkspaceAdapters } from '../../../adapters/workspace';
|
||||||
import { rootCurrentWorkspaceAtom } from '../../../atoms/root';
|
import { rootCurrentWorkspaceAtom } from '../../../atoms/root';
|
||||||
import { useReferenceLinkEffect } from '../../../hooks/affine/use-reference-link-effect';
|
|
||||||
import { useCurrentWorkspace } from '../../../hooks/current/use-current-workspace';
|
import { useCurrentWorkspace } from '../../../hooks/current/use-current-workspace';
|
||||||
import { usePinboardHandler } from '../../../hooks/use-pinboard-handler';
|
|
||||||
import { useSyncRecentViewsWithRouter } from '../../../hooks/use-recent-views';
|
import { useSyncRecentViewsWithRouter } from '../../../hooks/use-recent-views';
|
||||||
import { useRouterHelper } from '../../../hooks/use-router-helper';
|
import { useRouterHelper } from '../../../hooks/use-router-helper';
|
||||||
import { WorkspaceLayout } from '../../../layouts/workspace-layout';
|
import { WorkspaceLayout } from '../../../layouts/workspace-layout';
|
||||||
@@ -42,41 +38,19 @@ const WorkspaceDetail: React.FC = () => {
|
|||||||
assertExists(currentWorkspace);
|
assertExists(currentWorkspace);
|
||||||
assertExists(currentPageId);
|
assertExists(currentPageId);
|
||||||
const blockSuiteWorkspace = currentWorkspace.blockSuiteWorkspace;
|
const blockSuiteWorkspace = currentWorkspace.blockSuiteWorkspace;
|
||||||
const { setPageMeta, getPageMeta } = usePageMetaHelper(blockSuiteWorkspace);
|
|
||||||
const { deletePin } = usePinboardHandler({
|
|
||||||
blockSuiteWorkspace,
|
|
||||||
metas: useBlockSuitePageMeta(currentWorkspace.blockSuiteWorkspace),
|
|
||||||
});
|
|
||||||
|
|
||||||
useSyncRecentViewsWithRouter(router, blockSuiteWorkspace);
|
useSyncRecentViewsWithRouter(router, blockSuiteWorkspace);
|
||||||
|
|
||||||
useReferenceLinkEffect({
|
const onLoad = useCallback(
|
||||||
pageLinkClicked: useCallback(
|
(page: Page, editor: EditorContainer) => {
|
||||||
({ pageId }: { pageId: string }) => {
|
const dispose = editor.slots.pageLinkClicked.on(({ pageId }) => {
|
||||||
assertExists(currentWorkspace);
|
return openPage(blockSuiteWorkspace.id, pageId);
|
||||||
return openPage(currentWorkspace.id, pageId);
|
});
|
||||||
},
|
return () => {
|
||||||
[currentWorkspace, openPage]
|
dispose.dispose();
|
||||||
),
|
};
|
||||||
subpageUnlinked: useCallback(
|
},
|
||||||
({ pageId }: { pageId: string }) => {
|
[blockSuiteWorkspace.id, openPage]
|
||||||
deletePin(pageId);
|
);
|
||||||
},
|
|
||||||
[deletePin]
|
|
||||||
),
|
|
||||||
subpageLinked: useCallback(
|
|
||||||
({ pageId }: { pageId: string }) => {
|
|
||||||
const meta = currentPageId && getPageMeta(currentPageId);
|
|
||||||
if (!meta || meta.subpageIds?.includes(pageId)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setPageMeta(currentPageId, {
|
|
||||||
subpageIds: [...(meta.subpageIds ?? []), pageId],
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[currentPageId, getPageMeta, setPageMeta]
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (currentWorkspace) {
|
if (currentWorkspace) {
|
||||||
@@ -90,6 +64,7 @@ const WorkspaceDetail: React.FC = () => {
|
|||||||
<PageDetail
|
<PageDetail
|
||||||
currentWorkspace={currentWorkspace}
|
currentWorkspace={currentWorkspace}
|
||||||
currentPageId={currentPageId}
|
currentPageId={currentPageId}
|
||||||
|
onLoadEditor={onLoad}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else if (currentWorkspace.flavour === WorkspaceFlavour.LOCAL) {
|
} else if (currentWorkspace.flavour === WorkspaceFlavour.LOCAL) {
|
||||||
@@ -99,6 +74,7 @@ const WorkspaceDetail: React.FC = () => {
|
|||||||
<PageDetail
|
<PageDetail
|
||||||
currentWorkspace={currentWorkspace}
|
currentWorkspace={currentWorkspace}
|
||||||
currentPageId={currentPageId}
|
currentPageId={currentPageId}
|
||||||
|
onLoadEditor={onLoad}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export type EditorProps = {
|
|||||||
page: Page;
|
page: Page;
|
||||||
mode: 'page' | 'edgeless';
|
mode: 'page' | 'edgeless';
|
||||||
onInit: (page: Page, editor: Readonly<EditorContainer>) => void;
|
onInit: (page: Page, editor: Readonly<EditorContainer>) => void;
|
||||||
onLoad?: (page: Page, editor: EditorContainer) => void;
|
onLoad?: (page: Page, editor: EditorContainer) => () => void;
|
||||||
style?: CSSProperties;
|
style?: CSSProperties;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -67,7 +67,7 @@ const BlockSuiteEditorImpl = (props: EditorProps): ReactElement => {
|
|||||||
if (page.root === null) {
|
if (page.root === null) {
|
||||||
props.onInit(page, editor);
|
props.onInit(page, editor);
|
||||||
}
|
}
|
||||||
props.onLoad?.(page, editor);
|
return props.onLoad?.(page, editor);
|
||||||
}
|
}
|
||||||
}, [props.page, props.onInit, props.onLoad, editor, props, page]);
|
}, [props.page, props.onInit, props.onLoad, editor, props, page]);
|
||||||
|
|
||||||
|
|||||||
@@ -103,9 +103,6 @@ export const toast = (
|
|||||||
easing: 'cubic-bezier(0.21, 1.02, 0.73, 1)',
|
easing: 'cubic-bezier(0.21, 1.02, 0.73, 1)',
|
||||||
fill: 'forwards' as const,
|
fill: 'forwards' as const,
|
||||||
}; // satisfies KeyframeAnimationOptions;
|
}; // satisfies KeyframeAnimationOptions;
|
||||||
// FIXME: Vitest not support element.animate,
|
|
||||||
// can try it in `apps/web/src/components/__tests__/PinBoard.spec.tsx` `delete pivot`
|
|
||||||
typeof element.animate === 'function' && element.animate(fadeIn, options);
|
|
||||||
|
|
||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
if (typeof element.animate !== 'function') return;
|
if (typeof element.animate !== 'function') return;
|
||||||
|
|||||||
Vendored
+1
-24
@@ -1,8 +1,7 @@
|
|||||||
import { DebugLogger } from '@affine/debug';
|
import { DebugLogger } from '@affine/debug';
|
||||||
import markdown from '@affine/templates/AFFiNE-beta-0.5.4.md';
|
import markdown from '@affine/templates/AFFiNE-beta-0.5.4.md';
|
||||||
import { ContentParser } from '@blocksuite/blocks/content-parser';
|
import { ContentParser } from '@blocksuite/blocks/content-parser';
|
||||||
import type { Page, Workspace } from '@blocksuite/store';
|
import type { Page } from '@blocksuite/store';
|
||||||
import { nanoid } from '@blocksuite/store';
|
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
@@ -57,25 +56,3 @@ export function _initPageWithDemoMarkdown(page: Page): void {
|
|||||||
contentParser.importMarkdown(demoText, frameId);
|
contentParser.importMarkdown(demoText, frameId);
|
||||||
page.workspace.setPageMeta(page.id, { title: demoTitle });
|
page.workspace.setPageMeta(page.id, { title: demoTitle });
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ensureRootPinboard(blockSuiteWorkspace: Workspace) {
|
|
||||||
const metas = blockSuiteWorkspace.meta.pageMetas;
|
|
||||||
const rootMeta = metas.find(m => m.isRootPinboard);
|
|
||||||
|
|
||||||
if (rootMeta) {
|
|
||||||
return rootMeta.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
const rootPinboardPage = blockSuiteWorkspace.createPage(nanoid());
|
|
||||||
|
|
||||||
const title = `${blockSuiteWorkspace.meta.name}'s Pinboard`;
|
|
||||||
|
|
||||||
_initEmptyPage(rootPinboardPage, title);
|
|
||||||
|
|
||||||
blockSuiteWorkspace.meta.setPageMeta(rootPinboardPage.id, {
|
|
||||||
isRootPinboard: true,
|
|
||||||
title,
|
|
||||||
});
|
|
||||||
|
|
||||||
return rootPinboardPage.id;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ declare module '@blocksuite/store' {
|
|||||||
init?: boolean;
|
init?: boolean;
|
||||||
// todo: support `number` in the future
|
// todo: support `number` in the future
|
||||||
isPublic?: boolean;
|
isPublic?: boolean;
|
||||||
isRootPinboard?: boolean;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
// eslint-disable-next-line @typescript-eslint/triple-slash-reference
|
// eslint-disable-next-line @typescript-eslint/triple-slash-reference
|
||||||
/// <reference path='../../../apps/electron/layers/preload/preload.d.ts' />
|
/// <reference path='../../../apps/electron/layers/preload/preload.d.ts' />
|
||||||
import type { Workspace as RemoteWorkspace } from '@affine/workspace/affine/api';
|
import type { Workspace as RemoteWorkspace } from '@affine/workspace/affine/api';
|
||||||
|
import type { EditorContainer } from '@blocksuite/editor';
|
||||||
|
import type { Page } from '@blocksuite/store';
|
||||||
import type { Workspace as BlockSuiteWorkspace } from '@blocksuite/store';
|
import type { Workspace as BlockSuiteWorkspace } from '@blocksuite/store';
|
||||||
import type { createStore } from 'jotai';
|
import type { createStore } from 'jotai';
|
||||||
import type { FC, PropsWithChildren } from 'react';
|
import type { FC, PropsWithChildren } from 'react';
|
||||||
@@ -183,6 +185,7 @@ type SettingProps<Flavour extends keyof WorkspaceRegistry> =
|
|||||||
type PageDetailProps<Flavour extends keyof WorkspaceRegistry> =
|
type PageDetailProps<Flavour extends keyof WorkspaceRegistry> =
|
||||||
UIBaseProps<Flavour> & {
|
UIBaseProps<Flavour> & {
|
||||||
currentPageId: string;
|
currentPageId: string;
|
||||||
|
onLoadEditor: (page: Page, editor: EditorContainer) => () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
type PageListProps<_Flavour extends keyof WorkspaceRegistry> = {
|
type PageListProps<_Flavour extends keyof WorkspaceRegistry> = {
|
||||||
|
|||||||
@@ -1,15 +1,7 @@
|
|||||||
import type { Page } from '@playwright/test';
|
import type { Page } from '@playwright/test';
|
||||||
|
|
||||||
import { getMetas } from './utils';
|
|
||||||
|
|
||||||
export const webUrl = 'http://localhost:8080';
|
export const webUrl = 'http://localhost:8080';
|
||||||
|
|
||||||
export async function openHomePage(page: Page) {
|
export async function openHomePage(page: Page) {
|
||||||
await page.goto(webUrl);
|
await page.goto(webUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function initHomePageWithPinboard(page: Page) {
|
|
||||||
await openHomePage(page);
|
|
||||||
await page.waitForSelector('[data-testid="sidebar-pinboard-container"]');
|
|
||||||
return (await getMetas(page)).find(m => m.isRootPinboard);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -47,24 +47,3 @@ export async function clickPageMoreActions(page: Page) {
|
|||||||
.getByTestId('editor-option-menu')
|
.getByTestId('editor-option-menu')
|
||||||
.click();
|
.click();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated
|
|
||||||
*/
|
|
||||||
export async function createPinboardPage(
|
|
||||||
page: Page,
|
|
||||||
parentId: string,
|
|
||||||
title: string
|
|
||||||
) {
|
|
||||||
await newPage(page);
|
|
||||||
await page.focus('.affine-default-page-block-title');
|
|
||||||
await page.type('.affine-default-page-block-title', title, {
|
|
||||||
delay: 100,
|
|
||||||
});
|
|
||||||
await clickPageMoreActions(page);
|
|
||||||
await page.getByTestId('move-to-menu-item').click();
|
|
||||||
await page
|
|
||||||
.getByTestId('pinboard-menu')
|
|
||||||
.getByTestId(`pinboard-${parentId}`)
|
|
||||||
.click();
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,12 +2,8 @@ import { test } from '@affine-test/kit/playwright';
|
|||||||
import { expect, type Page } from '@playwright/test';
|
import { expect, type Page } from '@playwright/test';
|
||||||
|
|
||||||
import { withCtrlOrMeta } from '../libs/keyboard';
|
import { withCtrlOrMeta } from '../libs/keyboard';
|
||||||
import { initHomePageWithPinboard, openHomePage } from '../libs/load-page';
|
import { openHomePage } from '../libs/load-page';
|
||||||
import {
|
import { newPage, waitMarkdownImported } from '../libs/page-logic';
|
||||||
createPinboardPage,
|
|
||||||
newPage,
|
|
||||||
waitMarkdownImported,
|
|
||||||
} from '../libs/page-logic';
|
|
||||||
|
|
||||||
const openQuickSearchByShortcut = async (page: Page) =>
|
const openQuickSearchByShortcut = async (page: Page) =>
|
||||||
await withCtrlOrMeta(page, () => page.keyboard.press('k', { delay: 50 }));
|
await withCtrlOrMeta(page, () => page.keyboard.press('k', { delay: 50 }));
|
||||||
@@ -171,12 +167,6 @@ test('Focus title after creating a new page', async ({ page }) => {
|
|||||||
await titleIsFocused(page);
|
await titleIsFocused(page);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.skip('Show navigation path if page is a subpage', async ({ page }) => {
|
|
||||||
const rootPinboardMeta = await initHomePageWithPinboard(page);
|
|
||||||
await createPinboardPage(page, rootPinboardMeta?.id ?? '', 'test1');
|
|
||||||
await openQuickSearchByShortcut(page);
|
|
||||||
expect(await page.getByTestId('navigation-path').count()).toBe(1);
|
|
||||||
});
|
|
||||||
test('Not show navigation path if page is not a subpage or current page is not in editor', async ({
|
test('Not show navigation path if page is not a subpage or current page is not in editor', async ({
|
||||||
page,
|
page,
|
||||||
}) => {
|
}) => {
|
||||||
@@ -185,38 +175,3 @@ test('Not show navigation path if page is not a subpage or current page is not i
|
|||||||
await openQuickSearchByShortcut(page);
|
await openQuickSearchByShortcut(page);
|
||||||
expect(await page.getByTestId('navigation-path').count()).toBe(0);
|
expect(await page.getByTestId('navigation-path').count()).toBe(0);
|
||||||
});
|
});
|
||||||
test.skip('Navigation path item click will jump to page, but not current active item', async ({
|
|
||||||
page,
|
|
||||||
}) => {
|
|
||||||
const rootPinboardMeta = await initHomePageWithPinboard(page);
|
|
||||||
await createPinboardPage(page, rootPinboardMeta?.id ?? '', 'test1');
|
|
||||||
await openQuickSearchByShortcut(page);
|
|
||||||
const oldUrl = page.url();
|
|
||||||
expect(
|
|
||||||
await page.locator('[data-testid="navigation-path-link"]').count()
|
|
||||||
).toBe(2);
|
|
||||||
await page.locator('[data-testid="navigation-path-link"]').nth(1).click();
|
|
||||||
expect(page.url()).toBe(oldUrl);
|
|
||||||
await page.locator('[data-testid="navigation-path-link"]').nth(0).click();
|
|
||||||
expect(page.url()).not.toBe(oldUrl);
|
|
||||||
});
|
|
||||||
test.skip('Navigation path expand', async ({ page }) => {
|
|
||||||
//
|
|
||||||
const rootPinboardMeta = await initHomePageWithPinboard(page);
|
|
||||||
await createPinboardPage(page, rootPinboardMeta?.id ?? '', 'test1');
|
|
||||||
await openQuickSearchByShortcut(page);
|
|
||||||
const top = await page
|
|
||||||
.getByTestId('navigation-path-expand-panel')
|
|
||||||
.evaluate(el => {
|
|
||||||
return window.getComputedStyle(el).getPropertyValue('top');
|
|
||||||
});
|
|
||||||
expect(parseInt(top)).toBeLessThan(0);
|
|
||||||
await page.getByTestId('navigation-path-expand-btn').click();
|
|
||||||
await page.waitForTimeout(500);
|
|
||||||
const expandTop = await page
|
|
||||||
.getByTestId('navigation-path-expand-panel')
|
|
||||||
.evaluate(el => {
|
|
||||||
return window.getComputedStyle(el).getPropertyValue('top');
|
|
||||||
});
|
|
||||||
expect(expandTop).toBe('0px');
|
|
||||||
});
|
|
||||||
|
|||||||
Reference in New Issue
Block a user