mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-12 04:18:54 +00:00
feat: new setting modal (#2834)
Co-authored-by: Alex Yang <himself65@outlook.com>
This commit is contained in:
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@@ -78,6 +78,7 @@ jobs:
|
|||||||
ENABLE_ALL_PAGE_FILTER: true
|
ENABLE_ALL_PAGE_FILTER: true
|
||||||
ENABLE_LEGACY_PROVIDER: true
|
ENABLE_LEGACY_PROVIDER: true
|
||||||
ENABLE_PRELOADING: false
|
ENABLE_PRELOADING: false
|
||||||
|
ENABLE_NEW_SETTING_MODAL: false
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
@@ -103,6 +104,7 @@ jobs:
|
|||||||
ENABLE_ALL_PAGE_FILTER: true
|
ENABLE_ALL_PAGE_FILTER: true
|
||||||
ENABLE_LEGACY_PROVIDER: false
|
ENABLE_LEGACY_PROVIDER: false
|
||||||
ENABLE_PRELOADING: false
|
ENABLE_PRELOADING: false
|
||||||
|
ENABLE_NEW_SETTING_MODAL: false
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
"@blocksuite/blocks": "0.0.0-20230607055421-9b20fcaf-nightly",
|
"@blocksuite/blocks": "0.0.0-20230607055421-9b20fcaf-nightly",
|
||||||
"@blocksuite/editor": "0.0.0-20230607055421-9b20fcaf-nightly",
|
"@blocksuite/editor": "0.0.0-20230607055421-9b20fcaf-nightly",
|
||||||
"@blocksuite/global": "0.0.0-20230607055421-9b20fcaf-nightly",
|
"@blocksuite/global": "0.0.0-20230607055421-9b20fcaf-nightly",
|
||||||
"@blocksuite/icons": "^2.1.19",
|
"@blocksuite/icons": "^2.1.21",
|
||||||
"@blocksuite/lit": "0.0.0-20230607055421-9b20fcaf-nightly",
|
"@blocksuite/lit": "0.0.0-20230607055421-9b20fcaf-nightly",
|
||||||
"@blocksuite/store": "0.0.0-20230607055421-9b20fcaf-nightly",
|
"@blocksuite/store": "0.0.0-20230607055421-9b20fcaf-nightly",
|
||||||
"@dnd-kit/core": "^6.0.8",
|
"@dnd-kit/core": "^6.0.8",
|
||||||
|
|||||||
@@ -46,4 +46,8 @@ export const buildFlags = {
|
|||||||
process.env.ENABLE_PRELOADING === undefined
|
process.env.ENABLE_PRELOADING === undefined
|
||||||
? true
|
? true
|
||||||
: process.env.ENABLE_PRELOADING === 'true',
|
: process.env.ENABLE_PRELOADING === 'true',
|
||||||
|
enableNewSettingModal:
|
||||||
|
process.env.ENABLE_NEW_SETTING_MODAL === undefined
|
||||||
|
? true
|
||||||
|
: process.env.ENABLE_PRELOADING === 'true',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ import { BlockSuiteWorkspace } from '../../shared';
|
|||||||
import { toast } from '../../utils';
|
import { toast } from '../../utils';
|
||||||
import {
|
import {
|
||||||
BlockSuitePageList,
|
BlockSuitePageList,
|
||||||
|
NewWorkspaceSettingDetail,
|
||||||
PageDetailEditor,
|
PageDetailEditor,
|
||||||
WorkspaceHeader,
|
WorkspaceHeader,
|
||||||
WorkspaceSettingDetail,
|
WorkspaceSettingDetail,
|
||||||
@@ -364,5 +365,18 @@ export const AffineAdapter: WorkspaceAdapter<WorkspaceFlavour.AFFINE> = {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
NewSettingsDetail: ({
|
||||||
|
currentWorkspace,
|
||||||
|
onDeleteWorkspace,
|
||||||
|
onTransformWorkspace,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<NewWorkspaceSettingDetail
|
||||||
|
onDeleteWorkspace={onDeleteWorkspace}
|
||||||
|
workspace={currentWorkspace}
|
||||||
|
onTransferWorkspace={onTransformWorkspace}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import { nanoid } from '@blocksuite/store';
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
BlockSuitePageList,
|
BlockSuitePageList,
|
||||||
|
NewWorkspaceSettingDetail,
|
||||||
PageDetailEditor,
|
PageDetailEditor,
|
||||||
WorkspaceHeader,
|
WorkspaceHeader,
|
||||||
WorkspaceSettingDetail,
|
WorkspaceSettingDetail,
|
||||||
@@ -115,5 +116,18 @@ export const LocalAdapter: WorkspaceAdapter<WorkspaceFlavour.LOCAL> = {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
NewSettingsDetail: ({
|
||||||
|
currentWorkspace,
|
||||||
|
onDeleteWorkspace,
|
||||||
|
onTransformWorkspace,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<NewWorkspaceSettingDetail
|
||||||
|
onDeleteWorkspace={onDeleteWorkspace}
|
||||||
|
workspace={currentWorkspace}
|
||||||
|
onTransferWorkspace={onTransformWorkspace}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { lazy } from 'react';
|
import { lazy } from 'react';
|
||||||
|
// export { WorkspaceSettingDetail as NewWorkspaceSettingDetail } from '../components/affine/new-workspace-setting-detail';
|
||||||
export const WorkspaceSettingDetail = lazy(() =>
|
export const WorkspaceSettingDetail = lazy(() =>
|
||||||
import('../components/affine/workspace-setting-detail').then(
|
import('../components/affine/workspace-setting-detail').then(
|
||||||
({ WorkspaceSettingDetail }) => ({
|
({ WorkspaceSettingDetail }) => ({
|
||||||
@@ -7,6 +7,13 @@ export const WorkspaceSettingDetail = lazy(() =>
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
export const NewWorkspaceSettingDetail = lazy(() =>
|
||||||
|
import('../components/affine/new-workspace-setting-detail').then(
|
||||||
|
({ WorkspaceSettingDetail }) => ({
|
||||||
|
default: WorkspaceSettingDetail,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
export const BlockSuitePageList = lazy(() =>
|
export const BlockSuitePageList = lazy(() =>
|
||||||
import('../components/blocksuite/block-suite-page-list').then(
|
import('../components/blocksuite/block-suite-page-list').then(
|
||||||
({ BlockSuitePageList }) => ({
|
({ BlockSuitePageList }) => ({
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ export const WorkspaceAdapters = {
|
|||||||
PageDetail: unimplemented,
|
PageDetail: unimplemented,
|
||||||
PageList: unimplemented,
|
PageList: unimplemented,
|
||||||
SettingsDetail: unimplemented,
|
SettingsDetail: unimplemented,
|
||||||
|
NewSettingsDetail: unimplemented,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
[WorkspaceFlavour.PUBLIC]: {
|
[WorkspaceFlavour.PUBLIC]: {
|
||||||
@@ -57,6 +58,7 @@ export const WorkspaceAdapters = {
|
|||||||
PageDetail: unimplemented,
|
PageDetail: unimplemented,
|
||||||
PageList: unimplemented,
|
PageList: unimplemented,
|
||||||
SettingsDetail: unimplemented,
|
SettingsDetail: unimplemented,
|
||||||
|
NewSettingsDetail: unimplemented,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} satisfies {
|
} satisfies {
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ export const openWorkspacesModalAtom = atom(false);
|
|||||||
export const openCreateWorkspaceModalAtom = atom<CreateWorkspaceMode>(false);
|
export const openCreateWorkspaceModalAtom = atom<CreateWorkspaceMode>(false);
|
||||||
export const openQuickSearchModalAtom = atom(false);
|
export const openQuickSearchModalAtom = atom(false);
|
||||||
export const openOnboardingModalAtom = atom(false);
|
export const openOnboardingModalAtom = atom(false);
|
||||||
|
export const openSettingModalAtom = atom(false);
|
||||||
|
|
||||||
export const openDisableCloudAlertModalAtom = atom(false);
|
export const openDisableCloudAlertModalAtom = atom(false);
|
||||||
|
|
||||||
|
|||||||
67
apps/web/src/atoms/settings.ts
Normal file
67
apps/web/src/atoms/settings.ts
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
import { useAtom } from 'jotai';
|
||||||
|
import { atomWithStorage } from 'jotai/utils';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
|
export type DateFormats =
|
||||||
|
| 'MM/dd/YYYY'
|
||||||
|
| 'dd/MM/YYYY'
|
||||||
|
| 'YYYY-MM-dd'
|
||||||
|
| 'YYYY.MM.dd'
|
||||||
|
| 'YYYY/MM/dd'
|
||||||
|
| 'dd-MMM-YYYY'
|
||||||
|
| 'dd MMMM YYYY';
|
||||||
|
|
||||||
|
export type AppSetting = {
|
||||||
|
clientBorder: boolean;
|
||||||
|
fullWidthLayout: boolean;
|
||||||
|
windowFrameStyle: 'frameless' | 'NativeTitleBar';
|
||||||
|
dateFormat: DateFormats;
|
||||||
|
startWeekOnMonday: boolean;
|
||||||
|
disableBlurBackground: boolean;
|
||||||
|
disableNoisyBackground: boolean;
|
||||||
|
autoCheckUpdate: boolean;
|
||||||
|
autoDownloadUpdate: boolean;
|
||||||
|
};
|
||||||
|
export const windowFrameStyleOptions: AppSetting['windowFrameStyle'][] = [
|
||||||
|
'frameless',
|
||||||
|
'NativeTitleBar',
|
||||||
|
];
|
||||||
|
|
||||||
|
export const dateFormatOptions: DateFormats[] = [
|
||||||
|
'MM/dd/YYYY',
|
||||||
|
'dd/MM/YYYY',
|
||||||
|
'YYYY-MM-dd',
|
||||||
|
'YYYY.MM.dd',
|
||||||
|
'YYYY/MM/dd',
|
||||||
|
'dd-MMM-YYYY',
|
||||||
|
'dd MMMM YYYY',
|
||||||
|
];
|
||||||
|
|
||||||
|
export const AppSettingAtom = atomWithStorage<AppSetting>('AFFiNE settings', {
|
||||||
|
clientBorder: false,
|
||||||
|
fullWidthLayout: false,
|
||||||
|
windowFrameStyle: 'frameless',
|
||||||
|
dateFormat: dateFormatOptions[0],
|
||||||
|
startWeekOnMonday: false,
|
||||||
|
disableBlurBackground: false,
|
||||||
|
disableNoisyBackground: false,
|
||||||
|
autoCheckUpdate: true,
|
||||||
|
autoDownloadUpdate: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const useAppSetting = () => {
|
||||||
|
const [settings, setSettings] = useAtom(AppSettingAtom);
|
||||||
|
|
||||||
|
return [
|
||||||
|
settings,
|
||||||
|
useCallback(
|
||||||
|
(patch: Partial<AppSetting>) => {
|
||||||
|
setSettings((prev: AppSetting) => ({
|
||||||
|
...prev,
|
||||||
|
...patch,
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
[setSettings]
|
||||||
|
),
|
||||||
|
] as const;
|
||||||
|
};
|
||||||
18
apps/web/src/components/affine/app-container.tsx
Normal file
18
apps/web/src/components/affine/app-container.tsx
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import {
|
||||||
|
AppContainer as AppContainerWithoutSettings,
|
||||||
|
type WorkspaceRootProps,
|
||||||
|
} from '@affine/component/workspace';
|
||||||
|
|
||||||
|
import { useAppSetting } from '../../atoms/settings';
|
||||||
|
|
||||||
|
export const AppContainer = (props: WorkspaceRootProps) => {
|
||||||
|
const [appSettings] = useAppSetting();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AppContainerWithoutSettings
|
||||||
|
useNoisyBackground={!appSettings.disableNoisyBackground}
|
||||||
|
useBlurBackground={!appSettings.disableBlurBackground}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
70
apps/web/src/components/affine/language-menu/index.tsx
Normal file
70
apps/web/src/components/affine/language-menu/index.tsx
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
import { Menu, MenuItem, MenuTrigger, styled } from '@affine/component';
|
||||||
|
import { LOCALES } from '@affine/i18n';
|
||||||
|
import { useI18N } from '@affine/i18n';
|
||||||
|
import type { FC, ReactElement } from 'react';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
|
export const StyledListItem = styled(MenuItem)(() => ({
|
||||||
|
width: '132px',
|
||||||
|
height: '38px',
|
||||||
|
textTransform: 'capitalize',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const LanguageMenuContent: FC<{
|
||||||
|
currentLanguage?: string;
|
||||||
|
}> = ({ currentLanguage }) => {
|
||||||
|
const i18n = useI18N();
|
||||||
|
const changeLanguage = useCallback(
|
||||||
|
(event: string) => {
|
||||||
|
return i18n.changeLanguage(event);
|
||||||
|
},
|
||||||
|
[i18n]
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{LOCALES.map(option => {
|
||||||
|
return (
|
||||||
|
<StyledListItem
|
||||||
|
key={option.name}
|
||||||
|
active={currentLanguage === option.originalName}
|
||||||
|
title={option.name}
|
||||||
|
onClick={() => {
|
||||||
|
changeLanguage(option.tag).catch(err => {
|
||||||
|
throw new Error('Failed to change language', err);
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{option.originalName}
|
||||||
|
</StyledListItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export const LanguageMenu: FC = () => {
|
||||||
|
const i18n = useI18N();
|
||||||
|
|
||||||
|
const currentLanguage = LOCALES.find(item => item.tag === i18n.language);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Menu
|
||||||
|
content={
|
||||||
|
(
|
||||||
|
<LanguageMenuContent
|
||||||
|
currentLanguage={currentLanguage?.originalName}
|
||||||
|
/>
|
||||||
|
) as ReactElement
|
||||||
|
}
|
||||||
|
placement="bottom-end"
|
||||||
|
trigger="click"
|
||||||
|
disablePortal={true}
|
||||||
|
>
|
||||||
|
<MenuTrigger
|
||||||
|
data-testid="language-menu-button"
|
||||||
|
style={{ textTransform: 'capitalize' }}
|
||||||
|
>
|
||||||
|
{currentLanguage?.originalName}
|
||||||
|
</MenuTrigger>
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import type {
|
||||||
|
WorkspaceFlavour,
|
||||||
|
WorkspaceRegistry,
|
||||||
|
} from '@affine/env/workspace';
|
||||||
|
import { useBlockSuiteWorkspaceName } from '@toeverything/hooks/use-block-suite-workspace-name';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
|
import type { AffineOfficialWorkspace } from '../../../shared';
|
||||||
|
|
||||||
|
export type WorkspaceSettingDetailProps = {
|
||||||
|
workspace: AffineOfficialWorkspace;
|
||||||
|
onDeleteWorkspace: () => Promise<void>;
|
||||||
|
onTransferWorkspace: <
|
||||||
|
From extends WorkspaceFlavour,
|
||||||
|
To extends WorkspaceFlavour
|
||||||
|
>(
|
||||||
|
from: From,
|
||||||
|
to: To,
|
||||||
|
workspace: WorkspaceRegistry[From]
|
||||||
|
) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WorkspaceSettingDetail: FC<WorkspaceSettingDetailProps> = ({
|
||||||
|
workspace,
|
||||||
|
}) => {
|
||||||
|
const [workspaceName] = useBlockSuiteWorkspaceName(
|
||||||
|
workspace.blockSuiteWorkspace ?? null
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h2>New Workspace Setting Coming Soon!</h2>
|
||||||
|
|
||||||
|
{workspaceName}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export const AccountSetting = () => {
|
||||||
|
return <div>AccountSetting</div>;
|
||||||
|
};
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
|
import { settingHeader } from './share.css';
|
||||||
|
export const SettingHeader: FC<{ title: string; subtitle?: string }> = ({
|
||||||
|
title,
|
||||||
|
subtitle,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div className={settingHeader}>
|
||||||
|
<div className="title">{title}</div>
|
||||||
|
<div className="subtitle">{subtitle}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import type { CSSProperties, FC, PropsWithChildren, ReactElement } from 'react';
|
||||||
|
|
||||||
|
import { settingRow } from './share.css';
|
||||||
|
|
||||||
|
export const SettingRow: FC<
|
||||||
|
PropsWithChildren<{
|
||||||
|
name: string;
|
||||||
|
desc: string | ReactElement;
|
||||||
|
style?: CSSProperties;
|
||||||
|
onClick?: () => void;
|
||||||
|
}>
|
||||||
|
> = ({ name, desc, children, onClick, style }) => {
|
||||||
|
return (
|
||||||
|
<div className={settingRow} style={style} onClick={onClick}>
|
||||||
|
<div className="left-col">
|
||||||
|
<div className="name">{name}</div>
|
||||||
|
<div className="desc">{desc}</div>
|
||||||
|
</div>
|
||||||
|
<div className="right-col">{children}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
import { globalStyle, style } from '@vanilla-extract/css';
|
||||||
|
|
||||||
|
export const settingHeader = style({
|
||||||
|
height: '68px',
|
||||||
|
borderBottom: '1px solid var(--affine-border-color)',
|
||||||
|
marginBottom: '24px',
|
||||||
|
});
|
||||||
|
|
||||||
|
globalStyle(`${settingHeader} .title`, {
|
||||||
|
fontSize: 'var(--affine-font-base)',
|
||||||
|
fontWeight: 600,
|
||||||
|
lineHeight: '24px',
|
||||||
|
marginBottom: '4px',
|
||||||
|
});
|
||||||
|
|
||||||
|
globalStyle(`${settingHeader} .subtitle`, {
|
||||||
|
fontSize: 'var(--affine-font-xs)',
|
||||||
|
lineHeight: '16px',
|
||||||
|
color: 'var(--affine-text-secondary-color)',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const wrapper = style({
|
||||||
|
borderBottom: '1px solid var(--affine-border-color)',
|
||||||
|
paddingBottom: '24px',
|
||||||
|
marginBottom: '24px',
|
||||||
|
selectors: {
|
||||||
|
'&:last-of-type': {
|
||||||
|
borderBottom: 'none',
|
||||||
|
paddingBottom: '0',
|
||||||
|
marginBottom: '0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
globalStyle(`${wrapper} .title`, {
|
||||||
|
fontSize: 'var(--affine-font-sm)',
|
||||||
|
fontWeight: 600,
|
||||||
|
lineHeight: '18px',
|
||||||
|
color: 'var(--affine-text-secondary-color)',
|
||||||
|
marginBottom: '16px',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const settingRow = style({
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: '25px',
|
||||||
|
color: 'var(--affine-text-primary-color)',
|
||||||
|
});
|
||||||
|
|
||||||
|
globalStyle(`${settingRow} .left-col`, {
|
||||||
|
flexShrink: 0,
|
||||||
|
maxWidth: '80%',
|
||||||
|
});
|
||||||
|
globalStyle(`${settingRow} .name`, {
|
||||||
|
marginBottom: '2px',
|
||||||
|
fontSize: 'var(--affine-font-sm)',
|
||||||
|
fontWeight: 600,
|
||||||
|
});
|
||||||
|
globalStyle(`${settingRow} .desc`, {
|
||||||
|
fontSize: 'var(--affine-font-xs)',
|
||||||
|
color: 'var(--affine-text-secondary-color)',
|
||||||
|
});
|
||||||
|
globalStyle(`${settingRow} .right-col`, {
|
||||||
|
flexGrow: 1,
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
paddingLeft: '15px',
|
||||||
|
});
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import type { FC, PropsWithChildren } from 'react';
|
||||||
|
|
||||||
|
import { wrapper } from './share.css';
|
||||||
|
export const Wrapper: FC<PropsWithChildren<{ title?: string }>> = ({
|
||||||
|
title,
|
||||||
|
children,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div className={wrapper}>
|
||||||
|
{title ? <div className="title">{title}</div> : null}
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
2
apps/web/src/components/affine/setting-modal/config.ts
Normal file
2
apps/web/src/components/affine/setting-modal/config.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
// Some settings are not implemented yet, but need to show in the setting modal when boss is watching.
|
||||||
|
export const IS_EXHIBITION = false;
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
import { Switch } from '@affine/component';
|
||||||
|
import { relatedLinks } from '@affine/component/contact-modal';
|
||||||
|
import { env } from '@affine/env';
|
||||||
|
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||||
|
import { ArrowRightSmallIcon, OpenInNewIcon } from '@blocksuite/icons';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
|
import { type AppSetting, useAppSetting } from '../../../../../atoms/settings';
|
||||||
|
import { SettingHeader } from '../../common/setting-header';
|
||||||
|
import { SettingRow } from '../../common/setting-row';
|
||||||
|
import { Wrapper } from '../../common/wrapper';
|
||||||
|
import { IS_EXHIBITION } from '../../config';
|
||||||
|
import { communityItem, communityWrapper, link } from './style.css';
|
||||||
|
|
||||||
|
export const AboutAffine = () => {
|
||||||
|
const t = useAFFiNEI18N();
|
||||||
|
const [appSettings, setAppSettings] = useAppSetting();
|
||||||
|
const changeSwitch = useCallback(
|
||||||
|
(key: keyof AppSetting, checked: boolean) => {
|
||||||
|
setAppSettings({ [key]: checked });
|
||||||
|
},
|
||||||
|
[setAppSettings]
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SettingHeader title={t['About AFFiNE']()} subtitle={t['None yet']()} />
|
||||||
|
{IS_EXHIBITION && env.isDesktop ? (
|
||||||
|
<Wrapper title={t['Version']()}>
|
||||||
|
<SettingRow
|
||||||
|
name={t['Check for updates']()}
|
||||||
|
desc={t['New version is ready']()}
|
||||||
|
></SettingRow>
|
||||||
|
<SettingRow
|
||||||
|
name={t['Check for updates automatically']()}
|
||||||
|
desc={t[
|
||||||
|
'If enabled, it will automatically check for new versions at regular intervals.'
|
||||||
|
]()}
|
||||||
|
>
|
||||||
|
<Switch
|
||||||
|
checked={appSettings.autoCheckUpdate}
|
||||||
|
onChange={checked => changeSwitch('autoCheckUpdate', checked)}
|
||||||
|
/>
|
||||||
|
</SettingRow>
|
||||||
|
<SettingRow
|
||||||
|
name={t['Download updates automatically']()}
|
||||||
|
desc={t[
|
||||||
|
'If enabled, new versions will be automatically downloaded to the current device.'
|
||||||
|
]()}
|
||||||
|
>
|
||||||
|
<Switch
|
||||||
|
checked={appSettings.autoCheckUpdate}
|
||||||
|
onChange={checked => changeSwitch('autoCheckUpdate', checked)}
|
||||||
|
/>
|
||||||
|
</SettingRow>
|
||||||
|
<SettingRow
|
||||||
|
name={t[`Discover what's new`]()}
|
||||||
|
desc={t['View the AFFiNE Changelog.']()}
|
||||||
|
style={{ cursor: 'pointer' }}
|
||||||
|
onClick={() => {
|
||||||
|
window.open(
|
||||||
|
'https://github.com/toeverything/AFFiNE/releases',
|
||||||
|
'_blank'
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ArrowRightSmallIcon />
|
||||||
|
</SettingRow>
|
||||||
|
</Wrapper>
|
||||||
|
) : null}
|
||||||
|
<Wrapper title={t['Contact with us']()}>
|
||||||
|
<a className={link} href="https://affine.pro" target="_blank">
|
||||||
|
{t['Official Website']()}
|
||||||
|
<OpenInNewIcon className="icon" />
|
||||||
|
</a>
|
||||||
|
<a className={link} href="https://community.affine.pro" target="_blank">
|
||||||
|
{t['AFFiNE Community']()}
|
||||||
|
<OpenInNewIcon className="icon" />
|
||||||
|
</a>
|
||||||
|
</Wrapper>
|
||||||
|
<Wrapper title={t['Communities']()}>
|
||||||
|
<div className={communityWrapper}>
|
||||||
|
{relatedLinks.map(({ icon, title, link }) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={communityItem}
|
||||||
|
onClick={() => {
|
||||||
|
window.open(link, '_blank');
|
||||||
|
}}
|
||||||
|
key={title}
|
||||||
|
>
|
||||||
|
{icon}
|
||||||
|
<p>{title}</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</Wrapper>
|
||||||
|
<Wrapper title={t['Info of legal']()}>
|
||||||
|
<a className={link} href="https://affine.pro/privacy" target="_blank">
|
||||||
|
{t['Privacy']()}
|
||||||
|
<OpenInNewIcon className="icon" />
|
||||||
|
</a>
|
||||||
|
<a className={link} href="https://affine.pro/terms" target="_blank">
|
||||||
|
{t['Terms of Use']()}
|
||||||
|
<OpenInNewIcon className="icon" />
|
||||||
|
</a>
|
||||||
|
</Wrapper>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import { globalStyle, style } from '@vanilla-extract/css';
|
||||||
|
|
||||||
|
export const link = style({
|
||||||
|
height: '18px',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
color: 'var(--affine-text-primary-color)',
|
||||||
|
fontSize: 'var(--affine-font-sm)',
|
||||||
|
fontWeight: 600,
|
||||||
|
marginBottom: '12px',
|
||||||
|
selectors: {
|
||||||
|
'&:last-of-type': {
|
||||||
|
marginBottom: '0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
globalStyle(`${link} .icon`, {
|
||||||
|
color: 'var(--affine-icon-color)',
|
||||||
|
fontSize: 'var(--affine-font-base)',
|
||||||
|
marginLeft: '5px',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const communityWrapper = style({
|
||||||
|
display: 'grid',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
gridTemplateColumns: 'repeat(auto-fill, 84px)',
|
||||||
|
gridGap: '6px',
|
||||||
|
});
|
||||||
|
export const communityItem = style({
|
||||||
|
width: '84px',
|
||||||
|
height: '58px',
|
||||||
|
borderRadius: '8px',
|
||||||
|
border: '1px solid var(--affine-border-color)',
|
||||||
|
color: 'var(--affine-text-primary-color)',
|
||||||
|
cursor: 'pointer',
|
||||||
|
});
|
||||||
|
globalStyle(`${communityItem} svg`, {
|
||||||
|
width: '24px',
|
||||||
|
height: '24px',
|
||||||
|
display: 'block',
|
||||||
|
margin: '8px auto 4px',
|
||||||
|
});
|
||||||
|
globalStyle(`${communityItem} p`, {
|
||||||
|
fontSize: 'var(--affine-font-xs)',
|
||||||
|
textAlign: 'center',
|
||||||
|
});
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
import { Menu, MenuItem, MenuTrigger } from '@affine/component';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import { type FC, useCallback } from 'react';
|
||||||
|
|
||||||
|
import {
|
||||||
|
dateFormatOptions,
|
||||||
|
type DateFormats,
|
||||||
|
useAppSetting,
|
||||||
|
} from '../../../../../atoms/settings';
|
||||||
|
|
||||||
|
const DateFormatMenuContent: FC<{
|
||||||
|
currentOption: DateFormats;
|
||||||
|
onSelect: (option: DateFormats) => void;
|
||||||
|
}> = ({ onSelect, currentOption }) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{dateFormatOptions.map(option => {
|
||||||
|
return (
|
||||||
|
<MenuItem
|
||||||
|
key={option}
|
||||||
|
active={currentOption === option}
|
||||||
|
onClick={() => {
|
||||||
|
onSelect(option);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{dayjs(new Date()).format(option)}
|
||||||
|
</MenuItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export const DateFormatSetting = () => {
|
||||||
|
const [appearanceSettings, setAppSettings] = useAppSetting();
|
||||||
|
const handleSelect = useCallback(
|
||||||
|
(option: DateFormats) => {
|
||||||
|
setAppSettings({ dateFormat: option });
|
||||||
|
},
|
||||||
|
[setAppSettings]
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<Menu
|
||||||
|
content={
|
||||||
|
<DateFormatMenuContent
|
||||||
|
onSelect={handleSelect}
|
||||||
|
currentOption={appearanceSettings.dateFormat}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
placement="bottom-end"
|
||||||
|
trigger="click"
|
||||||
|
disablePortal={true}
|
||||||
|
>
|
||||||
|
<MenuTrigger data-testid="date-format-menu-trigger">
|
||||||
|
{dayjs(new Date()).format(appearanceSettings.dateFormat)}
|
||||||
|
</MenuTrigger>
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,168 @@
|
|||||||
|
import { RadioButton, RadioButtonGroup, Switch } from '@affine/component';
|
||||||
|
import { env } from '@affine/env';
|
||||||
|
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||||
|
import { useTheme } from 'next-themes';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
|
import {
|
||||||
|
type AppSetting,
|
||||||
|
useAppSetting,
|
||||||
|
windowFrameStyleOptions,
|
||||||
|
} from '../../../../../atoms/settings';
|
||||||
|
import { LanguageMenu } from '../../../language-menu';
|
||||||
|
import { SettingHeader } from '../../common/setting-header';
|
||||||
|
import { SettingRow } from '../../common/setting-row';
|
||||||
|
import { Wrapper } from '../../common/wrapper';
|
||||||
|
import { IS_EXHIBITION } from '../../config';
|
||||||
|
import { DateFormatSetting } from './date-format-setting';
|
||||||
|
import { settingWrapper } from './style.css';
|
||||||
|
|
||||||
|
export const ThemeSettings = () => {
|
||||||
|
const t = useAFFiNEI18N();
|
||||||
|
const { setTheme, theme } = useTheme();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RadioButtonGroup
|
||||||
|
className={settingWrapper}
|
||||||
|
defaultValue={theme}
|
||||||
|
onValueChange={useCallback(
|
||||||
|
(value: string) => {
|
||||||
|
setTheme(value);
|
||||||
|
},
|
||||||
|
[setTheme]
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<RadioButton value="system">{t['system']()}</RadioButton>
|
||||||
|
<RadioButton value="light">{t['light']()}</RadioButton>
|
||||||
|
<RadioButton value="dark">{t['dark']()}</RadioButton>
|
||||||
|
</RadioButtonGroup>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AppearanceSettings = () => {
|
||||||
|
const t = useAFFiNEI18N();
|
||||||
|
|
||||||
|
const [appSettings, setAppSettings] = useAppSetting();
|
||||||
|
const changeSwitch = useCallback(
|
||||||
|
(key: keyof AppSetting, checked: boolean) => {
|
||||||
|
setAppSettings({ [key]: checked });
|
||||||
|
},
|
||||||
|
[setAppSettings]
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SettingHeader
|
||||||
|
title={t['Appearance Settings']()}
|
||||||
|
subtitle={t['Customize your AFFiNE Appearance']()}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Wrapper title={t['Theme']()}>
|
||||||
|
<SettingRow
|
||||||
|
name={t['Color Scheme']()}
|
||||||
|
desc={t['Choose your color scheme']()}
|
||||||
|
>
|
||||||
|
<ThemeSettings />
|
||||||
|
</SettingRow>
|
||||||
|
<SettingRow
|
||||||
|
name={t['Display Language']()}
|
||||||
|
desc={t['Select the language for the interface.']()}
|
||||||
|
>
|
||||||
|
<div className={settingWrapper}>
|
||||||
|
<LanguageMenu />
|
||||||
|
</div>
|
||||||
|
</SettingRow>
|
||||||
|
{IS_EXHIBITION && env.isDesktop ? (
|
||||||
|
<SettingRow
|
||||||
|
name={t['Client Border Style']()}
|
||||||
|
desc={t['Customize the appearance of the client.']()}
|
||||||
|
>
|
||||||
|
<Switch
|
||||||
|
checked={appSettings.clientBorder}
|
||||||
|
onChange={checked => changeSwitch('clientBorder', checked)}
|
||||||
|
/>
|
||||||
|
</SettingRow>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
<SettingRow
|
||||||
|
name={t['Full width Layout']()}
|
||||||
|
desc={t['Maximum display of content within a page.']()}
|
||||||
|
>
|
||||||
|
<Switch
|
||||||
|
checked={appSettings.fullWidthLayout}
|
||||||
|
onChange={checked => changeSwitch('fullWidthLayout', checked)}
|
||||||
|
/>
|
||||||
|
</SettingRow>
|
||||||
|
{IS_EXHIBITION && env.isDesktop ? (
|
||||||
|
<SettingRow
|
||||||
|
name={t['Window frame style']()}
|
||||||
|
desc={t['Customize appearance of Windows Client.']()}
|
||||||
|
>
|
||||||
|
<RadioButtonGroup
|
||||||
|
className={settingWrapper}
|
||||||
|
defaultValue={appSettings.windowFrameStyle}
|
||||||
|
onValueChange={(value: AppSetting['windowFrameStyle']) => {
|
||||||
|
setAppSettings({ windowFrameStyle: value });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{windowFrameStyleOptions.map(option => {
|
||||||
|
return (
|
||||||
|
<RadioButton value={option} key={option}>
|
||||||
|
{t[option]()}
|
||||||
|
</RadioButton>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</RadioButtonGroup>
|
||||||
|
</SettingRow>
|
||||||
|
) : null}
|
||||||
|
</Wrapper>
|
||||||
|
{IS_EXHIBITION ? (
|
||||||
|
<Wrapper title={t['Date']()}>
|
||||||
|
<SettingRow
|
||||||
|
name={t['Date Format']()}
|
||||||
|
desc={t['Customize your date style.']()}
|
||||||
|
>
|
||||||
|
<div className={settingWrapper}>
|
||||||
|
<DateFormatSetting />
|
||||||
|
</div>
|
||||||
|
</SettingRow>
|
||||||
|
<SettingRow
|
||||||
|
name={t['Start Week On Monday']()}
|
||||||
|
desc={t['By default, the week starts on Sunday.']()}
|
||||||
|
>
|
||||||
|
<Switch
|
||||||
|
checked={appSettings.startWeekOnMonday}
|
||||||
|
onChange={checked => changeSwitch('startWeekOnMonday', checked)}
|
||||||
|
/>
|
||||||
|
</SettingRow>
|
||||||
|
</Wrapper>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{env.isDesktop ? (
|
||||||
|
<Wrapper title={t['Sidebar']()}>
|
||||||
|
<SettingRow
|
||||||
|
name={t['Disable the noise background on the sidebar']()}
|
||||||
|
desc={t['None yet']()}
|
||||||
|
>
|
||||||
|
<Switch
|
||||||
|
checked={appSettings.disableNoisyBackground}
|
||||||
|
onChange={checked =>
|
||||||
|
changeSwitch('disableNoisyBackground', checked)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</SettingRow>
|
||||||
|
<SettingRow
|
||||||
|
name={t['Disable the blur sidebar']()}
|
||||||
|
desc={t['None yet']()}
|
||||||
|
>
|
||||||
|
<Switch
|
||||||
|
checked={appSettings.disableBlurBackground}
|
||||||
|
onChange={checked =>
|
||||||
|
changeSwitch('disableBlurBackground', checked)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</SettingRow>
|
||||||
|
</Wrapper>
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { style } from '@vanilla-extract/css';
|
||||||
|
|
||||||
|
export const settingWrapper = style({
|
||||||
|
flexGrow: 1,
|
||||||
|
display: 'flex',
|
||||||
|
width: '50%',
|
||||||
|
minWidth: '150px',
|
||||||
|
maxWidth: '250px',
|
||||||
|
});
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import {
|
||||||
|
AppearanceIcon,
|
||||||
|
InformationIcon,
|
||||||
|
KeyboardIcon,
|
||||||
|
} from '@blocksuite/icons';
|
||||||
|
import type { FC, SVGProps } from 'react';
|
||||||
|
|
||||||
|
import { AboutAffine } from './about';
|
||||||
|
import { AppearanceSettings } from './appearance';
|
||||||
|
import { Shortcuts } from './shortcuts';
|
||||||
|
|
||||||
|
export type GeneralSettingKeys = 'shortcuts' | 'appearance' | 'about';
|
||||||
|
|
||||||
|
export type GeneralSettingList = {
|
||||||
|
key: GeneralSettingKeys;
|
||||||
|
title: string;
|
||||||
|
icon: FC<SVGProps<SVGSVGElement>>;
|
||||||
|
}[];
|
||||||
|
|
||||||
|
export const generalSettingList: GeneralSettingList = [
|
||||||
|
{
|
||||||
|
key: 'appearance',
|
||||||
|
title: 'Appearance',
|
||||||
|
icon: AppearanceIcon,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'shortcuts',
|
||||||
|
title: 'Keyboard Shortcuts',
|
||||||
|
icon: KeyboardIcon,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'about',
|
||||||
|
title: 'About AFFiNE',
|
||||||
|
icon: InformationIcon,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const GeneralSetting = ({
|
||||||
|
generalKey,
|
||||||
|
}: {
|
||||||
|
generalKey: GeneralSettingKeys;
|
||||||
|
}) => {
|
||||||
|
switch (generalKey) {
|
||||||
|
case 'shortcuts':
|
||||||
|
return <Shortcuts />;
|
||||||
|
case 'appearance':
|
||||||
|
return <AppearanceSettings />;
|
||||||
|
case 'about':
|
||||||
|
return <AboutAffine />;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||||
|
|
||||||
|
import {
|
||||||
|
useEdgelessShortcuts,
|
||||||
|
useGeneralShortcuts,
|
||||||
|
useMarkdownShortcuts,
|
||||||
|
usePageShortcuts,
|
||||||
|
} from '../../../../../hooks/affine/use-shortcuts';
|
||||||
|
import { SettingHeader } from '../../common/setting-header';
|
||||||
|
import { Wrapper } from '../../common/wrapper';
|
||||||
|
import { shortcutRow } from './style.css';
|
||||||
|
|
||||||
|
export const Shortcuts = () => {
|
||||||
|
const t = useAFFiNEI18N();
|
||||||
|
|
||||||
|
const markdownShortcuts = useMarkdownShortcuts();
|
||||||
|
const pageShortcuts = usePageShortcuts();
|
||||||
|
const edgelessShortcuts = useEdgelessShortcuts();
|
||||||
|
const generalShortcuts = useGeneralShortcuts();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SettingHeader
|
||||||
|
title={t['Keyboard Shortcuts']()}
|
||||||
|
subtitle={t['Check Keyboard Shortcuts quickly']()}
|
||||||
|
/>
|
||||||
|
<Wrapper title={t['General']()}>
|
||||||
|
{Object.entries(generalShortcuts).map(([title, shortcuts]) => {
|
||||||
|
return (
|
||||||
|
<div key={title} className={shortcutRow}>
|
||||||
|
<span>{title}</span>
|
||||||
|
<span className="shortcut">{shortcuts}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Wrapper>
|
||||||
|
<Wrapper title={t['Page']()}>
|
||||||
|
{Object.entries(pageShortcuts).map(([title, shortcuts]) => {
|
||||||
|
return (
|
||||||
|
<div key={title} className={shortcutRow}>
|
||||||
|
<span>{title}</span>
|
||||||
|
<span className="shortcut">{shortcuts}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Wrapper>
|
||||||
|
<Wrapper title={t['Edgeless']()}>
|
||||||
|
{Object.entries(edgelessShortcuts).map(([title, shortcuts]) => {
|
||||||
|
return (
|
||||||
|
<div key={title} className={shortcutRow}>
|
||||||
|
<span>{title}</span>
|
||||||
|
<span className="shortcut">{shortcuts}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Wrapper>
|
||||||
|
<Wrapper title={t['Markdown Syntax']()}>
|
||||||
|
{Object.entries(markdownShortcuts).map(([title, shortcuts]) => {
|
||||||
|
return (
|
||||||
|
<div key={title} className={shortcutRow}>
|
||||||
|
<span>{title}</span>
|
||||||
|
<span className="shortcut">{shortcuts}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Wrapper>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { globalStyle, style } from '@vanilla-extract/css';
|
||||||
|
|
||||||
|
export const shortcutRow = style({
|
||||||
|
height: '32px',
|
||||||
|
marginBottom: '12px',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
fontSize: 'var(--affine-font-base)',
|
||||||
|
selectors: {
|
||||||
|
'&:last-of-type': {
|
||||||
|
marginBottom: '0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
globalStyle(`${shortcutRow} .shortcut`, {
|
||||||
|
border: '1px solid var(--affine-border-color)',
|
||||||
|
borderRadius: '8px',
|
||||||
|
padding: '4px 18px',
|
||||||
|
});
|
||||||
140
apps/web/src/components/affine/setting-modal/index.tsx
Normal file
140
apps/web/src/components/affine/setting-modal/index.tsx
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
import { Modal, ModalCloseButton, ModalWrapper } from '@affine/component';
|
||||||
|
import type {
|
||||||
|
AffineLegacyCloudWorkspace,
|
||||||
|
LocalWorkspace,
|
||||||
|
} from '@affine/env/workspace';
|
||||||
|
import { WorkspaceFlavour } from '@affine/env/workspace';
|
||||||
|
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||||
|
import { ContactWithUsIcon } from '@blocksuite/icons';
|
||||||
|
import type { NextRouter } from 'next/router';
|
||||||
|
import type React from 'react';
|
||||||
|
import { useCallback, useMemo, useState } from 'react';
|
||||||
|
|
||||||
|
import { useCurrentWorkspace } from '../../../hooks/current/use-current-workspace';
|
||||||
|
import { useWorkspaces } from '../../../hooks/use-workspaces';
|
||||||
|
import type { BlockSuiteWorkspace } from '../../../shared';
|
||||||
|
import { AccountSetting } from './account-setting';
|
||||||
|
import {
|
||||||
|
GeneralSetting,
|
||||||
|
type GeneralSettingKeys,
|
||||||
|
generalSettingList,
|
||||||
|
} from './general-setting';
|
||||||
|
import { SettingSidebar } from './setting-sidebar';
|
||||||
|
import { settingContent } from './style.css';
|
||||||
|
import type { Workspace } from './type';
|
||||||
|
import { WorkSpaceSetting } from './workspace-setting';
|
||||||
|
|
||||||
|
export type QuickSearchModalProps = {
|
||||||
|
currentWorkspace?: BlockSuiteWorkspace;
|
||||||
|
workspaceList?: BlockSuiteWorkspace[];
|
||||||
|
open: boolean;
|
||||||
|
setOpen: (value: boolean) => void;
|
||||||
|
router: NextRouter;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SettingModal: React.FC<QuickSearchModalProps> = ({
|
||||||
|
open,
|
||||||
|
setOpen,
|
||||||
|
}) => {
|
||||||
|
const t = useAFFiNEI18N();
|
||||||
|
const workspaces = useWorkspaces();
|
||||||
|
const [currentWorkspace] = useCurrentWorkspace();
|
||||||
|
|
||||||
|
const workspaceList = useMemo(() => {
|
||||||
|
return workspaces.filter(
|
||||||
|
({ flavour }) => flavour !== WorkspaceFlavour.PUBLIC
|
||||||
|
) as Workspace[];
|
||||||
|
}, [workspaces]);
|
||||||
|
|
||||||
|
const [currentRef, setCurrentRef] = useState<{
|
||||||
|
workspace: Workspace | null;
|
||||||
|
generalKey: GeneralSettingKeys | null;
|
||||||
|
isAccount: boolean;
|
||||||
|
}>({
|
||||||
|
workspace: null,
|
||||||
|
generalKey: generalSettingList[0].key,
|
||||||
|
isAccount: false,
|
||||||
|
});
|
||||||
|
const handleClose = useCallback(() => {
|
||||||
|
setOpen(false);
|
||||||
|
}, [setOpen]);
|
||||||
|
|
||||||
|
const onGeneralSettingClick = useCallback((key: GeneralSettingKeys) => {
|
||||||
|
setCurrentRef({
|
||||||
|
workspace: null,
|
||||||
|
generalKey: key,
|
||||||
|
isAccount: false,
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
const onWorkspaceSettingClick = useCallback((workspace: Workspace) => {
|
||||||
|
setCurrentRef({
|
||||||
|
workspace: workspace,
|
||||||
|
generalKey: null,
|
||||||
|
isAccount: false,
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
const onAccountSettingClick = useCallback(() => {
|
||||||
|
setCurrentRef({
|
||||||
|
workspace: null,
|
||||||
|
generalKey: null,
|
||||||
|
isAccount: true,
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
open={open}
|
||||||
|
onClose={handleClose}
|
||||||
|
wrapperPosition={['center', 'center']}
|
||||||
|
data-testid="setting-modal"
|
||||||
|
>
|
||||||
|
<ModalWrapper
|
||||||
|
width={1080}
|
||||||
|
height={760}
|
||||||
|
style={{
|
||||||
|
maxHeight: '85vh',
|
||||||
|
maxWidth: '70vw',
|
||||||
|
overflow: 'hidden',
|
||||||
|
display: 'flex',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ModalCloseButton top={16} right={20} onClick={handleClose} />
|
||||||
|
|
||||||
|
<SettingSidebar
|
||||||
|
generalSettingList={generalSettingList}
|
||||||
|
onGeneralSettingClick={onGeneralSettingClick}
|
||||||
|
currentWorkspace={
|
||||||
|
currentWorkspace as AffineLegacyCloudWorkspace | LocalWorkspace
|
||||||
|
}
|
||||||
|
workspaceList={workspaceList}
|
||||||
|
onWorkspaceSettingClick={onWorkspaceSettingClick}
|
||||||
|
selectedGeneralKey={currentRef.generalKey}
|
||||||
|
selectedWorkspace={currentRef.workspace}
|
||||||
|
onAccountSettingClick={onAccountSettingClick}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className={settingContent}>
|
||||||
|
<div className="wrapper">
|
||||||
|
<div className="content">
|
||||||
|
{currentRef.workspace ? (
|
||||||
|
<WorkSpaceSetting workspace={currentRef.workspace} />
|
||||||
|
) : null}
|
||||||
|
{currentRef.generalKey ? (
|
||||||
|
<GeneralSetting generalKey={currentRef.generalKey} />
|
||||||
|
) : null}
|
||||||
|
{currentRef.isAccount ? <AccountSetting /> : null}
|
||||||
|
</div>
|
||||||
|
<div className="footer">
|
||||||
|
<ContactWithUsIcon />
|
||||||
|
<a href="https://community.affine.pro/home" target="_blank">
|
||||||
|
{t[
|
||||||
|
'Need more customization options? You can suggest them to us in the community.'
|
||||||
|
]()}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ModalWrapper>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
import { WorkspaceAvatar } from '@affine/component/workspace-avatar';
|
||||||
|
import type {
|
||||||
|
AffineLegacyCloudWorkspace,
|
||||||
|
LocalWorkspace,
|
||||||
|
} from '@affine/env/workspace';
|
||||||
|
import { useBlockSuiteWorkspaceName } from '@toeverything/hooks/use-block-suite-workspace-name';
|
||||||
|
import clsx from 'clsx';
|
||||||
|
|
||||||
|
import type {
|
||||||
|
GeneralSettingKeys,
|
||||||
|
GeneralSettingList,
|
||||||
|
} from '../general-setting';
|
||||||
|
import type { Workspace } from '../type';
|
||||||
|
import {
|
||||||
|
accountButton,
|
||||||
|
settingSlideBar,
|
||||||
|
sidebarItemsWrapper,
|
||||||
|
sidebarSelectItem,
|
||||||
|
sidebarSubtitle,
|
||||||
|
sidebarTitle,
|
||||||
|
} from './style.css';
|
||||||
|
|
||||||
|
export const SettingSidebar = ({
|
||||||
|
generalSettingList,
|
||||||
|
onGeneralSettingClick,
|
||||||
|
currentWorkspace,
|
||||||
|
workspaceList,
|
||||||
|
onWorkspaceSettingClick,
|
||||||
|
selectedWorkspace,
|
||||||
|
selectedGeneralKey,
|
||||||
|
onAccountSettingClick,
|
||||||
|
}: {
|
||||||
|
generalSettingList: GeneralSettingList;
|
||||||
|
onGeneralSettingClick: (key: GeneralSettingKeys) => void;
|
||||||
|
currentWorkspace: Workspace;
|
||||||
|
workspaceList: Workspace[];
|
||||||
|
onWorkspaceSettingClick: (
|
||||||
|
workspace: AffineLegacyCloudWorkspace | LocalWorkspace
|
||||||
|
) => void;
|
||||||
|
|
||||||
|
selectedWorkspace: Workspace | null;
|
||||||
|
selectedGeneralKey: string | null;
|
||||||
|
onAccountSettingClick: () => void;
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div className={settingSlideBar}>
|
||||||
|
<div className={sidebarTitle}>Settings</div>
|
||||||
|
<div className={sidebarSubtitle}>General</div>
|
||||||
|
<div className={sidebarItemsWrapper}>
|
||||||
|
{generalSettingList.map(({ title, icon, key }) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={clsx(sidebarSelectItem, {
|
||||||
|
active: key === selectedGeneralKey,
|
||||||
|
})}
|
||||||
|
key={key}
|
||||||
|
title={title}
|
||||||
|
onClick={() => {
|
||||||
|
onGeneralSettingClick(key);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{icon({ className: 'icon' })}
|
||||||
|
<span className="setting-name">{title}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={sidebarSubtitle}>Workspace</div>
|
||||||
|
<div className={clsx(sidebarItemsWrapper, 'scroll')}>
|
||||||
|
{workspaceList.map(workspace => {
|
||||||
|
return (
|
||||||
|
<WorkspaceListItem
|
||||||
|
key={workspace.id}
|
||||||
|
workspace={workspace}
|
||||||
|
onClick={() => {
|
||||||
|
onWorkspaceSettingClick(workspace);
|
||||||
|
}}
|
||||||
|
isCurrent={workspace.id === currentWorkspace.id}
|
||||||
|
isActive={workspace.id === selectedWorkspace?.id}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={accountButton} onClick={onAccountSettingClick}>
|
||||||
|
<div className="avatar"></div>
|
||||||
|
<div className="content">
|
||||||
|
<div className="name" title="xxx">
|
||||||
|
Account NameAccount Name
|
||||||
|
</div>
|
||||||
|
<div className="email" title="xxx">
|
||||||
|
xxxxxxxx@gmail.comxxxxxxxx@gmail.com
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const WorkspaceListItem = ({
|
||||||
|
workspace,
|
||||||
|
onClick,
|
||||||
|
isCurrent,
|
||||||
|
isActive,
|
||||||
|
}: {
|
||||||
|
workspace: AffineLegacyCloudWorkspace | LocalWorkspace;
|
||||||
|
onClick: () => void;
|
||||||
|
isCurrent: boolean;
|
||||||
|
isActive: boolean;
|
||||||
|
}) => {
|
||||||
|
const [workspaceName] = useBlockSuiteWorkspaceName(
|
||||||
|
workspace.blockSuiteWorkspace ?? null
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={clsx(sidebarSelectItem, { active: isActive })}
|
||||||
|
title={workspaceName}
|
||||||
|
onClick={onClick}
|
||||||
|
>
|
||||||
|
<WorkspaceAvatar size={14} workspace={workspace} className="icon" />
|
||||||
|
<span className="setting-name">{workspaceName}</span>
|
||||||
|
{isCurrent ? <div className="current-label">Current</div> : null}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
import { globalStyle, style } from '@vanilla-extract/css';
|
||||||
|
|
||||||
|
export const settingSlideBar = style({
|
||||||
|
width: '25%',
|
||||||
|
maxWidth: '242px',
|
||||||
|
// TODO: use color variable
|
||||||
|
// background: 'var(--affine-background-secondary-color)',
|
||||||
|
backgroundColor: '#F4F4F5',
|
||||||
|
padding: '20px 16px',
|
||||||
|
height: '100%',
|
||||||
|
flexShrink: 0,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const sidebarTitle = style({
|
||||||
|
fontSize: 'var(--affine-font-h-6)',
|
||||||
|
fontWeight: '600',
|
||||||
|
lineHeight: 'var(--affine-line-height)',
|
||||||
|
paddingLeft: '8px',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const sidebarSubtitle = style({
|
||||||
|
fontSize: 'var(--affine-font-sm)',
|
||||||
|
lineHeight: 'var(--affine-line-height)',
|
||||||
|
color: 'var(--affine-text-secondary-color)',
|
||||||
|
paddingLeft: '8px',
|
||||||
|
marginTop: '20px',
|
||||||
|
marginBottom: '4px',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const sidebarItemsWrapper = style({
|
||||||
|
selectors: {
|
||||||
|
'&.scroll': {
|
||||||
|
flexGrow: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const sidebarSelectItem = style({
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
padding: '0 8px',
|
||||||
|
height: '30px',
|
||||||
|
marginBottom: '4px',
|
||||||
|
fontSize: 'var(--affine-font-sm)',
|
||||||
|
borderRadius: '8px',
|
||||||
|
cursor: 'pointer',
|
||||||
|
userSelect: 'none',
|
||||||
|
':hover': {
|
||||||
|
background: 'var(--affine-hover-color)',
|
||||||
|
},
|
||||||
|
selectors: {
|
||||||
|
'&.active': {
|
||||||
|
background: 'var(--affine-hover-color)',
|
||||||
|
},
|
||||||
|
[`${sidebarItemsWrapper} &:last-of-type`]: {
|
||||||
|
marginBottom: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
globalStyle(`${settingSlideBar} .icon`, {
|
||||||
|
width: '16px',
|
||||||
|
height: '16px',
|
||||||
|
marginRight: '10px',
|
||||||
|
flexShrink: 0,
|
||||||
|
});
|
||||||
|
globalStyle(`${settingSlideBar} .setting-name`, {
|
||||||
|
minWidth: 0,
|
||||||
|
overflow: 'hidden',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
flexGrow: 1,
|
||||||
|
});
|
||||||
|
globalStyle(`${settingSlideBar} .current-label`, {
|
||||||
|
height: '20px',
|
||||||
|
borderRadius: '8px',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
padding: '0 5px',
|
||||||
|
// TODO: use color variable
|
||||||
|
background: '#1E96EB',
|
||||||
|
fontSize: 'var(--affine-font-xs)',
|
||||||
|
fontWeight: '600',
|
||||||
|
color: 'var(--affine-white)',
|
||||||
|
marginLeft: '10px',
|
||||||
|
flexShrink: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const accountButton = style({
|
||||||
|
height: '42px',
|
||||||
|
padding: '4px 8px',
|
||||||
|
borderRadius: '8px',
|
||||||
|
cursor: 'pointer',
|
||||||
|
userSelect: 'none',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
':hover': {
|
||||||
|
background: 'var(--affine-hover-color)',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
globalStyle(`${accountButton} .avatar`, {
|
||||||
|
width: '28px',
|
||||||
|
height: '28px',
|
||||||
|
border: '1px solid',
|
||||||
|
borderColor: 'var(--affine-white)',
|
||||||
|
borderRadius: '14px',
|
||||||
|
flexShrink: '0',
|
||||||
|
marginRight: '10px',
|
||||||
|
background: 'red',
|
||||||
|
});
|
||||||
|
globalStyle(`${accountButton} .content`, {
|
||||||
|
flexGrow: '1',
|
||||||
|
minWidth: 0,
|
||||||
|
});
|
||||||
|
globalStyle(`${accountButton} .name`, {
|
||||||
|
fontSize: 'var(--affine-font-sm)',
|
||||||
|
fontWeight: 600,
|
||||||
|
overflow: 'hidden',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
flexGrow: 1,
|
||||||
|
});
|
||||||
|
globalStyle(`${accountButton} .email`, {
|
||||||
|
fontSize: 'var(--affine-font-xs)',
|
||||||
|
color: 'var(--affine-text-secondary-color)',
|
||||||
|
overflow: 'hidden',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
flexGrow: 1,
|
||||||
|
});
|
||||||
38
apps/web/src/components/affine/setting-modal/style.css.ts
Normal file
38
apps/web/src/components/affine/setting-modal/style.css.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import { globalStyle, style } from '@vanilla-extract/css';
|
||||||
|
|
||||||
|
export const settingContent = style({
|
||||||
|
flexGrow: '1',
|
||||||
|
height: '100%',
|
||||||
|
padding: '40px 0',
|
||||||
|
});
|
||||||
|
|
||||||
|
globalStyle(`${settingContent} .wrapper`, {
|
||||||
|
width: '66%',
|
||||||
|
minWidth: '450px',
|
||||||
|
height: '100%',
|
||||||
|
maxWidth: '560px',
|
||||||
|
margin: '0 auto',
|
||||||
|
overflowY: 'auto',
|
||||||
|
});
|
||||||
|
globalStyle(`${settingContent} .content`, {
|
||||||
|
minHeight: '100%',
|
||||||
|
paddingBottom: '80px',
|
||||||
|
});
|
||||||
|
globalStyle(`${settingContent} .footer`, {
|
||||||
|
cursor: 'pointer',
|
||||||
|
paddingTop: '40px',
|
||||||
|
marginTop: '-80px',
|
||||||
|
fontSize: 'var(--affine-font-sm)',
|
||||||
|
display: 'flex',
|
||||||
|
});
|
||||||
|
|
||||||
|
globalStyle(`${settingContent} .footer a`, {
|
||||||
|
color: 'var(--affine-text-primary-color)',
|
||||||
|
});
|
||||||
|
|
||||||
|
globalStyle(`${settingContent} .footer > svg`, {
|
||||||
|
fontSize: 'var(--affine-font-base)',
|
||||||
|
color: 'var(--affine-icon-color)',
|
||||||
|
marginRight: '12px',
|
||||||
|
marginTop: '2px',
|
||||||
|
});
|
||||||
6
apps/web/src/components/affine/setting-modal/type.ts
Normal file
6
apps/web/src/components/affine/setting-modal/type.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import type {
|
||||||
|
AffineLegacyCloudWorkspace,
|
||||||
|
LocalWorkspace,
|
||||||
|
} from '@affine/env/workspace';
|
||||||
|
|
||||||
|
export type Workspace = AffineLegacyCloudWorkspace | LocalWorkspace;
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { assertExists } from '@blocksuite/store';
|
||||||
|
import React, { Suspense, useCallback } from 'react';
|
||||||
|
|
||||||
|
import { getUIAdapter } from '../../../../adapters/workspace';
|
||||||
|
import { useOnTransformWorkspace } from '../../../../hooks/root/use-on-transform-workspace';
|
||||||
|
import { useAppHelper } from '../../../../hooks/use-workspaces';
|
||||||
|
import type { Workspace } from '../type';
|
||||||
|
|
||||||
|
export const WorkSpaceSetting = ({ workspace }: { workspace: Workspace }) => {
|
||||||
|
const helper = useAppHelper();
|
||||||
|
const { NewSettingsDetail } = getUIAdapter(workspace.flavour);
|
||||||
|
|
||||||
|
const onDeleteWorkspace = useCallback(async () => {
|
||||||
|
assertExists(currentWorkspace);
|
||||||
|
const workspaceId = currentWorkspace.id;
|
||||||
|
return helper.deleteWorkspace(workspaceId);
|
||||||
|
}, [helper]);
|
||||||
|
const onTransformWorkspace = useOnTransformWorkspace();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Suspense fallback={<div>loading</div>}>
|
||||||
|
<NewSettingsDetail
|
||||||
|
onTransformWorkspace={onTransformWorkspace}
|
||||||
|
onDeleteWorkspace={onDeleteWorkspace}
|
||||||
|
currentWorkspace={workspace}
|
||||||
|
/>
|
||||||
|
</Suspense>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Menu, MenuItem } from '@affine/component';
|
import { Menu, MenuItem } from '@affine/component';
|
||||||
import { AffineIcon, SignOutIcon } from '@blocksuite/icons';
|
import { AffineLogoSBlue2_1Icon, SignOutIcon } from '@blocksuite/icons';
|
||||||
import type { CSSProperties } from 'react';
|
import type { CSSProperties } from 'react';
|
||||||
import { forwardRef } from 'react';
|
import { forwardRef } from 'react';
|
||||||
|
|
||||||
@@ -90,7 +90,7 @@ export const WorkspaceAvatar = forwardRef<HTMLDivElement, WorkspaceAvatarProps>(
|
|||||||
{props.name ? (
|
{props.name ? (
|
||||||
props.name.substring(0, 1)
|
props.name.substring(0, 1)
|
||||||
) : (
|
) : (
|
||||||
<AffineIcon fontSize={24} color={'#5438FF'} />
|
<AffineLogoSBlue2_1Icon fontSize={24} color={'#5438FF'} />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -4,3 +4,16 @@ export const pluginContainer = style({
|
|||||||
height: '100%',
|
height: '100%',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const editor = style({
|
||||||
|
height: 'calc(100% - 52px)',
|
||||||
|
|
||||||
|
selectors: {
|
||||||
|
'&.full-screen': {
|
||||||
|
padding: '0 5%',
|
||||||
|
vars: {
|
||||||
|
'--affine-editor-width': '100%',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import type {
|
|||||||
PluginUIAdapter,
|
PluginUIAdapter,
|
||||||
} from '@toeverything/plugin-infra/type';
|
} from '@toeverything/plugin-infra/type';
|
||||||
import type { PluginBlockSuiteAdapter } from '@toeverything/plugin-infra/type';
|
import type { PluginBlockSuiteAdapter } from '@toeverything/plugin-infra/type';
|
||||||
|
import clsx from 'clsx';
|
||||||
import { useAtomValue, useSetAtom } from 'jotai';
|
import { useAtomValue, useSetAtom } from 'jotai';
|
||||||
import Head from 'next/head';
|
import Head from 'next/head';
|
||||||
import type { FC, ReactElement } from 'react';
|
import type { FC, ReactElement } from 'react';
|
||||||
@@ -32,8 +33,10 @@ import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels';
|
|||||||
|
|
||||||
import { pageSettingFamily } from '../atoms';
|
import { pageSettingFamily } from '../atoms';
|
||||||
import { contentLayoutAtom } from '../atoms/layout';
|
import { contentLayoutAtom } from '../atoms/layout';
|
||||||
|
import { useAppSetting } from '../atoms/settings';
|
||||||
import type { AffineOfficialWorkspace } from '../shared';
|
import type { AffineOfficialWorkspace } from '../shared';
|
||||||
import { BlockSuiteEditor as Editor } from './blocksuite/block-suite-editor';
|
import { BlockSuiteEditor as Editor } from './blocksuite/block-suite-editor';
|
||||||
|
import { editor } from './page-detail-editor.css';
|
||||||
import { pluginContainer } from './page-detail-editor.css';
|
import { pluginContainer } from './page-detail-editor.css';
|
||||||
|
|
||||||
export type PageDetailEditorProps = {
|
export type PageDetailEditorProps = {
|
||||||
@@ -71,12 +74,15 @@ const EditorWrapper = memo(function EditorWrapper({
|
|||||||
(DEFAULT_HELLO_WORLD_PAGE_ID === pageId ? 'edgeless' : 'page');
|
(DEFAULT_HELLO_WORLD_PAGE_ID === pageId ? 'edgeless' : 'page');
|
||||||
|
|
||||||
const setEditor = useSetAtom(rootCurrentEditorAtom);
|
const setEditor = useSetAtom(rootCurrentEditorAtom);
|
||||||
|
const [appSettings] = useAppSetting();
|
||||||
|
|
||||||
assertExists(meta);
|
assertExists(meta);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Editor
|
<Editor
|
||||||
style={{
|
className={clsx(editor, {
|
||||||
height: 'calc(100% - 52px)',
|
'full-screen': appSettings?.fullWidthLayout,
|
||||||
}}
|
})}
|
||||||
key={`${workspace.flavour}-${workspace.id}-${pageId}`}
|
key={`${workspace.flavour}-${workspace.id}-${pageId}`}
|
||||||
mode={isPublic ? 'page' : currentMode}
|
mode={isPublic ? 'page' : currentMode}
|
||||||
page={page}
|
page={page}
|
||||||
|
|||||||
@@ -1,91 +0,0 @@
|
|||||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
|
||||||
|
|
||||||
interface ShortcutTip {
|
|
||||||
[x: string]: string;
|
|
||||||
}
|
|
||||||
export const useMacKeyboardShortcuts = (): ShortcutTip => {
|
|
||||||
const t = useAFFiNEI18N();
|
|
||||||
return {
|
|
||||||
[t['Undo']()]: '⌘+Z',
|
|
||||||
[t['Redo']()]: '⌘+⇧+Z',
|
|
||||||
[t['Bold']()]: '⌘+B',
|
|
||||||
[t['Italic']()]: '⌘+I',
|
|
||||||
[t['Underline']()]: '⌘+U',
|
|
||||||
[t['Strikethrough']()]: '⌘+⇧+S',
|
|
||||||
[t['Inline code']()]: ' ⌘+E',
|
|
||||||
[t['Code block']()]: '⌘+⌥+C',
|
|
||||||
[t['Link']()]: '⌘+K',
|
|
||||||
[t['Quick search']()]: '⌘+K',
|
|
||||||
[t['Body text']()]: '⌘+⌥+0',
|
|
||||||
[t['Heading']({ number: '1' })]: '⌘+⌥+1',
|
|
||||||
[t['Heading']({ number: '2' })]: '⌘+⌥+2',
|
|
||||||
[t['Heading']({ number: '3' })]: '⌘+⌥+3',
|
|
||||||
[t['Heading']({ number: '4' })]: '⌘+⌥+4',
|
|
||||||
[t['Heading']({ number: '5' })]: '⌘+⌥+5',
|
|
||||||
[t['Heading']({ number: '6' })]: '⌘+⌥+6',
|
|
||||||
[t['Increase indent']()]: 'Tab',
|
|
||||||
[t['Reduce indent']()]: '⇧+Tab',
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useMacMarkdownShortcuts = (): ShortcutTip => {
|
|
||||||
const t = useAFFiNEI18N();
|
|
||||||
return {
|
|
||||||
[t['Bold']()]: '**Text** ',
|
|
||||||
[t['Italic']()]: '*Text* ',
|
|
||||||
[t['Underline']()]: '~Text~ ',
|
|
||||||
[t['Strikethrough']()]: '~~Text~~ ',
|
|
||||||
[t['Divider']()]: '***',
|
|
||||||
[t['Inline code']()]: '`Text` ',
|
|
||||||
[t['Code block']()]: '``` Space',
|
|
||||||
[t['Heading']({ number: '1' })]: '# Text',
|
|
||||||
[t['Heading']({ number: '2' })]: '## Text',
|
|
||||||
[t['Heading']({ number: '3' })]: '### Text',
|
|
||||||
[t['Heading']({ number: '4' })]: '#### Text',
|
|
||||||
[t['Heading']({ number: '5' })]: '##### Text',
|
|
||||||
[t['Heading']({ number: '6' })]: '###### Text',
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useWindowsKeyboardShortcuts = (): ShortcutTip => {
|
|
||||||
const t = useAFFiNEI18N();
|
|
||||||
return {
|
|
||||||
[t['Undo']()]: 'Ctrl+Z',
|
|
||||||
[t['Redo']()]: 'Ctrl+Y',
|
|
||||||
[t['Bold']()]: 'Ctrl+B',
|
|
||||||
[t['Italic']()]: 'Ctrl+I',
|
|
||||||
[t['Underline']()]: 'Ctrl+U',
|
|
||||||
[t['Strikethrough']()]: 'Ctrl+Shift+S',
|
|
||||||
[t['Inline code']()]: ' Ctrl+E',
|
|
||||||
[t['Code block']()]: 'Ctrl+Alt+C',
|
|
||||||
[t['Link']()]: 'Ctrl+K',
|
|
||||||
[t['Quick search']()]: 'Ctrl+K',
|
|
||||||
[t['Body text']()]: 'Ctrl+Shift+0',
|
|
||||||
[t['Heading']({ number: '1' })]: 'Ctrl+Shift+1',
|
|
||||||
[t['Heading']({ number: '2' })]: 'Ctrl+Shift+2',
|
|
||||||
[t['Heading']({ number: '3' })]: 'Ctrl+Shift+3',
|
|
||||||
[t['Heading']({ number: '4' })]: 'Ctrl+Shift+4',
|
|
||||||
[t['Heading']({ number: '5' })]: 'Ctrl+Shift+5',
|
|
||||||
[t['Heading']({ number: '6' })]: 'Ctrl+Shift+6',
|
|
||||||
[t['Increase indent']()]: 'Tab',
|
|
||||||
[t['Reduce indent']()]: 'Shift+Tab',
|
|
||||||
};
|
|
||||||
};
|
|
||||||
export const useWinMarkdownShortcuts = (): ShortcutTip => {
|
|
||||||
const t = useAFFiNEI18N();
|
|
||||||
return {
|
|
||||||
[t['Bold']()]: '**Text** ',
|
|
||||||
[t['Italic']()]: '*Text* ',
|
|
||||||
[t['Underline']()]: '~Text~ ',
|
|
||||||
[t['Strikethrough']()]: '~~Text~~ ',
|
|
||||||
[t['Divider']()]: '***',
|
|
||||||
[t['Inline code']()]: '`Text` ',
|
|
||||||
[t['Code block']()]: '``` Text',
|
|
||||||
[t['Heading']({ number: '1' })]: '# Text',
|
|
||||||
[t['Heading']({ number: '2' })]: '## Text',
|
|
||||||
[t['Heading']({ number: '3' })]: '### Text',
|
|
||||||
[t['Heading']({ number: '4' })]: '#### Text',
|
|
||||||
[t['Heading']({ number: '5' })]: '##### Text',
|
|
||||||
[t['Heading']({ number: '6' })]: '###### Text',
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@@ -3,16 +3,14 @@ import {
|
|||||||
MuiClickAwayListener,
|
MuiClickAwayListener,
|
||||||
MuiSlide,
|
MuiSlide,
|
||||||
} from '@affine/component';
|
} from '@affine/component';
|
||||||
import { env } from '@affine/env';
|
|
||||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||||
import { useEffect, useState } from 'react';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
useMacKeyboardShortcuts,
|
useEdgelessShortcuts,
|
||||||
useMacMarkdownShortcuts,
|
useGeneralShortcuts,
|
||||||
useWindowsKeyboardShortcuts,
|
useMarkdownShortcuts,
|
||||||
useWinMarkdownShortcuts,
|
usePageShortcuts,
|
||||||
} from './config';
|
} from '../../../hooks/affine/use-shortcuts';
|
||||||
import { KeyboardIcon } from './icons';
|
import { KeyboardIcon } from './icons';
|
||||||
import {
|
import {
|
||||||
StyledListItem,
|
StyledListItem,
|
||||||
@@ -26,25 +24,13 @@ type ModalProps = {
|
|||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const checkIsMac = () => {
|
|
||||||
return env.isBrowser && env.isMacOs;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ShortcutsModal = ({ open, onClose }: ModalProps) => {
|
export const ShortcutsModal = ({ open, onClose }: ModalProps) => {
|
||||||
const t = useAFFiNEI18N();
|
const t = useAFFiNEI18N();
|
||||||
const macMarkdownShortcuts = useMacMarkdownShortcuts();
|
|
||||||
const winMarkdownShortcuts = useWinMarkdownShortcuts();
|
|
||||||
const macKeyboardShortcuts = useMacKeyboardShortcuts();
|
|
||||||
const windowsKeyboardShortcuts = useWindowsKeyboardShortcuts();
|
|
||||||
const [isMac, setIsMac] = useState(false);
|
|
||||||
const markdownShortcuts = isMac ? macMarkdownShortcuts : winMarkdownShortcuts;
|
|
||||||
const keyboardShortcuts = isMac
|
|
||||||
? macKeyboardShortcuts
|
|
||||||
: windowsKeyboardShortcuts;
|
|
||||||
|
|
||||||
useEffect(() => {
|
const markdownShortcuts = useMarkdownShortcuts();
|
||||||
setIsMac(checkIsMac());
|
const pageShortcuts = usePageShortcuts();
|
||||||
}, []);
|
const edgelessShortcuts = useEdgelessShortcuts();
|
||||||
|
const generalShortcuts = useGeneralShortcuts();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MuiSlide direction="left" in={open} mountOnEnter unmountOnExit>
|
<MuiSlide direction="left" in={open} mountOnEnter unmountOnExit>
|
||||||
@@ -72,9 +58,27 @@ export const ShortcutsModal = ({ open, onClose }: ModalProps) => {
|
|||||||
/>
|
/>
|
||||||
</StyledModalHeader>
|
</StyledModalHeader>
|
||||||
<StyledSubTitle style={{ marginTop: 0 }}>
|
<StyledSubTitle style={{ marginTop: 0 }}>
|
||||||
{t['Keyboard Shortcuts']()}
|
{t['General']()}
|
||||||
</StyledSubTitle>
|
</StyledSubTitle>
|
||||||
{Object.entries(keyboardShortcuts).map(([title, shortcuts]) => {
|
{Object.entries(generalShortcuts).map(([title, shortcuts]) => {
|
||||||
|
return (
|
||||||
|
<StyledListItem key={title}>
|
||||||
|
<span>{title}</span>
|
||||||
|
<span>{shortcuts}</span>
|
||||||
|
</StyledListItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
<StyledSubTitle>{t['Page']()}</StyledSubTitle>
|
||||||
|
{Object.entries(pageShortcuts).map(([title, shortcuts]) => {
|
||||||
|
return (
|
||||||
|
<StyledListItem key={title}>
|
||||||
|
<span>{title}</span>
|
||||||
|
<span>{shortcuts}</span>
|
||||||
|
</StyledListItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
<StyledSubTitle>{t['Edgeless']()}</StyledSubTitle>
|
||||||
|
{Object.entries(edgelessShortcuts).map(([title, shortcuts]) => {
|
||||||
return (
|
return (
|
||||||
<StyledListItem key={title}>
|
<StyledListItem key={title}>
|
||||||
<span>{title}</span>
|
<span>{title}</span>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
appSidebarOpenAtom,
|
appSidebarOpenAtom,
|
||||||
AppUpdaterButton,
|
AppUpdaterButton,
|
||||||
CategoryDivider,
|
CategoryDivider,
|
||||||
|
MenuItem,
|
||||||
MenuLinkItem,
|
MenuLinkItem,
|
||||||
QuickSearchInput,
|
QuickSearchInput,
|
||||||
SidebarContainer,
|
SidebarContainer,
|
||||||
@@ -25,6 +26,7 @@ import type { ReactElement } from 'react';
|
|||||||
import React, { useCallback, useEffect, useMemo } from 'react';
|
import React, { useCallback, useEffect, useMemo } from 'react';
|
||||||
|
|
||||||
import { useHistoryAtom } from '../../atoms/history';
|
import { useHistoryAtom } from '../../atoms/history';
|
||||||
|
import { useAppSetting } from '../../atoms/settings';
|
||||||
import type { AllWorkspace } from '../../shared';
|
import type { AllWorkspace } from '../../shared';
|
||||||
import FavoriteList from '../pure/workspace-slider-bar/favorite/favorite-list';
|
import FavoriteList from '../pure/workspace-slider-bar/favorite/favorite-list';
|
||||||
import { WorkspaceSelector } from '../pure/workspace-slider-bar/WorkspaceSelector';
|
import { WorkspaceSelector } from '../pure/workspace-slider-bar/WorkspaceSelector';
|
||||||
@@ -32,6 +34,7 @@ import { WorkspaceSelector } from '../pure/workspace-slider-bar/WorkspaceSelecto
|
|||||||
export type RootAppSidebarProps = {
|
export type RootAppSidebarProps = {
|
||||||
isPublicWorkspace: boolean;
|
isPublicWorkspace: boolean;
|
||||||
onOpenQuickSearchModal: () => void;
|
onOpenQuickSearchModal: () => void;
|
||||||
|
onOpenSettingModal: () => void;
|
||||||
onOpenWorkspaceListModal: () => void;
|
onOpenWorkspaceListModal: () => void;
|
||||||
currentWorkspace: AllWorkspace | null;
|
currentWorkspace: AllWorkspace | null;
|
||||||
openPage: (pageId: string) => void;
|
openPage: (pageId: string) => void;
|
||||||
@@ -88,8 +91,11 @@ export const RootAppSidebar = ({
|
|||||||
paths,
|
paths,
|
||||||
onOpenQuickSearchModal,
|
onOpenQuickSearchModal,
|
||||||
onOpenWorkspaceListModal,
|
onOpenWorkspaceListModal,
|
||||||
|
onOpenSettingModal,
|
||||||
}: RootAppSidebarProps): ReactElement => {
|
}: RootAppSidebarProps): ReactElement => {
|
||||||
const currentWorkspaceId = currentWorkspace?.id || null;
|
const currentWorkspaceId = currentWorkspace?.id || null;
|
||||||
|
const [appSettings] = useAppSetting();
|
||||||
|
|
||||||
const blockSuiteWorkspace = currentWorkspace?.blockSuiteWorkspace;
|
const blockSuiteWorkspace = currentWorkspace?.blockSuiteWorkspace;
|
||||||
const t = useAFFiNEI18N();
|
const t = useAFFiNEI18N();
|
||||||
const onClickNewPage = useCallback(async () => {
|
const onClickNewPage = useCallback(async () => {
|
||||||
@@ -143,7 +149,10 @@ export const RootAppSidebar = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<AppSidebar router={router}>
|
<AppSidebar
|
||||||
|
router={router}
|
||||||
|
hasBackground={!appSettings.disableBlurBackground}
|
||||||
|
>
|
||||||
<SidebarContainer>
|
<SidebarContainer>
|
||||||
<WorkspaceSelector
|
<WorkspaceSelector
|
||||||
currentWorkspace={currentWorkspace}
|
currentWorkspace={currentWorkspace}
|
||||||
@@ -168,6 +177,25 @@ export const RootAppSidebar = ({
|
|||||||
>
|
>
|
||||||
<span data-testid="settings">{t['Settings']()}</span>
|
<span data-testid="settings">{t['Settings']()}</span>
|
||||||
</RouteMenuLinkItem>
|
</RouteMenuLinkItem>
|
||||||
|
{config.enableNewSettingModal ? (
|
||||||
|
<MenuItem icon={<SettingsIcon />} onClick={onOpenSettingModal}>
|
||||||
|
<span data-testid="new-settings">
|
||||||
|
{t['Settings']()}
|
||||||
|
<i
|
||||||
|
style={{
|
||||||
|
background: 'var(--affine-palette-line-blue)',
|
||||||
|
borderRadius: '2px',
|
||||||
|
fontSize: '8px',
|
||||||
|
padding: '0 5px',
|
||||||
|
color: 'var(--affine-white)',
|
||||||
|
marginLeft: '15px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
NEW
|
||||||
|
</i>
|
||||||
|
</span>
|
||||||
|
</MenuItem>
|
||||||
|
) : null}
|
||||||
</SidebarContainer>
|
</SidebarContainer>
|
||||||
|
|
||||||
<SidebarScrollableContainer>
|
<SidebarScrollableContainer>
|
||||||
|
|||||||
237
apps/web/src/hooks/affine/use-shortcuts.ts
Normal file
237
apps/web/src/hooks/affine/use-shortcuts.ts
Normal file
@@ -0,0 +1,237 @@
|
|||||||
|
import { env } from '@affine/env';
|
||||||
|
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
|
interface ShortcutTip {
|
||||||
|
[x: string]: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useWinGeneralKeyboardShortcuts = (): ShortcutTip => {
|
||||||
|
const t = useAFFiNEI18N();
|
||||||
|
return useMemo(
|
||||||
|
() => ({
|
||||||
|
[t['Cancel']()]: 'ESC',
|
||||||
|
[t['Quick Search']()]: 'Ctrl + K',
|
||||||
|
[t['New Page']()]: 'Ctrl + N',
|
||||||
|
// not implement yet
|
||||||
|
// [t['Append to Daily Note']()]: 'Ctrl + Alt + A',
|
||||||
|
[t['Expand/Collapse Sidebar']()]: 'Ctrl + /',
|
||||||
|
// not implement yet
|
||||||
|
// [t['Go Back']()]: 'Ctrl + [',
|
||||||
|
// [t['Go Forward']()]: 'Ctrl + ]',
|
||||||
|
}),
|
||||||
|
[t]
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export const useMacGeneralKeyboardShortcuts = (): ShortcutTip => {
|
||||||
|
const t = useAFFiNEI18N();
|
||||||
|
return useMemo(
|
||||||
|
() => ({
|
||||||
|
[t['Cancel']()]: 'ESC',
|
||||||
|
[t['Quick Search']()]: '⌘ + K',
|
||||||
|
[t['New Page']()]: '⌘ + N',
|
||||||
|
// not implement yet
|
||||||
|
// [t['Append to Daily Note']()]: '⌘ + ⌥ + A',
|
||||||
|
[t['Expand/Collapse Sidebar']()]: '⌘ + /',
|
||||||
|
// not implement yet
|
||||||
|
// [t['Go Back']()]: '⌘ + [',
|
||||||
|
// [t['Go Forward']()]: '⌘ + ]',
|
||||||
|
}),
|
||||||
|
[t]
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useMacEdgelessKeyboardShortcuts = (): ShortcutTip => {
|
||||||
|
const t = useAFFiNEI18N();
|
||||||
|
return useMemo(
|
||||||
|
() => ({
|
||||||
|
[t['Select All']()]: '⌘ + A',
|
||||||
|
[t['Undo']()]: '⌘ + Z',
|
||||||
|
[t['Redo']()]: '⌘ + ⇧ + Z',
|
||||||
|
[t['Zoom in']()]: '⌘ + +',
|
||||||
|
[t['Zoom out']()]: '⌘ + -',
|
||||||
|
[t['Zoom to 100%']()]: '⌘ + 0',
|
||||||
|
[t['Zoom to fit']()]: '⌘ + 1',
|
||||||
|
[t['Select']()]: 'V',
|
||||||
|
[t['Text']()]: 'T',
|
||||||
|
[t['Shape']()]: 'S',
|
||||||
|
[t['Image']()]: 'I',
|
||||||
|
[t['Straight Connector']()]: 'L',
|
||||||
|
[t['Elbowed Connector']()]: 'X',
|
||||||
|
// not implement yet
|
||||||
|
// [t['Curve Connector']()]: 'C',
|
||||||
|
[t['Pen']()]: 'P',
|
||||||
|
[t['Hand']()]: 'H',
|
||||||
|
[t['Note']()]: 'N',
|
||||||
|
// not implement yet
|
||||||
|
// [t['Group']()]: '⌘ + G',
|
||||||
|
// [t['Ungroup']()]: '⌘ + ⇧ + G',
|
||||||
|
}),
|
||||||
|
[t]
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export const useWinEdgelessKeyboardShortcuts = (): ShortcutTip => {
|
||||||
|
const t = useAFFiNEI18N();
|
||||||
|
return useMemo(
|
||||||
|
() => ({
|
||||||
|
[t['Select All']()]: 'Ctrl + A',
|
||||||
|
[t['Undo']()]: 'Ctrl + Z',
|
||||||
|
[t['Redo']()]: 'Ctrl + Y/Ctrl + Shift + Z',
|
||||||
|
[t['Zoom in']()]: 'Ctrl + +',
|
||||||
|
[t['Zoom out']()]: 'Ctrl + -',
|
||||||
|
[t['Zoom to 100%']()]: 'Ctrl + 0',
|
||||||
|
[t['Zoom to fit']()]: 'Ctrl + 1',
|
||||||
|
[t['Select']()]: 'V',
|
||||||
|
[t['Text']()]: 'T',
|
||||||
|
[t['Shape']()]: 'S',
|
||||||
|
[t['Image']()]: 'I',
|
||||||
|
[t['Straight Connector']()]: 'L',
|
||||||
|
[t['Elbowed Connector']()]: 'X',
|
||||||
|
// not implement yet
|
||||||
|
// [t['Curve Connector']()]: 'C',
|
||||||
|
[t['Pen']()]: 'P',
|
||||||
|
[t['Hand']()]: 'H',
|
||||||
|
[t['Note']()]: 'N',
|
||||||
|
// not implement yet
|
||||||
|
// [t['Group']()]: 'Ctrl + G',
|
||||||
|
// [t['Ungroup']()]: 'Ctrl + Shift + G',
|
||||||
|
}),
|
||||||
|
[t]
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export const useMacPageKeyboardShortcuts = (): ShortcutTip => {
|
||||||
|
const t = useAFFiNEI18N();
|
||||||
|
return useMemo(
|
||||||
|
() => ({
|
||||||
|
[t['Undo']()]: '⌘+Z',
|
||||||
|
[t['Redo']()]: '⌘+⇧+Z',
|
||||||
|
[t['Bold']()]: '⌘+B',
|
||||||
|
[t['Italic']()]: '⌘+I',
|
||||||
|
[t['Underline']()]: '⌘+U',
|
||||||
|
[t['Strikethrough']()]: '⌘+⇧+S',
|
||||||
|
[t['Inline code']()]: ' ⌘+E',
|
||||||
|
[t['Code block']()]: '⌘+⌥+C',
|
||||||
|
[t['Link']()]: '⌘+K',
|
||||||
|
[t['Quick search']()]: '⌘+K',
|
||||||
|
[t['Body text']()]: '⌘+⌥+0',
|
||||||
|
[t['Heading']({ number: '1' })]: '⌘+⌥+1',
|
||||||
|
[t['Heading']({ number: '2' })]: '⌘+⌥+2',
|
||||||
|
[t['Heading']({ number: '3' })]: '⌘+⌥+3',
|
||||||
|
[t['Heading']({ number: '4' })]: '⌘+⌥+4',
|
||||||
|
[t['Heading']({ number: '5' })]: '⌘+⌥+5',
|
||||||
|
[t['Heading']({ number: '6' })]: '⌘+⌥+6',
|
||||||
|
[t['Increase indent']()]: 'Tab',
|
||||||
|
[t['Reduce indent']()]: '⇧+Tab',
|
||||||
|
[t['Group as Database']()]: '⌘ + G',
|
||||||
|
// not implement yet
|
||||||
|
// [t['Move Up']()]: '⌘ + ⌥ + ↑',
|
||||||
|
// [t['Move Down']()]: '⌘ + ⌥ + ↓',
|
||||||
|
}),
|
||||||
|
[t]
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useMacMarkdownShortcuts = (): ShortcutTip => {
|
||||||
|
const t = useAFFiNEI18N();
|
||||||
|
return useMemo(
|
||||||
|
() => ({
|
||||||
|
[t['Bold']()]: '**Text** ',
|
||||||
|
[t['Italic']()]: '*Text* ',
|
||||||
|
[t['Underline']()]: '~Text~ ',
|
||||||
|
[t['Strikethrough']()]: '~~Text~~ ',
|
||||||
|
[t['Divider']()]: '***',
|
||||||
|
[t['Inline code']()]: '`Text` ',
|
||||||
|
[t['Code block']()]: '``` Space',
|
||||||
|
[t['Heading']({ number: '1' })]: '# Text',
|
||||||
|
[t['Heading']({ number: '2' })]: '## Text',
|
||||||
|
[t['Heading']({ number: '3' })]: '### Text',
|
||||||
|
[t['Heading']({ number: '4' })]: '#### Text',
|
||||||
|
[t['Heading']({ number: '5' })]: '##### Text',
|
||||||
|
[t['Heading']({ number: '6' })]: '###### Text',
|
||||||
|
}),
|
||||||
|
[t]
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useWinPageKeyboardShortcuts = (): ShortcutTip => {
|
||||||
|
const t = useAFFiNEI18N();
|
||||||
|
return useMemo(
|
||||||
|
() => ({
|
||||||
|
[t['Undo']()]: 'Ctrl+Z',
|
||||||
|
[t['Redo']()]: 'Ctrl+Y',
|
||||||
|
[t['Bold']()]: 'Ctrl+B',
|
||||||
|
[t['Italic']()]: 'Ctrl+I',
|
||||||
|
[t['Underline']()]: 'Ctrl+U',
|
||||||
|
[t['Strikethrough']()]: 'Ctrl+Shift+S',
|
||||||
|
[t['Inline code']()]: ' Ctrl+E',
|
||||||
|
[t['Code block']()]: 'Ctrl+Alt+C',
|
||||||
|
[t['Link']()]: 'Ctrl+K',
|
||||||
|
[t['Quick search']()]: 'Ctrl+K',
|
||||||
|
[t['Body text']()]: 'Ctrl+Shift+0',
|
||||||
|
[t['Heading']({ number: '1' })]: 'Ctrl+Shift+1',
|
||||||
|
[t['Heading']({ number: '2' })]: 'Ctrl+Shift+2',
|
||||||
|
[t['Heading']({ number: '3' })]: 'Ctrl+Shift+3',
|
||||||
|
[t['Heading']({ number: '4' })]: 'Ctrl+Shift+4',
|
||||||
|
[t['Heading']({ number: '5' })]: 'Ctrl+Shift+5',
|
||||||
|
[t['Heading']({ number: '6' })]: 'Ctrl+Shift+6',
|
||||||
|
[t['Increase indent']()]: 'Tab',
|
||||||
|
[t['Reduce indent']()]: 'Shift+Tab',
|
||||||
|
[t['Group as Database']()]: 'Ctrl + G',
|
||||||
|
// not implement yet
|
||||||
|
// [t['Move Up']()]: 'Ctrl + Alt + ↑',
|
||||||
|
// [t['Move Down']()]: 'Ctrl + Alt + ↓',
|
||||||
|
}),
|
||||||
|
[t]
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export const useWinMarkdownShortcuts = (): ShortcutTip => {
|
||||||
|
const t = useAFFiNEI18N();
|
||||||
|
return useMemo(
|
||||||
|
() => ({
|
||||||
|
[t['Bold']()]: '**Text** ',
|
||||||
|
[t['Italic']()]: '*Text* ',
|
||||||
|
[t['Underline']()]: '~Text~ ',
|
||||||
|
[t['Strikethrough']()]: '~~Text~~ ',
|
||||||
|
[t['Divider']()]: '***',
|
||||||
|
[t['Inline code']()]: '`Text` ',
|
||||||
|
[t['Code block']()]: '``` Text',
|
||||||
|
[t['Heading']({ number: '1' })]: '# Text',
|
||||||
|
[t['Heading']({ number: '2' })]: '## Text',
|
||||||
|
[t['Heading']({ number: '3' })]: '### Text',
|
||||||
|
[t['Heading']({ number: '4' })]: '#### Text',
|
||||||
|
[t['Heading']({ number: '5' })]: '##### Text',
|
||||||
|
[t['Heading']({ number: '6' })]: '###### Text',
|
||||||
|
}),
|
||||||
|
[t]
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useMarkdownShortcuts = (): ShortcutTip => {
|
||||||
|
const macMarkdownShortcuts = useMacMarkdownShortcuts();
|
||||||
|
const winMarkdownShortcuts = useWinMarkdownShortcuts();
|
||||||
|
const isMac = env.isBrowser && env.isMacOs;
|
||||||
|
return isMac ? macMarkdownShortcuts : winMarkdownShortcuts;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const usePageShortcuts = (): ShortcutTip => {
|
||||||
|
const macPageShortcuts = useMacPageKeyboardShortcuts();
|
||||||
|
const winPageShortcuts = useWinPageKeyboardShortcuts();
|
||||||
|
const isMac = env.isBrowser && env.isMacOs;
|
||||||
|
return isMac ? macPageShortcuts : winPageShortcuts;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useEdgelessShortcuts = (): ShortcutTip => {
|
||||||
|
const macEdgelessShortcuts = useMacEdgelessKeyboardShortcuts();
|
||||||
|
const winEdgelessShortcuts = useWinEdgelessKeyboardShortcuts();
|
||||||
|
const isMac = env.isBrowser && env.isMacOs;
|
||||||
|
|
||||||
|
return isMac ? macEdgelessShortcuts : winEdgelessShortcuts;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useGeneralShortcuts = (): ShortcutTip => {
|
||||||
|
const macGeneralShortcuts = useMacGeneralKeyboardShortcuts();
|
||||||
|
const winGeneralShortcuts = useWinGeneralKeyboardShortcuts();
|
||||||
|
const isMac = env.isBrowser && env.isMacOs;
|
||||||
|
|
||||||
|
return isMac ? macGeneralShortcuts : winGeneralShortcuts;
|
||||||
|
};
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { AppContainer, MainContainer } from '@affine/component/workspace';
|
import { MainContainer } from '@affine/component/workspace';
|
||||||
import type { AffinePublicWorkspace } from '@affine/env/workspace';
|
import type { AffinePublicWorkspace } from '@affine/env/workspace';
|
||||||
import { useAtom } from 'jotai';
|
import { useAtom } from 'jotai';
|
||||||
import Head from 'next/head';
|
import Head from 'next/head';
|
||||||
@@ -7,6 +7,7 @@ import type React from 'react';
|
|||||||
import { lazy, Suspense } from 'react';
|
import { lazy, Suspense } from 'react';
|
||||||
|
|
||||||
import { openQuickSearchModalAtom } from '../atoms';
|
import { openQuickSearchModalAtom } from '../atoms';
|
||||||
|
import { AppContainer } from '../components/affine/app-container';
|
||||||
import { useRouterTitle } from '../hooks/use-router-title';
|
import { useRouterTitle } from '../hooks/use-router-title';
|
||||||
|
|
||||||
const QuickSearchModal = lazy(() =>
|
const QuickSearchModal = lazy(() =>
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { appSidebarResizingAtom } from '@affine/component/app-sidebar';
|
|||||||
import type { DraggableTitleCellData } from '@affine/component/page-list';
|
import type { DraggableTitleCellData } from '@affine/component/page-list';
|
||||||
import { StyledTitleLink } from '@affine/component/page-list';
|
import { StyledTitleLink } from '@affine/component/page-list';
|
||||||
import {
|
import {
|
||||||
AppContainer,
|
|
||||||
MainContainer,
|
MainContainer,
|
||||||
ToolContainer,
|
ToolContainer,
|
||||||
WorkspaceFallback,
|
WorkspaceFallback,
|
||||||
@@ -42,12 +41,17 @@ import type { FC, PropsWithChildren, ReactElement } from 'react';
|
|||||||
import { lazy, Suspense, useCallback, useEffect, useMemo } from 'react';
|
import { lazy, Suspense, useCallback, useEffect, useMemo } from 'react';
|
||||||
|
|
||||||
import { WorkspaceAdapters } from '../adapters/workspace';
|
import { WorkspaceAdapters } from '../adapters/workspace';
|
||||||
import { openQuickSearchModalAtom, openWorkspacesModalAtom } from '../atoms';
|
import {
|
||||||
|
openQuickSearchModalAtom,
|
||||||
|
openSettingModalAtom,
|
||||||
|
openWorkspacesModalAtom,
|
||||||
|
} from '../atoms';
|
||||||
import { useTrackRouterHistoryEffect } from '../atoms/history';
|
import { useTrackRouterHistoryEffect } from '../atoms/history';
|
||||||
import {
|
import {
|
||||||
publicWorkspaceAtom,
|
publicWorkspaceAtom,
|
||||||
publicWorkspaceIdAtom,
|
publicWorkspaceIdAtom,
|
||||||
} from '../atoms/public-workspace';
|
} from '../atoms/public-workspace';
|
||||||
|
import { AppContainer } from '../components/affine/app-container';
|
||||||
import type { IslandItemNames } from '../components/pure/help-island';
|
import type { IslandItemNames } from '../components/pure/help-island';
|
||||||
import { HelpIsland } from '../components/pure/help-island';
|
import { HelpIsland } from '../components/pure/help-island';
|
||||||
import {
|
import {
|
||||||
@@ -71,6 +75,11 @@ const QuickSearchModal = lazy(() =>
|
|||||||
default: module.QuickSearchModal,
|
default: module.QuickSearchModal,
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
|
const SettingModal = lazy(() =>
|
||||||
|
import('../components/affine/setting-modal').then(module => ({
|
||||||
|
default: module.SettingModal,
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
|
||||||
export const PublicQuickSearch: FC = () => {
|
export const PublicQuickSearch: FC = () => {
|
||||||
const publicWorkspace = useAtomValue(publicWorkspaceAtom);
|
const publicWorkspace = useAtomValue(publicWorkspaceAtom);
|
||||||
@@ -119,6 +128,25 @@ export const QuickSearch: FC = () => {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
export const Setting: FC = () => {
|
||||||
|
const [currentWorkspace] = useCurrentWorkspace();
|
||||||
|
const router = useRouter();
|
||||||
|
const [openSettingModal, setOpenSettingModalAtom] =
|
||||||
|
useAtom(openSettingModalAtom);
|
||||||
|
const blockSuiteWorkspace = currentWorkspace?.blockSuiteWorkspace;
|
||||||
|
const isPublicWorkspace =
|
||||||
|
router.pathname.split('/')[1] === 'public-workspace';
|
||||||
|
if (!blockSuiteWorkspace || isPublicWorkspace) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<SettingModal
|
||||||
|
open={openSettingModal}
|
||||||
|
setOpen={setOpenSettingModalAtom}
|
||||||
|
router={router}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const logger = new DebugLogger('workspace-layout');
|
const logger = new DebugLogger('workspace-layout');
|
||||||
|
|
||||||
@@ -390,6 +418,12 @@ export const WorkspaceLayoutInner: FC<PropsWithChildren> = ({ children }) => {
|
|||||||
setOpenQuickSearchModalAtom(true);
|
setOpenQuickSearchModalAtom(true);
|
||||||
}, [setOpenQuickSearchModalAtom]);
|
}, [setOpenQuickSearchModalAtom]);
|
||||||
|
|
||||||
|
const [, setOpenSettingModalAtom] = useAtom(openSettingModalAtom);
|
||||||
|
|
||||||
|
const handleOpenSettingModal = useCallback(() => {
|
||||||
|
setOpenSettingModalAtom(true);
|
||||||
|
}, [setOpenSettingModalAtom]);
|
||||||
|
|
||||||
const resizing = useAtomValue(appSidebarResizingAtom);
|
const resizing = useAtomValue(appSidebarResizingAtom);
|
||||||
|
|
||||||
const sensors = useSensors(
|
const sensors = useSensors(
|
||||||
@@ -444,6 +478,7 @@ export const WorkspaceLayoutInner: FC<PropsWithChildren> = ({ children }) => {
|
|||||||
<RootAppSidebar
|
<RootAppSidebar
|
||||||
isPublicWorkspace={isPublicWorkspace}
|
isPublicWorkspace={isPublicWorkspace}
|
||||||
onOpenQuickSearchModal={handleOpenQuickSearchModal}
|
onOpenQuickSearchModal={handleOpenQuickSearchModal}
|
||||||
|
onOpenSettingModal={handleOpenSettingModal}
|
||||||
currentWorkspace={currentWorkspace}
|
currentWorkspace={currentWorkspace}
|
||||||
onOpenWorkspaceListModal={handleOpenWorkspaceListModal}
|
onOpenWorkspaceListModal={handleOpenWorkspaceListModal}
|
||||||
openPage={useCallback(
|
openPage={useCallback(
|
||||||
@@ -476,6 +511,7 @@ export const WorkspaceLayoutInner: FC<PropsWithChildren> = ({ children }) => {
|
|||||||
<PageListTitleCellDragOverlay />
|
<PageListTitleCellDragOverlay />
|
||||||
</DndContext>
|
</DndContext>
|
||||||
<QuickSearch />
|
<QuickSearch />
|
||||||
|
<Setting />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Button } from '@affine/component';
|
import { Button } from '@affine/component';
|
||||||
import { AppContainer, MainContainer } from '@affine/component/workspace';
|
import { MainContainer } from '@affine/component/workspace';
|
||||||
import { DebugLogger } from '@affine/debug';
|
import { DebugLogger } from '@affine/debug';
|
||||||
import type { BroadCastChannelProvider } from '@affine/env/workspace';
|
import type { BroadCastChannelProvider } from '@affine/env/workspace';
|
||||||
import { WorkspaceFlavour } from '@affine/env/workspace';
|
import { WorkspaceFlavour } from '@affine/env/workspace';
|
||||||
@@ -10,6 +10,7 @@ import { Typography } from '@mui/material';
|
|||||||
import type React from 'react';
|
import type React from 'react';
|
||||||
import { useEffect, useMemo, useState } from 'react';
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
|
|
||||||
|
import { AppContainer } from '../../components/affine/app-container';
|
||||||
import { BlockSuitePageList } from '../../components/blocksuite/block-suite-page-list';
|
import { BlockSuitePageList } from '../../components/blocksuite/block-suite-page-list';
|
||||||
import { toast } from '../../utils';
|
import { toast } from '../../utils';
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { AppContainer, MainContainer } from '@affine/component/workspace';
|
import { MainContainer } from '@affine/component/workspace';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { lazy, Suspense } from 'react';
|
import { lazy, Suspense } from 'react';
|
||||||
|
|
||||||
|
import { AppContainer } from '../../components/affine/app-container';
|
||||||
import type { NextPageWithLayout } from '../../shared';
|
import type { NextPageWithLayout } from '../../shared';
|
||||||
|
|
||||||
const Editor = lazy(() =>
|
const Editor = lazy(() =>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Button } from '@affine/component';
|
import { Button } from '@affine/component';
|
||||||
import { AppContainer, MainContainer } from '@affine/component/workspace';
|
import { MainContainer } from '@affine/component/workspace';
|
||||||
import { currentAffineUserAtom } from '@affine/workspace/affine/atom';
|
import { currentAffineUserAtom } from '@affine/workspace/affine/atom';
|
||||||
import {
|
import {
|
||||||
clearLoginStorage,
|
clearLoginStorage,
|
||||||
@@ -14,6 +14,7 @@ import { useAtom } from 'jotai';
|
|||||||
import type { NextPage } from 'next';
|
import type { NextPage } from 'next';
|
||||||
import { lazy, Suspense, useMemo } from 'react';
|
import { lazy, Suspense, useMemo } from 'react';
|
||||||
|
|
||||||
|
import { AppContainer } from '../../components/affine/app-container';
|
||||||
import { toast } from '../../utils';
|
import { toast } from '../../utils';
|
||||||
|
|
||||||
const Viewer = lazy(() =>
|
const Viewer = lazy(() =>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { AppContainer, MainContainer } from '@affine/component/workspace';
|
import { MainContainer } from '@affine/component/workspace';
|
||||||
import { config } from '@affine/env';
|
import { config } from '@affine/env';
|
||||||
import { NoSsr } from '@mui/material';
|
import { NoSsr } from '@mui/material';
|
||||||
import { affinePluginsAtom } from '@toeverything/plugin-infra/manager';
|
import { affinePluginsAtom } from '@toeverything/plugin-infra/manager';
|
||||||
@@ -6,6 +6,8 @@ import { useAtomValue } from 'jotai';
|
|||||||
import type { ReactElement } from 'react';
|
import type { ReactElement } from 'react';
|
||||||
import { Suspense } from 'react';
|
import { Suspense } from 'react';
|
||||||
|
|
||||||
|
import { AppContainer } from '../components/affine/app-container';
|
||||||
|
|
||||||
const Plugins = () => {
|
const Plugins = () => {
|
||||||
const plugins = useAtomValue(affinePluginsAtom);
|
const plugins = useAtomValue(affinePluginsAtom);
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -54,7 +54,7 @@
|
|||||||
"@blocksuite/blocks": "0.0.0-20230607055421-9b20fcaf-nightly",
|
"@blocksuite/blocks": "0.0.0-20230607055421-9b20fcaf-nightly",
|
||||||
"@blocksuite/editor": "0.0.0-20230607055421-9b20fcaf-nightly",
|
"@blocksuite/editor": "0.0.0-20230607055421-9b20fcaf-nightly",
|
||||||
"@blocksuite/global": "0.0.0-20230607055421-9b20fcaf-nightly",
|
"@blocksuite/global": "0.0.0-20230607055421-9b20fcaf-nightly",
|
||||||
"@blocksuite/icons": "^2.1.19",
|
"@blocksuite/icons": "^2.1.21",
|
||||||
"@blocksuite/lit": "0.0.0-20230607055421-9b20fcaf-nightly",
|
"@blocksuite/lit": "0.0.0-20230607055421-9b20fcaf-nightly",
|
||||||
"@blocksuite/store": "0.0.0-20230607055421-9b20fcaf-nightly",
|
"@blocksuite/store": "0.0.0-20230607055421-9b20fcaf-nightly",
|
||||||
"@types/react": "^18.2.12",
|
"@types/react": "^18.2.12",
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Trans } from '@affine/i18n';
|
import { Trans } from '@affine/i18n';
|
||||||
import { AffineLogoSimCBlue1_1Icon, CloseIcon } from '@blocksuite/icons';
|
import { AffineLogoSBlue2_1Icon, CloseIcon } from '@blocksuite/icons';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
downloadCloseButtonStyle,
|
downloadCloseButtonStyle,
|
||||||
@@ -17,7 +17,7 @@ export const DownloadTips = ({ onClose }: { onClose: () => void }) => {
|
|||||||
data-testid="download-client-tip"
|
data-testid="download-client-tip"
|
||||||
>
|
>
|
||||||
<div className={downloadTipStyle}>
|
<div className={downloadTipStyle}>
|
||||||
<AffineLogoSimCBlue1_1Icon className={downloadTipIconStyle} />
|
<AffineLogoSBlue2_1Icon className={downloadTipIconStyle} />
|
||||||
<div className={downloadMessageStyle}>
|
<div className={downloadMessageStyle}>
|
||||||
<Trans i18nKey="com.affine.banner.content">
|
<Trans i18nKey="com.affine.banner.content">
|
||||||
Enjoying the demo?
|
Enjoying the demo?
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ export const navWrapperStyle = style({
|
|||||||
zIndex: 2,
|
zIndex: 2,
|
||||||
paddingBottom: '8px',
|
paddingBottom: '8px',
|
||||||
backgroundColor: 'transparent',
|
backgroundColor: 'transparent',
|
||||||
|
borderRight: '1px solid var(--affine-border-color)',
|
||||||
'@media': {
|
'@media': {
|
||||||
[`(max-width: ${floatingMaxWidth}px)`]: {
|
[`(max-width: ${floatingMaxWidth}px)`]: {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
@@ -39,6 +40,9 @@ export const navWrapperStyle = style({
|
|||||||
'&[data-enable-animation="true"]': {
|
'&[data-enable-animation="true"]': {
|
||||||
transition: 'margin-left .3s, width .3s',
|
transition: 'margin-left .3s, width .3s',
|
||||||
},
|
},
|
||||||
|
'&.has-background': {
|
||||||
|
backgroundColor: 'var(--affine-white-60)',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { env } from '@affine/env';
|
import { env } from '@affine/env';
|
||||||
import { Skeleton } from '@mui/material';
|
import { Skeleton } from '@mui/material';
|
||||||
import { assignInlineVars } from '@vanilla-extract/dynamic';
|
import { assignInlineVars } from '@vanilla-extract/dynamic';
|
||||||
|
import clsx from 'clsx';
|
||||||
import { useAtom, useAtomValue } from 'jotai';
|
import { useAtom, useAtomValue } from 'jotai';
|
||||||
import type { PropsWithChildren, ReactElement } from 'react';
|
import type { PropsWithChildren, ReactElement } from 'react';
|
||||||
import { useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
@@ -25,7 +26,11 @@ import { ResizeIndicator } from './resize-indicator';
|
|||||||
import type { SidebarHeaderProps } from './sidebar-header';
|
import type { SidebarHeaderProps } from './sidebar-header';
|
||||||
import { SidebarHeader } from './sidebar-header';
|
import { SidebarHeader } from './sidebar-header';
|
||||||
|
|
||||||
export type AppSidebarProps = PropsWithChildren<SidebarHeaderProps>;
|
export type AppSidebarProps = PropsWithChildren<
|
||||||
|
SidebarHeaderProps & {
|
||||||
|
hasBackground?: boolean;
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
|
||||||
function useEnableAnimation() {
|
function useEnableAnimation() {
|
||||||
const [enable, setEnable] = useState(false);
|
const [enable, setEnable] = useState(false);
|
||||||
@@ -91,7 +96,9 @@ export function AppSidebar(props: AppSidebarProps): ReactElement {
|
|||||||
style={assignInlineVars({
|
style={assignInlineVars({
|
||||||
[navWidthVar]: `${appSidebarWidth}px`,
|
[navWidthVar]: `${appSidebarWidth}px`,
|
||||||
})}
|
})}
|
||||||
className={navWrapperStyle}
|
className={clsx(navWrapperStyle, {
|
||||||
|
'has-background': env.isDesktop && props.hasBackground,
|
||||||
|
})}
|
||||||
data-open={open}
|
data-open={open}
|
||||||
data-is-macos-electron={isMacosDesktop}
|
data-is-macos-electron={isMacosDesktop}
|
||||||
data-enable-animation={enableAnimation && !isResizing}
|
data-enable-animation={enableAnimation && !isResizing}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ export type EditorProps = {
|
|||||||
onInit: (page: Page, editor: Readonly<EditorContainer>) => void;
|
onInit: (page: Page, editor: Readonly<EditorContainer>) => void;
|
||||||
onLoad?: (page: Page, editor: EditorContainer) => () => void;
|
onLoad?: (page: Page, editor: EditorContainer) => () => void;
|
||||||
style?: CSSProperties;
|
style?: CSSProperties;
|
||||||
|
className?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ErrorBoundaryProps = {
|
export type ErrorBoundaryProps = {
|
||||||
@@ -122,7 +123,9 @@ const BlockSuiteEditorImpl = (props: EditorProps): ReactElement => {
|
|||||||
}, [editor, page]);
|
}, [editor, page]);
|
||||||
|
|
||||||
// issue: https://github.com/toeverything/AFFiNE/issues/2004
|
// issue: https://github.com/toeverything/AFFiNE/issues/2004
|
||||||
const className = `editor-wrapper ${editor.mode}-mode`;
|
const className = `editor-wrapper ${editor.mode}-mode ${
|
||||||
|
props.className || ''
|
||||||
|
}`;
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-testid={`editor-${page.id}`}
|
data-testid={`editor-${page.id}`}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import {
|
|||||||
StyledSmallLink,
|
StyledSmallLink,
|
||||||
StyledSubTitle,
|
StyledSubTitle,
|
||||||
} from './style';
|
} from './style';
|
||||||
const linkList = [
|
export const relatedLinks = [
|
||||||
{
|
{
|
||||||
icon: <GithubIcon />,
|
icon: <GithubIcon />,
|
||||||
title: 'GitHub',
|
title: 'GitHub',
|
||||||
@@ -107,7 +107,7 @@ export const ContactModal = ({
|
|||||||
{t['Get in touch! Join our communities']()}
|
{t['Get in touch! Join our communities']()}
|
||||||
</StyledSubTitle>
|
</StyledSubTitle>
|
||||||
<FlexWrapper justifyContent="center">
|
<FlexWrapper justifyContent="center">
|
||||||
{linkList.map(({ icon, title, link }) => {
|
{relatedLinks.map(({ icon, title, link }) => {
|
||||||
return (
|
return (
|
||||||
<StyledSmallLink key={title} href={link} target="_blank">
|
<StyledSmallLink key={title} href={link} target="_blank">
|
||||||
{icon}
|
{icon}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
// License on the MIT
|
// License on the MIT
|
||||||
// https://github.com/emilkowalski/sonner/blob/5cb703edc108a23fd74979235c2f3c4005edd2a7/src/index.tsx
|
// https://github.com/emilkowalski/sonner/blob/5cb703edc108a23fd74979235c2f3c4005edd2a7/src/index.tsx
|
||||||
|
|
||||||
import { CloseIcon, InformationFillIcon } from '@blocksuite/icons';
|
import { CloseIcon, InformationIcon } from '@blocksuite/icons';
|
||||||
import * as Toast from '@radix-ui/react-toast';
|
import * as Toast from '@radix-ui/react-toast';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { useAtom, useAtomValue, useSetAtom } from 'jotai';
|
import { useAtom, useAtomValue, useSetAtom } from 'jotai';
|
||||||
@@ -303,7 +303,7 @@ function NotificationCard(props: NotificationCardProps): ReactElement {
|
|||||||
[styles.lightInfoIconStyle]: notification.theme !== 'dark',
|
[styles.lightInfoIconStyle]: notification.theme !== 'dark',
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<InformationFillIcon />
|
<InformationIcon />
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.notificationTitleContactStyle}>
|
<div className={styles.notificationTitleContactStyle}>
|
||||||
{notification.title}
|
{notification.title}
|
||||||
|
|||||||
@@ -14,7 +14,10 @@ export const appStyle = style({
|
|||||||
'&[data-is-resizing="true"]': {
|
'&[data-is-resizing="true"]': {
|
||||||
cursor: 'col-resize',
|
cursor: 'col-resize',
|
||||||
},
|
},
|
||||||
'&:before': {
|
'&.blur-background': {
|
||||||
|
backgroundColor: 'var(--affine-background-primary-color)',
|
||||||
|
},
|
||||||
|
'&.noisy-background::before': {
|
||||||
content: '""',
|
content: '""',
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
inset: 0,
|
inset: 0,
|
||||||
|
|||||||
@@ -1,22 +1,33 @@
|
|||||||
import { clsx } from 'clsx';
|
import { clsx } from 'clsx';
|
||||||
import type { PropsWithChildren, ReactElement } from 'react';
|
import type { FC, PropsWithChildren, ReactElement } from 'react';
|
||||||
|
|
||||||
import { AppSidebarFallback } from '../app-sidebar';
|
import { AppSidebarFallback } from '../app-sidebar';
|
||||||
import { appStyle, mainContainerStyle, toolStyle } from './index.css';
|
import { appStyle, mainContainerStyle, toolStyle } from './index.css';
|
||||||
|
|
||||||
export type WorkspaceRootProps = PropsWithChildren<{
|
export type WorkspaceRootProps = PropsWithChildren<{
|
||||||
resizing?: boolean;
|
resizing?: boolean;
|
||||||
|
useNoisyBackground?: boolean;
|
||||||
|
useBlurBackground?: boolean;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export const AppContainer = (props: WorkspaceRootProps): ReactElement => {
|
export const AppContainer: FC<WorkspaceRootProps> = ({
|
||||||
const noisyBackground = environment.isDesktop && environment.isMacOs;
|
resizing,
|
||||||
|
useNoisyBackground,
|
||||||
|
useBlurBackground,
|
||||||
|
children,
|
||||||
|
}) => {
|
||||||
|
const noisyBackground =
|
||||||
|
useNoisyBackground && environment.isDesktop && environment.isMacOs;
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={appStyle}
|
className={clsx(appStyle, {
|
||||||
|
'noisy-background': noisyBackground,
|
||||||
|
'blur-background': environment.isDesktop && useBlurBackground,
|
||||||
|
})}
|
||||||
data-noise-background={noisyBackground}
|
data-noise-background={noisyBackground}
|
||||||
data-is-resizing={props.resizing}
|
data-is-resizing={resizing}
|
||||||
>
|
>
|
||||||
{props.children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
export * from './menu';
|
export * from './menu';
|
||||||
// export { StyledMenuItem as MenuItem } from './styles';
|
// export { StyledMenuItem as MenuItem } from './styles';
|
||||||
export * from './menu-item';
|
export * from './menu-item';
|
||||||
|
export * from './menu-trigger';
|
||||||
export * from './pure-menu';
|
export * from './pure-menu';
|
||||||
|
|||||||
22
packages/component/src/ui/menu/menu-trigger.tsx
Normal file
22
packages/component/src/ui/menu/menu-trigger.tsx
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { ArrowDownSmallIcon } from '@blocksuite/icons';
|
||||||
|
import { forwardRef } from 'react';
|
||||||
|
|
||||||
|
import type { ButtonProps } from '../button';
|
||||||
|
import { StyledButton } from './styles';
|
||||||
|
|
||||||
|
export const MenuTrigger = forwardRef<HTMLButtonElement, ButtonProps>(
|
||||||
|
({ children, ...props }, ref) => {
|
||||||
|
return (
|
||||||
|
<StyledButton
|
||||||
|
ref={ref}
|
||||||
|
icon={<ArrowDownSmallIcon />}
|
||||||
|
iconPosition="end"
|
||||||
|
noBorder={true}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</StyledButton>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
MenuTrigger.displayName = 'MenuTrigger';
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import type { CSSProperties } from 'react';
|
import type { CSSProperties } from 'react';
|
||||||
|
|
||||||
import { displayFlex, styled, textEllipsis } from '../../styles';
|
import { displayFlex, styled, textEllipsis } from '../../styles';
|
||||||
|
import { Button } from '../button';
|
||||||
import StyledPopperContainer from '../shared/container';
|
import StyledPopperContainer from '../shared/container';
|
||||||
|
|
||||||
export const StyledMenuWrapper = styled(StyledPopperContainer, {
|
export const StyledMenuWrapper = styled(StyledPopperContainer, {
|
||||||
@@ -100,3 +101,16 @@ export const StyledMenuItem = styled('button')<{
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const StyledButton = styled(Button)(() => {
|
||||||
|
return {
|
||||||
|
width: '100%',
|
||||||
|
height: '32px',
|
||||||
|
borderRadius: '8px',
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
...displayFlex('space-between', 'center'),
|
||||||
|
border: `1px solid var(--affine-border-color)`,
|
||||||
|
padding: '0 10px',
|
||||||
|
fontSize: 'var(--affine-font-base)',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|||||||
1
packages/env/src/config.ts
vendored
1
packages/env/src/config.ts
vendored
@@ -48,6 +48,7 @@ export const buildFlagsSchema = z.object({
|
|||||||
enableLegacyCloud: z.boolean(),
|
enableLegacyCloud: z.boolean(),
|
||||||
changelogUrl: z.string(),
|
changelogUrl: z.string(),
|
||||||
enablePreloading: z.boolean(),
|
enablePreloading: z.boolean(),
|
||||||
|
enableNewSettingModal: z.boolean(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const blockSuiteFeatureFlags = z.object({
|
export const blockSuiteFeatureFlags = z.object({
|
||||||
|
|||||||
14
packages/env/src/workspace.ts
vendored
14
packages/env/src/workspace.ts
vendored
@@ -201,6 +201,19 @@ type SettingProps<Flavour extends keyof WorkspaceRegistry> =
|
|||||||
) => void;
|
) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type NewSettingProps<Flavour extends keyof WorkspaceRegistry> =
|
||||||
|
UIBaseProps<Flavour> & {
|
||||||
|
onDeleteWorkspace: () => Promise<void>;
|
||||||
|
onTransformWorkspace: <
|
||||||
|
From extends keyof WorkspaceRegistry,
|
||||||
|
To extends keyof WorkspaceRegistry
|
||||||
|
>(
|
||||||
|
from: From,
|
||||||
|
to: To,
|
||||||
|
workspace: WorkspaceRegistry[From]
|
||||||
|
) => void;
|
||||||
|
};
|
||||||
|
|
||||||
type PageDetailProps<Flavour extends keyof WorkspaceRegistry> =
|
type PageDetailProps<Flavour extends keyof WorkspaceRegistry> =
|
||||||
UIBaseProps<Flavour> & {
|
UIBaseProps<Flavour> & {
|
||||||
currentPageId: string;
|
currentPageId: string;
|
||||||
@@ -218,6 +231,7 @@ export interface WorkspaceUISchema<Flavour extends keyof WorkspaceRegistry> {
|
|||||||
PageDetail: FC<PageDetailProps<Flavour>>;
|
PageDetail: FC<PageDetailProps<Flavour>>;
|
||||||
PageList: FC<PageListProps<Flavour>>;
|
PageList: FC<PageListProps<Flavour>>;
|
||||||
SettingsDetail: FC<SettingProps<Flavour>>;
|
SettingsDetail: FC<SettingProps<Flavour>>;
|
||||||
|
NewSettingsDetail: FC<NewSettingProps<Flavour>>;
|
||||||
Provider: FC<PropsWithChildren>;
|
Provider: FC<PropsWithChildren>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -297,5 +297,67 @@
|
|||||||
"Update Available": "Update available",
|
"Update Available": "Update available",
|
||||||
"dark": "Dark",
|
"dark": "Dark",
|
||||||
"system": "System",
|
"system": "System",
|
||||||
"light": "Light"
|
"light": "Light",
|
||||||
|
"Need more customization options? You can suggest them to us in the community.": "Need more customization options? You can suggest them to us in the community.",
|
||||||
|
"Check Keyboard Shortcuts quickly": "Check Keyboard Shortcuts quickly",
|
||||||
|
"Quick Search": "Quick Search",
|
||||||
|
"Append to Daily Note": "Append to Daily Note",
|
||||||
|
"Expand/Collapse Sidebar": "Expand/Collapse Sidebar",
|
||||||
|
"Go Back": "Go Back",
|
||||||
|
"Go Forward": "Go Forward",
|
||||||
|
"Select All": "Select All",
|
||||||
|
"Zoom in": "Zoom in",
|
||||||
|
"Zoom out": "Zoom out",
|
||||||
|
"Zoom to 100%": "Zoom to 100%",
|
||||||
|
"Zoom to fit": "Zoom to fit",
|
||||||
|
"Image": "Image",
|
||||||
|
"Straight Connector": "Straight Connector",
|
||||||
|
"Elbowed Connector": "Elbowed Connector",
|
||||||
|
"Curve Connector": "Curve Connector",
|
||||||
|
"Hand": "Hand",
|
||||||
|
"Note": "Note",
|
||||||
|
"Group": "Group",
|
||||||
|
"Ungroup": "Ungroup",
|
||||||
|
"Group as Database": "Group as Database",
|
||||||
|
"Move Up": "Move Up",
|
||||||
|
"Move Down": "Move Down",
|
||||||
|
"Appearance Settings": "Appearance Settings",
|
||||||
|
"Customize your AFFiNE Appearance": "Customize your AFFiNE Appearance",
|
||||||
|
"Theme": "Theme",
|
||||||
|
"Date": "Date",
|
||||||
|
"Sidebar": "Sidebar",
|
||||||
|
"Color Scheme": "Color Scheme",
|
||||||
|
"Choose your color scheme": "Choose your color scheme",
|
||||||
|
"Display Language": "Display Language",
|
||||||
|
"Select the language for the interface.": "Select the language for the interface.",
|
||||||
|
"Client Border Style": "Client Border Style",
|
||||||
|
"Customize the appearance of the client.": "Customize the appearance of the client.",
|
||||||
|
"Full width Layout": "Full width Layout",
|
||||||
|
"Maximum display of content within a page.": "Maximum display of content within a page.",
|
||||||
|
"Window frame style": "Window frame style",
|
||||||
|
"Customize appearance of Windows Client.": "Customize appearance of Windows Client.",
|
||||||
|
"Date Format": "Date Format",
|
||||||
|
"Customize your date style.": "Customize your date style.",
|
||||||
|
"Start Week On Monday": "Start Week On Monday",
|
||||||
|
"By default, the week starts on Sunday.": "By default, the week starts on Sunday.",
|
||||||
|
"Disable the noise background on the sidebar": "Disable the noise background on the sidebar",
|
||||||
|
"None yet": "None yet",
|
||||||
|
"Disable the blur sidebar": "Disable the blur sidebar",
|
||||||
|
"frameless": "Frameless",
|
||||||
|
"NativeTitleBar": "Native Titlebar",
|
||||||
|
"About AFFiNE": "About AFFiNE",
|
||||||
|
"Version": "Version",
|
||||||
|
"Contact with us": "Contact with us",
|
||||||
|
"Communities": "Communities",
|
||||||
|
"Info of legal": "Info of legal",
|
||||||
|
"Check for updates": "Check for updates",
|
||||||
|
"New version is ready": "New version is ready",
|
||||||
|
"Check for updates automatically": "Check for updates automatically",
|
||||||
|
"If enabled, it will automatically check for new versions at regular intervals.": "If enabled, it will automatically check for new versions at regular intervals.",
|
||||||
|
"Download updates automatically": "Download updates automatically",
|
||||||
|
"If enabled, new versions will be automatically downloaded to the current device.": " If enabled, new versions will be automatically downloaded to the current device.",
|
||||||
|
"Discover what's new": "Discover what's new",
|
||||||
|
"View the AFFiNE Changelog.": "View the AFFiNE Changelog.",
|
||||||
|
"Privacy": "Privacy",
|
||||||
|
"Terms of Use": "Terms of Use"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
"@blocksuite/blocks": "0.0.0-20230607055421-9b20fcaf-nightly",
|
"@blocksuite/blocks": "0.0.0-20230607055421-9b20fcaf-nightly",
|
||||||
"@blocksuite/editor": "0.0.0-20230607055421-9b20fcaf-nightly",
|
"@blocksuite/editor": "0.0.0-20230607055421-9b20fcaf-nightly",
|
||||||
"@blocksuite/global": "0.0.0-20230607055421-9b20fcaf-nightly",
|
"@blocksuite/global": "0.0.0-20230607055421-9b20fcaf-nightly",
|
||||||
"@blocksuite/icons": "^2.1.19",
|
"@blocksuite/icons": "^2.1.21",
|
||||||
"@blocksuite/lit": "0.0.0-20230607055421-9b20fcaf-nightly",
|
"@blocksuite/lit": "0.0.0-20230607055421-9b20fcaf-nightly",
|
||||||
"@blocksuite/store": "0.0.0-20230607055421-9b20fcaf-nightly",
|
"@blocksuite/store": "0.0.0-20230607055421-9b20fcaf-nightly",
|
||||||
"react": "18.3.0-canary-16d053d59-20230506",
|
"react": "18.3.0-canary-16d053d59-20230506",
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ test('Drag resizer can resize sidebar', async ({ page }) => {
|
|||||||
});
|
});
|
||||||
await page.mouse.up();
|
await page.mouse.up();
|
||||||
const boundingBox = await page.getByTestId('app-sidebar').boundingBox();
|
const boundingBox = await page.getByTestId('app-sidebar').boundingBox();
|
||||||
expect(boundingBox?.width).toBe(400);
|
expect(boundingBox?.width).toBe(399);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Sidebar in between sm & md breakpoint', async ({ page }) => {
|
test('Sidebar in between sm & md breakpoint', async ({ page }) => {
|
||||||
|
|||||||
@@ -16,5 +16,5 @@ test('Open shortcuts modal', async ({ page }) => {
|
|||||||
await shortcutsIcon.click();
|
await shortcutsIcon.click();
|
||||||
await page.waitForTimeout(1000);
|
await page.waitForTimeout(1000);
|
||||||
const shortcutsModal = page.locator('[data-testid=shortcuts-modal]');
|
const shortcutsModal = page.locator('[data-testid=shortcuts-modal]');
|
||||||
await expect(shortcutsModal).toContainText('Keyboard shortcuts');
|
await expect(shortcutsModal).toContainText('Page');
|
||||||
});
|
});
|
||||||
|
|||||||
14
yarn.lock
14
yarn.lock
@@ -69,7 +69,7 @@ __metadata:
|
|||||||
"@blocksuite/blocks": 0.0.0-20230607055421-9b20fcaf-nightly
|
"@blocksuite/blocks": 0.0.0-20230607055421-9b20fcaf-nightly
|
||||||
"@blocksuite/editor": 0.0.0-20230607055421-9b20fcaf-nightly
|
"@blocksuite/editor": 0.0.0-20230607055421-9b20fcaf-nightly
|
||||||
"@blocksuite/global": 0.0.0-20230607055421-9b20fcaf-nightly
|
"@blocksuite/global": 0.0.0-20230607055421-9b20fcaf-nightly
|
||||||
"@blocksuite/icons": ^2.1.19
|
"@blocksuite/icons": ^2.1.21
|
||||||
"@blocksuite/lit": 0.0.0-20230607055421-9b20fcaf-nightly
|
"@blocksuite/lit": 0.0.0-20230607055421-9b20fcaf-nightly
|
||||||
"@blocksuite/store": 0.0.0-20230607055421-9b20fcaf-nightly
|
"@blocksuite/store": 0.0.0-20230607055421-9b20fcaf-nightly
|
||||||
"@dnd-kit/core": ^6.0.8
|
"@dnd-kit/core": ^6.0.8
|
||||||
@@ -398,7 +398,7 @@ __metadata:
|
|||||||
"@blocksuite/blocks": 0.0.0-20230607055421-9b20fcaf-nightly
|
"@blocksuite/blocks": 0.0.0-20230607055421-9b20fcaf-nightly
|
||||||
"@blocksuite/editor": 0.0.0-20230607055421-9b20fcaf-nightly
|
"@blocksuite/editor": 0.0.0-20230607055421-9b20fcaf-nightly
|
||||||
"@blocksuite/global": 0.0.0-20230607055421-9b20fcaf-nightly
|
"@blocksuite/global": 0.0.0-20230607055421-9b20fcaf-nightly
|
||||||
"@blocksuite/icons": ^2.1.19
|
"@blocksuite/icons": ^2.1.21
|
||||||
"@blocksuite/lit": 0.0.0-20230607055421-9b20fcaf-nightly
|
"@blocksuite/lit": 0.0.0-20230607055421-9b20fcaf-nightly
|
||||||
"@blocksuite/store": 0.0.0-20230607055421-9b20fcaf-nightly
|
"@blocksuite/store": 0.0.0-20230607055421-9b20fcaf-nightly
|
||||||
"@storybook/addon-actions": ^7.0.21
|
"@storybook/addon-actions": ^7.0.21
|
||||||
@@ -455,7 +455,7 @@ __metadata:
|
|||||||
"@blocksuite/blocks": 0.0.0-20230607055421-9b20fcaf-nightly
|
"@blocksuite/blocks": 0.0.0-20230607055421-9b20fcaf-nightly
|
||||||
"@blocksuite/editor": 0.0.0-20230607055421-9b20fcaf-nightly
|
"@blocksuite/editor": 0.0.0-20230607055421-9b20fcaf-nightly
|
||||||
"@blocksuite/global": 0.0.0-20230607055421-9b20fcaf-nightly
|
"@blocksuite/global": 0.0.0-20230607055421-9b20fcaf-nightly
|
||||||
"@blocksuite/icons": ^2.1.19
|
"@blocksuite/icons": ^2.1.21
|
||||||
"@blocksuite/lit": 0.0.0-20230607055421-9b20fcaf-nightly
|
"@blocksuite/lit": 0.0.0-20230607055421-9b20fcaf-nightly
|
||||||
"@blocksuite/store": 0.0.0-20230607055421-9b20fcaf-nightly
|
"@blocksuite/store": 0.0.0-20230607055421-9b20fcaf-nightly
|
||||||
"@dnd-kit/core": ^6.0.8
|
"@dnd-kit/core": ^6.0.8
|
||||||
@@ -3984,13 +3984,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@blocksuite/icons@npm:^2.1.19":
|
"@blocksuite/icons@npm:^2.1.21":
|
||||||
version: 2.1.19
|
version: 2.1.21
|
||||||
resolution: "@blocksuite/icons@npm:2.1.19"
|
resolution: "@blocksuite/icons@npm:2.1.21"
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
"@types/react": ^18.0.25
|
"@types/react": ^18.0.25
|
||||||
react: ^18.2.0
|
react: ^18.2.0
|
||||||
checksum: 6b4abbac571324241a1ada723553f8e7fd2e91c0efd57fda68ea24ffc301e4ebb2691ba59744593109b153039dc94299b139f674fc656be48037d0e29454f71b
|
checksum: ade86c53243691da1aae2bf2abca88b0d9594590a59cf30ec361cba8cb4268737e7129fc0a61ad87e610d709e3eb3d10c8fea3bb76beeeebb334dd14f1001ea1
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user