refactor: workspace header (#1880)

This commit is contained in:
Himself65 2023-04-11 21:39:39 -05:00 committed by GitHub
parent 2e823c2fee
commit a06113d48c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
42 changed files with 1653 additions and 470 deletions

View File

@ -1,11 +1,16 @@
import { getLoginStorage } from '@affine/workspace/affine/login';
import type { AffinePublicWorkspace } from '@affine/workspace/type';
import { WorkspaceFlavour } from '@affine/workspace/type';
import { createEmptyBlockSuiteWorkspace } from '@affine/workspace/utils';
import { atom } from 'jotai';
import { BlockSuiteWorkspace } from '../../shared';
import { affineApis } from '../../shared/apis';
function createPublicWorkspace(workspaceId: string, binary: ArrayBuffer) {
function createPublicWorkspace(
workspaceId: string,
binary: ArrayBuffer
): AffinePublicWorkspace {
const blockSuiteWorkspace = createEmptyBlockSuiteWorkspace(
workspaceId,
(k: string) =>
@ -22,12 +27,18 @@ function createPublicWorkspace(workspaceId: string, binary: ArrayBuffer) {
blockSuiteWorkspace.awarenessStore.setFlag('enable_edgeless_toolbar', false);
blockSuiteWorkspace.awarenessStore.setFlag('enable_slash_menu', false);
blockSuiteWorkspace.awarenessStore.setFlag('enable_drag_handle', false);
return blockSuiteWorkspace;
return {
flavour: WorkspaceFlavour.PUBLIC,
id: workspaceId,
blockSuiteWorkspace,
// maybe we can add some sync providers here
providers: [],
};
}
export const publicWorkspaceIdAtom = atom<string | null>(null);
export const publicWorkspacePageIdAtom = atom<string | null>(null);
export const publicPageBlockSuiteAtom = atom<Promise<BlockSuiteWorkspace>>(
export const publicPageBlockSuiteAtom = atom<Promise<AffinePublicWorkspace>>(
async get => {
const workspaceId = get(publicWorkspaceIdAtom);
const pageId = get(publicWorkspacePageIdAtom);
@ -41,7 +52,7 @@ export const publicPageBlockSuiteAtom = atom<Promise<BlockSuiteWorkspace>>(
return createPublicWorkspace(workspaceId, binary);
}
);
export const publicBlockSuiteAtom = atom<Promise<BlockSuiteWorkspace>>(
export const publicWorkspaceAtom = atom<Promise<AffinePublicWorkspace>>(
async get => {
const workspaceId = get(publicWorkspaceIdAtom);
if (!workspaceId) {

View File

@ -1,119 +0,0 @@
import { useTranslation } from '@affine/i18n';
import { CloseIcon } from '@blocksuite/icons';
import type { HTMLAttributes, PropsWithChildren } from 'react';
import type React from 'react';
import { forwardRef, useEffect, useMemo, useState } from 'react';
import {
useSidebarFloating,
useSidebarStatus,
} from '../../../hooks/use-sidebar-status';
import { SidebarSwitch } from '../../affine/sidebar-switch';
import { EditorOptionMenu } from './header-right-items/EditorOptionMenu';
import SyncUser from './header-right-items/SyncUser';
import ThemeModeSwitch from './header-right-items/theme-mode-switch';
import TrashButtonGroup from './header-right-items/TrashButtonGroup';
import {
StyledBrowserWarning,
StyledCloseButton,
StyledHeader,
StyledHeaderContainer,
StyledHeaderRightSide,
} from './styles';
import { OSWarningMessage, shouldShowWarning } from './utils';
const BrowserWarning = ({
show,
onClose,
}: {
show: boolean;
onClose: () => void;
}) => {
return (
<StyledBrowserWarning show={show}>
<OSWarningMessage />
<StyledCloseButton onClick={onClose}>
<CloseIcon />
</StyledCloseButton>
</StyledBrowserWarning>
);
};
type HeaderRightItemNames =
| 'editorOptionMenu'
| 'trashButtonGroup'
| 'themeModeSwitch'
| 'syncUser';
const HeaderRightItems: Record<HeaderRightItemNames, React.FC> = {
editorOptionMenu: EditorOptionMenu,
trashButtonGroup: TrashButtonGroup,
themeModeSwitch: ThemeModeSwitch,
syncUser: SyncUser,
};
export type HeaderProps = PropsWithChildren<{
rightItems?: HeaderRightItemNames[];
}>;
export const Header = forwardRef<
HTMLDivElement,
HeaderProps & HTMLAttributes<HTMLDivElement>
>(
(
{ rightItems = ['syncUser', 'themeModeSwitch'], children, ...props },
ref
) => {
const [showWarning, setShowWarning] = useState(false);
useEffect(() => {
setShowWarning(shouldShowWarning());
}, []);
const [open] = useSidebarStatus();
const sidebarFloating = useSidebarFloating();
const { t } = useTranslation();
return (
<StyledHeaderContainer
sidebarFloating={sidebarFloating && open}
ref={ref}
hasWarning={showWarning}
{...props}
>
<BrowserWarning
show={showWarning}
onClose={() => {
setShowWarning(false);
}}
/>
<StyledHeader
hasWarning={showWarning}
data-testid="editor-header-items"
data-tauri-drag-region
>
<SidebarSwitch
visible={!open}
tooltipContent={t('Expand sidebar')}
testid="sliderBar-arrowButton-expand"
/>
{children}
<StyledHeaderRightSide>
{useMemo(
() =>
rightItems.map(itemName => {
const Item = HeaderRightItems[itemName];
return <Item key={itemName} />;
}),
[rightItems]
)}
{/*<ShareMenu />*/}
</StyledHeaderRightSide>
</StyledHeader>
</StyledHeaderContainer>
);
}
);
Header.displayName = 'Header';
export default Header;

View File

@ -1,158 +0,0 @@
import type { PopperProps } from '@affine/component';
import { QuickSearchTips } from '@affine/component';
import { getEnvironment } from '@affine/env';
import { ArrowDownSmallIcon } from '@blocksuite/icons';
import { assertExists } from '@blocksuite/store';
import { useAtomValue, useSetAtom } from 'jotai';
import type { HTMLAttributes } from 'react';
import { forwardRef, useCallback, useRef } from 'react';
import { currentEditorAtom, openQuickSearchModalAtom } from '../../../atoms';
import { useGuideHidden } from '../../../hooks/use-is-first-load';
import { usePageMeta } from '../../../hooks/use-page-meta';
import { useElementResizeEffect } from '../../../hooks/use-workspaces';
import type { BlockSuiteWorkspace } from '../../../shared';
import { PageNotFoundError } from '../../affine/affine-error-eoundary';
import { QuickSearchButton } from '../../pure/quick-search-button';
import { EditorModeSwitch } from './editor-mode-switch';
import Header from './header';
import {
StyledQuickSearchTipButton,
StyledQuickSearchTipContent,
StyledSearchArrowWrapper,
StyledSwitchWrapper,
StyledTitle,
StyledTitleContainer,
StyledTitleWrapper,
} from './styles';
export type BlockSuiteEditorHeaderProps = React.PropsWithChildren<{
blockSuiteWorkspace: BlockSuiteWorkspace;
pageId: string;
isPublic?: boolean;
isPreview?: boolean;
}>;
export const BlockSuiteEditorHeader = forwardRef<
HTMLDivElement,
BlockSuiteEditorHeaderProps & HTMLAttributes<HTMLDivElement>
>(
(
{ blockSuiteWorkspace, pageId, children, isPublic, isPreview, ...props },
ref
) => {
const page = blockSuiteWorkspace.getPage(pageId);
// fixme(himself65): remove this atom and move it to props
const setOpenQuickSearch = useSetAtom(openQuickSearchModalAtom);
if (!page) {
throw new PageNotFoundError(blockSuiteWorkspace, pageId);
}
const pageMeta = usePageMeta(blockSuiteWorkspace).find(
meta => meta.id === pageId
);
assertExists(pageMeta);
const title = pageMeta.title;
const { trash: isTrash } = pageMeta;
const [isTipsHidden, setTipsHidden] = useGuideHidden();
const isMac = () => {
const env = getEnvironment();
return env.isBrowser && env.isMacOs;
};
const popperRef: PopperProps['popperRef'] = useRef(null);
useElementResizeEffect(
useAtomValue(currentEditorAtom),
useCallback(() => {
if (isTipsHidden.quickSearchTips || !popperRef.current) {
return;
}
popperRef.current.update();
}, [isTipsHidden.quickSearchTips])
);
const TipsContent = (
<StyledQuickSearchTipContent>
<div>
Click button
{
<span
style={{
fontSize: '24px',
verticalAlign: 'middle',
}}
>
<ArrowDownSmallIcon />
</span>
}
or use
{isMac() ? ' ⌘ + K' : ' Ctrl + K'} to activate Quick Search. Then you
can search keywords or quickly open recently viewed pages.
</div>
<StyledQuickSearchTipButton
data-testid="quick-search-got-it"
onClick={() =>
setTipsHidden({ ...isTipsHidden, quickSearchTips: true })
}
>
Got it
</StyledQuickSearchTipButton>
</StyledQuickSearchTipContent>
);
return (
<Header
ref={ref}
rightItems={
// fixme(himself65): other right items not supported in public mode
isPublic || isPreview
? ['themeModeSwitch']
: isTrash
? ['trashButtonGroup']
: [
'syncUser',
/* 'shareMenu', */ 'themeModeSwitch',
'editorOptionMenu',
]
}
{...props}
>
{children}
{!isPublic && (
<StyledTitleContainer data-tauri-drag-region>
<StyledTitleWrapper>
<StyledSwitchWrapper>
<EditorModeSwitch
blockSuiteWorkspace={blockSuiteWorkspace}
pageId={pageId}
style={{
marginRight: '12px',
}}
/>
</StyledSwitchWrapper>
<StyledTitle>{title || 'Untitled'}</StyledTitle>
<QuickSearchTips
data-testid="quick-search-tips"
content={TipsContent}
placement="bottom"
popperRef={popperRef}
open={!isTipsHidden.quickSearchTips}
offset={[0, -5]}
>
<StyledSearchArrowWrapper>
<QuickSearchButton
onClick={() => {
setOpenQuickSearch(true);
}}
/>
</StyledSearchArrowWrapper>
</QuickSearchTips>
</StyledTitleWrapper>
</StyledTitleContainer>
)}
</Header>
);
}
);
BlockSuiteEditorHeader.displayName = 'BlockSuiteEditorHeader';

View File

@ -0,0 +1,669 @@
{
"v": "5.9.0",
"fr": 29.9700012207031,
"ip": 0,
"op": 60.0000024438501,
"w": 500,
"h": 500,
"nm": "edgeless-hover",
"ddd": 0,
"assets": [],
"layers": [
{
"ddd": 0,
"ind": 1,
"ty": 4,
"nm": "Layer 3/paper-edgeless-icons Outlines",
"sr": 1,
"ks": {
"o": { "a": 0, "k": 100, "ix": 11 },
"r": { "a": 0, "k": 0, "ix": 10 },
"p": { "a": 0, "k": [250, 250, 0], "ix": 2, "l": 2 },
"a": { "a": 0, "k": [183.5, 183.5, 0], "ix": 1, "l": 2 },
"s": { "a": 0, "k": [100, 100, 100], "ix": 6, "l": 2 }
},
"ao": 0,
"shapes": [
{
"ty": "gr",
"it": [
{
"ind": 0,
"ty": "sh",
"ix": 1,
"ks": {
"a": 0,
"k": {
"i": [
[0, 0],
[0, -5.604],
[0, 0],
[-5.614, 0],
[0, 0],
[0, 5.605],
[0, 0],
[5.615, 0]
],
"o": [
[-5.614, 0],
[0, 0],
[0, 5.605],
[0, 0],
[5.615, 0],
[0, 0],
[0, -5.604],
[0, 0]
],
"v": [
[-30.525, -40.7],
[-40.699, -30.525],
[-40.699, 30.524],
[-30.525, 40.699],
[30.525, 40.699],
[40.699, 30.524],
[40.699, -30.525],
[30.525, -40.7]
],
"c": true
},
"ix": 2
},
"nm": "Path 1",
"mn": "ADBE Vector Shape - Group",
"hd": false
},
{
"ind": 1,
"ty": "sh",
"ix": 2,
"ks": {
"a": 0,
"k": {
"i": [
[22.447, 0],
[0, 0],
[0, 22.437],
[0, 0],
[-22.446, 0],
[0, 0],
[0, -22.437],
[0, 0]
],
"o": [
[0, 0],
[-22.446, 0],
[0, 0],
[0, -22.437],
[0, 0],
[22.447, 0],
[0, 0],
[0, 22.437]
],
"v": [
[30.525, 71.224],
[-30.525, 71.224],
[-71.225, 30.524],
[-71.225, -30.525],
[-30.525, -71.225],
[30.525, -71.225],
[71.224, -30.525],
[71.224, 30.524]
],
"c": true
},
"ix": 2
},
"nm": "Path 2",
"mn": "ADBE Vector Shape - Group",
"hd": false
},
{
"ty": "mm",
"mm": 1,
"nm": "Merge Paths 1",
"mn": "ADBE Vector Filter - Merge",
"hd": false
},
{
"ty": "fl",
"c": {
"a": 0,
"k": [0.466666696586, 0.458823559331, 0.490196108351, 1],
"ix": 4
},
"o": { "a": 0, "k": 100, "ix": 5 },
"r": 1,
"bm": 0,
"nm": "Fill 1",
"mn": "ADBE Vector Graphic - Fill",
"hd": false
},
{
"ty": "tr",
"p": {
"a": 1,
"k": [
{
"i": { "x": 0.833, "y": 0.833 },
"o": { "x": 0.167, "y": 0.167 },
"t": 0,
"s": [295.322, 295.323],
"to": [0, 0],
"ti": [0, 0]
},
{
"i": { "x": 0.833, "y": 0.833 },
"o": { "x": 0.167, "y": 0.167 },
"t": 30,
"s": [315.322, 315.323],
"to": [0, 0],
"ti": [0, 0]
},
{ "t": 58.0000023623884, "s": [295.322, 295.323] }
],
"ix": 2
},
"a": { "a": 0, "k": [0, 0], "ix": 1 },
"s": { "a": 0, "k": [100, 100], "ix": 3 },
"r": { "a": 0, "k": 0, "ix": 6 },
"o": { "a": 0, "k": 100, "ix": 7 },
"sk": { "a": 0, "k": 0, "ix": 4 },
"sa": { "a": 0, "k": 0, "ix": 5 },
"nm": "Transform"
}
],
"nm": "Group 1",
"np": 4,
"cix": 2,
"bm": 0,
"ix": 1,
"mn": "ADBE Vector Group",
"hd": false
},
{
"ty": "gr",
"it": [
{
"ind": 0,
"ty": "sh",
"ix": 1,
"ks": {
"a": 0,
"k": {
"i": [
[8.426, 0],
[0, 8.426],
[0, 0],
[-8.426, 0],
[0, -8.426],
[0, 0]
],
"o": [
[-8.426, 0],
[0, 0],
[0, -8.426],
[8.426, 0],
[0, 0],
[0, 8.426]
],
"v": [
[0, 30.525],
[-15.262, 15.263],
[-15.262, -15.262],
[0, -30.525],
[15.262, -15.262],
[15.262, 15.263]
],
"c": true
},
"ix": 2
},
"nm": "Path 1",
"mn": "ADBE Vector Shape - Group",
"hd": false
},
{
"ty": "fl",
"c": {
"a": 0,
"k": [0.466666696586, 0.458823559331, 0.490196108351, 1],
"ix": 4
},
"o": { "a": 0, "k": 100, "ix": 5 },
"r": 1,
"bm": 0,
"nm": "Fill 1",
"mn": "ADBE Vector Graphic - Fill",
"hd": false
},
{
"ty": "tr",
"p": {
"a": 1,
"k": [
{
"i": { "x": 0.833, "y": 0.833 },
"o": { "x": 0.167, "y": 0.167 },
"t": 0,
"s": [76.561, 183.399],
"to": [0, 0],
"ti": [0, 0]
},
{
"i": { "x": 0.833, "y": 0.833 },
"o": { "x": 0.167, "y": 0.167 },
"t": 30,
"s": [56.561, 183.399],
"to": [0, 0],
"ti": [0, 0]
},
{ "t": 58.0000023623884, "s": [76.561, 183.399] }
],
"ix": 2
},
"a": { "a": 0, "k": [0, 0], "ix": 1 },
"s": { "a": 0, "k": [100, 100], "ix": 3 },
"r": { "a": 0, "k": 0, "ix": 6 },
"o": { "a": 0, "k": 100, "ix": 7 },
"sk": { "a": 0, "k": 0, "ix": 4 },
"sa": { "a": 0, "k": 0, "ix": 5 },
"nm": "Transform"
}
],
"nm": "Group 2",
"np": 2,
"cix": 2,
"bm": 0,
"ix": 2,
"mn": "ADBE Vector Group",
"hd": false
},
{
"ty": "gr",
"it": [
{
"ind": 0,
"ty": "sh",
"ix": 1,
"ks": {
"a": 0,
"k": {
"i": [
[8.426, 0],
[0, 8.426],
[5.395, 13.057],
[9.956, 9.956],
[13.037, 5.406],
[14.1, 0],
[0, 8.426],
[-8.426, 0],
[-16.753, -6.955],
[-12.808, -12.818],
[-6.955, -16.773],
[0, -18.105]
],
"o": [
[-8.426, 0],
[0, -14.09],
[-5.416, -13.016],
[-9.957, -9.956],
[-13.026, -5.385],
[-8.426, 0],
[0, -8.426],
[18.134, 0],
[16.763, 6.936],
[12.788, 12.778],
[6.936, 16.773],
[0, 8.426]
],
"v": [
[61.049, 76.312],
[45.788, 61.05],
[37.66, 20.151],
[14.497, -14.487],
[-20.161, -37.659],
[-61.049, -45.787],
[-76.311, -61.049],
[-61.049, -76.312],
[-8.475, -65.839],
[36.09, -36.069],
[65.858, 8.486],
[76.311, 61.05]
],
"c": true
},
"ix": 2
},
"nm": "Path 1",
"mn": "ADBE Vector Shape - Group",
"hd": false
},
{
"ty": "fl",
"c": {
"a": 0,
"k": [0.466666696586, 0.458823559331, 0.490196108351, 1],
"ix": 4
},
"o": { "a": 0, "k": 100, "ix": 5 },
"r": 1,
"bm": 0,
"nm": "Fill 1",
"mn": "ADBE Vector Graphic - Fill",
"hd": false
},
{
"ty": "tr",
"p": {
"a": 1,
"k": [
{
"i": { "x": 0.833, "y": 0.833 },
"o": { "x": 0.167, "y": 0.167 },
"t": 0,
"s": [254.447, 112.349],
"to": [0, 0],
"ti": [0, 0]
},
{
"i": { "x": 0.833, "y": 0.833 },
"o": { "x": 0.167, "y": 0.167 },
"t": 30,
"s": [274.447, 92.349],
"to": [0, 0],
"ti": [0, 0]
},
{ "t": 58.0000023623884, "s": [254.447, 112.349] }
],
"ix": 2
},
"a": { "a": 0, "k": [0, 0], "ix": 1 },
"s": { "a": 0, "k": [100, 100], "ix": 3 },
"r": { "a": 0, "k": 0, "ix": 6 },
"o": { "a": 0, "k": 100, "ix": 7 },
"sk": { "a": 0, "k": 0, "ix": 4 },
"sa": { "a": 0, "k": 0, "ix": 5 },
"nm": "Transform"
}
],
"nm": "Group 3",
"np": 2,
"cix": 2,
"bm": 0,
"ix": 3,
"mn": "ADBE Vector Group",
"hd": false
},
{
"ty": "gr",
"it": [
{
"ind": 0,
"ty": "sh",
"ix": 1,
"ks": {
"a": 0,
"k": {
"i": [
[22.446, 0],
[0, -22.437],
[-22.446, 0],
[0, 22.436]
],
"o": [
[-22.446, 0],
[0, 22.436],
[22.446, 0],
[0, -22.437]
],
"v": [
[0, -40.7],
[-40.7, 0],
[0, 40.699],
[40.7, 0]
],
"c": true
},
"ix": 2
},
"nm": "Path 1",
"mn": "ADBE Vector Shape - Group",
"hd": false
},
{
"ind": 1,
"ty": "sh",
"ix": 2,
"ks": {
"a": 0,
"k": {
"i": [
[39.269, 0],
[0, 39.268],
[-39.269, 0],
[0, -39.269]
],
"o": [
[-39.269, 0],
[0, -39.269],
[39.269, 0],
[0, 39.268]
],
"v": [
[0, 71.224],
[-71.224, 0],
[0, -71.225],
[71.224, 0]
],
"c": true
},
"ix": 2
},
"nm": "Path 2",
"mn": "ADBE Vector Shape - Group",
"hd": false
},
{
"ty": "mm",
"mm": 1,
"nm": "Merge Paths 1",
"mn": "ADBE Vector Filter - Merge",
"hd": false
},
{
"ty": "fl",
"c": {
"a": 0,
"k": [0.466666696586, 0.458823559331, 0.490196108351, 1],
"ix": 4
},
"o": { "a": 0, "k": 100, "ix": 5 },
"r": 1,
"bm": 0,
"nm": "Fill 1",
"mn": "ADBE Vector Graphic - Fill",
"hd": false
},
{
"ty": "tr",
"p": {
"a": 1,
"k": [
{
"i": { "x": 0.833, "y": 0.833 },
"o": { "x": 0.167, "y": 0.167 },
"t": 0,
"s": [71.474, 295.323],
"to": [0, 0],
"ti": [0, 0]
},
{
"i": { "x": 0.833, "y": 0.833 },
"o": { "x": 0.167, "y": 0.167 },
"t": 30,
"s": [51.474, 315.323],
"to": [0, 0],
"ti": [0, 0]
},
{ "t": 58.0000023623884, "s": [71.474, 295.323] }
],
"ix": 2
},
"a": { "a": 0, "k": [0, 0], "ix": 1 },
"s": { "a": 0, "k": [100, 100], "ix": 3 },
"r": { "a": 0, "k": 0, "ix": 6 },
"o": { "a": 0, "k": 100, "ix": 7 },
"sk": { "a": 0, "k": 0, "ix": 4 },
"sa": { "a": 0, "k": 0, "ix": 5 },
"nm": "Transform"
}
],
"nm": "Group 4",
"np": 4,
"cix": 2,
"bm": 0,
"ix": 4,
"mn": "ADBE Vector Group",
"hd": false
},
{
"ty": "gr",
"it": [
{
"ind": 0,
"ty": "sh",
"ix": 1,
"ks": {
"a": 0,
"k": {
"i": [
[22.446, 0],
[0, -22.437],
[-22.446, 0],
[0, 22.436]
],
"o": [
[-22.446, 0],
[0, 22.436],
[22.446, 0],
[0, -22.437]
],
"v": [
[0, -40.7],
[-40.7, 0.001],
[0, 40.7],
[40.7, 0.001]
],
"c": true
},
"ix": 2
},
"nm": "Path 1",
"mn": "ADBE Vector Shape - Group",
"hd": false
},
{
"ind": 1,
"ty": "sh",
"ix": 2,
"ks": {
"a": 0,
"k": {
"i": [
[39.269, 0],
[0, 39.268],
[-39.269, 0],
[0, -39.269]
],
"o": [
[-39.269, 0],
[0, -39.269],
[39.269, 0],
[0, 39.268]
],
"v": [
[0, 71.225],
[-71.224, 0.001],
[0, -71.225],
[71.224, 0.001]
],
"c": true
},
"ix": 2
},
"nm": "Path 2",
"mn": "ADBE Vector Shape - Group",
"hd": false
},
{
"ty": "mm",
"mm": 1,
"nm": "Merge Paths 1",
"mn": "ADBE Vector Filter - Merge",
"hd": false
},
{
"ty": "fl",
"c": {
"a": 0,
"k": [0.466666696586, 0.458823559331, 0.490196108351, 1],
"ix": 4
},
"o": { "a": 0, "k": 100, "ix": 5 },
"r": 1,
"bm": 0,
"nm": "Fill 1",
"mn": "ADBE Vector Graphic - Fill",
"hd": false
},
{
"ty": "tr",
"p": {
"a": 1,
"k": [
{
"i": { "x": 0.833, "y": 0.833 },
"o": { "x": 0.167, "y": 0.167 },
"t": 0,
"s": [71.474, 71.475],
"to": [0, 0],
"ti": [0, 0]
},
{
"i": { "x": 0.833, "y": 0.833 },
"o": { "x": 0.167, "y": 0.167 },
"t": 30,
"s": [51.474, 51.475],
"to": [0, 0],
"ti": [0, 0]
},
{ "t": 58.0000023623884, "s": [71.474, 71.475] }
],
"ix": 2
},
"a": { "a": 0, "k": [0, 0], "ix": 1 },
"s": { "a": 0, "k": [100, 100], "ix": 3 },
"r": { "a": 0, "k": 0, "ix": 6 },
"o": { "a": 0, "k": 100, "ix": 7 },
"sk": { "a": 0, "k": 0, "ix": 4 },
"sa": { "a": 0, "k": 0, "ix": 5 },
"nm": "Transform"
}
],
"nm": "Group 5",
"np": 4,
"cix": 2,
"bm": 0,
"ix": 5,
"mn": "ADBE Vector Group",
"hd": false
}
],
"ip": 0,
"op": 60.0000024438501,
"st": 0,
"bm": 0
}
],
"markers": []
}

View File

@ -0,0 +1,521 @@
{
"v": "5.9.0",
"fr": 29.9700012207031,
"ip": 0,
"op": 60.0000024438501,
"w": 500,
"h": 500,
"nm": "page-hover",
"ddd": 0,
"assets": [],
"layers": [
{
"ddd": 0,
"ind": 1,
"ty": 4,
"nm": "Layer 1/paper-edgeless-icons Outlines",
"sr": 1,
"ks": {
"o": { "a": 0, "k": 100, "ix": 11 },
"r": { "a": 0, "k": 0, "ix": 10 },
"p": { "a": 0, "k": [250, 250, 0], "ix": 2, "l": 2 },
"a": { "a": 0, "k": [183, 183, 0], "ix": 1, "l": 2 },
"s": { "a": 0, "k": [100, 100, 100], "ix": 6, "l": 2 }
},
"ao": 0,
"shapes": [
{
"ty": "gr",
"it": [
{
"ind": 0,
"ty": "sh",
"ix": 1,
"ks": {
"a": 0,
"k": {
"i": [
[22.557, 0],
[0, 0],
[0, 8.639],
[-8.64, 0],
[0, 0],
[-2.201, 1.651],
[0, 9.536],
[0, 0],
[-8.64, 0],
[0, -8.64],
[0, 0],
[16.892, -16.892]
],
"o": [
[0, 0],
[-8.64, 0],
[0, -8.64],
[0, 0],
[17.442, 0],
[10.412, -10.453],
[0, 0],
[0, -8.64],
[8.639, 0],
[0, 0],
[0, 18.155],
[-9.027, 8.987]
],
"v": [
[46.947, 182.579],
[-109.545, 182.579],
[-125.194, 166.93],
[-109.545, 151.281],
[46.947, 151.281],
[78.001, 145.086],
[93.895, 114.766],
[93.895, -166.93],
[109.545, -182.579],
[125.194, -166.93],
[125.194, 114.766],
[99.743, 167.561]
],
"c": true
},
"ix": 2
},
"nm": "Path 1",
"mn": "ADBE Vector Shape - Group",
"hd": false
},
{
"ty": "fl",
"c": {
"a": 0,
"k": [0.466666696586, 0.458823559331, 0.490196108351, 1],
"ix": 4
},
"o": { "a": 0, "k": 100, "ix": 5 },
"r": 1,
"bm": 0,
"nm": "Fill 1",
"mn": "ADBE Vector Graphic - Fill",
"hd": false
},
{
"ty": "tr",
"p": {
"a": 1,
"k": [
{
"i": { "x": 0.833, "y": 0.833 },
"o": { "x": 0.167, "y": 0.167 },
"t": 0,
"s": [240.204, 182.829],
"to": [0, 0],
"ti": [0, 0]
},
{
"i": { "x": 0.833, "y": 0.833 },
"o": { "x": 0.167, "y": 0.167 },
"t": 30,
"s": [260.204, 202.829],
"to": [0, 0],
"ti": [0, 0]
},
{ "t": 58.0000023623884, "s": [240.204, 182.829] }
],
"ix": 2
},
"a": { "a": 0, "k": [0, 0], "ix": 1 },
"s": { "a": 0, "k": [100, 100], "ix": 3 },
"r": { "a": 0, "k": 0, "ix": 6 },
"o": { "a": 0, "k": 100, "ix": 7 },
"sk": { "a": 0, "k": 0, "ix": 4 },
"sa": { "a": 0, "k": 0, "ix": 5 },
"nm": "Transform"
}
],
"nm": "Group 1",
"np": 2,
"cix": 2,
"bm": 0,
"ix": 1,
"mn": "ADBE Vector Group",
"hd": false
},
{
"ty": "gr",
"it": [
{
"ind": 0,
"ty": "sh",
"ix": 1,
"ks": {
"a": 0,
"k": {
"i": [
[8.64, 0],
[0, 8.639],
[0, 0],
[-16.892, 16.882],
[-22.598, 0],
[0, 0],
[0, -8.64],
[8.639, 0],
[0, 0],
[2.171, -1.65],
[0, -9.546],
[0, 0]
],
"o": [
[-8.64, 0],
[0, 0],
[0, -18.145],
[8.976, -8.986],
[0, 0],
[8.639, 0],
[0, 8.64],
[0, 0],
[-17.453, 0],
[-10.443, 10.484],
[0, 0],
[0, 8.639]
],
"v": [
[-109.545, 182.579],
[-125.194, 166.93],
[-125.194, -114.766],
[-99.743, -167.562],
[-46.948, -182.579],
[109.545, -182.579],
[125.194, -166.93],
[109.545, -151.281],
[-46.948, -151.281],
[-77.972, -145.107],
[-93.896, -114.766],
[-93.896, 166.93]
],
"c": true
},
"ix": 2
},
"nm": "Path 1",
"mn": "ADBE Vector Shape - Group",
"hd": false
},
{
"ty": "fl",
"c": {
"a": 0,
"k": [0.466666696586, 0.458823559331, 0.490196108351, 1],
"ix": 4
},
"o": { "a": 0, "k": 100, "ix": 5 },
"r": 1,
"bm": 0,
"nm": "Fill 1",
"mn": "ADBE Vector Graphic - Fill",
"hd": false
},
{
"ty": "tr",
"p": {
"a": 1,
"k": [
{
"i": { "x": 0.833, "y": 0.833 },
"o": { "x": 0.167, "y": 0.167 },
"t": 0,
"s": [125.443, 182.829],
"to": [0, 0],
"ti": [0, 0]
},
{
"i": { "x": 0.833, "y": 0.833 },
"o": { "x": 0.167, "y": 0.167 },
"t": 30,
"s": [105.443, 162.829],
"to": [0, 0],
"ti": [0, 0]
},
{ "t": 58.0000023623884, "s": [125.443, 182.829] }
],
"ix": 2
},
"a": { "a": 0, "k": [0, 0], "ix": 1 },
"s": { "a": 0, "k": [100, 100], "ix": 3 },
"r": { "a": 0, "k": 0, "ix": 6 },
"o": { "a": 0, "k": 100, "ix": 7 },
"sk": { "a": 0, "k": 0, "ix": 4 },
"sa": { "a": 0, "k": 0, "ix": 5 },
"nm": "Transform"
}
],
"nm": "Group 2",
"np": 2,
"cix": 2,
"bm": 0,
"ix": 2,
"mn": "ADBE Vector Group",
"hd": false
},
{
"ty": "gr",
"it": [
{
"ind": 0,
"ty": "sh",
"ix": 1,
"ks": {
"a": 0,
"k": {
"i": [
[8.64, 0],
[0, 0],
[0, 8.639],
[-8.64, 0],
[0, 0],
[0, -8.64]
],
"o": [
[0, 0],
[-8.64, 0],
[0, -8.64],
[0, 0],
[8.64, 0],
[0, 8.639]
],
"v": [
[67.813, 15.649],
[-67.813, 15.649],
[-83.463, 0],
[-67.813, -15.649],
[67.813, -15.649],
[83.462, 0]
],
"c": true
},
"ix": 2
},
"nm": "Path 1",
"mn": "ADBE Vector Shape - Group",
"hd": false
},
{
"ty": "fl",
"c": {
"a": 0,
"k": [0.466666696586, 0.458823559331, 0.490196108351, 1],
"ix": 4
},
"o": { "a": 0, "k": 100, "ix": 5 },
"r": 1,
"bm": 0,
"nm": "Fill 1",
"mn": "ADBE Vector Graphic - Fill",
"hd": false
},
{
"ty": "tr",
"p": {
"a": 1,
"k": [
{
"i": { "x": 0.833, "y": 0.833 },
"o": { "x": 0.167, "y": 0.167 },
"t": 0,
"s": [182.824, 271.513],
"to": [0, 0],
"ti": [0, 0]
},
{
"i": { "x": 0.833, "y": 0.833 },
"o": { "x": 0.167, "y": 0.167 },
"t": 30,
"s": [212.824, 281.513],
"to": [0, 0],
"ti": [0, 0]
},
{ "t": 58.0000023623884, "s": [182.824, 271.513] }
],
"ix": 2
},
"a": { "a": 0, "k": [0, 0], "ix": 1 },
"s": { "a": 0, "k": [100, 100], "ix": 3 },
"r": { "a": 0, "k": 0, "ix": 6 },
"o": { "a": 0, "k": 100, "ix": 7 },
"sk": { "a": 0, "k": 0, "ix": 4 },
"sa": { "a": 0, "k": 0, "ix": 5 },
"nm": "Transform"
}
],
"nm": "Group 3",
"np": 2,
"cix": 2,
"bm": 0,
"ix": 3,
"mn": "ADBE Vector Group",
"hd": false
},
{
"ty": "gr",
"it": [
{
"ind": 0,
"ty": "sh",
"ix": 1,
"ks": {
"a": 0,
"k": {
"i": [
[0, 0],
[0, -5.757],
[0, 0],
[-5.756, 0],
[0, 0],
[0, 5.756],
[0, 0],
[5.747, 0]
],
"o": [
[-5.756, 0],
[0, 0],
[0, 5.756],
[0, 0],
[5.747, 0],
[0, 0],
[0, -5.757],
[0, 0]
],
"v": [
[-41.732, -31.294],
[-52.165, -20.86],
[-52.165, 20.87],
[-41.732, 31.303],
[41.73, 31.303],
[52.163, 20.87],
[52.163, -20.86],
[41.73, -31.294]
],
"c": true
},
"ix": 2
},
"nm": "Path 1",
"mn": "ADBE Vector Shape - Group",
"hd": false
},
{
"ind": 1,
"ty": "sh",
"ix": 2,
"ks": {
"a": 0,
"k": {
"i": [
[23.006, 0],
[0, 0],
[0, 23.015],
[0, 0],
[-23.015, 0],
[0, 0],
[0, -23.016],
[0, 0]
],
"o": [
[0, 0],
[-23.015, 0],
[0, 0],
[0, -23.016],
[0, 0],
[23.006, 0],
[0, 0],
[0, 23.015]
],
"v": [
[41.73, 62.592],
[-41.732, 62.592],
[-83.463, 20.87],
[-83.463, -20.86],
[-41.732, -62.592],
[41.73, -62.592],
[83.463, -20.86],
[83.463, 20.87]
],
"c": true
},
"ix": 2
},
"nm": "Path 2",
"mn": "ADBE Vector Shape - Group",
"hd": false
},
{
"ty": "mm",
"mm": 1,
"nm": "Merge Paths 1",
"mn": "ADBE Vector Filter - Merge",
"hd": false
},
{
"ty": "fl",
"c": {
"a": 0,
"k": [0.466666696586, 0.458823559331, 0.490196108351, 1],
"ix": 4
},
"o": { "a": 0, "k": 100, "ix": 5 },
"r": 1,
"bm": 0,
"nm": "Fill 1",
"mn": "ADBE Vector Graphic - Fill",
"hd": false
},
{
"ty": "tr",
"p": {
"a": 1,
"k": [
{
"i": { "x": 0.833, "y": 0.833 },
"o": { "x": 0.167, "y": 0.167 },
"t": 0,
"s": [182.824, 141.088],
"to": [0, 0],
"ti": [0, 0]
},
{
"i": { "x": 0.833, "y": 0.833 },
"o": { "x": 0.167, "y": 0.167 },
"t": 30,
"s": [152.824, 131.088],
"to": [0, 0],
"ti": [0, 0]
},
{ "t": 58.0000023623884, "s": [182.824, 141.088] }
],
"ix": 2
},
"a": { "a": 0, "k": [0, 0], "ix": 1 },
"s": { "a": 0, "k": [100, 100], "ix": 3 },
"r": { "a": 0, "k": 0, "ix": 6 },
"o": { "a": 0, "k": 100, "ix": 7 },
"sk": { "a": 0, "k": 0, "ix": 4 },
"sa": { "a": 0, "k": 0, "ix": 5 },
"nm": "Transform"
}
],
"nm": "Group 4",
"np": 4,
"cix": 2,
"bm": 0,
"ix": 4,
"mn": "ADBE Vector Group",
"hd": false
}
],
"ip": 0,
"op": 60.0000024438501,
"st": 0,
"bm": 0
}
],
"markers": []
}

View File

@ -0,0 +1,177 @@
import { useTranslation } from '@affine/i18n';
import { CloseIcon } from '@blocksuite/icons';
import type { Page } from '@blocksuite/store';
import type { HTMLAttributes, PropsWithChildren } from 'react';
import type React from 'react';
import { forwardRef, useEffect, useMemo, useState } from 'react';
import {
useSidebarFloating,
useSidebarStatus,
} from '../../../hooks/use-sidebar-status';
import type { AffineOfficialWorkspace } from '../../../shared';
import { SidebarSwitch } from '../../affine/sidebar-switch';
import { EditorOptionMenu } from './header-right-items/EditorOptionMenu';
import SyncUser from './header-right-items/SyncUser';
import ThemeModeSwitch from './header-right-items/theme-mode-switch';
import TrashButtonGroup from './header-right-items/TrashButtonGroup';
import {
StyledBrowserWarning,
StyledCloseButton,
StyledHeader,
StyledHeaderContainer,
StyledHeaderRightSide,
} from './styles';
import { OSWarningMessage, shouldShowWarning } from './utils';
const BrowserWarning = ({
show,
onClose,
}: {
show: boolean;
onClose: () => void;
}) => {
return (
<StyledBrowserWarning show={show}>
<OSWarningMessage />
<StyledCloseButton onClick={onClose}>
<CloseIcon />
</StyledCloseButton>
</StyledBrowserWarning>
);
};
export type BaseHeaderProps<
Workspace extends AffineOfficialWorkspace = AffineOfficialWorkspace
> = {
workspace: Workspace;
currentPage: Page | null;
isPublic: boolean;
isPreview: boolean;
};
export const enum HeaderRightItemName {
EditorOptionMenu = 'editorOptionMenu',
TrashButtonGroup = 'trashButtonGroup',
ThemeModeSwitch = 'themeModeSwitch',
SyncUser = 'syncUser',
ShareMenu = 'shareMenu',
}
type HeaderItem = {
Component: React.FC<BaseHeaderProps>;
// todo: public workspace should be one of the flavour
availableWhen: (
workspace: AffineOfficialWorkspace,
currentPage: Page | null,
status: {
isPublic: boolean;
isPreview: boolean;
}
) => boolean;
};
const HeaderRightItems: Record<HeaderRightItemName, HeaderItem> = {
[HeaderRightItemName.TrashButtonGroup]: {
Component: TrashButtonGroup,
availableWhen: (_, currentPage) => {
return currentPage?.meta.trash === true;
},
},
[HeaderRightItemName.SyncUser]: {
Component: SyncUser,
availableWhen: (_, currentPage, { isPublic, isPreview }) => {
return !isPublic && !isPreview;
},
},
[HeaderRightItemName.ThemeModeSwitch]: {
Component: ThemeModeSwitch,
availableWhen: (_, currentPage) => {
return currentPage?.meta.trash !== true;
},
},
[HeaderRightItemName.EditorOptionMenu]: {
Component: EditorOptionMenu,
availableWhen: (_, currentPage, { isPublic, isPreview }) => {
return !!currentPage && !isPublic && !isPreview;
},
},
[HeaderRightItemName.ShareMenu]: {
Component: () => null,
availableWhen: (_, currentPage, { isPublic, isPreview }) => {
return false;
},
},
};
export type HeaderProps = BaseHeaderProps;
export const Header = forwardRef<
HTMLDivElement,
PropsWithChildren<HeaderProps> & HTMLAttributes<HTMLDivElement>
>((props, ref) => {
const [showWarning, setShowWarning] = useState(false);
useEffect(() => {
setShowWarning(shouldShowWarning());
}, []);
const [open] = useSidebarStatus();
const sidebarFloating = useSidebarFloating();
const { t } = useTranslation();
return (
<StyledHeaderContainer
sidebarFloating={sidebarFloating && open}
ref={ref}
hasWarning={showWarning}
{...props}
>
<BrowserWarning
show={showWarning}
onClose={() => {
setShowWarning(false);
}}
/>
<StyledHeader
hasWarning={showWarning}
data-testid="editor-header-items"
data-tauri-drag-region
>
<SidebarSwitch
visible={!open}
tooltipContent={t('Expand sidebar')}
testid="sliderBar-arrowButton-expand"
/>
{props.children}
<StyledHeaderRightSide>
{useMemo(() => {
return Object.entries(HeaderRightItems).map(
([name, { availableWhen, Component }]) => {
if (
availableWhen(props.workspace, props.currentPage, {
isPreview: props.isPreview,
isPublic: props.isPublic,
})
) {
return (
<Component
workspace={props.workspace}
currentPage={props.currentPage}
isPreview={props.isPreview}
isPublic={props.isPublic}
key={name}
/>
);
}
return null;
}
);
}, [props])}
{/*<ShareMenu />*/}
</StyledHeaderRightSide>
</StyledHeader>
</StyledHeaderContainer>
);
});
Header.displayName = 'Header';

View File

@ -0,0 +1,128 @@
import type { PopperProps } from '@affine/component';
import { QuickSearchTips } from '@affine/component';
import { getEnvironment } from '@affine/env';
import { ArrowDownSmallIcon } from '@blocksuite/icons';
import { assertExists } from '@blocksuite/store';
import { useAtomValue, useSetAtom } from 'jotai';
import type { HTMLAttributes, PropsWithChildren } from 'react';
import { forwardRef, useCallback, useRef } from 'react';
import { currentEditorAtom, openQuickSearchModalAtom } from '../../../atoms';
import { useGuideHidden } from '../../../hooks/use-is-first-load';
import { usePageMeta } from '../../../hooks/use-page-meta';
import { useElementResizeEffect } from '../../../hooks/use-workspaces';
import { QuickSearchButton } from '../../pure/quick-search-button';
import { EditorModeSwitch } from './editor-mode-switch';
import type { BaseHeaderProps } from './header';
import { Header } from './header';
import {
StyledQuickSearchTipButton,
StyledQuickSearchTipContent,
StyledSearchArrowWrapper,
StyledSwitchWrapper,
StyledTitle,
StyledTitleContainer,
StyledTitleWrapper,
} from './styles';
export type WorkspaceHeaderProps = BaseHeaderProps;
export const WorkspaceHeader = forwardRef<
HTMLDivElement,
PropsWithChildren<WorkspaceHeaderProps> & HTMLAttributes<HTMLDivElement>
>((props, ref) => {
const { workspace, currentPage, children, isPublic } = props;
// fixme(himself65): remove this atom and move it to props
const setOpenQuickSearch = useSetAtom(openQuickSearchModalAtom);
const pageMeta = usePageMeta(workspace.blockSuiteWorkspace).find(
meta => meta.id === currentPage?.id
);
assertExists(pageMeta);
const title = pageMeta.title;
const [isTipsHidden, setTipsHidden] = useGuideHidden();
const isMac = () => {
const env = getEnvironment();
return env.isBrowser && env.isMacOs;
};
const popperRef: PopperProps['popperRef'] = useRef(null);
useElementResizeEffect(
useAtomValue(currentEditorAtom),
useCallback(() => {
if (isTipsHidden.quickSearchTips || !popperRef.current) {
return;
}
popperRef.current.update();
}, [isTipsHidden.quickSearchTips])
);
const TipsContent = (
<StyledQuickSearchTipContent>
<div>
Click button
{
<span
style={{
fontSize: '24px',
verticalAlign: 'middle',
}}
>
<ArrowDownSmallIcon />
</span>
}
or use
{isMac() ? ' ⌘ + K' : ' Ctrl + K'} to activate Quick Search. Then you
can search keywords or quickly open recently viewed pages.
</div>
<StyledQuickSearchTipButton
data-testid="quick-search-got-it"
onClick={() =>
setTipsHidden({ ...isTipsHidden, quickSearchTips: true })
}
>
Got it
</StyledQuickSearchTipButton>
</StyledQuickSearchTipContent>
);
return (
<Header ref={ref} {...props}>
{children}
{!isPublic && currentPage && (
<StyledTitleContainer data-tauri-drag-region>
<StyledTitleWrapper>
<StyledSwitchWrapper>
<EditorModeSwitch
blockSuiteWorkspace={workspace.blockSuiteWorkspace}
pageId={currentPage.id}
style={{
marginRight: '12px',
}}
/>
</StyledSwitchWrapper>
<StyledTitle>{title || 'Untitled'}</StyledTitle>
<QuickSearchTips
data-testid="quick-search-tips"
content={TipsContent}
placement="bottom"
popperRef={popperRef}
open={!isTipsHidden.quickSearchTips}
offset={[0, -5]}
>
<StyledSearchArrowWrapper>
<QuickSearchButton
onClick={() => {
setOpenQuickSearch(true);
}}
/>
</StyledSearchArrowWrapper>
</QuickSearchTips>
</StyledTitleWrapper>
</StyledTitleContainer>
)}
</Header>
);
});
WorkspaceHeader.displayName = 'BlockSuiteEditorHeader';

View File

@ -10,14 +10,14 @@ import { useCallback } from 'react';
import { currentEditorAtom, workspacePreferredModeAtom } from '../atoms';
import { usePageMeta } from '../hooks/use-page-meta';
import type { BlockSuiteWorkspace } from '../shared';
import type { AffineOfficialWorkspace } from '../shared';
import { PageNotFoundError } from './affine/affine-error-eoundary';
import { BlockSuiteEditorHeader } from './blocksuite/header';
import { WorkspaceHeader } from './blocksuite/workspace-header';
export type PageDetailEditorProps = {
isPublic?: boolean;
isPreview?: boolean;
blockSuiteWorkspace: BlockSuiteWorkspace;
workspace: AffineOfficialWorkspace;
pageId: string;
onInit: (page: Page, editor: Readonly<EditorContainer>) => void;
onLoad?: (page: Page, editor: EditorContainer) => void;
@ -33,7 +33,7 @@ const Editor = dynamic(
);
export const PageDetailEditor: React.FC<PageDetailEditorProps> = ({
blockSuiteWorkspace,
workspace,
pageId,
onInit,
onLoad,
@ -41,6 +41,7 @@ export const PageDetailEditor: React.FC<PageDetailEditorProps> = ({
isPublic,
isPreview,
}) => {
const blockSuiteWorkspace = workspace.blockSuiteWorkspace;
const page = blockSuiteWorkspace.getPage(pageId);
if (!page) {
throw new PageNotFoundError(blockSuiteWorkspace, pageId);
@ -58,14 +59,14 @@ export const PageDetailEditor: React.FC<PageDetailEditorProps> = ({
<Head>
<title>{title}</title>
</Head>
<BlockSuiteEditorHeader
isPublic={isPublic}
isPreview={isPreview}
blockSuiteWorkspace={blockSuiteWorkspace}
pageId={pageId}
<WorkspaceHeader
isPublic={isPublic ?? false}
isPreview={isPreview ?? false}
workspace={workspace}
currentPage={page}
>
{header}
</BlockSuiteEditorHeader>
</WorkspaceHeader>
<Editor
style={{
height: 'calc(100% - 52px)',

View File

@ -7,6 +7,8 @@ import {
import { WorkspaceList } from '@affine/component/workspace-list';
import { useTranslation } from '@affine/i18n';
import type { AccessTokenMessage } from '@affine/workspace/affine/login';
import type { AffineWorkspace, LocalWorkspace } from '@affine/workspace/type';
import { WorkspaceFlavour } from '@affine/workspace/type';
import { HelpIcon, PlusIcon } from '@blocksuite/icons';
import type { DragEndEvent } from '@dnd-kit/core';
import { useCallback } from 'react';
@ -99,7 +101,11 @@ export const WorkspaceListModal = ({
<StyledModalContent>
<WorkspaceList
disabled={disabled}
items={workspaces}
items={
workspaces.filter(
({ flavour }) => flavour !== WorkspaceFlavour.PUBLIC
) as (AffineWorkspace | LocalWorkspace)[]
}
currentWorkspaceId={currentWorkspaceId}
onClick={onClickWorkspace}
onSettingClick={onClickWorkspaceSetting}

View File

@ -3,21 +3,25 @@ import type { ReactNode } from 'react';
import type React from 'react';
import { openQuickSearchModalAtom } from '../../../atoms';
import Header from '../../blocksuite/header/header';
import { StyledPageListTittleWrapper } from '../../blocksuite/header/styles';
import type { HeaderProps } from '../../blocksuite/workspace-header/header';
import { Header } from '../../blocksuite/workspace-header/header';
import { StyledPageListTittleWrapper } from '../../blocksuite/workspace-header/styles';
import { QuickSearchButton } from '../quick-search-button';
export type WorkspaceTitleProps = React.PropsWithChildren<{
icon?: ReactNode;
}>;
export type WorkspaceTitleProps = React.PropsWithChildren<
HeaderProps & {
icon?: ReactNode;
}
>;
export const WorkspaceTitle: React.FC<WorkspaceTitleProps> = ({
icon,
children,
...props
}) => {
const setOpenQuickSearch = useSetAtom(openQuickSearchModalAtom);
return (
<Header>
<Header {...props}>
<StyledPageListTittleWrapper>
{icon}
{children}

View File

@ -1,8 +1,10 @@
import { PermissionType } from '@affine/workspace/affine/api';
import { WorkspaceFlavour } from '@affine/workspace/type';
import type { AffineOfficialWorkspace } from '../../shared';
export function useIsWorkspaceOwner(workspace: AffineOfficialWorkspace) {
if (workspace.flavour === 'local') return true;
if (workspace.flavour === WorkspaceFlavour.LOCAL) return true;
if (workspace.flavour === WorkspaceFlavour.PUBLIC) return false;
return workspace.permission === PermissionType.Owner;
}

View File

@ -34,6 +34,9 @@ export function findSuitablePageId(
null
);
}
case WorkspaceFlavour.PUBLIC: {
return null;
}
}
}

View File

@ -19,7 +19,7 @@ import {
openWorkspacesModalAtom,
} from '../atoms';
import {
publicBlockSuiteAtom,
publicWorkspaceAtom,
publicWorkspaceIdAtom,
} from '../atoms/public-workspace';
import { HelpIsland } from '../components/pure/help-island';
@ -62,14 +62,14 @@ const QuickSearchModal = dynamic(
);
export const PublicQuickSearch: FC = () => {
const blockSuiteWorkspace = useAtomValue(publicBlockSuiteAtom);
const publicWorkspace = useAtomValue(publicWorkspaceAtom);
const router = useRouter();
const [openQuickSearchModal, setOpenQuickSearchModalAtom] = useAtom(
openQuickSearchModalAtom
);
return (
<QuickSearchModal
blockSuiteWorkspace={blockSuiteWorkspace}
blockSuiteWorkspace={publicWorkspace.blockSuiteWorkspace}
open={openQuickSearchModal}
setOpen={setOpenQuickSearchModalAtom}
router={router}

View File

@ -9,7 +9,7 @@ import { Suspense } from 'react';
import { openQuickSearchModalAtom } from '../atoms';
import {
publicBlockSuiteAtom,
publicWorkspaceAtom,
publicWorkspaceIdAtom,
} from '../atoms/public-workspace';
import { StyledTableContainer } from '../components/blocksuite/block-suite-page-list/page-list/styles';
@ -21,14 +21,14 @@ const QuickSearchModal = dynamic(
);
export const PublicQuickSearch: React.FC = () => {
const blockSuiteWorkspace = useAtomValue(publicBlockSuiteAtom);
const publicWorkspace = useAtomValue(publicWorkspaceAtom);
const router = useRouter();
const [openQuickSearchModal, setOpenQuickSearchModalAtom] = useAtom(
openQuickSearchModalAtom
);
return (
<QuickSearchModal
blockSuiteWorkspace={blockSuiteWorkspace}
blockSuiteWorkspace={publicWorkspace.blockSuiteWorkspace}
open={openQuickSearchModal}
setOpen={setOpenQuickSearchModalAtom}
router={router}

View File

@ -1,145 +0,0 @@
import { createEmptyBlockSuiteWorkspace } from '@affine/workspace/utils';
import { ContentParser } from '@blocksuite/blocks/content-parser';
import type {
GetStaticPaths,
GetStaticProps,
InferGetStaticPropsType,
NextPage,
} from 'next';
import Head from 'next/head';
import React, { useEffect, useState } from 'react';
import { PageDetailEditor } from '../../components/page-detail-editor';
import { PageLoading } from '../../components/pure/loading';
import {
StyledPage,
StyledToolWrapper,
StyledWrapper,
} from '../../layouts/styles';
import type { BlockSuiteWorkspace } from '../../shared';
export type PreviewPageProps = {
text: string;
title: string;
};
export type PreviewPageParams = {
previewId: string;
};
const PreviewPage: NextPage<PreviewPageProps> = ({
text,
title,
}: InferGetStaticPropsType<typeof getStaticProps>) => {
const [blockSuiteWorkspace, setBlockSuiteWorkspace] =
useState<BlockSuiteWorkspace | null>(null);
useEffect(() => {
const blockSuiteWorkspace = createEmptyBlockSuiteWorkspace(
'preview',
(_: string) => undefined
);
blockSuiteWorkspace.slots.pageAdded.once(() => {
setBlockSuiteWorkspace(blockSuiteWorkspace);
});
blockSuiteWorkspace.createPage('preview');
return () => {
blockSuiteWorkspace.removePage('preview');
};
}, []);
if (!blockSuiteWorkspace || !blockSuiteWorkspace.getPage('preview')) {
return <PageLoading />;
}
return (
<>
<Head>
<title>{title}</title>
</Head>
<StyledPage>
<StyledWrapper>
<PageDetailEditor
isPreview
blockSuiteWorkspace={blockSuiteWorkspace}
pageId="preview"
onInit={(page, editor) => {
blockSuiteWorkspace.setPageMeta(page.id, { title });
const pageBlockId = page.addBlock('affine:page', {
title: new page.Text(title),
});
page.addBlock('affine:surface', {}, null);
const frameId = page.addBlock('affine:frame', {}, pageBlockId);
page.addBlock('affine:paragraph', {}, frameId);
const contentParser = new ContentParser(page);
contentParser.importMarkdown(text, frameId).then(() => {
page.resetHistory();
});
}}
/>
<StyledToolWrapper>
{/* fixme(himself65): remove this */}
<div id="toolWrapper" style={{ marginBottom: '12px' }}>
{/* Slot for block hub */}
</div>
</StyledToolWrapper>
</StyledWrapper>
</StyledPage>
</>
);
};
export default PreviewPage;
export const getStaticProps: GetStaticProps<
PreviewPageProps,
PreviewPageParams
> = async context => {
const name = context.params?.previewId;
const fs = await import('node:fs/promises');
const path = await import('node:path');
const markdown: string = await fs.readFile(
path.resolve(
process.cwd(),
'..',
'..',
'packages',
'templates',
'src',
`${name}.md`
),
'utf8'
);
const title = markdown
.split('\n')
.splice(0, 1)
.join('')
.replaceAll('#', '')
.trim();
if (!name) {
return {
redirect: {
destination: '/404',
},
props: {
text: '',
title: '',
},
};
}
return {
props: {
text: markdown.split('\n').slice(1).join('\n'),
title,
},
};
};
export const getStaticPaths: GetStaticPaths<PreviewPageParams> = () => {
return {
paths: [
{ params: { previewId: 'AFFiNE-Docs' } },
{ params: { previewId: 'Welcome-to-AFFiNE-Abbey-Alpha-Wood' } },
{ params: { previewId: 'Welcome-to-AFFiNE-Alpha-Downhills' } },
{ params: { previewId: 'Welcome-to-the-AFFiNE-Alpha' } },
],
fallback: false,
};
};

View File

@ -10,7 +10,7 @@ import { Suspense, useCallback, useEffect } from 'react';
import { currentWorkspaceIdAtom, openQuickSearchModalAtom } from '../../atoms';
import {
publicBlockSuiteAtom,
publicWorkspaceAtom,
publicWorkspaceIdAtom,
} from '../../atoms/public-workspace';
import { QueryParamError } from '../../components/affine/affine-error-eoundary';
@ -31,7 +31,8 @@ const ListPageInner: React.FC<{
workspaceId: string;
}> = ({ workspaceId }) => {
const router = useRouter();
const blockSuiteWorkspace = useAtomValue(publicBlockSuiteAtom);
const publicWorkspace = useAtomValue(publicWorkspaceAtom);
const blockSuiteWorkspace = publicWorkspace.blockSuiteWorkspace;
const handleClickPage = useCallback(
(pageId: string) => {
return router.push({
@ -84,6 +85,7 @@ const ListPage: NextPageWithLayout = () => {
const router = useRouter();
const workspaceId = router.query.workspaceId;
const setWorkspaceId = useSetAtom(publicWorkspaceIdAtom);
// todo: remove this atom usage here
const setCurrentWorkspaceId = useSetAtom(currentWorkspaceIdAtom);
useEffect(() => {
if (!router.isReady) {

View File

@ -55,7 +55,8 @@ export const StyledBreadcrumbs = styled(Link)(({ theme }) => {
const PublicWorkspaceDetailPageInner: React.FC<{
pageId: string;
}> = ({ pageId }) => {
const blockSuiteWorkspace = useAtomValue(publicPageBlockSuiteAtom);
const publicWorkspace = useAtomValue(publicPageBlockSuiteAtom);
const blockSuiteWorkspace = publicWorkspace.blockSuiteWorkspace;
if (!blockSuiteWorkspace) {
throw new Error('cannot find workspace');
}
@ -83,7 +84,7 @@ const PublicWorkspaceDetailPageInner: React.FC<{
<PageDetailEditor
isPublic={true}
pageId={pageId}
blockSuiteWorkspace={blockSuiteWorkspace}
workspace={publicWorkspace}
onLoad={(_, editor) => {
const { page } = editor;
page.awarenessStore.setReadonly(page, true);

View File

@ -92,7 +92,15 @@ const AllPage: NextPageWithLayout = () => {
<Head>
<title>{t('All Pages')} - AFFiNE</title>
</Head>
<WorkspaceTitle icon={<FolderIcon />}>{t('All pages')}</WorkspaceTitle>
<WorkspaceTitle
workspace={currentWorkspace}
currentPage={null}
isPreview={false}
isPublic={false}
icon={<FolderIcon />}
>
{t('All pages')}
</WorkspaceTitle>
<PageList
onOpenPage={onClickPage}
blockSuiteWorkspace={currentWorkspace.blockSuiteWorkspace}
@ -106,7 +114,15 @@ const AllPage: NextPageWithLayout = () => {
<Head>
<title>{t('All Pages')} - AFFiNE</title>
</Head>
<WorkspaceTitle icon={<FolderIcon />}>{t('All pages')}</WorkspaceTitle>
<WorkspaceTitle
workspace={currentWorkspace}
currentPage={null}
isPreview={false}
isPublic={false}
icon={<FolderIcon />}
>
{t('All pages')}
</WorkspaceTitle>
<PageList
onOpenPage={onClickPage}
blockSuiteWorkspace={currentWorkspace.blockSuiteWorkspace}

View File

@ -41,7 +41,15 @@ const FavouritePage: NextPageWithLayout = () => {
<Head>
<title>{t('Favorites')} - AFFiNE</title>
</Head>
<WorkspaceTitle icon={<FavoriteIcon />}>{t('Favorites')}</WorkspaceTitle>
<WorkspaceTitle
workspace={currentWorkspace}
currentPage={null}
isPreview={false}
isPublic={false}
icon={<FavoriteIcon />}
>
{t('Favorites')}
</WorkspaceTitle>
<PageList
blockSuiteWorkspace={blockSuiteWorkspace}
onClickPage={onClickPage}

View File

@ -148,7 +148,13 @@ const SettingPage: NextPageWithLayout = () => {
<Head>
<title>{t('Settings')} - AFFiNE</title>
</Head>
<WorkspaceTitle icon={<SettingsIcon />}>
<WorkspaceTitle
workspace={currentWorkspace}
currentPage={null}
isPreview={false}
isPublic={false}
icon={<SettingsIcon />}
>
{t('Workspace Settings')}
</WorkspaceTitle>
<Setting
@ -168,7 +174,13 @@ const SettingPage: NextPageWithLayout = () => {
<Head>
<title>{t('Settings')} - AFFiNE</title>
</Head>
<WorkspaceTitle icon={<SettingsIcon />}>
<WorkspaceTitle
workspace={currentWorkspace}
currentPage={null}
isPreview={false}
isPublic={false}
icon={<SettingsIcon />}
>
{t('Workspace Settings')}
</WorkspaceTitle>
<Setting

View File

@ -44,7 +44,13 @@ const TrashPage: NextPageWithLayout = () => {
<Head>
<title>{t('Trash')} - AFFiNE</title>
</Head>
<WorkspaceTitle icon={<DeleteTemporarilyIcon />}>
<WorkspaceTitle
workspace={currentWorkspace}
currentPage={null}
isPreview={false}
isPublic={false}
icon={<DeleteTemporarilyIcon />}
>
{t('Trash')}
</WorkspaceTitle>
<PageList

View File

@ -247,7 +247,7 @@ export const AffinePlugin: WorkspacePlugin<WorkspaceFlavour.AFFINE> = {
<>
<PageDetailEditor
pageId={currentPageId}
blockSuiteWorkspace={currentWorkspace.blockSuiteWorkspace}
workspace={currentWorkspace}
onInit={initPage}
/>
</>

View File

@ -1,10 +1,9 @@
import type {
AppEvents,
LoadPriority,
WorkspaceCRUD,
WorkspaceUISchema,
} from '@affine/workspace/type';
import { WorkspaceFlavour } from '@affine/workspace/type';
import { LoadPriority, WorkspaceFlavour } from '@affine/workspace/type';
import { AffinePlugin } from './affine';
import { LocalPlugin } from './local';
@ -19,9 +18,33 @@ export interface WorkspacePlugin<Flavour extends WorkspaceFlavour> {
UI: WorkspaceUISchema<Flavour>;
}
const unimplemented = () => {
throw new Error('Not implemented');
};
export const WorkspacePlugins = {
[WorkspaceFlavour.AFFINE]: AffinePlugin,
[WorkspaceFlavour.LOCAL]: LocalPlugin,
[WorkspaceFlavour.PUBLIC]: {
flavour: WorkspaceFlavour.PUBLIC,
loadPriority: LoadPriority.LOW,
Events: {
'app:first-init': async () => {},
},
// todo: implement this
CRUD: {
get: unimplemented,
list: unimplemented,
delete: unimplemented,
create: unimplemented,
},
// todo: implement this
UI: {
Provider: unimplemented,
PageDetail: unimplemented,
PageList: unimplemented,
SettingsDetail: unimplemented,
},
},
} satisfies {
[Key in WorkspaceFlavour]: WorkspacePlugin<Key>;
};

View File

@ -59,7 +59,7 @@ export const LocalPlugin: WorkspacePlugin<WorkspaceFlavour.LOCAL> = {
<PageDetailEditor
pageId={currentPageId}
onInit={initPage}
blockSuiteWorkspace={currentWorkspace.blockSuiteWorkspace}
workspace={currentWorkspace}
/>
</>
);

View File

@ -1,11 +1,15 @@
import type { AffineWorkspace, LocalWorkspace } from '@affine/workspace/type';
import type { AffinePublicWorkspace } from '@affine/workspace/type';
import { Workspace as BlockSuiteWorkspace } from '@blocksuite/store';
import type { NextPage } from 'next';
import type { ReactElement, ReactNode } from 'react';
export { BlockSuiteWorkspace };
export type AffineOfficialWorkspace = AffineWorkspace | LocalWorkspace;
export type AffineOfficialWorkspace =
| AffineWorkspace
| LocalWorkspace
| AffinePublicWorkspace;
export type AllWorkspace = AffineOfficialWorkspace;

View File

@ -1,4 +1,8 @@
import type { AffineWorkspace, LocalWorkspace } from '@affine/workspace/type';
import type {
AffinePublicWorkspace,
AffineWorkspace,
LocalWorkspace,
} from '@affine/workspace/type';
import type { Workspace } from '@blocksuite/store';
import * as RadixAvatar from '@radix-ui/react-avatar';
import { useBlockSuiteWorkspaceAvatarUrl } from '@toeverything/hooks/use-blocksuite-workspace-avatar-url';
@ -31,7 +35,7 @@ function stringToColour(str: string) {
export type WorkspaceAvatarProps = {
size?: number;
workspace: LocalWorkspace | AffineWorkspace | null;
workspace: AffineWorkspace | LocalWorkspace | AffinePublicWorkspace | null;
className?: string;
};

View File

@ -54,6 +54,13 @@ export interface LocalWorkspace {
providers: Provider[];
}
export interface AffinePublicWorkspace {
flavour: WorkspaceFlavour.PUBLIC;
id: string;
blockSuiteWorkspace: BlockSuiteWorkspace;
providers: Provider[];
}
export const enum LoadPriority {
HIGH = 1,
MEDIUM = 2,
@ -63,6 +70,7 @@ export const enum LoadPriority {
export const enum WorkspaceFlavour {
AFFINE = 'affine',
LOCAL = 'local',
PUBLIC = 'affine-public',
}
export const settingPanel = {
@ -79,6 +87,7 @@ export type SettingPanel = (typeof settingPanel)[keyof typeof settingPanel];
export interface WorkspaceRegistry {
[WorkspaceFlavour.AFFINE]: AffineWorkspace;
[WorkspaceFlavour.LOCAL]: LocalWorkspace;
[WorkspaceFlavour.PUBLIC]: AffinePublicWorkspace;
}
export interface WorkspaceCRUD<Flavour extends keyof WorkspaceRegistry> {

View File

@ -10,7 +10,7 @@ import {
import { test } from '../libs/playwright';
import { assertCurrentWorkspaceFlavour } from '../libs/workspace';
test.describe('Local first favorite and cancel favorite page', () => {
test.describe('Local first favorite and cancel favorite page', () => {
test('New a page and open it ,then favorite it', async ({ page }) => {
await openHomePage(page);
await waitMarkdownImported(page);