mirror of
https://github.com/toeverything/AFFiNE.git
synced 2024-12-27 10:23:17 +03:00
refactor: remove unused code (#2484)
This commit is contained in:
parent
281a068cfb
commit
f01997f8ee
@ -81,7 +81,7 @@ export const LocalPlugin: WorkspaceAdapter<WorkspaceFlavour.LOCAL> = {
|
||||
Provider: ({ children }) => {
|
||||
return <>{children}</>;
|
||||
},
|
||||
PageDetail: ({ currentWorkspace, currentPageId }) => {
|
||||
PageDetail: ({ currentWorkspace, currentPageId, onLoadEditor }) => {
|
||||
const page = currentWorkspace.blockSuiteWorkspace.getPage(currentPageId);
|
||||
if (!page) {
|
||||
throw new PageNotFoundError(
|
||||
@ -94,6 +94,7 @@ export const LocalPlugin: WorkspaceAdapter<WorkspaceFlavour.LOCAL> = {
|
||||
<PageDetailEditor
|
||||
pageId={currentPageId}
|
||||
onInit={initPage}
|
||||
onLoad={onLoadEditor}
|
||||
workspace={currentWorkspace}
|
||||
/>
|
||||
</>
|
||||
|
@ -36,6 +36,7 @@ const Editor: React.FC<{
|
||||
globalThis.page = page;
|
||||
// @ts-ignore
|
||||
globalThis.editor = editor;
|
||||
return () => void 0;
|
||||
}, []);
|
||||
|
||||
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',
|
||||
},
|
||||
};
|
||||
});
|
@ -108,14 +108,12 @@ const PageMenu = () => {
|
||||
{mode === 'page' ? t['Edgeless']() : t['Page']()}
|
||||
</MenuItem>
|
||||
<Export />
|
||||
{!pageMeta.isRootPinboard && (
|
||||
<MoveToTrash
|
||||
testId="editor-option-menu-delete"
|
||||
onItemClick={() => {
|
||||
setOpenConfirm(true);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<MoveToTrash
|
||||
testId="editor-option-menu-delete"
|
||||
onItemClick={() => {
|
||||
setOpenConfirm(true);
|
||||
}}
|
||||
/>
|
||||
<div className={styles.horizontalDividerContainer}>
|
||||
<div className={styles.horizontalDivider} />
|
||||
</div>
|
||||
|
@ -21,7 +21,7 @@ export type PageDetailEditorProps = {
|
||||
workspace: AffineOfficialWorkspace;
|
||||
pageId: string;
|
||||
onInit: (page: Page, editor: Readonly<EditorContainer>) => void;
|
||||
onLoad?: (page: Page, editor: EditorContainer) => void;
|
||||
onLoad?: (page: Page, editor: EditorContainer) => () => void;
|
||||
header?: React.ReactNode;
|
||||
};
|
||||
|
||||
@ -85,7 +85,10 @@ export const PageDetailEditor: React.FC<PageDetailEditorProps> = ({
|
||||
updatedDate: Date.now(),
|
||||
});
|
||||
localStorage.setItem('last_page_id', page.id);
|
||||
onLoad?.(page, editor);
|
||||
if (onLoad) {
|
||||
return onLoad(page, editor);
|
||||
}
|
||||
return () => {};
|
||||
},
|
||||
[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';
|
||||
|
||||
export type FavoriteListProps = {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -9,7 +9,7 @@ import { useAtom, useAtomValue } from 'jotai';
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/router';
|
||||
import type { ReactElement } from 'react';
|
||||
import { Suspense, useCallback, useEffect } from 'react';
|
||||
import { Suspense, useEffect } from 'react';
|
||||
|
||||
import {
|
||||
publicPageBlockSuiteAtom,
|
||||
@ -19,7 +19,6 @@ import {
|
||||
import { PageDetailEditor } from '../../../components/page-detail-editor';
|
||||
import { WorkspaceAvatar } from '../../../components/pure/footer';
|
||||
import { PageLoading } from '../../../components/pure/loading';
|
||||
import { useReferenceLinkEffect } from '../../../hooks/affine/use-reference-link-effect';
|
||||
import { useRouterHelper } from '../../../hooks/use-router-helper';
|
||||
import {
|
||||
PublicQuickSearch,
|
||||
@ -64,14 +63,6 @@ const PublicWorkspaceDetailPageInner = (): ReactElement => {
|
||||
}
|
||||
const router = useRouter();
|
||||
const { openPage } = useRouterHelper(router);
|
||||
useReferenceLinkEffect({
|
||||
pageLinkClicked: useCallback(
|
||||
({ pageId }: { pageId: string }) => {
|
||||
return openPage(blockSuiteWorkspace.id, pageId);
|
||||
},
|
||||
[blockSuiteWorkspace.id, openPage]
|
||||
),
|
||||
});
|
||||
const t = useAFFiNEI18N();
|
||||
const [name] = useBlockSuiteWorkspaceName(blockSuiteWorkspace);
|
||||
const [avatar] = useBlockSuiteWorkspaceAvatarUrl(blockSuiteWorkspace);
|
||||
@ -86,6 +77,12 @@ const PublicWorkspaceDetailPageInner = (): ReactElement => {
|
||||
onLoad={(_, editor) => {
|
||||
const { page } = editor;
|
||||
page.awarenessStore.setReadonly(page, true);
|
||||
const dispose = editor.slots.pageLinkClicked.on(({ pageId }) => {
|
||||
return openPage(blockSuiteWorkspace.id, pageId);
|
||||
});
|
||||
return () => {
|
||||
dispose.dispose();
|
||||
};
|
||||
}}
|
||||
onInit={initPage}
|
||||
header={
|
||||
|
@ -4,11 +4,9 @@ import { config } from '@affine/env';
|
||||
import { Unreachable } from '@affine/env/constant';
|
||||
import { rootCurrentPageIdAtom } from '@affine/workspace/atom';
|
||||
import { WorkspaceFlavour } from '@affine/workspace/type';
|
||||
import type { EditorContainer } from '@blocksuite/editor';
|
||||
import type { Page } 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 { useAtomValue } from 'jotai';
|
||||
import { useRouter } from 'next/router';
|
||||
@ -17,9 +15,7 @@ import { useCallback, useEffect } from 'react';
|
||||
|
||||
import { WorkspaceAdapters } from '../../../adapters/workspace';
|
||||
import { rootCurrentWorkspaceAtom } from '../../../atoms/root';
|
||||
import { useReferenceLinkEffect } from '../../../hooks/affine/use-reference-link-effect';
|
||||
import { useCurrentWorkspace } from '../../../hooks/current/use-current-workspace';
|
||||
import { usePinboardHandler } from '../../../hooks/use-pinboard-handler';
|
||||
import { useSyncRecentViewsWithRouter } from '../../../hooks/use-recent-views';
|
||||
import { useRouterHelper } from '../../../hooks/use-router-helper';
|
||||
import { WorkspaceLayout } from '../../../layouts/workspace-layout';
|
||||
@ -42,41 +38,19 @@ const WorkspaceDetail: React.FC = () => {
|
||||
assertExists(currentWorkspace);
|
||||
assertExists(currentPageId);
|
||||
const blockSuiteWorkspace = currentWorkspace.blockSuiteWorkspace;
|
||||
const { setPageMeta, getPageMeta } = usePageMetaHelper(blockSuiteWorkspace);
|
||||
const { deletePin } = usePinboardHandler({
|
||||
blockSuiteWorkspace,
|
||||
metas: useBlockSuitePageMeta(currentWorkspace.blockSuiteWorkspace),
|
||||
});
|
||||
|
||||
useSyncRecentViewsWithRouter(router, blockSuiteWorkspace);
|
||||
|
||||
useReferenceLinkEffect({
|
||||
pageLinkClicked: useCallback(
|
||||
({ pageId }: { pageId: string }) => {
|
||||
assertExists(currentWorkspace);
|
||||
return openPage(currentWorkspace.id, pageId);
|
||||
},
|
||||
[currentWorkspace, openPage]
|
||||
),
|
||||
subpageUnlinked: useCallback(
|
||||
({ pageId }: { pageId: string }) => {
|
||||
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]
|
||||
),
|
||||
});
|
||||
const onLoad = useCallback(
|
||||
(page: Page, editor: EditorContainer) => {
|
||||
const dispose = editor.slots.pageLinkClicked.on(({ pageId }) => {
|
||||
return openPage(blockSuiteWorkspace.id, pageId);
|
||||
});
|
||||
return () => {
|
||||
dispose.dispose();
|
||||
};
|
||||
},
|
||||
[blockSuiteWorkspace.id, openPage]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (currentWorkspace) {
|
||||
@ -90,6 +64,7 @@ const WorkspaceDetail: React.FC = () => {
|
||||
<PageDetail
|
||||
currentWorkspace={currentWorkspace}
|
||||
currentPageId={currentPageId}
|
||||
onLoadEditor={onLoad}
|
||||
/>
|
||||
);
|
||||
} else if (currentWorkspace.flavour === WorkspaceFlavour.LOCAL) {
|
||||
@ -99,6 +74,7 @@ const WorkspaceDetail: React.FC = () => {
|
||||
<PageDetail
|
||||
currentWorkspace={currentWorkspace}
|
||||
currentPageId={currentPageId}
|
||||
onLoadEditor={onLoad}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ export type EditorProps = {
|
||||
page: Page;
|
||||
mode: 'page' | 'edgeless';
|
||||
onInit: (page: Page, editor: Readonly<EditorContainer>) => void;
|
||||
onLoad?: (page: Page, editor: EditorContainer) => void;
|
||||
onLoad?: (page: Page, editor: EditorContainer) => () => void;
|
||||
style?: CSSProperties;
|
||||
};
|
||||
|
||||
@ -67,7 +67,7 @@ const BlockSuiteEditorImpl = (props: EditorProps): ReactElement => {
|
||||
if (page.root === null) {
|
||||
props.onInit(page, editor);
|
||||
}
|
||||
props.onLoad?.(page, editor);
|
||||
return props.onLoad?.(page, editor);
|
||||
}
|
||||
}, [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)',
|
||||
fill: 'forwards' as const,
|
||||
}; // 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 () => {
|
||||
if (typeof element.animate !== 'function') return;
|
||||
|
25
packages/env/src/blocksuite.ts
vendored
25
packages/env/src/blocksuite.ts
vendored
@ -1,8 +1,7 @@
|
||||
import { DebugLogger } from '@affine/debug';
|
||||
import markdown from '@affine/templates/AFFiNE-beta-0.5.4.md';
|
||||
import { ContentParser } from '@blocksuite/blocks/content-parser';
|
||||
import type { Page, Workspace } from '@blocksuite/store';
|
||||
import { nanoid } from '@blocksuite/store';
|
||||
import type { Page } from '@blocksuite/store';
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
@ -57,25 +56,3 @@ export function _initPageWithDemoMarkdown(page: Page): void {
|
||||
contentParser.importMarkdown(demoText, frameId);
|
||||
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;
|
||||
// todo: support `number` in the future
|
||||
isPublic?: boolean;
|
||||
isRootPinboard?: boolean;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,8 @@
|
||||
// eslint-disable-next-line @typescript-eslint/triple-slash-reference
|
||||
/// <reference path='../../../apps/electron/layers/preload/preload.d.ts' />
|
||||
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 { createStore } from 'jotai';
|
||||
import type { FC, PropsWithChildren } from 'react';
|
||||
@ -183,6 +185,7 @@ type SettingProps<Flavour extends keyof WorkspaceRegistry> =
|
||||
type PageDetailProps<Flavour extends keyof WorkspaceRegistry> =
|
||||
UIBaseProps<Flavour> & {
|
||||
currentPageId: string;
|
||||
onLoadEditor: (page: Page, editor: EditorContainer) => () => void;
|
||||
};
|
||||
|
||||
type PageListProps<_Flavour extends keyof WorkspaceRegistry> = {
|
||||
|
@ -1,15 +1,7 @@
|
||||
import type { Page } from '@playwright/test';
|
||||
|
||||
import { getMetas } from './utils';
|
||||
|
||||
export const webUrl = 'http://localhost:8080';
|
||||
|
||||
export async function openHomePage(page: Page) {
|
||||
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')
|
||||
.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 { withCtrlOrMeta } from '../libs/keyboard';
|
||||
import { initHomePageWithPinboard, openHomePage } from '../libs/load-page';
|
||||
import {
|
||||
createPinboardPage,
|
||||
newPage,
|
||||
waitMarkdownImported,
|
||||
} from '../libs/page-logic';
|
||||
import { openHomePage } from '../libs/load-page';
|
||||
import { newPage, waitMarkdownImported } from '../libs/page-logic';
|
||||
|
||||
const openQuickSearchByShortcut = async (page: Page) =>
|
||||
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);
|
||||
});
|
||||
|
||||
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 ({
|
||||
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);
|
||||
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');
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user