mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-21 03:56:23 +08:00
Merge remote-tracking branch 'refs/remotes/origin/feat/poc'
Conflicts: package.json packages/data-center/package.json pnpm-lock.yaml
This commit is contained in:
@@ -1,5 +1,4 @@
|
|||||||
**/webpack.config.js
|
**/webpack.config.js
|
||||||
**/jest.config.js
|
|
||||||
**/scripts/*.js
|
**/scripts/*.js
|
||||||
**/node_modules/**
|
**/node_modules/**
|
||||||
.github/**
|
.github/**
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ jobs:
|
|||||||
scope: '@toeverything'
|
scope: '@toeverything'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
||||||
- run: node scripts/module-resolve/ci.js
|
- run: node scripts/module-resolve/ci.cjs
|
||||||
|
|
||||||
- name: Restore cache
|
- name: Restore cache
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v3
|
||||||
|
|||||||
@@ -244,7 +244,7 @@ jobs:
|
|||||||
scope: '@toeverything'
|
scope: '@toeverything'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
||||||
- run: node scripts/module-resolve/ci.js
|
- run: node scripts/module-resolve/ci.cjs
|
||||||
|
|
||||||
- name: Restore cache
|
- name: Restore cache
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v3
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ jobs:
|
|||||||
scope: '@toeverything'
|
scope: '@toeverything'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
||||||
- run: node scripts/module-resolve/ci.js
|
- run: node scripts/module-resolve/ci.cjs
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install --no-frozen-lockfile
|
run: pnpm install --no-frozen-lockfile
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ Thumbs.db
|
|||||||
out/
|
out/
|
||||||
|
|
||||||
module-resolve.js
|
module-resolve.js
|
||||||
|
module-resolve.cjs
|
||||||
/test-results/
|
/test-results/
|
||||||
/playwright-report/
|
/playwright-report/
|
||||||
/playwright/.cache/
|
/playwright/.cache/
|
||||||
|
|||||||
+2
-2
@@ -1,11 +1,11 @@
|
|||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
|
||||||
function getCustomize() {
|
function getCustomize() {
|
||||||
const customed = fs.existsSync('./module-resolve.js');
|
const customed = fs.existsSync('./module-resolve.cjs');
|
||||||
if (!customed) {
|
if (!customed) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const script = require('./module-resolve.js');
|
const script = require('./module-resolve.cjs');
|
||||||
return script && script.resolve;
|
return script && script.resolve;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Vendored
+6
-1
@@ -1,3 +1,8 @@
|
|||||||
{
|
{
|
||||||
"recommendations": ["ms-playwright.playwright", "esbenp.prettier-vscode"]
|
"recommendations": [
|
||||||
|
"ms-playwright.playwright",
|
||||||
|
"esbenp.prettier-vscode",
|
||||||
|
"deepscan.vscode-deepscan",
|
||||||
|
"streetsidesoftware.code-spell-checker"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -4,6 +4,7 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"author": "toeverything",
|
"author": "toeverything",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "cross-env NODE_ENV=development pnpm --filter=!@affine/app build && pnpm --filter @affine/app dev",
|
"dev": "cross-env NODE_ENV=development pnpm --filter=!@affine/app build && pnpm --filter @affine/app dev",
|
||||||
"dev:ac": "pnpm --filter=!@affine/app build && cross-env NODE_API_SERVER=ac pnpm --filter @affine/app dev",
|
"dev:ac": "pnpm --filter=!@affine/app build && cross-env NODE_API_SERVER=ac pnpm --filter @affine/app dev",
|
||||||
@@ -47,8 +48,7 @@
|
|||||||
"lint-staged": "^13.1.0",
|
"lint-staged": "^13.1.0",
|
||||||
"prettier": "^2.7.1",
|
"prettier": "^2.7.1",
|
||||||
"ts-jest": "^29.0.3",
|
"ts-jest": "^29.0.3",
|
||||||
"typescript": "^4.9.3",
|
"typescript": "^4.9.3"
|
||||||
"vitest": "^0.26.3"
|
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
"root": true,
|
"root": true,
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
/** @type {import('ts-jest').JestConfigWithTsJest} */
|
|
||||||
module.exports = {
|
|
||||||
preset: 'ts-jest',
|
|
||||||
testEnvironment: 'node',
|
|
||||||
};
|
|
||||||
@@ -10,10 +10,10 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@affine/datacenter": "workspace:*",
|
"@affine/datacenter": "workspace:*",
|
||||||
"@blocksuite/blocks": "=0.3.1-20230106060050-1aad55d",
|
"@blocksuite/blocks": "0.3.1-20230109032243-37ad3ba",
|
||||||
"@blocksuite/editor": "=0.3.1-20230106060050-1aad55d",
|
"@blocksuite/editor": "0.3.1-20230109032243-37ad3ba",
|
||||||
"@blocksuite/icons": "^2.0.2",
|
"@blocksuite/icons": "^2.0.2",
|
||||||
"@blocksuite/store": "=0.3.1-20230106060050-1aad55d",
|
"@blocksuite/store": "0.3.1-20230109032243-37ad3ba",
|
||||||
"@emotion/css": "^11.10.0",
|
"@emotion/css": "^11.10.0",
|
||||||
"@emotion/react": "^11.10.4",
|
"@emotion/react": "^11.10.4",
|
||||||
"@emotion/server": "^11.10.0",
|
"@emotion/server": "^11.10.0",
|
||||||
@@ -49,8 +49,8 @@
|
|||||||
"eslint-config-next": "12.3.1",
|
"eslint-config-next": "12.3.1",
|
||||||
"eslint-config-prettier": "^8.5.0",
|
"eslint-config-prettier": "^8.5.0",
|
||||||
"eslint-plugin-prettier": "^4.2.1",
|
"eslint-plugin-prettier": "^4.2.1",
|
||||||
"raw-loader": "^4.0.2",
|
|
||||||
"next-pwa": "^5.6.0",
|
"next-pwa": "^5.6.0",
|
||||||
|
"raw-loader": "^4.0.2",
|
||||||
"typescript": "4.8.3"
|
"typescript": "4.8.3"
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
TelegramIcon,
|
TelegramIcon,
|
||||||
RedditIcon,
|
RedditIcon,
|
||||||
LinkIcon,
|
LinkIcon,
|
||||||
} from './icons';
|
} from './Icons';
|
||||||
import logo from './affine-text-logo.png';
|
import logo from './affine-text-logo.png';
|
||||||
import {
|
import {
|
||||||
StyledBigLink,
|
StyledBigLink,
|
||||||
|
|||||||
@@ -0,0 +1,94 @@
|
|||||||
|
import { styled } from '@/styles';
|
||||||
|
import { Modal, ModalWrapper, ModalCloseButton } from '@/ui/modal';
|
||||||
|
import { Button } from '@/ui/button';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import Input from '@/ui/input';
|
||||||
|
import { useTemporaryHelper } from '@/providers/temporary-helper-provider';
|
||||||
|
import { KeyboardEvent } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
interface ICloseParams {
|
||||||
|
workspaceId?: string;
|
||||||
|
}
|
||||||
|
interface ModalProps {
|
||||||
|
open: boolean;
|
||||||
|
onClose: (opts: ICloseParams) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CreateWorkspaceModal = ({ open, onClose }: ModalProps) => {
|
||||||
|
const [workspaceName, setWorkspaceName] = useState('');
|
||||||
|
const { createWorkspace, setActiveWorkspace } = useTemporaryHelper();
|
||||||
|
const handleCreateWorkspace = () => {
|
||||||
|
const workspace = createWorkspace(workspaceName);
|
||||||
|
onClose({ workspaceId: workspace.id });
|
||||||
|
setActiveWorkspace(workspace);
|
||||||
|
};
|
||||||
|
const handleKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
|
||||||
|
if (event.key === 'Enter') {
|
||||||
|
// 👇 Get input value
|
||||||
|
handleCreateWorkspace();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const { t } = useTranslation();
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Modal open={open} onClose={onClose}>
|
||||||
|
<ModalWrapper width={620} height={334} style={{ padding: '10px' }}>
|
||||||
|
<Header>
|
||||||
|
<ContentTitle>{t('New Workspace')}</ContentTitle>
|
||||||
|
<ModalCloseButton
|
||||||
|
top={6}
|
||||||
|
right={6}
|
||||||
|
onClick={() => {
|
||||||
|
onClose({});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Header>
|
||||||
|
<Content>
|
||||||
|
<p>{t('Workspace description')}</p>
|
||||||
|
<Input
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
|
onChange={value => {
|
||||||
|
setWorkspaceName(value);
|
||||||
|
}}
|
||||||
|
></Input>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
handleCreateWorkspace();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('Create')}
|
||||||
|
</Button>
|
||||||
|
</Content>
|
||||||
|
</ModalWrapper>
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const Header = styled('div')({
|
||||||
|
position: 'relative',
|
||||||
|
height: '44px',
|
||||||
|
});
|
||||||
|
|
||||||
|
const Content = styled('div')({
|
||||||
|
display: 'flex',
|
||||||
|
padding: '0 48px',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '16px',
|
||||||
|
});
|
||||||
|
|
||||||
|
const ContentTitle = styled('span')({
|
||||||
|
fontSize: '20px',
|
||||||
|
lineHeight: '28px',
|
||||||
|
fontWeight: 600,
|
||||||
|
textAlign: 'left',
|
||||||
|
paddingBottom: '16px',
|
||||||
|
});
|
||||||
|
|
||||||
|
// const Footer = styled('div')({
|
||||||
|
// height: '70px',
|
||||||
|
// paddingLeft: '24px',
|
||||||
|
// marginTop: '32px',
|
||||||
|
// textAlign: 'center',
|
||||||
|
// });
|
||||||
@@ -13,79 +13,83 @@ import {
|
|||||||
ConnectorIcon,
|
ConnectorIcon,
|
||||||
UndoIcon,
|
UndoIcon,
|
||||||
RedoIcon,
|
RedoIcon,
|
||||||
} from './icons';
|
} from './Icons';
|
||||||
import { Tooltip } from '@/ui/tooltip';
|
import { Tooltip } from '@/ui/tooltip';
|
||||||
import Slide from '@mui/material/Slide';
|
import Slide from '@mui/material/Slide';
|
||||||
import useCurrentPageMeta from '@/hooks/use-current-page-meta';
|
import useCurrentPageMeta from '@/hooks/use-current-page-meta';
|
||||||
import { useAppState } from '@/providers/app-state-provider';
|
import { useAppState } from '@/providers/app-state-provider';
|
||||||
import useHistoryUpdated from '@/hooks/use-history-update';
|
import useHistoryUpdated from '@/hooks/use-history-update';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
const toolbarList1 = [
|
const useToolbarList1 = () => {
|
||||||
{
|
const { t } = useTranslation();
|
||||||
flavor: 'select',
|
return [
|
||||||
icon: <SelectIcon />,
|
{
|
||||||
toolTip: 'Select',
|
flavor: 'select',
|
||||||
disable: false,
|
icon: <SelectIcon />,
|
||||||
callback: () => {
|
toolTip: t('Select'),
|
||||||
window.dispatchEvent(
|
disable: false,
|
||||||
new CustomEvent('affine.switch-mouse-mode', {
|
callback: () => {
|
||||||
detail: {
|
window.dispatchEvent(
|
||||||
type: 'default',
|
new CustomEvent('affine.switch-mouse-mode', {
|
||||||
},
|
detail: {
|
||||||
})
|
type: 'default',
|
||||||
);
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
{
|
||||||
{
|
flavor: 'text',
|
||||||
flavor: 'text',
|
icon: <TextIcon />,
|
||||||
icon: <TextIcon />,
|
toolTip: t('Text'),
|
||||||
toolTip: 'Text (coming soon)',
|
disable: true,
|
||||||
disable: true,
|
},
|
||||||
},
|
{
|
||||||
{
|
flavor: 'shape',
|
||||||
flavor: 'shape',
|
icon: <ShapeIcon />,
|
||||||
icon: <ShapeIcon />,
|
toolTip: t('Shape'),
|
||||||
toolTip: 'Shape',
|
disable: false,
|
||||||
disable: false,
|
callback: () => {
|
||||||
callback: () => {
|
window.dispatchEvent(
|
||||||
window.dispatchEvent(
|
new CustomEvent('affine.switch-mouse-mode', {
|
||||||
new CustomEvent('affine.switch-mouse-mode', {
|
detail: {
|
||||||
detail: {
|
type: 'shape',
|
||||||
type: 'shape',
|
color: 'black',
|
||||||
color: 'black',
|
shape: 'rectangle',
|
||||||
shape: 'rectangle',
|
},
|
||||||
},
|
})
|
||||||
})
|
);
|
||||||
);
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
flavor: 'sticky',
|
||||||
|
icon: <StickerIcon />,
|
||||||
|
toolTip: t('Sticky'),
|
||||||
|
disable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
flavor: 'pen',
|
||||||
|
icon: <PenIcon />,
|
||||||
|
toolTip: t('Pen'),
|
||||||
|
disable: true,
|
||||||
},
|
},
|
||||||
},
|
|
||||||
{
|
|
||||||
flavor: 'sticky',
|
|
||||||
icon: <StickerIcon />,
|
|
||||||
toolTip: 'Sticky (coming soon)',
|
|
||||||
disable: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
flavor: 'pen',
|
|
||||||
icon: <PenIcon />,
|
|
||||||
toolTip: 'Pen (coming soon)',
|
|
||||||
disable: true,
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
{
|
||||||
flavor: 'connector',
|
flavor: 'connector',
|
||||||
icon: <ConnectorIcon />,
|
icon: <ConnectorIcon />,
|
||||||
toolTip: 'Connector (coming soon)',
|
toolTip: t('Connector'),
|
||||||
disable: true,
|
disable: true,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
};
|
||||||
|
|
||||||
const UndoRedo = () => {
|
const UndoRedo = () => {
|
||||||
const [canUndo, setCanUndo] = useState(false);
|
const [canUndo, setCanUndo] = useState(false);
|
||||||
const [canRedo, setCanRedo] = useState(false);
|
const [canRedo, setCanRedo] = useState(false);
|
||||||
const { currentPage } = useAppState();
|
const { currentPage } = useAppState();
|
||||||
const onHistoryUpdated = useHistoryUpdated();
|
const onHistoryUpdated = useHistoryUpdated();
|
||||||
|
const { t } = useTranslation();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
onHistoryUpdated(page => {
|
onHistoryUpdated(page => {
|
||||||
setCanUndo(page.canUndo);
|
setCanUndo(page.canUndo);
|
||||||
@@ -95,7 +99,7 @@ const UndoRedo = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledToolbarWrapper>
|
<StyledToolbarWrapper>
|
||||||
<Tooltip content="Undo" placement="right-start">
|
<Tooltip content={t('Undo')} placement="right-start">
|
||||||
<StyledToolbarItem
|
<StyledToolbarItem
|
||||||
disable={!canUndo}
|
disable={!canUndo}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -105,7 +109,7 @@ const UndoRedo = () => {
|
|||||||
<UndoIcon />
|
<UndoIcon />
|
||||||
</StyledToolbarItem>
|
</StyledToolbarItem>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip content="Redo" placement="right-start">
|
<Tooltip content={t('Redo')} placement="right-start">
|
||||||
<StyledToolbarItem
|
<StyledToolbarItem
|
||||||
disable={!canRedo}
|
disable={!canRedo}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -131,7 +135,7 @@ export const EdgelessToolbar = () => {
|
|||||||
>
|
>
|
||||||
<StyledEdgelessToolbar aria-label="edgeless-toolbar">
|
<StyledEdgelessToolbar aria-label="edgeless-toolbar">
|
||||||
<StyledToolbarWrapper>
|
<StyledToolbarWrapper>
|
||||||
{toolbarList1.map(
|
{useToolbarList1().map(
|
||||||
({ icon, toolTip, flavor, disable, callback }, index) => {
|
({ icon, toolTip, flavor, disable, callback }, index) => {
|
||||||
return (
|
return (
|
||||||
<Tooltip key={index} content={toolTip} placement="right-start">
|
<Tooltip key={index} content={toolTip} placement="right-start">
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ import type {
|
|||||||
AnimateRadioProps,
|
AnimateRadioProps,
|
||||||
AnimateRadioItemProps,
|
AnimateRadioItemProps,
|
||||||
} from './type';
|
} from './type';
|
||||||
import { useTheme } from '@/providers/themeProvider';
|
import { useTheme } from '@/providers/ThemeProvider';
|
||||||
import { EdgelessIcon, PaperIcon } from './icons';
|
import { EdgelessIcon, PaperIcon } from './Icons';
|
||||||
import useCurrentPageMeta from '@/hooks/use-current-page-meta';
|
import useCurrentPageMeta from '@/hooks/use-current-page-meta';
|
||||||
import { usePageHelper } from '@/hooks/use-page-helper';
|
import { usePageHelper } from '@/hooks/use-page-helper';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Button } from '@/ui/button';
|
import { Button } from '@/ui/button';
|
||||||
import { FC, useRef, ChangeEvent, ReactElement } from 'react';
|
import { FC, useRef, ChangeEvent, ReactElement } from 'react';
|
||||||
import { styled } from '@/styles';
|
import { styled } from '@/styles';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
interface Props {
|
interface Props {
|
||||||
uploadType?: string;
|
uploadType?: string;
|
||||||
children?: ReactElement;
|
children?: ReactElement;
|
||||||
@@ -9,6 +10,7 @@ interface Props {
|
|||||||
}
|
}
|
||||||
export const Upload: FC<Props> = props => {
|
export const Upload: FC<Props> = props => {
|
||||||
const { fileChange, accept } = props;
|
const { fileChange, accept } = props;
|
||||||
|
const { t } = useTranslation();
|
||||||
const input_ref = useRef<HTMLInputElement>(null);
|
const input_ref = useRef<HTMLInputElement>(null);
|
||||||
const _chooseFile = () => {
|
const _chooseFile = () => {
|
||||||
if (input_ref.current) {
|
if (input_ref.current) {
|
||||||
@@ -28,7 +30,7 @@ export const Upload: FC<Props> = props => {
|
|||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<UploadStyle onClick={_chooseFile}>
|
<UploadStyle onClick={_chooseFile}>
|
||||||
{props.children ?? <Button>Upload</Button>}
|
{props.children ?? <Button>{t('Upload')}</Button>}
|
||||||
<input
|
<input
|
||||||
ref={input_ref}
|
ref={input_ref}
|
||||||
type="file"
|
type="file"
|
||||||
|
|||||||
+2
-2
@@ -8,8 +8,8 @@ import {
|
|||||||
import { Content } from '@/ui/layout';
|
import { Content } from '@/ui/layout';
|
||||||
import { useAppState } from '@/providers/app-state-provider/context';
|
import { useAppState } from '@/providers/app-state-provider/context';
|
||||||
import EditorModeSwitch from '@/components/editor-mode-switch';
|
import EditorModeSwitch from '@/components/editor-mode-switch';
|
||||||
import QuickSearchButton from './quick-search-button';
|
import QuickSearchButton from './QuickSearchButton';
|
||||||
import Header from './header';
|
import Header from './Header';
|
||||||
import usePropsUpdated from '@/hooks/use-props-updated';
|
import usePropsUpdated from '@/hooks/use-props-updated';
|
||||||
import useCurrentPageMeta from '@/hooks/use-current-page-meta';
|
import useCurrentPageMeta from '@/hooks/use-current-page-meta';
|
||||||
|
|
||||||
+3
-3
@@ -8,10 +8,10 @@ import {
|
|||||||
} from './styles';
|
} from './styles';
|
||||||
import CloseIcon from '@mui/icons-material/Close';
|
import CloseIcon from '@mui/icons-material/Close';
|
||||||
import { getWarningMessage, shouldShowWarning } from './utils';
|
import { getWarningMessage, shouldShowWarning } from './utils';
|
||||||
import EditorOptionMenu from './header-right-items/editor-option-menu';
|
import EditorOptionMenu from './header-right-items/EditorOptionMenu';
|
||||||
import TrashButtonGroup from './header-right-items/trash-button-group';
|
import TrashButtonGroup from './header-right-items/TrashButtonGroup';
|
||||||
import ThemeModeSwitch from './header-right-items/theme-mode-switch';
|
import ThemeModeSwitch from './header-right-items/theme-mode-switch';
|
||||||
import SyncUser from './header-right-items/sync-user';
|
import SyncUser from './header-right-items/SyncUser';
|
||||||
|
|
||||||
const BrowserWarning = ({
|
const BrowserWarning = ({
|
||||||
show,
|
show,
|
||||||
+2
-2
@@ -1,7 +1,7 @@
|
|||||||
import { PropsWithChildren, ReactNode } from 'react';
|
import { PropsWithChildren, ReactNode } from 'react';
|
||||||
import Header from './header';
|
import Header from './Header';
|
||||||
import { StyledPageListTittleWrapper } from './styles';
|
import { StyledPageListTittleWrapper } from './styles';
|
||||||
import QuickSearchButton from './quick-search-button';
|
import QuickSearchButton from './QuickSearchButton';
|
||||||
|
|
||||||
export type PageListHeaderProps = PropsWithChildren<{
|
export type PageListHeaderProps = PropsWithChildren<{
|
||||||
icon?: ReactNode;
|
icon?: ReactNode;
|
||||||
+1
-1
@@ -2,7 +2,7 @@ import React from 'react';
|
|||||||
import { IconButton, IconButtonProps } from '@/ui/button';
|
import { IconButton, IconButtonProps } from '@/ui/button';
|
||||||
import { Tooltip } from '@/ui/tooltip';
|
import { Tooltip } from '@/ui/tooltip';
|
||||||
import { ArrowDownIcon } from '@blocksuite/icons';
|
import { ArrowDownIcon } from '@blocksuite/icons';
|
||||||
import { useModal } from '@/providers/global-modal-provider';
|
import { useModal } from '@/providers/GlobalModalProvider';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
export const QuickSearchButton = ({
|
export const QuickSearchButton = ({
|
||||||
onClick,
|
onClick,
|
||||||
+1
-1
@@ -13,7 +13,7 @@ import {
|
|||||||
} from '@blocksuite/icons';
|
} from '@blocksuite/icons';
|
||||||
import { useAppState } from '@/providers/app-state-provider';
|
import { useAppState } from '@/providers/app-state-provider';
|
||||||
import { usePageHelper } from '@/hooks/use-page-helper';
|
import { usePageHelper } from '@/hooks/use-page-helper';
|
||||||
import { useConfirm } from '@/providers/confirm-provider';
|
import { useConfirm } from '@/providers/ConfirmProvider';
|
||||||
import useCurrentPageMeta from '@/hooks/use-current-page-meta';
|
import useCurrentPageMeta from '@/hooks/use-current-page-meta';
|
||||||
import { toast } from '@/ui/toast';
|
import { toast } from '@/ui/toast';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
import { CloudUnsyncedIcon, CloudInsyncIcon } from '@blocksuite/icons';
|
import { CloudUnsyncedIcon, CloudInsyncIcon } from '@blocksuite/icons';
|
||||||
import { useModal } from '@/providers/global-modal-provider';
|
import { useModal } from '@/providers/GlobalModalProvider';
|
||||||
import { useAppState } from '@/providers/app-state-provider/context';
|
import { useAppState } from '@/providers/app-state-provider/context';
|
||||||
import { IconButton } from '@/ui/button';
|
import { IconButton } from '@/ui/button';
|
||||||
|
|
||||||
+8
-8
@@ -1,9 +1,10 @@
|
|||||||
import { Button } from '@/ui/button';
|
import { Button } from '@/ui/button';
|
||||||
import { usePageHelper } from '@/hooks/use-page-helper';
|
import { usePageHelper } from '@/hooks/use-page-helper';
|
||||||
import { useAppState } from '@/providers/app-state-provider';
|
import { useAppState } from '@/providers/app-state-provider';
|
||||||
import { useConfirm } from '@/providers/confirm-provider';
|
import { useConfirm } from '@/providers/ConfirmProvider';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import useCurrentPageMeta from '@/hooks/use-current-page-meta';
|
import useCurrentPageMeta from '@/hooks/use-current-page-meta';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
export const TrashButtonGroup = () => {
|
export const TrashButtonGroup = () => {
|
||||||
const { permanentlyDeletePage } = usePageHelper();
|
const { permanentlyDeletePage } = usePageHelper();
|
||||||
@@ -12,7 +13,7 @@ export const TrashButtonGroup = () => {
|
|||||||
const { confirm } = useConfirm();
|
const { confirm } = useConfirm();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { id = '' } = useCurrentPageMeta() || {};
|
const { id = '' } = useCurrentPageMeta() || {};
|
||||||
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Button
|
<Button
|
||||||
@@ -23,7 +24,7 @@ export const TrashButtonGroup = () => {
|
|||||||
toggleDeletePage(id);
|
toggleDeletePage(id);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Restore it
|
{t('Restore it')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
bold={true}
|
bold={true}
|
||||||
@@ -31,10 +32,9 @@ export const TrashButtonGroup = () => {
|
|||||||
type="danger"
|
type="danger"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
confirm({
|
confirm({
|
||||||
title: 'Permanently delete',
|
title: t('TrashButtonGroupTitle'),
|
||||||
content:
|
content: t('TrashButtonGroupDescription'),
|
||||||
"Once deleted, you can't undo this action. Do you confirm?",
|
confirmText: t('Delete'),
|
||||||
confirmText: 'Delete',
|
|
||||||
confirmType: 'danger',
|
confirmType: 'danger',
|
||||||
}).then(confirm => {
|
}).then(confirm => {
|
||||||
if (confirm) {
|
if (confirm) {
|
||||||
@@ -44,7 +44,7 @@ export const TrashButtonGroup = () => {
|
|||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Delete permanently
|
{t('Delete permanently')}
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useTheme } from '@/providers/themeProvider';
|
import { useTheme } from '@/providers/ThemeProvider';
|
||||||
import { MoonIcon, SunIcon } from './icons';
|
import { MoonIcon, SunIcon } from './Icons';
|
||||||
import { StyledThemeModeSwitch, StyledSwitchItem } from './style';
|
import { StyledThemeModeSwitch, StyledSwitchItem } from './style';
|
||||||
|
|
||||||
export const ThemeModeSwitch = () => {
|
export const ThemeModeSwitch = () => {
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
export * from './header';
|
export * from './Header';
|
||||||
export * from './editor-header';
|
export * from './EditorHeader';
|
||||||
export * from './page-list-header';
|
export * from './PageListHeader';
|
||||||
|
|||||||
@@ -5,12 +5,12 @@ import {
|
|||||||
StyledIslandWrapper,
|
StyledIslandWrapper,
|
||||||
StyledTransformIcon,
|
StyledTransformIcon,
|
||||||
} from './style';
|
} from './style';
|
||||||
import { CloseIcon, ContactIcon, HelpIcon, KeyboardIcon } from './icons';
|
import { CloseIcon, ContactIcon, HelpIcon, KeyboardIcon } from './Icons';
|
||||||
import Grow from '@mui/material/Grow';
|
import Grow from '@mui/material/Grow';
|
||||||
import { Tooltip } from '@/ui/tooltip';
|
import { Tooltip } from '@/ui/tooltip';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useModal } from '@/providers/global-modal-provider';
|
import { useModal } from '@/providers/GlobalModalProvider';
|
||||||
import { useTheme } from '@/providers/themeProvider';
|
import { useTheme } from '@/providers/ThemeProvider';
|
||||||
import useCurrentPageMeta from '@/hooks/use-current-page-meta';
|
import useCurrentPageMeta from '@/hooks/use-current-page-meta';
|
||||||
export type IslandItemNames = 'contact' | 'shortcuts';
|
export type IslandItemNames = 'contact' | 'shortcuts';
|
||||||
export const HelpIsland = ({
|
export const HelpIsland = ({
|
||||||
|
|||||||
@@ -4,8 +4,9 @@ import { Modal, ModalWrapper, ModalCloseButton } from '@/ui/modal';
|
|||||||
import { Button } from '@/ui/button';
|
import { Button } from '@/ui/button';
|
||||||
import Input from '@/ui/input';
|
import Input from '@/ui/input';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { getDataCenter } from '@affine/datacenter';
|
// import { getDataCenter } from '@affine/datacenter';
|
||||||
import { Avatar } from '@mui/material';
|
import { Avatar } from '@mui/material';
|
||||||
|
import { setMember } from '@/hooks/mock-data/mock';
|
||||||
interface LoginModalProps {
|
interface LoginModalProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
@@ -54,29 +55,34 @@ export const InviteMembers = ({
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const [userData, setUserData] = useState<any>({});
|
const [userData, setUserData] = useState<any>({});
|
||||||
const inputChange = (value: string) => {
|
const inputChange = (value: string) => {
|
||||||
setEmail(value);
|
|
||||||
setShowMember(true);
|
setShowMember(true);
|
||||||
if (gmailReg.test(value)) {
|
if (gmailReg.test(value)) {
|
||||||
|
setEmail(value);
|
||||||
setShowTip(false);
|
setShowTip(false);
|
||||||
debounce(
|
setUserData({
|
||||||
() => {
|
name: 'wxl',
|
||||||
// getDataCenter()
|
avatar: 'https://avatars.githubusercontent.com/u/20501502?v=4',
|
||||||
// .then(dc =>
|
email: value,
|
||||||
// dc.apis.getUserByEmail({
|
});
|
||||||
// email: value,
|
// debounce(
|
||||||
// workspace_id: workspaceId,
|
// () => {
|
||||||
// })
|
// getDataCenter()
|
||||||
// )
|
// .then(dc =>
|
||||||
// .then(data => {
|
// dc.apis.getUserByEmail({
|
||||||
// if (data?.name) {
|
// email: value,
|
||||||
// setUserData(data);
|
// workspace_id: workspaceId,
|
||||||
// setShowTip(false);
|
// })
|
||||||
// }
|
// )
|
||||||
// });
|
// .then(data => {
|
||||||
},
|
// if (data?.name) {
|
||||||
300,
|
// setUserData(data);
|
||||||
true
|
// setShowTip(false);
|
||||||
)();
|
// }
|
||||||
|
// });
|
||||||
|
// },
|
||||||
|
// 300,
|
||||||
|
// true
|
||||||
|
// )();
|
||||||
} else {
|
} else {
|
||||||
setShowTip(true);
|
setShowTip(true);
|
||||||
}
|
}
|
||||||
@@ -134,6 +140,8 @@ export const InviteMembers = ({
|
|||||||
shape="circle"
|
shape="circle"
|
||||||
type="primary"
|
type="primary"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
setMember(workspaceId, userData);
|
||||||
|
onInviteSuccess();
|
||||||
// getDataCenter()
|
// getDataCenter()
|
||||||
// .then(dc => dc.apis.inviteMember({ id: workspaceId, email }))
|
// .then(dc => dc.apis.inviteMember({ id: workspaceId, email }))
|
||||||
// .then(() => {
|
// .then(() => {
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
import { styled } from '@/styles';
|
import { styled } from '@/styles';
|
||||||
import Loading from './loading';
|
import Loading from './Loading';
|
||||||
|
|
||||||
// Used for the full page loading
|
// Used for the full page loading
|
||||||
const StyledLoadingContainer = styled('div')(() => {
|
const StyledLoadingContainer = styled('div')(() => {
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
import Loading from './loading';
|
import Loading from './Loading';
|
||||||
export * from './page-loading';
|
export * from './PageLoading';
|
||||||
export default Loading;
|
export default Loading;
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { getDataCenter } from '@affine/datacenter';
|
import { getDataCenter } from '@affine/datacenter';
|
||||||
import { styled } from '@/styles';
|
import { styled } from '@/styles';
|
||||||
import { Button } from '@/ui/button';
|
import { Button } from '@/ui/button';
|
||||||
import { useModal } from '@/providers/global-modal-provider';
|
import { useModal } from '@/providers/GlobalModalProvider';
|
||||||
import { GoogleIcon, StayLogOutIcon } from './icons';
|
import { GoogleIcon, StayLogOutIcon } from './Icons';
|
||||||
|
|
||||||
export const GoogleLoginButton = () => {
|
export const GoogleLoginButton = () => {
|
||||||
const { triggerLoginModal } = useModal();
|
const { triggerLoginModal } = useModal();
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
import { useConfirm } from '@/providers/confirm-provider';
|
import { useConfirm } from '@/providers/ConfirmProvider';
|
||||||
import { PageMeta } from '@/providers/app-state-provider';
|
import { PageMeta } from '@/providers/app-state-provider';
|
||||||
import { Menu, MenuItem } from '@/ui/menu';
|
import { Menu, MenuItem } from '@/ui/menu';
|
||||||
import { Wrapper } from '@/ui/layout';
|
import { Wrapper } from '@/ui/layout';
|
||||||
@@ -12,18 +12,18 @@ import {
|
|||||||
StyledTitleWrapper,
|
StyledTitleWrapper,
|
||||||
} from './styles';
|
} from './styles';
|
||||||
import { Table, TableBody, TableCell, TableHead, TableRow } from '@/ui/table';
|
import { Table, TableBody, TableCell, TableHead, TableRow } from '@/ui/table';
|
||||||
import { OperationCell, TrashOperationCell } from './operation-cell';
|
import { OperationCell, TrashOperationCell } from './OperationCell';
|
||||||
import Empty from './empty';
|
import Empty from './Empty';
|
||||||
import { Content } from '@/ui/layout';
|
import { Content } from '@/ui/layout';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import DateCell from '@/components/page-list/date-cell';
|
import DateCell from '@/components/page-list/DateCell';
|
||||||
import { IconButton } from '@/ui/button';
|
import { IconButton } from '@/ui/button';
|
||||||
import { Tooltip } from '@/ui/tooltip';
|
import { Tooltip } from '@/ui/tooltip';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { useAppState } from '@/providers/app-state-provider/context';
|
import { useAppState } from '@/providers/app-state-provider/context';
|
||||||
import { toast } from '@/ui/toast';
|
import { toast } from '@/ui/toast';
|
||||||
import { usePageHelper } from '@/hooks/use-page-helper';
|
import { usePageHelper } from '@/hooks/use-page-helper';
|
||||||
import { useTheme } from '@/providers/themeProvider';
|
import { useTheme } from '@/providers/ThemeProvider';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
const FavoriteTag = ({
|
const FavoriteTag = ({
|
||||||
pageMeta: { favorite, id },
|
pageMeta: { favorite, id },
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { AddIcon } from '@blocksuite/icons';
|
import { AddIcon } from '@blocksuite/icons';
|
||||||
import { StyledModalFooterContent } from './style';
|
import { StyledModalFooterContent } from './style';
|
||||||
import { useModal } from '@/providers/global-modal-provider';
|
import { useModal } from '@/providers/GlobalModalProvider';
|
||||||
import { Command } from 'cmdk';
|
import { Command } from 'cmdk';
|
||||||
import { usePageHelper } from '@/hooks/use-page-helper';
|
import { usePageHelper } from '@/hooks/use-page-helper';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
import { Command } from 'cmdk';
|
import { Command } from 'cmdk';
|
||||||
import { StyledListItem, StyledNotFound } from './style';
|
import { StyledListItem, StyledNotFound } from './style';
|
||||||
import { useModal } from '@/providers/global-modal-provider';
|
import { useModal } from '@/providers/GlobalModalProvider';
|
||||||
import { PaperIcon, EdgelessIcon } from '@blocksuite/icons';
|
import { PaperIcon, EdgelessIcon } from '@blocksuite/icons';
|
||||||
import { Dispatch, SetStateAction, useEffect, useState } from 'react';
|
import { Dispatch, SetStateAction, useEffect, useState } from 'react';
|
||||||
import { useAppState } from '@/providers/app-state-provider';
|
import { useAppState } from '@/providers/app-state-provider';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { useSwitchToConfig } from './config';
|
import { useSwitchToConfig } from './config';
|
||||||
import { NoResultSVG } from './noResultSVG';
|
import { NoResultSVG } from './NoResultSVG';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import usePageHelper from '@/hooks/use-page-helper';
|
import usePageHelper from '@/hooks/use-page-helper';
|
||||||
import usePageMetaList from '@/hooks/use-page-meta-list';
|
import usePageMetaList from '@/hooks/use-page-meta-list';
|
||||||
@@ -6,12 +6,12 @@ import {
|
|||||||
StyledModalDivider,
|
StyledModalDivider,
|
||||||
StyledShortcut,
|
StyledShortcut,
|
||||||
} from './style';
|
} from './style';
|
||||||
import { Input } from './input';
|
import { Input } from './Input';
|
||||||
import { Results } from './results';
|
import { Results } from './Results';
|
||||||
import { Footer } from './footer';
|
import { Footer } from './Footer';
|
||||||
import { Command } from 'cmdk';
|
import { Command } from 'cmdk';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useModal } from '@/providers/global-modal-provider';
|
import { useModal } from '@/providers/GlobalModalProvider';
|
||||||
import { getUaHelper } from '@/utils';
|
import { getUaHelper } from '@/utils';
|
||||||
import { useAppState } from '@/providers/app-state-provider';
|
import { useAppState } from '@/providers/app-state-provider';
|
||||||
type TransitionsModalProps = {
|
type TransitionsModalProps = {
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ export const useMacKeyboardShortcuts = (): ShortcutTip => {
|
|||||||
[t('Strikethrough')]: '⌘+⇧+S',
|
[t('Strikethrough')]: '⌘+⇧+S',
|
||||||
[t('Inline code')]: ' ⌘+E',
|
[t('Inline code')]: ' ⌘+E',
|
||||||
[t('Code block')]: '⌘+⌥+C',
|
[t('Code block')]: '⌘+⌥+C',
|
||||||
[t('Link')]: '⌘+K',
|
[t('Hyperlink(with selected text)')]: '⌘+K',
|
||||||
|
[t('Quick search')]: '⌘+K',
|
||||||
[t('Body text')]: '⌘+⌥+0',
|
[t('Body text')]: '⌘+⌥+0',
|
||||||
[t('Heading', { number: '1' })]: '⌘+⌥+1',
|
[t('Heading', { number: '1' })]: '⌘+⌥+1',
|
||||||
[t('Heading', { number: '2' })]: '⌘+⌥+2',
|
[t('Heading', { number: '2' })]: '⌘+⌥+2',
|
||||||
@@ -56,7 +57,8 @@ export const useWindowsKeyboardShortcuts = (): ShortcutTip => {
|
|||||||
[t('Strikethrough')]: 'Ctrl+Shift+S',
|
[t('Strikethrough')]: 'Ctrl+Shift+S',
|
||||||
[t('Inline code')]: ' Ctrl+E',
|
[t('Inline code')]: ' Ctrl+E',
|
||||||
[t('Code block')]: 'Ctrl+Alt+C',
|
[t('Code block')]: 'Ctrl+Alt+C',
|
||||||
[t('Link')]: 'Ctrl+K',
|
[t('Hyperlink(with selected text)')]: 'Ctrl+K',
|
||||||
|
[t('Quick search')]: 'Ctrl+K',
|
||||||
[t('Body text')]: 'Ctrl+Shift+0',
|
[t('Body text')]: 'Ctrl+Shift+0',
|
||||||
[t('Heading', { number: '1' })]: 'Ctrl+Shift+1',
|
[t('Heading', { number: '1' })]: 'Ctrl+Shift+1',
|
||||||
[t('Heading', { number: '2' })]: 'Ctrl+Shift+2',
|
[t('Heading', { number: '2' })]: 'Ctrl+Shift+2',
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
import { KeyboardIcon } from './icons';
|
import { KeyboardIcon } from './Icons';
|
||||||
import {
|
import {
|
||||||
StyledListItem,
|
StyledListItem,
|
||||||
StyledModalHeader,
|
StyledModalHeader,
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { stringToColour } from '@/utils';
|
||||||
|
|
||||||
|
interface IWorkspaceAvatar {
|
||||||
|
size: number;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WorkspaceAvatar = (props: IWorkspaceAvatar) => {
|
||||||
|
const size = props.size || 20;
|
||||||
|
const sizeStr = size + 'px';
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: sizeStr,
|
||||||
|
height: sizeStr,
|
||||||
|
border: '1px solid #fff',
|
||||||
|
color: '#fff',
|
||||||
|
fontSize: Math.ceil(0.5 * size) + 'px',
|
||||||
|
background: stringToColour(props.name || 'AFFiNE'),
|
||||||
|
borderRadius: '50%',
|
||||||
|
textAlign: 'center',
|
||||||
|
lineHeight: size + 'px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{(props.name || 'AFFiNE').substring(0, 1)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,228 @@
|
|||||||
|
import { styled } from '@/styles';
|
||||||
|
import { Modal, ModalWrapper, ModalCloseButton } from '@/ui/modal';
|
||||||
|
import { Button } from '@/ui/button';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { SignOut } from '@/hooks/mock-data/mock';
|
||||||
|
import { CreateWorkspaceModal } from '../create-workspace';
|
||||||
|
import {
|
||||||
|
CloudUnsyncedIcon,
|
||||||
|
CloudInsyncIcon,
|
||||||
|
UsersIcon,
|
||||||
|
AddIcon,
|
||||||
|
} from '@blocksuite/icons';
|
||||||
|
import { useConfirm } from '@/providers/confirm-provider';
|
||||||
|
import { toast } from '@/ui/toast';
|
||||||
|
import { WorkspaceAvatar } from '@/components/workspace-avatar';
|
||||||
|
import { useTemporaryHelper } from '@/providers/temporary-helper-provider';
|
||||||
|
|
||||||
|
interface LoginModalProps {
|
||||||
|
open: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WorkspaceModal = ({ open, onClose }: LoginModalProps) => {
|
||||||
|
const [createWorkspaceOpen, setCreateWorkspaceOpen] = useState(false);
|
||||||
|
const { confirm } = useConfirm();
|
||||||
|
const {
|
||||||
|
user,
|
||||||
|
login,
|
||||||
|
workspaceMetaList,
|
||||||
|
setActiveWorkspace,
|
||||||
|
updateWorkspaceMeta,
|
||||||
|
} = useTemporaryHelper();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Modal open={open} onClose={onClose}>
|
||||||
|
<ModalWrapper
|
||||||
|
width={820}
|
||||||
|
style={{ padding: '10px', display: 'flex', flexDirection: 'column' }}
|
||||||
|
>
|
||||||
|
<Header>
|
||||||
|
<ContentTitle>My Workspaces</ContentTitle>
|
||||||
|
{/* <LanguageMenu /> */}
|
||||||
|
<ModalCloseButton
|
||||||
|
top={6}
|
||||||
|
right={6}
|
||||||
|
onClick={() => {
|
||||||
|
onClose();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Header>
|
||||||
|
<Content>
|
||||||
|
<WorkspaceList>
|
||||||
|
{workspaceMetaList.map((item, index) => {
|
||||||
|
return (
|
||||||
|
<WorkspaceItem
|
||||||
|
onClick={() => {
|
||||||
|
setActiveWorkspace(item);
|
||||||
|
onClose();
|
||||||
|
}}
|
||||||
|
key={index}
|
||||||
|
>
|
||||||
|
<span style={{ width: '100px' }}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
float: 'left',
|
||||||
|
marginTop: '6px',
|
||||||
|
marginLeft: '10px',
|
||||||
|
marginRight: '10px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<WorkspaceAvatar size={50} name={item.name} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
width: '235px',
|
||||||
|
fontSize: '16px',
|
||||||
|
display: 'inline-block',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
overflow: 'hidden',
|
||||||
|
position: 'relative',
|
||||||
|
top: '20px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item.name || 'AFFiNE'}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
position: 'relative',
|
||||||
|
top: '20px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{(item.type === 'local' || !item.type) && (
|
||||||
|
<CloudUnsyncedIcon fontSize={24} />
|
||||||
|
)}
|
||||||
|
{item.type === 'cloud' && (
|
||||||
|
<CloudInsyncIcon fontSize={24} />
|
||||||
|
)}
|
||||||
|
{item.isPublish && <UsersIcon fontSize={24} />}
|
||||||
|
</span>
|
||||||
|
{/* {item.isLocal ? 'isLocal' : ''}/ */}
|
||||||
|
</WorkspaceItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
<li>
|
||||||
|
<Button
|
||||||
|
style={{
|
||||||
|
marginTop: '20px',
|
||||||
|
}}
|
||||||
|
type="primary"
|
||||||
|
onClick={() => {
|
||||||
|
setCreateWorkspaceOpen(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<AddIcon
|
||||||
|
style={{
|
||||||
|
fontSize: '20px',
|
||||||
|
top: '5px',
|
||||||
|
position: 'relative',
|
||||||
|
marginRight: '10px',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
Create Or Import
|
||||||
|
</Button>
|
||||||
|
</li>
|
||||||
|
</WorkspaceList>
|
||||||
|
<p style={{ fontSize: '14px', color: '#ccc', margin: '12px 0' }}>
|
||||||
|
Tips:Workspace is your virtual space to capture, create and plan
|
||||||
|
as just one person or together as a team.
|
||||||
|
</p>
|
||||||
|
</Content>
|
||||||
|
<Footer>
|
||||||
|
{!user ? (
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
login();
|
||||||
|
toast('login success');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Sign in AFFiNE Cloud
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
SignOut();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Sign out of AFFiNE Cloud
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Footer>
|
||||||
|
<CreateWorkspaceModal
|
||||||
|
open={createWorkspaceOpen}
|
||||||
|
onClose={({ workspaceId }) => {
|
||||||
|
setCreateWorkspaceOpen(false);
|
||||||
|
onClose();
|
||||||
|
confirm({
|
||||||
|
title: 'Enable AFFiNE Cloud?',
|
||||||
|
content: `If enabled, the data in this workspace will be backed up and synchronized via AFFiNE Cloud.`,
|
||||||
|
confirmText: user ? 'Enable' : 'Sign in and Enable',
|
||||||
|
cancelText: 'Skip',
|
||||||
|
}).then(confirm => {
|
||||||
|
if (confirm) {
|
||||||
|
if (user) {
|
||||||
|
workspaceId &&
|
||||||
|
updateWorkspaceMeta(workspaceId, { isPublish: true });
|
||||||
|
} else {
|
||||||
|
login();
|
||||||
|
workspaceId &&
|
||||||
|
updateWorkspaceMeta(workspaceId, { isPublish: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
></CreateWorkspaceModal>
|
||||||
|
</ModalWrapper>
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const Header = styled('div')({
|
||||||
|
position: 'relative',
|
||||||
|
height: '44px',
|
||||||
|
});
|
||||||
|
|
||||||
|
const Content = styled('div')({
|
||||||
|
padding: '0 20px',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '16px',
|
||||||
|
flex: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
const ContentTitle = styled('span')({
|
||||||
|
fontSize: '20px',
|
||||||
|
lineHeight: '28px',
|
||||||
|
fontWeight: 600,
|
||||||
|
textAlign: 'left',
|
||||||
|
paddingBottom: '16px',
|
||||||
|
});
|
||||||
|
|
||||||
|
const Footer = styled('div')({
|
||||||
|
height: '70px',
|
||||||
|
paddingLeft: '24px',
|
||||||
|
marginTop: '32px',
|
||||||
|
textAlign: 'center',
|
||||||
|
});
|
||||||
|
|
||||||
|
const WorkspaceList = styled('div')({
|
||||||
|
display: 'grid',
|
||||||
|
gridRowGap: '10px',
|
||||||
|
gridColumnGap: '10px',
|
||||||
|
fontSize: '16px',
|
||||||
|
gridTemplateColumns: 'repeat(2, 1fr)',
|
||||||
|
});
|
||||||
|
|
||||||
|
const WorkspaceItem = styled('div')({
|
||||||
|
cursor: 'pointer',
|
||||||
|
padding: '8px',
|
||||||
|
border: '1px solid #eee',
|
||||||
|
':hover': {
|
||||||
|
background: '#eee',
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
import { LOCALES } from '@/libs/i18n/resources/index';
|
||||||
|
import UnfoldMoreIcon from '@mui/icons-material/UnfoldMore';
|
||||||
|
import type { TooltipProps } from '@mui/material';
|
||||||
|
import { styled } from '@/styles';
|
||||||
|
import { Button, Tooltip } from '@mui/material';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
export const LanguageMenu = () => {
|
||||||
|
const { i18n } = useTranslation();
|
||||||
|
const changeLanguage = (event: string) => {
|
||||||
|
i18n.changeLanguage(event);
|
||||||
|
};
|
||||||
|
const [show, setShow] = useState(false);
|
||||||
|
const currentLanguage = LOCALES.find(item => item.tag === i18n.language);
|
||||||
|
const [languageName, setLanguageName] = useState(
|
||||||
|
currentLanguage?.originalName
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<StyledTooltip
|
||||||
|
title={
|
||||||
|
<>
|
||||||
|
{LOCALES.map(option => {
|
||||||
|
return (
|
||||||
|
<ListItem
|
||||||
|
key={option.name}
|
||||||
|
title={option.name}
|
||||||
|
onClick={() => {
|
||||||
|
changeLanguage(option.tag);
|
||||||
|
setShow(false);
|
||||||
|
setLanguageName(option.originalName);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{option.originalName}
|
||||||
|
</ListItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
open={show}
|
||||||
|
>
|
||||||
|
<StyledTitleButton
|
||||||
|
variant="text"
|
||||||
|
onClick={() => {
|
||||||
|
setShow(!show);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<StyledContainer>
|
||||||
|
<StyledText>{languageName}</StyledText>
|
||||||
|
<UnfoldMoreIcon />
|
||||||
|
</StyledContainer>
|
||||||
|
</StyledTitleButton>
|
||||||
|
</StyledTooltip>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const StyledContainer = styled('div')(() => ({
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledText = styled('span')(({ theme }) => ({
|
||||||
|
marginRight: '4px',
|
||||||
|
marginLeft: '16px',
|
||||||
|
fontSize: theme.font.sm,
|
||||||
|
fontWeight: '500',
|
||||||
|
textTransform: 'capitalize',
|
||||||
|
}));
|
||||||
|
const StyledTooltip = styled(({ className, ...props }: TooltipProps) => (
|
||||||
|
<Tooltip {...props} classes={{ popper: className }} />
|
||||||
|
))(({ theme }) => ({
|
||||||
|
zIndex: theme.zIndex.modal,
|
||||||
|
'& .MuiTooltip-tooltip': {
|
||||||
|
backgroundColor: theme.colors.popoverBackground,
|
||||||
|
boxShadow: theme.shadow.modal,
|
||||||
|
color: theme.colors.popoverColor,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const ListItem = styled(Button)(({ theme }) => ({
|
||||||
|
display: 'block',
|
||||||
|
width: '100%',
|
||||||
|
color: theme.colors.popoverColor,
|
||||||
|
fontSize: theme.font.sm,
|
||||||
|
textTransform: 'capitalize',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledTitleButton = styled(Button)(({ theme }) => ({
|
||||||
|
position: 'absolute',
|
||||||
|
right: '50px',
|
||||||
|
color: theme.colors.popoverColor,
|
||||||
|
fontSize: theme.font.sm,
|
||||||
|
}));
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import { displayFlex, styled } from '@/styles';
|
||||||
|
|
||||||
|
export const StyledTitle = styled.div(() => {
|
||||||
|
return {
|
||||||
|
...displayFlex('center', 'center'),
|
||||||
|
fontSize: '20px',
|
||||||
|
fontWeight: 500,
|
||||||
|
marginTop: '60px',
|
||||||
|
lineHeight: 1,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
export const StyledContent = styled.div(() => {
|
||||||
|
return {
|
||||||
|
padding: '0 40px',
|
||||||
|
marginTop: '32px',
|
||||||
|
fontSize: '18px',
|
||||||
|
lineHeight: '25px',
|
||||||
|
'p:not(last-of-type)': {
|
||||||
|
marginBottom: '10px',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
export const StyledButton = styled.div(({ theme }) => {
|
||||||
|
return {
|
||||||
|
width: '146px',
|
||||||
|
height: '42px',
|
||||||
|
background: theme.colors.primaryColor,
|
||||||
|
color: '#FFFFFF',
|
||||||
|
fontSize: '18px',
|
||||||
|
fontWeight: 500,
|
||||||
|
borderRadius: '21px',
|
||||||
|
margin: '52px auto 0',
|
||||||
|
cursor: 'pointer',
|
||||||
|
...displayFlex('center', 'center'),
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { Workspace } from '@/hooks/mock-data/mock';
|
||||||
|
import { styled } from '@/styles';
|
||||||
|
|
||||||
|
export const ExportPageTitleContainer = styled('div')(() => {
|
||||||
|
return {
|
||||||
|
display: 'flex',
|
||||||
|
marginTop: '60px',
|
||||||
|
fontWeight: '500',
|
||||||
|
flex: 1,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
export const ExportPage = ({ workspace }: { workspace: Workspace }) => {
|
||||||
|
return (
|
||||||
|
<ExportPageTitleContainer>
|
||||||
|
Export Workspace{' '}
|
||||||
|
<code style={{ margin: '0 10px' }}>{workspace.name}</code> Is Comming
|
||||||
|
</ExportPageTitleContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,206 @@
|
|||||||
|
import {
|
||||||
|
StyledMemberAvatar,
|
||||||
|
StyledMemberButtonContainer,
|
||||||
|
StyledMemberEmail,
|
||||||
|
StyledMemberInfo,
|
||||||
|
StyledMemberListContainer,
|
||||||
|
StyledMemberListItem,
|
||||||
|
StyledMemberName,
|
||||||
|
StyledMemberNameContainer,
|
||||||
|
StyledMemberRoleContainer,
|
||||||
|
StyledMemberTitleContainer,
|
||||||
|
StyledMoreVerticalButton,
|
||||||
|
} from './style';
|
||||||
|
import { MoreVerticalIcon, EmailIcon, TrashIcon } from '@blocksuite/icons';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { Button, IconButton } from '@/ui/button';
|
||||||
|
import { InviteMembers } from '../invite-members/index';
|
||||||
|
import { Menu, MenuItem } from '@/ui/menu';
|
||||||
|
import { Empty } from '@/ui/empty';
|
||||||
|
import {
|
||||||
|
deleteMember,
|
||||||
|
getMembers,
|
||||||
|
User,
|
||||||
|
Workspace,
|
||||||
|
} from '@/hooks/mock-data/mock';
|
||||||
|
import { useTemporaryHelper } from '@/providers/temporary-helper-provider';
|
||||||
|
import { StyledMemberWarp } from './general/style';
|
||||||
|
import { useConfirm } from '@/providers/ConfirmProvider';
|
||||||
|
|
||||||
|
// import { useAppState } from '@/providers/app-state-provider';
|
||||||
|
export const MembersPage = ({ workspace }: { workspace: Workspace }) => {
|
||||||
|
const [isInviteModalShow, setIsInviteModalShow] = useState(false);
|
||||||
|
const [members, setMembers] = useState<User[]>([]);
|
||||||
|
const { user, login, updateWorkspaceMeta } = useTemporaryHelper();
|
||||||
|
const { confirm } = useConfirm();
|
||||||
|
// const refreshMembers = useCallback(() => {
|
||||||
|
// getDataCenter()
|
||||||
|
// .then(dc =>
|
||||||
|
// dc.apis.getWorkspaceMembers({
|
||||||
|
// id: workspace.id,
|
||||||
|
// })
|
||||||
|
// )
|
||||||
|
// .then(data => {
|
||||||
|
// setMembers(data);
|
||||||
|
// })
|
||||||
|
// .catch(err => {
|
||||||
|
// console.log(err);
|
||||||
|
// });
|
||||||
|
// }, [workspace.id]);
|
||||||
|
const setMembersList = () => {
|
||||||
|
const members = getMembers(workspace.id);
|
||||||
|
members && setMembers(members);
|
||||||
|
};
|
||||||
|
useEffect(() => {
|
||||||
|
setMembersList();
|
||||||
|
// refreshMembers();
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{workspace.type === 'cloud' ? (
|
||||||
|
<>
|
||||||
|
<StyledMemberTitleContainer>
|
||||||
|
<StyledMemberNameContainer>
|
||||||
|
Users({members.length})
|
||||||
|
</StyledMemberNameContainer>
|
||||||
|
<StyledMemberRoleContainer>Access level</StyledMemberRoleContainer>
|
||||||
|
</StyledMemberTitleContainer>
|
||||||
|
<StyledMemberListContainer>
|
||||||
|
{members.length === 0 && (
|
||||||
|
<Empty
|
||||||
|
width={648}
|
||||||
|
sx={{ marginTop: '60px' }}
|
||||||
|
height={300}
|
||||||
|
></Empty>
|
||||||
|
)}
|
||||||
|
{members.length ? (
|
||||||
|
members.map((member, index) => {
|
||||||
|
return (
|
||||||
|
<StyledMemberListItem key={index}>
|
||||||
|
<StyledMemberNameContainer>
|
||||||
|
<StyledMemberAvatar alt="member avatar">
|
||||||
|
<EmailIcon></EmailIcon>
|
||||||
|
</StyledMemberAvatar>
|
||||||
|
|
||||||
|
<StyledMemberInfo>
|
||||||
|
<StyledMemberName>{member.name}</StyledMemberName>
|
||||||
|
|
||||||
|
<StyledMemberEmail>{member.email}</StyledMemberEmail>
|
||||||
|
</StyledMemberInfo>
|
||||||
|
</StyledMemberNameContainer>
|
||||||
|
<StyledMemberRoleContainer>
|
||||||
|
{/* {member.accepted
|
||||||
|
? member.type !== 99
|
||||||
|
? 'Member'
|
||||||
|
: 'Workspace Owner'
|
||||||
|
: 'Pending'} */}
|
||||||
|
Pending
|
||||||
|
</StyledMemberRoleContainer>
|
||||||
|
<StyledMoreVerticalButton>
|
||||||
|
<Menu
|
||||||
|
content={
|
||||||
|
<>
|
||||||
|
<MenuItem
|
||||||
|
onClick={() => {
|
||||||
|
deleteMember(workspace.id, 0);
|
||||||
|
setMembersList();
|
||||||
|
// confirm({
|
||||||
|
// title: 'Delete Member?',
|
||||||
|
// content: `will delete member`,
|
||||||
|
// confirmText: 'Delete',
|
||||||
|
// confirmType: 'danger',
|
||||||
|
// }).then(confirm => {
|
||||||
|
// getDataCenter()
|
||||||
|
// .then(dc =>
|
||||||
|
// dc.apis.removeMember({
|
||||||
|
// permissionId: member.id,
|
||||||
|
// })
|
||||||
|
// )
|
||||||
|
// .then(() => {
|
||||||
|
// // console.log('data: ', data);
|
||||||
|
// toast('Moved to Trash');
|
||||||
|
// // refreshMembers();
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
}}
|
||||||
|
icon={<TrashIcon />}
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</MenuItem>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
placement="bottom-end"
|
||||||
|
disablePortal={true}
|
||||||
|
>
|
||||||
|
<IconButton>
|
||||||
|
<MoreVerticalIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Menu>
|
||||||
|
</StyledMoreVerticalButton>
|
||||||
|
</StyledMemberListItem>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
|
</StyledMemberListContainer>
|
||||||
|
<StyledMemberButtonContainer>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
setIsInviteModalShow(true);
|
||||||
|
}}
|
||||||
|
type="primary"
|
||||||
|
shape="circle"
|
||||||
|
>
|
||||||
|
Invite Members
|
||||||
|
</Button>
|
||||||
|
<InviteMembers
|
||||||
|
onClose={() => {
|
||||||
|
setIsInviteModalShow(false);
|
||||||
|
}}
|
||||||
|
onInviteSuccess={() => {
|
||||||
|
setMembersList();
|
||||||
|
setIsInviteModalShow(false);
|
||||||
|
// refreshMembers();
|
||||||
|
}}
|
||||||
|
workspaceId={workspace.id}
|
||||||
|
open={isInviteModalShow}
|
||||||
|
></InviteMembers>
|
||||||
|
</StyledMemberButtonContainer>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<StyledMemberWarp>
|
||||||
|
<div style={{ flex: 1 }}>
|
||||||
|
Collaborating with other members requires AFFiNE Cloud service.
|
||||||
|
</div>
|
||||||
|
<div style={{ height: '40px' }}>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
shape="circle"
|
||||||
|
onClick={() => {
|
||||||
|
confirm({
|
||||||
|
title: 'Enable AFFiNE Cloud?',
|
||||||
|
content: `If enabled, the data in this workspace will be backed up and synchronized via AFFiNE Cloud.`,
|
||||||
|
confirmText: user ? 'Enable' : 'Sign in and Enable',
|
||||||
|
cancelText: 'Skip',
|
||||||
|
}).then(confirm => {
|
||||||
|
if (confirm) {
|
||||||
|
if (user) {
|
||||||
|
updateWorkspaceMeta(workspace.id, { isPublish: true });
|
||||||
|
} else {
|
||||||
|
login();
|
||||||
|
updateWorkspaceMeta(workspace.id, { isPublish: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Enable AFFiNE Cloud
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</StyledMemberWarp>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
import {
|
||||||
|
StyledCopyButtonContainer,
|
||||||
|
StyledPublishContent,
|
||||||
|
StyledPublishCopyContainer,
|
||||||
|
StyledPublishExplanation,
|
||||||
|
StyledSettingH2,
|
||||||
|
} from './style';
|
||||||
|
|
||||||
|
import { Button } from '@/ui/button';
|
||||||
|
import Input from '@/ui/input';
|
||||||
|
import { toast } from '@/ui/toast';
|
||||||
|
import { Workspace } from '@/hooks/mock-data/mock';
|
||||||
|
import { useTemporaryHelper } from '@/providers/temporary-helper-provider';
|
||||||
|
import { useConfirm } from '@/providers/ConfirmProvider';
|
||||||
|
|
||||||
|
export const PublishPage = ({ workspace }: { workspace: Workspace }) => {
|
||||||
|
console.log('workspace: ', workspace);
|
||||||
|
const shareUrl =
|
||||||
|
window.location.host + '/workspace/' + workspace.id + '?share=true';
|
||||||
|
|
||||||
|
const { login, updateWorkspaceMeta, user } = useTemporaryHelper();
|
||||||
|
const { confirm } = useConfirm();
|
||||||
|
|
||||||
|
const togglePublic = (flag: boolean) => {
|
||||||
|
updateWorkspaceMeta(workspace.id, { isPublish: flag });
|
||||||
|
};
|
||||||
|
|
||||||
|
const copyUrl = () => {
|
||||||
|
navigator.clipboard.writeText(shareUrl);
|
||||||
|
toast('Copied url to clipboard');
|
||||||
|
};
|
||||||
|
|
||||||
|
const enableAffineCloud = () => {
|
||||||
|
confirm({
|
||||||
|
title: 'Enable AFFiNE Cloud?',
|
||||||
|
content: `If enabled, the data in this workspace will be backed up and synchronized via AFFiNE Cloud.`,
|
||||||
|
confirmText: user ? 'Enable' : 'Sign in and Enable',
|
||||||
|
cancelText: 'Skip',
|
||||||
|
}).then(confirm => {
|
||||||
|
if (confirm) {
|
||||||
|
if (user) {
|
||||||
|
updateWorkspaceMeta(workspace.id, { type: 'cloud' });
|
||||||
|
} else {
|
||||||
|
login();
|
||||||
|
updateWorkspaceMeta(workspace.id, { type: 'cloud' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{workspace.type === 'cloud' ? (
|
||||||
|
<div>
|
||||||
|
<StyledPublishContent>
|
||||||
|
{workspace?.isPublish ? (
|
||||||
|
<>
|
||||||
|
<StyledPublishExplanation>
|
||||||
|
Publishing to web requires AFFiNE Cloud service .
|
||||||
|
</StyledPublishExplanation>
|
||||||
|
<StyledSettingH2>Share with link</StyledSettingH2>
|
||||||
|
<StyledPublishCopyContainer>
|
||||||
|
<Input width={500} value={shareUrl} disabled={true}></Input>
|
||||||
|
<StyledCopyButtonContainer>
|
||||||
|
<Button onClick={copyUrl} type="primary" shape="circle">
|
||||||
|
Copy Link
|
||||||
|
</Button>
|
||||||
|
</StyledCopyButtonContainer>
|
||||||
|
</StyledPublishCopyContainer>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<StyledPublishExplanation>
|
||||||
|
After publishing to the web, everyone can view the content of
|
||||||
|
this workspace through the link.
|
||||||
|
</StyledPublishExplanation>
|
||||||
|
)}
|
||||||
|
</StyledPublishContent>
|
||||||
|
{workspace.isPublish ? (
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
togglePublic(false);
|
||||||
|
}}
|
||||||
|
type="primary"
|
||||||
|
shape="circle"
|
||||||
|
>
|
||||||
|
Stop publishing
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
togglePublic(true);
|
||||||
|
}}
|
||||||
|
type="primary"
|
||||||
|
shape="circle"
|
||||||
|
>
|
||||||
|
Publish to web
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<StyledPublishContent>
|
||||||
|
<>
|
||||||
|
<StyledPublishExplanation>
|
||||||
|
Publishing to web requires AFFiNE Cloud service.
|
||||||
|
</StyledPublishExplanation>
|
||||||
|
|
||||||
|
<StyledPublishCopyContainer>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
enableAffineCloud();
|
||||||
|
}}
|
||||||
|
type="primary"
|
||||||
|
shape="circle"
|
||||||
|
>
|
||||||
|
Enable AFFiNE Cloud
|
||||||
|
</Button>
|
||||||
|
</StyledPublishCopyContainer>
|
||||||
|
</>
|
||||||
|
</StyledPublishContent>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
import {
|
||||||
|
StyledPublishContent,
|
||||||
|
StyledPublishCopyContainer,
|
||||||
|
StyledPublishExplanation,
|
||||||
|
} from './style';
|
||||||
|
import { DownloadIcon } from '@blocksuite/icons';
|
||||||
|
import { Button } from '@/ui/button';
|
||||||
|
import { Menu, MenuItem } from '@/ui/menu';
|
||||||
|
import { deleteMember, Workspace } from '@/hooks/mock-data/mock';
|
||||||
|
import { useTemporaryHelper } from '@/providers/temporary-helper-provider';
|
||||||
|
|
||||||
|
export const SyncPage = ({ workspace }: { workspace: Workspace }) => {
|
||||||
|
const { currentWorkspace, updateWorkspaceMeta } = useTemporaryHelper();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<StyledPublishContent>
|
||||||
|
{currentWorkspace?.type === 'local' ? (
|
||||||
|
<>
|
||||||
|
<StyledPublishExplanation>
|
||||||
|
{currentWorkspace.name} is Local Workspace. All data is stored on
|
||||||
|
the current device. You can enable AFFiNE Cloud for this workspace
|
||||||
|
to keep data in sync with the cloud.
|
||||||
|
</StyledPublishExplanation>
|
||||||
|
|
||||||
|
<StyledPublishCopyContainer>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
updateWorkspaceMeta(currentWorkspace.id, {
|
||||||
|
type: 'cloud',
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
type="primary"
|
||||||
|
shape="circle"
|
||||||
|
>
|
||||||
|
Enable AFFiNE Cloud
|
||||||
|
</Button>
|
||||||
|
</StyledPublishCopyContainer>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<StyledPublishExplanation>
|
||||||
|
<code>{currentWorkspace && currentWorkspace.name}</code> is Cloud
|
||||||
|
Workspace. All data will be synchronized and saved to the AFFiNE
|
||||||
|
</StyledPublishExplanation>
|
||||||
|
<StyledPublishCopyContainer>
|
||||||
|
<Menu
|
||||||
|
content={
|
||||||
|
<>
|
||||||
|
<MenuItem
|
||||||
|
onClick={() => {
|
||||||
|
deleteMember(workspace.id, 0);
|
||||||
|
}}
|
||||||
|
icon={<DownloadIcon />}
|
||||||
|
>
|
||||||
|
Download core data to device
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem
|
||||||
|
onClick={() => {
|
||||||
|
deleteMember(workspace.id, 0);
|
||||||
|
}}
|
||||||
|
icon={<DownloadIcon />}
|
||||||
|
>
|
||||||
|
Download all data to device
|
||||||
|
</MenuItem>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
placement="bottom-end"
|
||||||
|
disablePortal={true}
|
||||||
|
>
|
||||||
|
<Button>Download all data to device</Button>
|
||||||
|
</Menu>
|
||||||
|
</StyledPublishCopyContainer>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</StyledPublishContent>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,164 @@
|
|||||||
|
import Modal, { ModalCloseButton } from '@/ui/modal';
|
||||||
|
import {
|
||||||
|
StyledSettingContainer,
|
||||||
|
StyledSettingContent,
|
||||||
|
StyledSettingSidebar,
|
||||||
|
StyledSettingSidebarHeader,
|
||||||
|
StyledSettingTabContainer,
|
||||||
|
StyledSettingTagIconContainer,
|
||||||
|
WorkspaceSettingTagItem,
|
||||||
|
} from './style';
|
||||||
|
import {
|
||||||
|
EditIcon,
|
||||||
|
UsersIcon,
|
||||||
|
PublishIcon,
|
||||||
|
CloudInsyncIcon,
|
||||||
|
} from '@blocksuite/icons';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { GeneralPage } from './general';
|
||||||
|
import { MembersPage } from './MembersPage';
|
||||||
|
import { PublishPage } from './PublishPage';
|
||||||
|
import { ExportPage } from './ExportPage';
|
||||||
|
import { SyncPage } from './SyncPage';
|
||||||
|
import { useTemporaryHelper } from '@/providers/temporary-helper-provider';
|
||||||
|
|
||||||
|
enum ActiveTab {
|
||||||
|
'general' = 'general',
|
||||||
|
'members' = 'members',
|
||||||
|
'publish' = 'publish',
|
||||||
|
'sync' = 'sync',
|
||||||
|
'export' = 'export',
|
||||||
|
}
|
||||||
|
|
||||||
|
type SettingTabProps = {
|
||||||
|
activeTab: ActiveTab;
|
||||||
|
onTabChange?: (tab: ActiveTab) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
type WorkspaceSettingProps = {
|
||||||
|
isShow: boolean;
|
||||||
|
onClose?: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const WorkspaceSettingTab = ({ activeTab, onTabChange }: SettingTabProps) => {
|
||||||
|
return (
|
||||||
|
<StyledSettingTabContainer>
|
||||||
|
<WorkspaceSettingTagItem
|
||||||
|
isActive={activeTab === ActiveTab.general}
|
||||||
|
onClick={() => {
|
||||||
|
onTabChange && onTabChange(ActiveTab.general);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<StyledSettingTagIconContainer>
|
||||||
|
<EditIcon />
|
||||||
|
</StyledSettingTagIconContainer>
|
||||||
|
General
|
||||||
|
</WorkspaceSettingTagItem>
|
||||||
|
|
||||||
|
<WorkspaceSettingTagItem
|
||||||
|
isActive={activeTab === ActiveTab.sync}
|
||||||
|
onClick={() => {
|
||||||
|
onTabChange && onTabChange(ActiveTab.sync);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<StyledSettingTagIconContainer>
|
||||||
|
<CloudInsyncIcon />
|
||||||
|
</StyledSettingTagIconContainer>
|
||||||
|
Sync
|
||||||
|
</WorkspaceSettingTagItem>
|
||||||
|
|
||||||
|
<WorkspaceSettingTagItem
|
||||||
|
isActive={activeTab === ActiveTab.members}
|
||||||
|
onClick={() => {
|
||||||
|
onTabChange && onTabChange(ActiveTab.members);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<StyledSettingTagIconContainer>
|
||||||
|
<UsersIcon />
|
||||||
|
</StyledSettingTagIconContainer>
|
||||||
|
Collaboration
|
||||||
|
</WorkspaceSettingTagItem>
|
||||||
|
<WorkspaceSettingTagItem
|
||||||
|
isActive={activeTab === ActiveTab.publish}
|
||||||
|
onClick={() => {
|
||||||
|
onTabChange && onTabChange(ActiveTab.publish);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<StyledSettingTagIconContainer>
|
||||||
|
<PublishIcon />
|
||||||
|
</StyledSettingTagIconContainer>
|
||||||
|
Publish
|
||||||
|
</WorkspaceSettingTagItem>
|
||||||
|
|
||||||
|
<WorkspaceSettingTagItem
|
||||||
|
isActive={activeTab === ActiveTab.export}
|
||||||
|
onClick={() => {
|
||||||
|
onTabChange && onTabChange(ActiveTab.export);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<StyledSettingTagIconContainer>
|
||||||
|
<PublishIcon />
|
||||||
|
</StyledSettingTagIconContainer>
|
||||||
|
Export
|
||||||
|
</WorkspaceSettingTagItem>
|
||||||
|
</StyledSettingTabContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WorkspaceSetting = ({
|
||||||
|
isShow,
|
||||||
|
onClose,
|
||||||
|
}: WorkspaceSettingProps) => {
|
||||||
|
// const { workspaces } = useAppState();
|
||||||
|
const [activeTab, setActiveTab] = useState<ActiveTab>(ActiveTab.general);
|
||||||
|
const handleTabChange = (tab: ActiveTab) => {
|
||||||
|
setActiveTab(tab);
|
||||||
|
};
|
||||||
|
|
||||||
|
const { currentWorkspace } = useTemporaryHelper();
|
||||||
|
const handleClickClose = () => {
|
||||||
|
onClose && onClose();
|
||||||
|
};
|
||||||
|
const isOwner = true;
|
||||||
|
useEffect(() => {
|
||||||
|
// reset tab when modal is closed
|
||||||
|
if (!isShow) {
|
||||||
|
setActiveTab(ActiveTab.general);
|
||||||
|
}
|
||||||
|
}, [isShow]);
|
||||||
|
return (
|
||||||
|
<Modal open={isShow}>
|
||||||
|
<StyledSettingContainer>
|
||||||
|
<ModalCloseButton onClick={handleClickClose} />
|
||||||
|
{isOwner ? (
|
||||||
|
<StyledSettingSidebar>
|
||||||
|
<StyledSettingSidebarHeader>
|
||||||
|
Workspace Settings
|
||||||
|
</StyledSettingSidebarHeader>
|
||||||
|
<WorkspaceSettingTab
|
||||||
|
activeTab={activeTab}
|
||||||
|
onTabChange={handleTabChange}
|
||||||
|
/>
|
||||||
|
</StyledSettingSidebar>
|
||||||
|
) : null}
|
||||||
|
<StyledSettingContent>
|
||||||
|
{activeTab === ActiveTab.general && currentWorkspace && (
|
||||||
|
<GeneralPage workspace={currentWorkspace} />
|
||||||
|
)}
|
||||||
|
{activeTab === ActiveTab.sync && currentWorkspace && (
|
||||||
|
<SyncPage workspace={currentWorkspace} />
|
||||||
|
)}
|
||||||
|
{activeTab === ActiveTab.members && currentWorkspace && (
|
||||||
|
<MembersPage workspace={currentWorkspace} />
|
||||||
|
)}
|
||||||
|
{activeTab === ActiveTab.publish && currentWorkspace && (
|
||||||
|
<PublishPage workspace={currentWorkspace} />
|
||||||
|
)}
|
||||||
|
{activeTab === ActiveTab.export && currentWorkspace && (
|
||||||
|
<ExportPage workspace={currentWorkspace} />
|
||||||
|
)}
|
||||||
|
</StyledSettingContent>
|
||||||
|
</StyledSettingContainer>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
+59
-70
@@ -1,62 +1,37 @@
|
|||||||
import {
|
import {
|
||||||
StyledDeleteButtonContainer,
|
StyledDeleteButtonContainer,
|
||||||
StyledSettingAvatar,
|
// StyledSettingAvatar,
|
||||||
StyledSettingAvatarContent,
|
StyledSettingAvatarContent,
|
||||||
StyledSettingInputContainer,
|
StyledSettingInputContainer,
|
||||||
} from './style';
|
} from './style';
|
||||||
import { StyledSettingH2 } from '../style';
|
import { StyledSettingH2 } from '../style';
|
||||||
|
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { Button } from '@/ui/button';
|
import { Button, TextButton } from '@/ui/button';
|
||||||
import Input from '@/ui/input';
|
import Input from '@/ui/input';
|
||||||
import { getDataCenter, Workspace, WorkspaceType } from '@affine/datacenter';
|
// import { useAppState } from '@/providers/app-state-provider';
|
||||||
import { useAppState } from '@/providers/app-state-provider';
|
|
||||||
import { WorkspaceDetails } from '@/components/workspace-slider-bar/WorkspaceSelector/SelectorPopperContent';
|
|
||||||
import { WorkspaceDelete } from './delete';
|
import { WorkspaceDelete } from './delete';
|
||||||
import { Workspace as StoreWorkspace } from '@blocksuite/store';
|
// import { debounce } from '@/utils';
|
||||||
import { debounce } from '@/utils';
|
|
||||||
import { WorkspaceLeave } from './leave';
|
import { WorkspaceLeave } from './leave';
|
||||||
import { Upload } from '@/components/file-upload';
|
import { Upload } from '@/components/file-upload';
|
||||||
|
import { Workspace } from '@/hooks/mock-data/mock';
|
||||||
export const GeneralPage = ({
|
import { WorkspaceAvatar } from '@/components/workspace-avatar';
|
||||||
workspace,
|
import { useTemporaryHelper } from '@/providers/temporary-helper-provider';
|
||||||
owner,
|
export const GeneralPage = ({ workspace }: { workspace: Workspace }) => {
|
||||||
}: {
|
// const { refreshWorkspacesMeta } = useAppState();
|
||||||
workspace: Workspace;
|
const { updateWorkspaceMeta } = useTemporaryHelper();
|
||||||
owner: WorkspaceDetails[string]['owner'];
|
|
||||||
workspaces: Record<string, StoreWorkspace | null>;
|
|
||||||
}) => {
|
|
||||||
const {
|
|
||||||
user,
|
|
||||||
currentWorkspace,
|
|
||||||
workspacesMeta,
|
|
||||||
workspaces,
|
|
||||||
refreshWorkspacesMeta,
|
|
||||||
} = useAppState();
|
|
||||||
const [showDelete, setShowDelete] = useState<boolean>(false);
|
const [showDelete, setShowDelete] = useState<boolean>(false);
|
||||||
const [showLeave, setShowLeave] = useState<boolean>(false);
|
const [showLeave, setShowLeave] = useState<boolean>(false);
|
||||||
const [uploading, setUploading] = useState<boolean>(false);
|
const [uploading, setUploading] = useState<boolean>(false);
|
||||||
const [workspaceName, setWorkspaceName] = useState<string>(
|
const [workspaceName, setWorkspaceName] = useState<string>('');
|
||||||
workspaces[workspace.id]?.meta.name ||
|
// const debouncedRefreshWorkspacesMeta = debounce(() => {
|
||||||
(workspace.type === WorkspaceType.Private && user ? user.name : '')
|
// refreshWorkspacesMeta();
|
||||||
);
|
// }, 100);
|
||||||
const debouncedRefreshWorkspacesMeta = debounce(() => {
|
const isOwner = true;
|
||||||
refreshWorkspacesMeta();
|
|
||||||
}, 100);
|
|
||||||
const isOwner = user && owner.id === user.id;
|
|
||||||
const handleChangeWorkSpaceName = (newName: string) => {
|
const handleChangeWorkSpaceName = (newName: string) => {
|
||||||
setWorkspaceName(newName);
|
setWorkspaceName(newName);
|
||||||
currentWorkspace?.meta.setName(newName);
|
|
||||||
workspaces[workspace.id]?.meta.setName(newName);
|
|
||||||
debouncedRefreshWorkspacesMeta();
|
|
||||||
};
|
};
|
||||||
const currentWorkspaceIndex = workspacesMeta.findIndex(
|
|
||||||
meta => meta.id === workspace.id
|
|
||||||
);
|
|
||||||
const nextWorkSpaceId =
|
|
||||||
currentWorkspaceIndex === workspacesMeta.length - 1
|
|
||||||
? workspacesMeta[currentWorkspaceIndex - 1]?.id
|
|
||||||
: workspacesMeta[currentWorkspaceIndex + 1]?.id;
|
|
||||||
const handleClickDelete = () => {
|
const handleClickDelete = () => {
|
||||||
setShowDelete(true);
|
setShowDelete(true);
|
||||||
};
|
};
|
||||||
@@ -70,9 +45,13 @@ export const GeneralPage = ({
|
|||||||
const handleCloseLeave = () => {
|
const handleCloseLeave = () => {
|
||||||
setShowLeave(false);
|
setShowLeave(false);
|
||||||
};
|
};
|
||||||
|
const handleUpdateWorkspaceName = () => {
|
||||||
|
workspace && updateWorkspaceMeta(workspace.id, { name: workspaceName });
|
||||||
|
};
|
||||||
|
|
||||||
const fileChange = async (file: File) => {
|
const fileChange = async (file: File) => {
|
||||||
// setUploading(true);
|
console.log('file: ', file);
|
||||||
|
setUploading(true);
|
||||||
// const blob = new Blob([file], { type: file.type });
|
// const blob = new Blob([file], { type: file.type });
|
||||||
// const blobId = await getDataCenter()
|
// const blobId = await getDataCenter()
|
||||||
// .then(dc => dc.apis.uploadBlob({ blob }))
|
// .then(dc => dc.apis.uploadBlob({ blob }))
|
||||||
@@ -81,7 +60,7 @@ export const GeneralPage = ({
|
|||||||
// });
|
// });
|
||||||
// if (blobId) {
|
// if (blobId) {
|
||||||
// currentWorkspace?.meta.setAvatar(blobId);
|
// currentWorkspace?.meta.setAvatar(blobId);
|
||||||
// workspaces[workspace.id]?.meta.setAvatar(blobId);
|
// // workspaces[workspace.id]?.meta.setAvatar(blobId);
|
||||||
// setUploading(false);
|
// setUploading(false);
|
||||||
// debouncedRefreshWorkspacesMeta();
|
// debouncedRefreshWorkspacesMeta();
|
||||||
// }
|
// }
|
||||||
@@ -89,18 +68,17 @@ export const GeneralPage = ({
|
|||||||
|
|
||||||
return workspace ? (
|
return workspace ? (
|
||||||
<div>
|
<div>
|
||||||
<StyledSettingH2 marginTop={56}>Workspace Avatar</StyledSettingH2>
|
<StyledSettingH2 marginTop={56}>Workspace Icon</StyledSettingH2>
|
||||||
<StyledSettingAvatarContent>
|
<StyledSettingAvatarContent>
|
||||||
<StyledSettingAvatar
|
<div
|
||||||
alt="workspace avatar"
|
style={{
|
||||||
src={
|
float: 'left',
|
||||||
workspaces[workspace.id]?.meta.avatar
|
|
||||||
? '/api/blob/' + workspaces[workspace.id]?.meta.avatar
|
marginRight: '5px',
|
||||||
: ''
|
}}
|
||||||
}
|
|
||||||
>
|
>
|
||||||
{workspaces[workspace.id]?.meta.name[0]}
|
<WorkspaceAvatar size={60} name={workspace.name} />
|
||||||
</StyledSettingAvatar>
|
</div>
|
||||||
<Upload
|
<Upload
|
||||||
accept="image/gif,image/jpeg,image/jpg,image/png,image/svg"
|
accept="image/gif,image/jpeg,image/jpg,image/png,image/svg"
|
||||||
fileChange={fileChange}
|
fileChange={fileChange}
|
||||||
@@ -108,31 +86,45 @@ export const GeneralPage = ({
|
|||||||
<Button loading={uploading}>Upload</Button>
|
<Button loading={uploading}>Upload</Button>
|
||||||
</Upload>
|
</Upload>
|
||||||
{/* TODO: add upload logic */}
|
{/* TODO: add upload logic */}
|
||||||
{/* {isOwner ? (
|
|
||||||
<StyledAvatarUploadBtn shape="round">upload</StyledAvatarUploadBtn>
|
|
||||||
) : null} */}
|
|
||||||
{/* <Button shape="round">remove</Button> */}
|
|
||||||
</StyledSettingAvatarContent>
|
</StyledSettingAvatarContent>
|
||||||
<StyledSettingH2 marginTop={36}>Workspace Name</StyledSettingH2>
|
<StyledSettingH2 marginTop={20}>Workspace Name</StyledSettingH2>
|
||||||
<StyledSettingInputContainer>
|
<StyledSettingInputContainer>
|
||||||
<Input
|
<Input
|
||||||
width={327}
|
width={327}
|
||||||
value={workspaceName}
|
value={workspaceName}
|
||||||
placeholder="Workspace Name"
|
placeholder="Workspace Name"
|
||||||
disabled={!isOwner}
|
|
||||||
maxLength={14}
|
maxLength={14}
|
||||||
minLength={1}
|
minLength={1}
|
||||||
onChange={handleChangeWorkSpaceName}
|
onChange={handleChangeWorkSpaceName}
|
||||||
></Input>
|
></Input>
|
||||||
|
<TextButton
|
||||||
|
onClick={() => {
|
||||||
|
handleUpdateWorkspaceName();
|
||||||
|
}}
|
||||||
|
style={{ marginLeft: '10px' }}
|
||||||
|
>
|
||||||
|
✔️
|
||||||
|
</TextButton>
|
||||||
</StyledSettingInputContainer>
|
</StyledSettingInputContainer>
|
||||||
<StyledSettingH2 marginTop={36}>Workspace Owner</StyledSettingH2>
|
{/* {userInfo ? (
|
||||||
|
<div>
|
||||||
|
<StyledSettingH2 marginTop={20}>Workspace Owner</StyledSettingH2>
|
||||||
|
<StyledSettingInputContainer>
|
||||||
|
<Input
|
||||||
|
width={327}
|
||||||
|
disabled
|
||||||
|
value={userInfo?.name}
|
||||||
|
placeholder="Workspace Owner"
|
||||||
|
></Input>
|
||||||
|
</StyledSettingInputContainer>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
''
|
||||||
|
)} */}
|
||||||
|
|
||||||
|
<StyledSettingH2 marginTop={20}>Workspace Type</StyledSettingH2>
|
||||||
<StyledSettingInputContainer>
|
<StyledSettingInputContainer>
|
||||||
<Input
|
<code>{workspace.type} </code>
|
||||||
width={327}
|
|
||||||
disabled
|
|
||||||
value={owner.name}
|
|
||||||
placeholder="Workspace Owner"
|
|
||||||
></Input>
|
|
||||||
</StyledSettingInputContainer>
|
</StyledSettingInputContainer>
|
||||||
<StyledDeleteButtonContainer>
|
<StyledDeleteButtonContainer>
|
||||||
{isOwner ? (
|
{isOwner ? (
|
||||||
@@ -143,9 +135,7 @@ export const GeneralPage = ({
|
|||||||
<WorkspaceDelete
|
<WorkspaceDelete
|
||||||
open={showDelete}
|
open={showDelete}
|
||||||
onClose={handleCloseDelete}
|
onClose={handleCloseDelete}
|
||||||
workspaceName={workspaceName}
|
workspace={workspace}
|
||||||
workspaceId={workspace.id}
|
|
||||||
nextWorkSpaceId={nextWorkSpaceId}
|
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
@@ -158,7 +148,6 @@ export const GeneralPage = ({
|
|||||||
onClose={handleCloseLeave}
|
onClose={handleCloseLeave}
|
||||||
workspaceName={workspaceName}
|
workspaceName={workspaceName}
|
||||||
workspaceId={workspace.id}
|
workspaceId={workspace.id}
|
||||||
nextWorkSpaceId={nextWorkSpaceId}
|
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
+29
-16
@@ -11,27 +11,25 @@ import {
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { ModalCloseButton } from '@/ui/modal';
|
import { ModalCloseButton } from '@/ui/modal';
|
||||||
import { Button } from '@/ui/button';
|
import { Button } from '@/ui/button';
|
||||||
import { getDataCenter } from '@affine/datacenter';
|
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { useAppState } from '@/providers/app-state-provider';
|
import {
|
||||||
|
deleteWorkspace,
|
||||||
|
getWorkspaces,
|
||||||
|
Workspace,
|
||||||
|
} from '@/hooks/mock-data/mock';
|
||||||
|
|
||||||
interface WorkspaceDeleteProps {
|
interface WorkspaceDeleteProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
workspaceName: string;
|
workspace: Workspace;
|
||||||
workspaceId: string;
|
|
||||||
nextWorkSpaceId: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const WorkspaceDelete = ({
|
export const WorkspaceDelete = ({
|
||||||
open,
|
open,
|
||||||
onClose,
|
onClose,
|
||||||
workspaceId,
|
workspace,
|
||||||
workspaceName,
|
|
||||||
nextWorkSpaceId,
|
|
||||||
}: WorkspaceDeleteProps) => {
|
}: WorkspaceDeleteProps) => {
|
||||||
const [deleteStr, setDeleteStr] = useState<string>('');
|
const [deleteStr, setDeleteStr] = useState<string>('');
|
||||||
const { refreshWorkspacesMeta } = useAppState();
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const handlerInputChange = (workspaceName: string) => {
|
const handlerInputChange = (workspaceName: string) => {
|
||||||
@@ -42,8 +40,14 @@ export const WorkspaceDelete = ({
|
|||||||
// const dc = await getDataCenter();
|
// const dc = await getDataCenter();
|
||||||
// await dc.apis.deleteWorkspace({ id: workspaceId });
|
// await dc.apis.deleteWorkspace({ id: workspaceId });
|
||||||
// router.push(`/workspace/${nextWorkSpaceId}`);
|
// router.push(`/workspace/${nextWorkSpaceId}`);
|
||||||
// refreshWorkspacesMeta();
|
deleteWorkspace(workspace.id);
|
||||||
// onClose();
|
const workspaceList = getWorkspaces();
|
||||||
|
if (workspaceList.length) {
|
||||||
|
router.push(`/workspace/${workspaceList[0].id}`);
|
||||||
|
} else {
|
||||||
|
router.push(`/workspace`);
|
||||||
|
}
|
||||||
|
onClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -51,11 +55,20 @@ export const WorkspaceDelete = ({
|
|||||||
<StyledModalWrapper>
|
<StyledModalWrapper>
|
||||||
<ModalCloseButton onClick={onClose} />
|
<ModalCloseButton onClick={onClose} />
|
||||||
<StyledModalHeader>Delete Workspace</StyledModalHeader>
|
<StyledModalHeader>Delete Workspace</StyledModalHeader>
|
||||||
<StyledTextContent>
|
{workspace.type === 'local' ? (
|
||||||
This action cannot be undone. This will permanently delete (
|
<StyledTextContent>
|
||||||
<StyledWorkspaceName>{workspaceName}</StyledWorkspaceName>) along with
|
Deleting (
|
||||||
all its content.
|
<StyledWorkspaceName>{workspace.name}</StyledWorkspaceName>) cannot
|
||||||
</StyledTextContent>
|
be undone, please proceed with caution. along with all its content.
|
||||||
|
</StyledTextContent>
|
||||||
|
) : (
|
||||||
|
<StyledTextContent>
|
||||||
|
Deleting (
|
||||||
|
<StyledWorkspaceName>{workspace.name}</StyledWorkspaceName>) will
|
||||||
|
delete both local and cloud data, this operation cannot be undone,
|
||||||
|
please proceed with caution.
|
||||||
|
</StyledTextContent>
|
||||||
|
)}
|
||||||
<StyledInputContent>
|
<StyledInputContent>
|
||||||
<Input
|
<Input
|
||||||
onChange={handlerInputChange}
|
onChange={handlerInputChange}
|
||||||
@@ -1 +1 @@
|
|||||||
export * from './delete';
|
export * from './Delete';
|
||||||
|
|||||||
@@ -16,7 +16,8 @@ export const StyledModalHeader = styled('div')(({ theme }) => {
|
|||||||
width: '460px',
|
width: '460px',
|
||||||
fontWeight: '600',
|
fontWeight: '600',
|
||||||
fontSize: '20px;',
|
fontSize: '20px;',
|
||||||
textAlign: 'center',
|
textAlign: 'left',
|
||||||
|
paddingLeft: '20px',
|
||||||
color: theme.colors.popoverColor,
|
color: theme.colors.popoverColor,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@@ -32,7 +33,7 @@ export const StyledTextContent = styled('div')(() => {
|
|||||||
fontWeight: '400',
|
fontWeight: '400',
|
||||||
fontSize: '18px',
|
fontSize: '18px',
|
||||||
lineHeight: '26px',
|
lineHeight: '26px',
|
||||||
textAlign: 'center',
|
textAlign: 'left',
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -41,7 +42,7 @@ export const StyledInputContent = styled('div')(() => {
|
|||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
margin: '40px 0 24px 0',
|
margin: '20px 0 24px 0',
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
export * from './general';
|
export * from './General';
|
||||||
|
|||||||
+4
-7
@@ -7,8 +7,7 @@ import {
|
|||||||
} from './style';
|
} from './style';
|
||||||
import { ModalCloseButton } from '@/ui/modal';
|
import { ModalCloseButton } from '@/ui/modal';
|
||||||
import { Button } from '@/ui/button';
|
import { Button } from '@/ui/button';
|
||||||
import { getDataCenter } from '@affine/datacenter';
|
// import { getDataCenter } from '@affine/datacenter';
|
||||||
import { useRouter } from 'next/router';
|
|
||||||
import { useAppState } from '@/providers/app-state-provider';
|
import { useAppState } from '@/providers/app-state-provider';
|
||||||
|
|
||||||
interface WorkspaceDeleteProps {
|
interface WorkspaceDeleteProps {
|
||||||
@@ -16,22 +15,20 @@ interface WorkspaceDeleteProps {
|
|||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
workspaceName: string;
|
workspaceName: string;
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
nextWorkSpaceId: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const WorkspaceLeave = ({
|
export const WorkspaceLeave = ({
|
||||||
open,
|
open,
|
||||||
onClose,
|
onClose,
|
||||||
nextWorkSpaceId,
|
|
||||||
workspaceId,
|
workspaceId,
|
||||||
}: WorkspaceDeleteProps) => {
|
}: WorkspaceDeleteProps) => {
|
||||||
const router = useRouter();
|
// const router = useRouter();
|
||||||
const { refreshWorkspacesMeta } = useAppState();
|
const { refreshWorkspacesMeta } = useAppState();
|
||||||
const handleLeave = async () => {
|
const handleLeave = async () => {
|
||||||
// const dc = await getDataCenter();
|
// const dc = await getDataCenter();
|
||||||
// await dc.apis.leaveWorkspace({ id: workspaceId });
|
// await dc.apis.leaveWorkspace({ id: workspaceId });
|
||||||
router.push(`/workspace/${nextWorkSpaceId}`);
|
// // router.push(`/workspace/${nextWorkSpaceId}`);
|
||||||
refreshWorkspacesMeta();
|
// refreshWorkspacesMeta();
|
||||||
onClose();
|
onClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1 +1 @@
|
|||||||
export * from './leave';
|
export * from './Leave';
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export const StyledSettingInputContainer = styled('div')(() => {
|
|||||||
|
|
||||||
export const StyledDeleteButtonContainer = styled('div')(() => {
|
export const StyledDeleteButtonContainer = styled('div')(() => {
|
||||||
return {
|
return {
|
||||||
marginTop: '154px',
|
marginTop: '30px',
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -26,3 +26,12 @@ export const StyledSettingAvatarContent = styled('div')(() => {
|
|||||||
export const StyledSettingAvatar = styled(MuiAvatar)(() => {
|
export const StyledSettingAvatar = styled(MuiAvatar)(() => {
|
||||||
return { height: '72px', width: '72px', marginRight: '24px' };
|
return { height: '72px', width: '72px', marginRight: '24px' };
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const StyledMemberWarp = styled('div')(() => {
|
||||||
|
return {
|
||||||
|
display: 'flex',
|
||||||
|
height: '500px',
|
||||||
|
flexDirection: 'column',
|
||||||
|
padding: '60px 0',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
export * from './workspace-setting';
|
export * from './WorkspaceSetting';
|
||||||
|
|||||||
@@ -117,6 +117,7 @@ export const StyledMemberTitleContainer = styled('div')(() => {
|
|||||||
display: 'flex',
|
display: 'flex',
|
||||||
marginTop: '60px',
|
marginTop: '60px',
|
||||||
fontWeight: '500',
|
fontWeight: '500',
|
||||||
|
flex: 1,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -199,11 +200,12 @@ export const StyledMoreVerticalButton = styled('button')(() => {
|
|||||||
|
|
||||||
export const StyledPublishExplanation = styled('div')(() => {
|
export const StyledPublishExplanation = styled('div')(() => {
|
||||||
return {
|
return {
|
||||||
marginTop: '56px',
|
|
||||||
paddingRight: '48px',
|
paddingRight: '48px',
|
||||||
fontWeight: '500',
|
fontWeight: '500',
|
||||||
fontSize: '18px',
|
fontSize: '18px',
|
||||||
lineHeight: '26px',
|
lineHeight: '26px',
|
||||||
|
flex: 1,
|
||||||
|
marginTop: '60px',
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -213,7 +215,8 @@ export const StyledPublishCopyContainer = styled('div')(() => {
|
|||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
height: '38px',
|
marginBottom: '20px',
|
||||||
|
paddingTop: '20px',
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -226,5 +229,7 @@ export const StyledCopyButtonContainer = styled('div')(() => {
|
|||||||
export const StyledPublishContent = styled('div')(() => {
|
export const StyledPublishContent = styled('div')(() => {
|
||||||
return {
|
return {
|
||||||
height: '494px',
|
height: '494px',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,366 +0,0 @@
|
|||||||
import Modal, { ModalCloseButton } from '@/ui/modal';
|
|
||||||
import {
|
|
||||||
StyledCopyButtonContainer,
|
|
||||||
StyledMemberAvatar,
|
|
||||||
StyledMemberButtonContainer,
|
|
||||||
StyledMemberEmail,
|
|
||||||
StyledMemberInfo,
|
|
||||||
StyledMemberListContainer,
|
|
||||||
StyledMemberListItem,
|
|
||||||
StyledMemberName,
|
|
||||||
StyledMemberNameContainer,
|
|
||||||
StyledMemberRoleContainer,
|
|
||||||
StyledMemberTitleContainer,
|
|
||||||
StyledMoreVerticalButton,
|
|
||||||
StyledPublishContent,
|
|
||||||
StyledPublishCopyContainer,
|
|
||||||
StyledPublishExplanation,
|
|
||||||
StyledSettingContainer,
|
|
||||||
StyledSettingContent,
|
|
||||||
StyledSettingH2,
|
|
||||||
StyledSettingSidebar,
|
|
||||||
StyledSettingSidebarHeader,
|
|
||||||
StyledSettingTabContainer,
|
|
||||||
StyledSettingTagIconContainer,
|
|
||||||
WorkspaceSettingTagItem,
|
|
||||||
} from './style';
|
|
||||||
import {
|
|
||||||
EditIcon,
|
|
||||||
UsersIcon,
|
|
||||||
PublishIcon,
|
|
||||||
MoreVerticalIcon,
|
|
||||||
EmailIcon,
|
|
||||||
TrashIcon,
|
|
||||||
} from '@blocksuite/icons';
|
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
|
||||||
import { Button, IconButton } from '@/ui/button';
|
|
||||||
import Input from '@/ui/input';
|
|
||||||
import { InviteMembers } from '../invite-members/index';
|
|
||||||
import { Workspace, Member, getDataCenter } from '@affine/datacenter';
|
|
||||||
import { Avatar } from '@mui/material';
|
|
||||||
import { Menu, MenuItem } from '@/ui/menu';
|
|
||||||
import { toast } from '@/ui/toast';
|
|
||||||
import { Empty } from '@/ui/empty';
|
|
||||||
import { useAppState } from '@/providers/app-state-provider';
|
|
||||||
import { WorkspaceDetails } from '../workspace-slider-bar/WorkspaceSelector/SelectorPopperContent';
|
|
||||||
import { GeneralPage } from './general';
|
|
||||||
|
|
||||||
enum ActiveTab {
|
|
||||||
'general' = 'general',
|
|
||||||
'members' = 'members',
|
|
||||||
'publish' = 'publish',
|
|
||||||
}
|
|
||||||
|
|
||||||
type SettingTabProps = {
|
|
||||||
activeTab: ActiveTab;
|
|
||||||
onTabChange?: (tab: ActiveTab) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
type WorkspaceSettingProps = {
|
|
||||||
isShow: boolean;
|
|
||||||
onClose?: () => void;
|
|
||||||
workspace: Workspace;
|
|
||||||
owner: WorkspaceDetails[string]['owner'];
|
|
||||||
};
|
|
||||||
|
|
||||||
const WorkspaceSettingTab = ({ activeTab, onTabChange }: SettingTabProps) => {
|
|
||||||
return (
|
|
||||||
<StyledSettingTabContainer>
|
|
||||||
<WorkspaceSettingTagItem
|
|
||||||
isActive={activeTab === ActiveTab.general}
|
|
||||||
onClick={() => {
|
|
||||||
onTabChange && onTabChange(ActiveTab.general);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<StyledSettingTagIconContainer>
|
|
||||||
<EditIcon />
|
|
||||||
</StyledSettingTagIconContainer>
|
|
||||||
General
|
|
||||||
</WorkspaceSettingTagItem>
|
|
||||||
<WorkspaceSettingTagItem
|
|
||||||
isActive={activeTab === ActiveTab.members}
|
|
||||||
onClick={() => {
|
|
||||||
onTabChange && onTabChange(ActiveTab.members);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<StyledSettingTagIconContainer>
|
|
||||||
<UsersIcon />
|
|
||||||
</StyledSettingTagIconContainer>
|
|
||||||
Members
|
|
||||||
</WorkspaceSettingTagItem>
|
|
||||||
<WorkspaceSettingTagItem
|
|
||||||
isActive={activeTab === ActiveTab.publish}
|
|
||||||
onClick={() => {
|
|
||||||
onTabChange && onTabChange(ActiveTab.publish);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<StyledSettingTagIconContainer>
|
|
||||||
<PublishIcon />
|
|
||||||
</StyledSettingTagIconContainer>
|
|
||||||
Publish
|
|
||||||
</WorkspaceSettingTagItem>
|
|
||||||
</StyledSettingTabContainer>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const WorkspaceSetting = ({
|
|
||||||
isShow,
|
|
||||||
onClose,
|
|
||||||
workspace,
|
|
||||||
owner,
|
|
||||||
}: WorkspaceSettingProps) => {
|
|
||||||
const { user, workspaces } = useAppState();
|
|
||||||
const [activeTab, setActiveTab] = useState<ActiveTab>(ActiveTab.general);
|
|
||||||
const handleTabChange = (tab: ActiveTab) => {
|
|
||||||
setActiveTab(tab);
|
|
||||||
};
|
|
||||||
const handleClickClose = () => {
|
|
||||||
onClose && onClose();
|
|
||||||
};
|
|
||||||
const isOwner = user && owner.id === user.id;
|
|
||||||
useEffect(() => {
|
|
||||||
// reset tab when modal is closed
|
|
||||||
if (!isShow) {
|
|
||||||
setActiveTab(ActiveTab.general);
|
|
||||||
}
|
|
||||||
}, [isShow]);
|
|
||||||
return (
|
|
||||||
<Modal open={isShow}>
|
|
||||||
<StyledSettingContainer>
|
|
||||||
<ModalCloseButton onClick={handleClickClose} />
|
|
||||||
{isOwner ? (
|
|
||||||
<StyledSettingSidebar>
|
|
||||||
<StyledSettingSidebarHeader>
|
|
||||||
Workspace Settings
|
|
||||||
</StyledSettingSidebarHeader>
|
|
||||||
<WorkspaceSettingTab
|
|
||||||
activeTab={activeTab}
|
|
||||||
onTabChange={handleTabChange}
|
|
||||||
/>
|
|
||||||
</StyledSettingSidebar>
|
|
||||||
) : null}
|
|
||||||
<StyledSettingContent>
|
|
||||||
{activeTab === ActiveTab.general && (
|
|
||||||
<GeneralPage
|
|
||||||
workspace={workspace}
|
|
||||||
owner={owner}
|
|
||||||
workspaces={workspaces}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{activeTab === ActiveTab.members && workspace && (
|
|
||||||
<MembersPage workspace={workspace} />
|
|
||||||
)}
|
|
||||||
{activeTab === ActiveTab.publish && (
|
|
||||||
<PublishPage workspace={workspace} />
|
|
||||||
)}
|
|
||||||
</StyledSettingContent>
|
|
||||||
</StyledSettingContainer>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const MembersPage = ({ workspace }: { workspace: Workspace }) => {
|
|
||||||
const [isInviteModalShow, setIsInviteModalShow] = useState(false);
|
|
||||||
const [members, setMembers] = useState<Member[]>([]);
|
|
||||||
const refreshMembers = useCallback(() => {
|
|
||||||
// getDataCenter()
|
|
||||||
// .then(dc =>
|
|
||||||
// dc.apis.getWorkspaceMembers({
|
|
||||||
// id: workspace.id,
|
|
||||||
// })
|
|
||||||
// )
|
|
||||||
// .then(data => {
|
|
||||||
// setMembers(data);
|
|
||||||
// })
|
|
||||||
// .catch(err => {
|
|
||||||
// console.log(err);
|
|
||||||
// });
|
|
||||||
}, [workspace.id]);
|
|
||||||
useEffect(() => {
|
|
||||||
refreshMembers();
|
|
||||||
}, [refreshMembers]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<StyledMemberTitleContainer>
|
|
||||||
<StyledMemberNameContainer>
|
|
||||||
Users({members.length})
|
|
||||||
</StyledMemberNameContainer>
|
|
||||||
<StyledMemberRoleContainer>Access level</StyledMemberRoleContainer>
|
|
||||||
</StyledMemberTitleContainer>
|
|
||||||
<StyledMemberListContainer>
|
|
||||||
{members.length === 0 && (
|
|
||||||
<Empty width={648} sx={{ marginTop: '60px' }} height={300}></Empty>
|
|
||||||
)}
|
|
||||||
{members.length ? (
|
|
||||||
members.map(member => {
|
|
||||||
return (
|
|
||||||
<StyledMemberListItem key={member.id}>
|
|
||||||
<StyledMemberNameContainer>
|
|
||||||
{member.user.type === 'Registered' ? (
|
|
||||||
<Avatar src={member.user.avatar_url}></Avatar>
|
|
||||||
) : (
|
|
||||||
<StyledMemberAvatar alt="member avatar">
|
|
||||||
<EmailIcon></EmailIcon>
|
|
||||||
</StyledMemberAvatar>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<StyledMemberInfo>
|
|
||||||
{member.user.type === 'Registered' ? (
|
|
||||||
<StyledMemberName>{member.user.name}</StyledMemberName>
|
|
||||||
) : (
|
|
||||||
<></>
|
|
||||||
)}
|
|
||||||
<StyledMemberEmail>{member.user.email}</StyledMemberEmail>
|
|
||||||
</StyledMemberInfo>
|
|
||||||
</StyledMemberNameContainer>
|
|
||||||
<StyledMemberRoleContainer>
|
|
||||||
{member.accepted
|
|
||||||
? member.type !== 99
|
|
||||||
? 'Member'
|
|
||||||
: 'Workspace Owner'
|
|
||||||
: 'Pending'}
|
|
||||||
</StyledMemberRoleContainer>
|
|
||||||
<StyledMoreVerticalButton>
|
|
||||||
<Menu
|
|
||||||
content={
|
|
||||||
<>
|
|
||||||
<MenuItem
|
|
||||||
onClick={() => {
|
|
||||||
// confirm({
|
|
||||||
// title: 'Delete Member?',
|
|
||||||
// content: `will delete member`,
|
|
||||||
// confirmText: 'Delete',
|
|
||||||
// confirmType: 'danger',
|
|
||||||
// }).then(confirm => {
|
|
||||||
// getDataCenter()
|
|
||||||
// .then(dc =>
|
|
||||||
// dc.apis.removeMember({
|
|
||||||
// permissionId: member.id,
|
|
||||||
// })
|
|
||||||
// )
|
|
||||||
// .then(() => {
|
|
||||||
// // console.log('data: ', data);
|
|
||||||
// toast('Moved to Trash');
|
|
||||||
// refreshMembers();
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
}}
|
|
||||||
icon={<TrashIcon />}
|
|
||||||
>
|
|
||||||
Delete
|
|
||||||
</MenuItem>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
placement="bottom-end"
|
|
||||||
disablePortal={true}
|
|
||||||
>
|
|
||||||
<IconButton>
|
|
||||||
<MoreVerticalIcon />
|
|
||||||
</IconButton>
|
|
||||||
</Menu>
|
|
||||||
</StyledMoreVerticalButton>
|
|
||||||
</StyledMemberListItem>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
) : (
|
|
||||||
<></>
|
|
||||||
)}
|
|
||||||
</StyledMemberListContainer>
|
|
||||||
<StyledMemberButtonContainer>
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
setIsInviteModalShow(true);
|
|
||||||
}}
|
|
||||||
type="primary"
|
|
||||||
shape="circle"
|
|
||||||
>
|
|
||||||
Invite Members
|
|
||||||
</Button>
|
|
||||||
<InviteMembers
|
|
||||||
onClose={() => {
|
|
||||||
setIsInviteModalShow(false);
|
|
||||||
}}
|
|
||||||
onInviteSuccess={() => {
|
|
||||||
refreshMembers();
|
|
||||||
}}
|
|
||||||
workspaceId={workspace.id}
|
|
||||||
open={isInviteModalShow}
|
|
||||||
></InviteMembers>
|
|
||||||
</StyledMemberButtonContainer>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const PublishPage = ({ workspace }: { workspace: Workspace }) => {
|
|
||||||
const shareUrl = window.location.host + '/workspace/' + workspace.id;
|
|
||||||
const [publicStatus, setPublicStatus] = useState<boolean | null>(
|
|
||||||
workspace.public
|
|
||||||
);
|
|
||||||
const togglePublic = (flag: boolean) => {
|
|
||||||
// getDataCenter()
|
|
||||||
// .then(dc =>
|
|
||||||
// dc.apis.updateWorkspace({
|
|
||||||
// id: workspace.id,
|
|
||||||
// public: flag,
|
|
||||||
// })
|
|
||||||
// )
|
|
||||||
// .then(data => {
|
|
||||||
// setPublicStatus(data?.public);
|
|
||||||
// toast('Updated Public Status Success');
|
|
||||||
// });
|
|
||||||
};
|
|
||||||
const copyUrl = () => {
|
|
||||||
navigator.clipboard.writeText(shareUrl);
|
|
||||||
toast('Copied url to clipboard');
|
|
||||||
};
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<StyledPublishContent>
|
|
||||||
{publicStatus ? (
|
|
||||||
<>
|
|
||||||
<StyledPublishExplanation>
|
|
||||||
The current workspace has been published to the web, everyone can
|
|
||||||
view the contents of this workspace through the link.
|
|
||||||
</StyledPublishExplanation>
|
|
||||||
<StyledSettingH2 marginTop={48}>Share with link</StyledSettingH2>
|
|
||||||
<StyledPublishCopyContainer>
|
|
||||||
<Input width={500} value={shareUrl} disabled={true}></Input>
|
|
||||||
<StyledCopyButtonContainer>
|
|
||||||
<Button onClick={copyUrl} type="primary" shape="circle">
|
|
||||||
Copy Link
|
|
||||||
</Button>
|
|
||||||
</StyledCopyButtonContainer>
|
|
||||||
</StyledPublishCopyContainer>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<StyledPublishExplanation>
|
|
||||||
After publishing to the web, everyone can view the content of this
|
|
||||||
workspace through the link.
|
|
||||||
</StyledPublishExplanation>
|
|
||||||
)}
|
|
||||||
</StyledPublishContent>
|
|
||||||
{!publicStatus ? (
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
togglePublic(true);
|
|
||||||
}}
|
|
||||||
type="primary"
|
|
||||||
shape="circle"
|
|
||||||
>
|
|
||||||
Publish to web
|
|
||||||
</Button>
|
|
||||||
) : (
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
togglePublic(false);
|
|
||||||
}}
|
|
||||||
type="primary"
|
|
||||||
shape="circle"
|
|
||||||
>
|
|
||||||
Stop publishing
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
+13
-18
@@ -13,7 +13,7 @@ import {
|
|||||||
import { WorkspaceSetting } from '@/components/workspace-setting';
|
import { WorkspaceSetting } from '@/components/workspace-setting';
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import { getDataCenter, WorkspaceType } from '@affine/datacenter';
|
import { getDataCenter, WorkspaceType } from '@affine/datacenter';
|
||||||
import { useModal } from '@/providers/global-modal-provider';
|
import { useModal } from '@/providers/GlobalModalProvider';
|
||||||
|
|
||||||
export type WorkspaceDetails = Record<
|
export type WorkspaceDetails = Record<
|
||||||
string,
|
string,
|
||||||
@@ -27,8 +27,7 @@ type SelectorPopperContentProps = {
|
|||||||
export const SelectorPopperContent = ({
|
export const SelectorPopperContent = ({
|
||||||
isShow,
|
isShow,
|
||||||
}: SelectorPopperContentProps) => {
|
}: SelectorPopperContentProps) => {
|
||||||
const { user, workspacesMeta, workspaces, refreshWorkspacesMeta } =
|
const { user, workspacesMeta, refreshWorkspacesMeta } = useAppState();
|
||||||
useAppState();
|
|
||||||
const [settingWorkspaceId, setSettingWorkspaceId] = useState<string | null>(
|
const [settingWorkspaceId, setSettingWorkspaceId] = useState<string | null>(
|
||||||
null
|
null
|
||||||
);
|
);
|
||||||
@@ -115,13 +114,9 @@ export const SelectorPopperContent = ({
|
|||||||
type={workspace.type}
|
type={workspace.type}
|
||||||
key={workspace.id}
|
key={workspace.id}
|
||||||
id={workspace.id}
|
id={workspace.id}
|
||||||
icon={
|
icon={`loading...`}
|
||||||
(workspaces[workspace.id]?.meta.avatar &&
|
|
||||||
`/api/blob/${workspaces[workspace.id]?.meta.avatar}`) ||
|
|
||||||
`loading...`
|
|
||||||
}
|
|
||||||
onClickSetting={handleClickSettingWorkspace}
|
onClickSetting={handleClickSettingWorkspace}
|
||||||
name={workspaces[workspace.id]?.meta.name || `loading...`}
|
name={`loading...`}
|
||||||
memberCount={workSpaceDetails[workspace.id]?.memberCount || 1}
|
memberCount={workSpaceDetails[workspace.id]?.memberCount || 1}
|
||||||
/>
|
/>
|
||||||
) : null;
|
) : null;
|
||||||
@@ -130,16 +125,16 @@ export const SelectorPopperContent = ({
|
|||||||
<CreateWorkspaceItem />
|
<CreateWorkspaceItem />
|
||||||
{settingWorkspace ? (
|
{settingWorkspace ? (
|
||||||
<WorkspaceSetting
|
<WorkspaceSetting
|
||||||
isShow={Boolean(settingWorkspaceId)}
|
isShow={false}
|
||||||
onClose={handleCloseWorkSpace}
|
onClose={handleCloseWorkSpace}
|
||||||
workspace={settingWorkspace}
|
// workspace={settingWorkspace}
|
||||||
owner={
|
// owner={
|
||||||
(settingWorkspaceId &&
|
// (settingWorkspaceId &&
|
||||||
workSpaceDetails[settingWorkspaceId]?.owner) || {
|
// workSpaceDetails[settingWorkspaceId]?.owner) || {
|
||||||
id: user.id,
|
// id: user.id,
|
||||||
name: user.name,
|
// name: user.name,
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
<StyledDivider />
|
<StyledDivider />
|
||||||
|
|||||||
+1
-1
@@ -1 +1 @@
|
|||||||
export * from './workspace-create';
|
export * from './WorkspaceCreate';
|
||||||
|
|||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
import { useModal } from '@/providers/global-modal-provider';
|
import { useModal } from '@/providers/GlobalModalProvider';
|
||||||
import { styled } from '@/styles';
|
import { styled } from '@/styles';
|
||||||
import { AffineIcon } from '../../icons/icons';
|
import { AffineIcon } from '../../icons/Icons';
|
||||||
import {
|
import {
|
||||||
WorkspaceItemAvatar,
|
WorkspaceItemAvatar,
|
||||||
LoginItemWrapper,
|
LoginItemWrapper,
|
||||||
|
|||||||
+39
-40
@@ -1,53 +1,52 @@
|
|||||||
import { Popper } from '@/ui/popper';
|
|
||||||
import { Avatar, WorkspaceName, SelectorWrapper } from './styles';
|
import { Avatar, WorkspaceName, SelectorWrapper } from './styles';
|
||||||
import { SelectorPopperContent } from './SelectorPopperContent';
|
import { useEffect, useState } from 'react';
|
||||||
import { useState } from 'react';
|
import { WorkspaceModal } from '@/components/workspace-modal';
|
||||||
import { useAppState } from '@/providers/app-state-provider';
|
import { WorkspaceAvatar } from '@/components/workspace-avatar';
|
||||||
import { WorkspaceType } from '@affine/datacenter';
|
import { useTemporaryHelper } from '@/providers/temporary-helper-provider';
|
||||||
import { AffineIcon } from '../icons/icons';
|
|
||||||
|
|
||||||
export const WorkspaceSelector = () => {
|
export const WorkspaceSelector = () => {
|
||||||
const [isShow, setIsShow] = useState(false);
|
const [workspaceListShow, setWorkspaceListShow] = useState(false);
|
||||||
const { currentWorkspace, workspacesMeta, currentWorkspaceId, user } =
|
const { currentWorkspace, workspaceMetaList } = useTemporaryHelper();
|
||||||
useAppState();
|
|
||||||
const workspaceMeta = workspacesMeta.find(
|
useEffect(() => {
|
||||||
meta => String(meta.id) === String(currentWorkspaceId)
|
if (workspaceMetaList.length === 0) {
|
||||||
);
|
setWorkspaceListShow(true);
|
||||||
|
}
|
||||||
|
}, [workspaceMetaList]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popper
|
<>
|
||||||
content={<SelectorPopperContent isShow={isShow} />}
|
<SelectorWrapper
|
||||||
zIndex={1000}
|
onClick={() => {
|
||||||
placement="bottom-start"
|
setWorkspaceListShow(true);
|
||||||
trigger="click"
|
}}
|
||||||
onVisibleChange={setIsShow}
|
data-testid="current-workspace"
|
||||||
>
|
>
|
||||||
<SelectorWrapper data-testid="current-workspace">
|
|
||||||
<Avatar
|
<Avatar
|
||||||
alt="Affine"
|
alt="Affine"
|
||||||
data-testid="workspace-avatar"
|
data-testid="workspace-avatar"
|
||||||
src={
|
src={currentWorkspace?.avatar}
|
||||||
workspaceMeta?.type === WorkspaceType.Private
|
|
||||||
? user
|
|
||||||
? user.avatar_url
|
|
||||||
: ''
|
|
||||||
: currentWorkspace?.meta.avatar &&
|
|
||||||
`/api/blob/${currentWorkspace?.meta.avatar}`
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
{workspaceMeta?.type === WorkspaceType.Private && user ? (
|
<div
|
||||||
user?.name[0]
|
style={{
|
||||||
) : (
|
float: 'left',
|
||||||
<AffineIcon />
|
}}
|
||||||
)}
|
>
|
||||||
|
<WorkspaceAvatar
|
||||||
|
size={28}
|
||||||
|
name={currentWorkspace?.name ?? 'AFFiNE'}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</Avatar>
|
</Avatar>
|
||||||
<WorkspaceName data-testid="workspace-name">
|
<WorkspaceName data-testid="workspace-name">
|
||||||
{workspaceMeta?.type === WorkspaceType.Private
|
{currentWorkspace?.name ?? 'AFFiNE'}
|
||||||
? user
|
|
||||||
? user.name
|
|
||||||
: 'AFFiNE'
|
|
||||||
: currentWorkspace?.meta.name || 'AFFiNE'}
|
|
||||||
</WorkspaceName>
|
</WorkspaceName>
|
||||||
</SelectorWrapper>
|
</SelectorWrapper>
|
||||||
</Popper>
|
<WorkspaceModal
|
||||||
|
open={workspaceListShow}
|
||||||
|
onClose={() => {
|
||||||
|
setWorkspaceListShow(false);
|
||||||
|
}}
|
||||||
|
></WorkspaceModal>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -21,15 +21,17 @@ import {
|
|||||||
ImportIcon,
|
ImportIcon,
|
||||||
TrashIcon,
|
TrashIcon,
|
||||||
AddIcon,
|
AddIcon,
|
||||||
|
SettingsIcon,
|
||||||
} from '@blocksuite/icons';
|
} from '@blocksuite/icons';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { Tooltip } from '@/ui/tooltip';
|
import { Tooltip } from '@/ui/tooltip';
|
||||||
import { useModal } from '@/providers/global-modal-provider';
|
import { useModal } from '@/providers/GlobalModalProvider';
|
||||||
import { useAppState } from '@/providers/app-state-provider/context';
|
import { useAppState } from '@/providers/app-state-provider/context';
|
||||||
import { IconButton } from '@/ui/button';
|
import { IconButton } from '@/ui/button';
|
||||||
import useLocalStorage from '@/hooks/use-local-storage';
|
import useLocalStorage from '@/hooks/use-local-storage';
|
||||||
import usePageMetaList from '@/hooks/use-page-meta-list';
|
import usePageMetaList from '@/hooks/use-page-meta-list';
|
||||||
import { usePageHelper } from '@/hooks/use-page-helper';
|
import { usePageHelper } from '@/hooks/use-page-helper';
|
||||||
|
import { WorkspaceSetting } from '@/components/workspace-setting';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
const FavoriteList = ({ showList }: { showList: boolean }) => {
|
const FavoriteList = ({ showList }: { showList: boolean }) => {
|
||||||
@@ -74,6 +76,8 @@ export const WorkSpaceSliderBar = () => {
|
|||||||
const [showTip, setShowTip] = useState(false);
|
const [showTip, setShowTip] = useState(false);
|
||||||
const [show, setShow] = useLocalStorage('AFFiNE_SLIDE_BAR', false, true);
|
const [show, setShow] = useLocalStorage('AFFiNE_SLIDE_BAR', false, true);
|
||||||
|
|
||||||
|
const [showWorkspaceSetting, setShowWorkspaceSetting] = useState(false);
|
||||||
|
|
||||||
const paths = {
|
const paths = {
|
||||||
all: currentWorkspaceId ? `/workspace/${currentWorkspaceId}/all` : '',
|
all: currentWorkspaceId ? `/workspace/${currentWorkspaceId}/all` : '',
|
||||||
favorite: currentWorkspaceId
|
favorite: currentWorkspaceId
|
||||||
@@ -146,6 +150,20 @@ export const WorkSpaceSliderBar = () => {
|
|||||||
</IconButton>
|
</IconButton>
|
||||||
</StyledListItem>
|
</StyledListItem>
|
||||||
<FavoriteList showList={showSubFavorite} />
|
<FavoriteList showList={showSubFavorite} />
|
||||||
|
<StyledListItem
|
||||||
|
onClick={() => {
|
||||||
|
setShowWorkspaceSetting(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SettingsIcon /> Setting
|
||||||
|
</StyledListItem>
|
||||||
|
|
||||||
|
<WorkspaceSetting
|
||||||
|
isShow={showWorkspaceSetting}
|
||||||
|
onClose={() => {
|
||||||
|
setShowWorkspaceSetting(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
<StyledListItem
|
<StyledListItem
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ export const StyledListItem = styled.div<{
|
|||||||
color: active ? theme.colors.primaryColor : theme.colors.popoverColor,
|
color: active ? theme.colors.primaryColor : theme.colors.popoverColor,
|
||||||
paddingLeft: '12px',
|
paddingLeft: '12px',
|
||||||
borderRadius: '5px',
|
borderRadius: '5px',
|
||||||
|
cursor: 'pointer',
|
||||||
...displayFlex('flex-start', 'center'),
|
...displayFlex('flex-start', 'center'),
|
||||||
...(disabled
|
...(disabled
|
||||||
? {
|
? {
|
||||||
|
|||||||
@@ -0,0 +1,160 @@
|
|||||||
|
export interface Workspace {
|
||||||
|
name: string; // 名称
|
||||||
|
id: string; //唯一标识
|
||||||
|
isPublish?: boolean; // 是否公开
|
||||||
|
isLocal?: boolean; // 是否全部数据都在本地
|
||||||
|
avatar?: string; // 封面
|
||||||
|
type: 'local' | 'cloud' | 'join'; // cloud: 云端(本次暂不支持),local: 本地,join : 加入别人的
|
||||||
|
workspaceOwner?: User; // 本地工作空间的拥有者
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface User {
|
||||||
|
name: string;
|
||||||
|
id: string;
|
||||||
|
email: string;
|
||||||
|
avatar: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateWorkspaceMeta(
|
||||||
|
workspaceId: string,
|
||||||
|
workspaceData: {
|
||||||
|
name?: string;
|
||||||
|
avatar?: string;
|
||||||
|
type?: 'local' | 'cloud' | 'join';
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
const workspacesMeta = getWorkspaces();
|
||||||
|
const newWorkspacesMeta = workspacesMeta.map((workspace: Workspace) => {
|
||||||
|
if (workspace.id === workspaceId) {
|
||||||
|
workspaceData.name && (workspace.name = workspaceData.name);
|
||||||
|
workspaceData.avatar && (workspace.avatar = workspaceData.avatar);
|
||||||
|
workspaceData.type && (workspace.type = workspaceData.type);
|
||||||
|
return workspaceData;
|
||||||
|
}
|
||||||
|
return workspace;
|
||||||
|
});
|
||||||
|
localStorage.setItem('affine-workspace', JSON.stringify(newWorkspacesMeta));
|
||||||
|
const activeWorkspace = getActiveWorkspace();
|
||||||
|
workspaceData.name && (activeWorkspace.name = workspaceData.name);
|
||||||
|
workspaceData.avatar && (activeWorkspace.avatar = workspaceData.avatar);
|
||||||
|
|
||||||
|
workspaceData.type && (activeWorkspace.type = workspaceData.type);
|
||||||
|
console.log(workspaceData);
|
||||||
|
setActiveWorkspace(activeWorkspace);
|
||||||
|
}
|
||||||
|
export function createWorkspace(workspaceName: string) {
|
||||||
|
const workspaceId = 'workspace-' + Date.now();
|
||||||
|
const workspaceData = {
|
||||||
|
name: workspaceName,
|
||||||
|
id: workspaceId,
|
||||||
|
isPublish: false,
|
||||||
|
isLocal: true,
|
||||||
|
avatar: '',
|
||||||
|
type: 'local',
|
||||||
|
} as Workspace;
|
||||||
|
const workspacesMeta = getWorkspaces();
|
||||||
|
workspacesMeta.push(workspaceData);
|
||||||
|
localStorage.setItem('affine-workspace', JSON.stringify(workspacesMeta));
|
||||||
|
setActiveWorkspace(workspaceData);
|
||||||
|
return { workspaceId };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getWorkspaces(): Workspace[] {
|
||||||
|
const workspacesMeta = JSON.parse(
|
||||||
|
localStorage.getItem('affine-workspace') ?? '[]'
|
||||||
|
);
|
||||||
|
return workspacesMeta;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteWorkspace(workspaceId: string) {
|
||||||
|
const workspacesMeta = getWorkspaces();
|
||||||
|
const newWorkspacesMeta = workspacesMeta.filter(() => {
|
||||||
|
return workspaceId !== workspaceId;
|
||||||
|
});
|
||||||
|
localStorage.setItem('affine-workspace', JSON.stringify(newWorkspacesMeta));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getMembers(id: string): User[] {
|
||||||
|
const memberMap = JSON.parse(localStorage.getItem('affine-member') ?? '{}');
|
||||||
|
return memberMap[id] || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setMember(workspaceId: string, member: User) {
|
||||||
|
const memberMap = JSON.parse(localStorage.getItem('affine-member') ?? '{}');
|
||||||
|
memberMap[workspaceId] = memberMap[workspaceId] || [];
|
||||||
|
memberMap[workspaceId].push(member);
|
||||||
|
localStorage.setItem('affine-member', JSON.stringify(memberMap));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteMember(workspaceId: string, index: number) {
|
||||||
|
const memberMap = JSON.parse(localStorage.getItem('affine-member') ?? '{}');
|
||||||
|
const memberList = memberMap[workspaceId];
|
||||||
|
memberList.splice(index, 1);
|
||||||
|
memberMap[workspaceId] = memberList;
|
||||||
|
localStorage.setItem('affine-member', JSON.stringify(memberMap));
|
||||||
|
}
|
||||||
|
export function leaveWorkspace() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setWorkspacePublish(id: string, isPublish: boolean): boolean {
|
||||||
|
const workspacesMeta = getWorkspaces();
|
||||||
|
const newWorkspacesMeta = workspacesMeta.map((workspace: Workspace) => {
|
||||||
|
if (workspace.id === id) {
|
||||||
|
workspace.isPublish = isPublish;
|
||||||
|
}
|
||||||
|
return workspace;
|
||||||
|
});
|
||||||
|
localStorage.setItem('affine-workspace', JSON.stringify(newWorkspacesMeta));
|
||||||
|
return isPublish;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getWorkspaceById(id: string) {
|
||||||
|
const workspacesMeta = getWorkspaces();
|
||||||
|
return workspacesMeta.find((workspace: Workspace) => {
|
||||||
|
return workspace.id === id;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPagesByWorkspaceId(workspaceId: string) {
|
||||||
|
if (!workspaceId) return [];
|
||||||
|
const workspacesMeta = [];
|
||||||
|
for (let i = 0; i < 10; i++) {
|
||||||
|
workspacesMeta.push({
|
||||||
|
id: 'page-' + i,
|
||||||
|
name: 'page ' + i,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getActiveWorkspace(): Workspace {
|
||||||
|
return JSON.parse(localStorage.getItem('affine-active-workspace') ?? '{}');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setActiveWorkspace(workspaceData: Workspace) {
|
||||||
|
console.log('workspaceData: ', workspaceData);
|
||||||
|
localStorage.setItem(
|
||||||
|
'affine-active-workspace',
|
||||||
|
JSON.stringify(workspaceData)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getUserInfo(): User {
|
||||||
|
return JSON.parse(localStorage.getItem('affine-user') ?? 'null');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Login(): void {
|
||||||
|
localStorage.setItem(
|
||||||
|
'affine-user',
|
||||||
|
JSON.stringify({
|
||||||
|
name: 'Diamond',
|
||||||
|
id: 'ttt',
|
||||||
|
email: 'diamond.shx@gmail.com',
|
||||||
|
avatar: 'string',
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SignOut(): void {
|
||||||
|
localStorage.removeItem('affine-user');
|
||||||
|
}
|
||||||
@@ -1,22 +1 @@
|
|||||||
{
|
{}
|
||||||
"// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.": "",
|
|
||||||
"Add A Below Block": "নীচে একটি ব্লক যোগ করুন",
|
|
||||||
"WarningTips": {
|
|
||||||
"IsNotfsApiSupported": "অ্যাফাইন ডেমোতে স্বাগতম। পরিবর্তনগুলি সংরক্ষণ করা শুরু করতে আপনি Chrome/Edge এর মতো ক্রোমিয়াম ভিত্তিক ব্রাউজারের সর্বশেষ সংস্করণের মাধ্যমে ডিস্কে ডেটা সিঙ্ক করতে পারেন",
|
|
||||||
"DoNotStore": "অ্যাফাইন সক্রিয় ডেভেলপমেন্ট এর অধীনে এবং বর্তমান সংস্করণটি অস্থিতিশীল। দয়া করে কোন তথ্য বা ডেটা সঞ্চয় করবেন না"
|
|
||||||
},
|
|
||||||
"Language": "ভাষা",
|
|
||||||
"Settings": "সেটিংস",
|
|
||||||
"Share": "শেয়ার করুন",
|
|
||||||
"Comment": "মন্তব্য",
|
|
||||||
"Delete": "মুছে ফেলুন",
|
|
||||||
"Copy Page Link": "পেজ লিংক কপি করুন",
|
|
||||||
"Duplicate Page": "সদৃশ পৃষ্ঠা তৈরি করুন",
|
|
||||||
"Logout": "লগআউট",
|
|
||||||
"Divide Here As A New Group": "একটি নতুন গ্রুপ হিসেবে বিভক্ত করুন",
|
|
||||||
"ComingSoon": "লেআউট সেটিংস শীঘ্রই আসছে...",
|
|
||||||
"Clear Workspace": "ওয়ার্কস্পেস পরিষ্কার করুন",
|
|
||||||
"Layout": "লেআউট",
|
|
||||||
"Turn into": "রূপান্তর করুন",
|
|
||||||
"Sync to Disk": "ডিস্ক এ সিঙ্ক করুন"
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
"Delete page?": "Delete page?",
|
"Delete page?": "Delete page?",
|
||||||
"Delete permanently?": "Delete permanently?",
|
"Delete permanently?": "Delete permanently?",
|
||||||
"will be moved to Trash": "{{title}} will be moved to Trash",
|
"will be moved to Trash": "{{title}} will be moved to Trash",
|
||||||
"Once deleted, you can't undo this action.": "Once deleted,you can't undo this action.",
|
"Once deleted, you can't undo this action.": "Once deleted, you can't undo this action.",
|
||||||
"Moved to Trash": "Moved to Trash",
|
"Moved to Trash": "Moved to Trash",
|
||||||
"Permanently deleted": "Permanently deleted",
|
"Permanently deleted": "Permanently deleted",
|
||||||
"restored": "{{title}} restored",
|
"restored": "{{title}} restored",
|
||||||
@@ -54,12 +54,26 @@
|
|||||||
"Strikethrough": "Strikethrough",
|
"Strikethrough": "Strikethrough",
|
||||||
"Inline code": "Inline code",
|
"Inline code": "Inline code",
|
||||||
"Code block": "Code block",
|
"Code block": "Code block",
|
||||||
"Link": "Link",
|
"Hyperlink(with selected text)": "Hyperlink(with selected text)",
|
||||||
"Body text": "Body text",
|
"Body text": "Body text",
|
||||||
"Heading": "Heading {{number}}",
|
"Heading": "Heading {{number}}",
|
||||||
"Increase indent": "Increase indent",
|
"Increase indent": "Increase indent",
|
||||||
"Reduce indent": "Reduce indent",
|
"Reduce indent": "Reduce indent",
|
||||||
"Markdown Syntax": "Markdown Syntax",
|
"Markdown Syntax": "Markdown Syntax",
|
||||||
"Divider": "Divider",
|
"Divider": "Divider",
|
||||||
"404 - Page Not Found": "404 - Page Not Found"
|
"404 - Page Not Found": "404 - Page Not Found",
|
||||||
|
"New Workspace": "New Workspace",
|
||||||
|
"Workspace description": "Workspace is your virtual space to capture, create and plan as just one person or together as a team.",
|
||||||
|
"Create": "Create",
|
||||||
|
"Select": "Select",
|
||||||
|
"Text": "Text (coming soon)",
|
||||||
|
"Shape": "Shape",
|
||||||
|
"Sticky": "Sticky (coming soon)",
|
||||||
|
"Pen": "Pen (coming soon)",
|
||||||
|
"Connector": "Connector (coming soon)",
|
||||||
|
"Upload": "Upload",
|
||||||
|
"Restore it": "Restore it",
|
||||||
|
"TrashButtonGroupTitle": "Permanently delete",
|
||||||
|
"TrashButtonGroupDescription": "Once deleted, you can't undo this action. Do you confirm?",
|
||||||
|
"Delete permanently": "Delete permanently"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,29 +1 @@
|
|||||||
{
|
{}
|
||||||
"// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.": "",
|
|
||||||
"ComingSoon": "Bientôt disponible",
|
|
||||||
"Duplicate Page": "Dupliquer la page",
|
|
||||||
"Copy Page Link": "Copier le lien de la page",
|
|
||||||
"Delete": "Supprimer",
|
|
||||||
"Comment": "Commentaire",
|
|
||||||
"Export As HTML": "Exporter en HTML",
|
|
||||||
"Export As Markdown": "Exporter en Markdown",
|
|
||||||
"Export As PDF (Unsupported)": "exporter en PDF (non supporté)",
|
|
||||||
"Logout": "Déconnexion",
|
|
||||||
"Export Workspace": "Exporter l'espace de travail",
|
|
||||||
"Import Workspace": "Importer l'espace de travail",
|
|
||||||
"Language": "Langue",
|
|
||||||
"Last edited by": "Dernière édition par {{name}}",
|
|
||||||
"Layout": "Mise en forme",
|
|
||||||
"Settings": "Réglages",
|
|
||||||
"Share": "Partager",
|
|
||||||
"Sync to Disk": "Synchroniser sur le disque",
|
|
||||||
"Turn into": "Transformer en",
|
|
||||||
"WarningTips": {
|
|
||||||
"DoNotStore": "Affine est en développement actif ; la version actuelle est INSTABLE. Veuillez NE PAS stocker d'informations ou de données",
|
|
||||||
"IsNotLocalWorkspace": "Bienvenue sur la démo d'AFFiNE. Pour commencer à sauvegarder vos modifications, vous pouvez SYNCHRONISER SUR LE DISQUE",
|
|
||||||
"IsNotfsApiSupported": "Bienvenue sur la démo d'AFFiNE. Pour commencer à sauvegarder vos modifications, vous pouvez SYNCHRONISER SUR LE DISQUE\navec la dernière version d'un navigateur basé sur Chromium tel que Chrome ou Edge."
|
|
||||||
},
|
|
||||||
"Add A Below Block": "Ajouter un bloc en-dessous",
|
|
||||||
"Divide Here As A New Group": "Séparer ici en un nouveau groupe",
|
|
||||||
"Clear Workspace": "Vider l'espace de travail"
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,27 +1 @@
|
|||||||
{
|
{}
|
||||||
"// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.": "",
|
|
||||||
"Clear Workspace": "Očisti radni prostor",
|
|
||||||
"ComingSoon": "Podešavanja za izgled dolaze",
|
|
||||||
"Comment": "Komentar",
|
|
||||||
"Copy Page Link": "Kopiraj link stranice",
|
|
||||||
"Delete": "Obriši",
|
|
||||||
"Duplicate Page": "Dupliraj stranicu",
|
|
||||||
"Export As HTML": "Izvezi kao HTML",
|
|
||||||
"Export As Markdown": "Izvezi kao Markdown",
|
|
||||||
"Export As PDF (Unsupported)": "Izvezi kao PDF (nepodržano)",
|
|
||||||
"Export Workspace": "Izvezi radnu površinu",
|
|
||||||
"Import Workspace": "Poboljšaj radnu površinu",
|
|
||||||
"Language": "Jezik",
|
|
||||||
"Last edited by": "Zadnju promenu uradio {{ime}}",
|
|
||||||
"Layout": "Izgled",
|
|
||||||
"Logout": "Odjava",
|
|
||||||
"Settings": "Podešavanja",
|
|
||||||
"Share": "Podeli",
|
|
||||||
"Sync to Disk": "Sinhroniziraj sa diskom",
|
|
||||||
"Turn into": "Promeni u",
|
|
||||||
"WarningTips": {
|
|
||||||
"DoNotStore": "AFFiNE je u stanju aktivnog razvoja i trenutna verzija je NESTABILNA. Molimo vas, NEMOJTE čuvati informacije ili podatke.",
|
|
||||||
"IsNotLocalWorkspace": "Dobrodošli u AFFiNE demo. Da bi započeli proces čuvanja promena možete kliknuti SINHRONIZUJ SA DISKOM.",
|
|
||||||
"IsNotfsApiSupported": "Dobrodošli u AFFiNE demo. Da bi započeli proces čuvanja promena možete SINHRONIZOVATI NA DISK sa poslednjom verzijom pretraživača tipa Chromium, kao što su Chrome/Edge."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,29 +1,65 @@
|
|||||||
{
|
{
|
||||||
"// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.": "",
|
"Quick search": "快速搜索",
|
||||||
"Sync to Disk": "同步到磁盘",
|
"All pages": "全部页面",
|
||||||
"Share": "分享",
|
"Favourites": "收藏夹",
|
||||||
"WarningTips": {
|
"No item": "没有项目",
|
||||||
"IsNotfsApiSupported": "欢迎来到AFFiNE 的演示界面。您可以使用最新版本的基于Chrome的浏览器(如Chrome/Edge)将数据同步到磁盘来进行保存",
|
"Import": "导入",
|
||||||
"IsNotLocalWorkspace": "欢迎来到AFFiNE 的演示界面,您可以同步到磁盘来进行保存操作。",
|
"Trash": "回收站",
|
||||||
"DoNotStore": "AFFiNE 正在积极开发中,当前版本不稳定。请不要存储信息或数据。"
|
"New Page": "新建文章",
|
||||||
},
|
"New Keyword Page": "新建 '{{query}}' 为标题的文章",
|
||||||
"ComingSoon": "布局设置即将到来",
|
"Find 0 result": "找到 0 个结果",
|
||||||
"Layout": "布局",
|
"Find results": "找到 {{number}} 个结果",
|
||||||
"Comment": "评论",
|
"Collapse sidebar": "关闭侧边栏",
|
||||||
"Settings": "设置",
|
"Expand sidebar": "展开侧边栏",
|
||||||
"Duplicate Page": "复制页面",
|
"Removed from Favourites": "已从收藏中移除",
|
||||||
"Copy Page Link": "复制页面链接",
|
"Remove from favourites": "从收藏中移除",
|
||||||
"Language": "当前语言",
|
"Added to Favourites": "已添加到收藏",
|
||||||
"Clear Workspace": "清空工作区域",
|
"Add to favourites": "添加到收藏",
|
||||||
"Export As Markdown": "导出 markdown",
|
"Paper": "文章",
|
||||||
"Export As HTML": "导出 HTML",
|
"Edgeless": "无边模式",
|
||||||
"Export As PDF (Unsupported)": "导出 PDF (暂不支持)",
|
"Switch to": "跳转到",
|
||||||
"Import Workspace": "导入 Workspace",
|
"Convert to ": "转换成 ",
|
||||||
"Export Workspace": "导出 Workspace",
|
"Page": "文章",
|
||||||
"Last edited by": "最后编辑者为 {{name}}",
|
"Export": "导出",
|
||||||
"Logout": "退出登录",
|
"Export to HTML": "导出到 HTML",
|
||||||
|
"Export to Markdown": "导出到 Markdown",
|
||||||
"Delete": "删除",
|
"Delete": "删除",
|
||||||
"Turn into": "转换为",
|
"Title": "标题",
|
||||||
"Add A Below Block": "在下方添加一个新块",
|
"Untitled": "无标题",
|
||||||
"Divide Here As A New Group": "从这里划分一个新组"
|
"Created": "创建时间",
|
||||||
|
"Updated": "更新时间",
|
||||||
|
"Open in new tab": "在新页面打开",
|
||||||
|
"Favourite": "收藏",
|
||||||
|
"Favourited": "已收藏",
|
||||||
|
"Delete page?": "删除文章?",
|
||||||
|
"Delete permanently?": "永久删除?",
|
||||||
|
"will be moved to Trash": "{{title}} 将被移动到回收站",
|
||||||
|
"Once deleted, you can't undo this action.": "一次性删除,无法恢复。",
|
||||||
|
"Moved to Trash": "已移动到回收站",
|
||||||
|
"Permanently deleted": "已永久删除",
|
||||||
|
"restored": "{{title}} 已恢复",
|
||||||
|
"Cancel": "取消",
|
||||||
|
"Keyboard Shortcuts": "快捷键",
|
||||||
|
"Contact Us": "联系我们",
|
||||||
|
"Official Website": "官网",
|
||||||
|
"Get in touch!": "Get in touch!",
|
||||||
|
"AFFiNE Community": "AFFiNE Community",
|
||||||
|
"How is AFFiNE Alpha different?": "How is AFFiNE Alpha different?",
|
||||||
|
"Shortcuts": "Shortcuts",
|
||||||
|
"Undo": "Undo",
|
||||||
|
"Redo": "Redo",
|
||||||
|
"Bold": "Bold",
|
||||||
|
"Italic": "Italic",
|
||||||
|
"Underline": "Underline",
|
||||||
|
"Strikethrough": "Strikethrough",
|
||||||
|
"Inline code": "Inline code",
|
||||||
|
"Code block": "Code block",
|
||||||
|
"Link": "Link",
|
||||||
|
"Body text": "Body text",
|
||||||
|
"Heading": "Heading {{number}}",
|
||||||
|
"Increase indent": "Increase indent",
|
||||||
|
"Reduce indent": "Reduce indent",
|
||||||
|
"Markdown Syntax": "Markdown Syntax",
|
||||||
|
"Divider": "Divider",
|
||||||
|
"404 - Page Not Found": "404 - Page Not Found"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,29 +1 @@
|
|||||||
{
|
{}
|
||||||
"// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.": "",
|
|
||||||
"Add A Below Block": "在下方新添塊",
|
|
||||||
"Clear Workspace": "清空工作區",
|
|
||||||
"ComingSoon": "自定義佈局功能即將與您見面",
|
|
||||||
"Comment": "評論",
|
|
||||||
"Copy Page Link": "拷貝頁面鏈接",
|
|
||||||
"Delete": "刪除",
|
|
||||||
"Divide Here As A New Group": "從此地劃分成新組",
|
|
||||||
"Duplicate Page": "複製界面",
|
|
||||||
"Export As HTML": "導出 HTML",
|
|
||||||
"Export As Markdown": "以 Markdown 導出",
|
|
||||||
"Export As PDF (Unsupported)": "導出為 PDF(即將可用)",
|
|
||||||
"Export Workspace": "導出 Workspace",
|
|
||||||
"Import Workspace": "導入 Workspace",
|
|
||||||
"Language": "語言",
|
|
||||||
"Last edited by": "最後編輯者為 {{name}}",
|
|
||||||
"Layout": "佈局",
|
|
||||||
"Logout": "退出登錄",
|
|
||||||
"Settings": "設置",
|
|
||||||
"Share": "分享",
|
|
||||||
"Sync to Disk": "同步到磁盤",
|
|
||||||
"Turn into": "轉換為",
|
|
||||||
"WarningTips": {
|
|
||||||
"DoNotStore": "我們正在積極開發 AFFiNE,目前版本尚不穩定,請避免存儲信息或數據。",
|
|
||||||
"IsNotLocalWorkspace": "歡迎來到 AFFiNE 演示界面。您可以通過「同步到磁盤」來保存更改。",
|
|
||||||
"IsNotfsApiSupported": "歡迎進入AFFiNE演示!使用最新版本的基於 Chromium 內核的瀏覽器如Chrome/Edge,您可以通過「同步到磁盤」來保存更改"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -10,17 +10,18 @@ import '../utils/print-build-info';
|
|||||||
import ProviderComposer from '@/components/provider-composer';
|
import ProviderComposer from '@/components/provider-composer';
|
||||||
import type { PropsWithChildren, ReactElement, ReactNode } from 'react';
|
import type { PropsWithChildren, ReactElement, ReactNode } from 'react';
|
||||||
import type { NextPage } from 'next';
|
import type { NextPage } from 'next';
|
||||||
import { AppStateProvider } from '@/providers/app-state-provider/provider';
|
import { AppStateProvider } from '@/providers/app-state-provider/Provider';
|
||||||
import ConfirmProvider from '@/providers/confirm-provider';
|
import ConfirmProvider from '@/providers/ConfirmProvider';
|
||||||
import { ModalProvider } from '@/providers/global-modal-provider';
|
import { ModalProvider } from '@/providers/GlobalModalProvider';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useAppState } from '@/providers/app-state-provider';
|
import { useAppState } from '@/providers/app-state-provider';
|
||||||
import { PageLoading } from '@/components/loading';
|
import { PageLoading } from '@/components/loading';
|
||||||
import Head from 'next/head';
|
import Head from 'next/head';
|
||||||
import '@/libs/i18n';
|
import '@/libs/i18n';
|
||||||
|
import TemporaryHelperProvider from '@/providers/temporary-helper-provider';
|
||||||
|
|
||||||
const ThemeProvider = dynamic(() => import('@/providers/themeProvider'), {
|
const ThemeProvider = dynamic(() => import('@/providers/ThemeProvider'), {
|
||||||
ssr: false,
|
ssr: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -52,6 +53,7 @@ const App = ({ Component, pageProps }: AppPropsWithLayout) => {
|
|||||||
<Logger />
|
<Logger />
|
||||||
<ProviderComposer
|
<ProviderComposer
|
||||||
contexts={[
|
contexts={[
|
||||||
|
<TemporaryHelperProvider key="TemporaryHelperProvider" />,
|
||||||
<ThemeProvider key="ThemeProvider" />,
|
<ThemeProvider key="ThemeProvider" />,
|
||||||
<AppStateProvider key="appStateProvider" />,
|
<AppStateProvider key="appStateProvider" />,
|
||||||
<ModalProvider key="ModalProvider" />,
|
<ModalProvider key="ModalProvider" />,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { NextPage } from 'next';
|
import type { NextPage } from 'next';
|
||||||
|
|
||||||
const Home: NextPage = () => {
|
const Home: NextPage = () => {
|
||||||
return <div>Home Page</div>;
|
return <div title="Home Page"></div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Home;
|
export default Home;
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import { WorkspaceModal } from '@/components/workspace-modal';
|
||||||
|
import { getWorkspaces } from '@/hooks/mock-data/mock';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { styled } from '@/styles';
|
||||||
|
import Button from '@/ui/button/Button';
|
||||||
|
|
||||||
|
const Page = () => {
|
||||||
|
const [open, setOpen] = useState<boolean>(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const data = getWorkspaces();
|
||||||
|
if (!data.length) {
|
||||||
|
setOpen(true);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
return (
|
||||||
|
<Workspace>
|
||||||
|
<h1>workspace</h1>
|
||||||
|
<div>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
setOpen(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
View Workspace List
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<WorkspaceModal
|
||||||
|
open={open}
|
||||||
|
onClose={() => {
|
||||||
|
setOpen(false);
|
||||||
|
}}
|
||||||
|
></WorkspaceModal>
|
||||||
|
</Workspace>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default Page;
|
||||||
|
|
||||||
|
const Workspace = styled.div(({ theme }) => {
|
||||||
|
return {
|
||||||
|
height: '100vh',
|
||||||
|
background: theme.colors.pageBackground,
|
||||||
|
color: '#FFFFFF',
|
||||||
|
fontSize: '18px',
|
||||||
|
fontWeight: 500,
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { useEffect } from 'react';
|
||||||
|
import type { Page } from '@blocksuite/store';
|
||||||
|
import '@blocksuite/blocks';
|
||||||
|
import { EditorContainer } from '@blocksuite/editor';
|
||||||
|
import type { CreateEditorHandler } from './context';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
setCreateEditorHandler: (handler: CreateEditorHandler) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DynamicBlocksuite = ({ setCreateEditorHandler }: Props) => {
|
||||||
|
useEffect(() => {
|
||||||
|
const createEditorHandler: CreateEditorHandler = (page: Page) => {
|
||||||
|
const editor = new EditorContainer();
|
||||||
|
editor.page = page;
|
||||||
|
return editor;
|
||||||
|
};
|
||||||
|
|
||||||
|
setCreateEditorHandler(createEditorHandler);
|
||||||
|
}, [setCreateEditorHandler]);
|
||||||
|
|
||||||
|
return <></>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DynamicBlocksuite;
|
||||||
+15
-53
@@ -1,4 +1,4 @@
|
|||||||
import { useMemo, useState, useEffect, useCallback, useRef } from 'react';
|
import { useMemo, useState, useCallback, useRef } from 'react';
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
import dynamic from 'next/dynamic';
|
import dynamic from 'next/dynamic';
|
||||||
import { getDataCenter } from '@affine/datacenter';
|
import { getDataCenter } from '@affine/datacenter';
|
||||||
@@ -8,12 +8,20 @@ import type {
|
|||||||
CreateEditorHandler,
|
CreateEditorHandler,
|
||||||
LoadWorkspaceHandler,
|
LoadWorkspaceHandler,
|
||||||
} from './context';
|
} from './context';
|
||||||
import { Page, Workspace as StoreWorkspace } from '@blocksuite/store';
|
import { Page } from '@blocksuite/store';
|
||||||
import { EditorContainer } from '@blocksuite/editor';
|
import { EditorContainer } from '@blocksuite/editor';
|
||||||
const DynamicBlocksuite = dynamic(() => import('./dynamic-blocksuite'), {
|
const DynamicBlocksuite = dynamic(() => import('./DynamicBlocksuite'), {
|
||||||
ssr: false,
|
ssr: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const loadWorkspaceHandler: LoadWorkspaceHandler = async workspaceId => {
|
||||||
|
if (workspaceId) {
|
||||||
|
const dc = await getDataCenter();
|
||||||
|
return dc.load(workspaceId, { providerId: 'affine' });
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
export const AppStateProvider = ({ children }: { children?: ReactNode }) => {
|
export const AppStateProvider = ({ children }: { children?: ReactNode }) => {
|
||||||
const refreshWorkspacesMeta = async () => {
|
const refreshWorkspacesMeta = async () => {
|
||||||
const dc = await getDataCenter();
|
const dc = await getDataCenter();
|
||||||
@@ -30,43 +38,10 @@ export const AppStateProvider = ({ children }: { children?: ReactNode }) => {
|
|||||||
currentWorkspace: null,
|
currentWorkspace: null,
|
||||||
currentPage: null,
|
currentPage: null,
|
||||||
editor: null,
|
editor: null,
|
||||||
// Synced is used to ensure that the provider has synced with the server,
|
|
||||||
// So after Synced set to true, the other state is sure to be set.
|
|
||||||
synced: false,
|
|
||||||
refreshWorkspacesMeta,
|
refreshWorkspacesMeta,
|
||||||
workspaces: {},
|
synced: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
(async () => {
|
|
||||||
const workspacesList = await Promise.all(
|
|
||||||
state.workspacesMeta.map(async ({ id }) => {
|
|
||||||
const workspace =
|
|
||||||
(await loadWorkspaceHandler?.(id, state.user)) || null;
|
|
||||||
return { id, workspace };
|
|
||||||
})
|
|
||||||
);
|
|
||||||
const workspaces: Record<string, StoreWorkspace | null> = {};
|
|
||||||
workspacesList.forEach(({ id, workspace }) => {
|
|
||||||
workspaces[id] = workspace;
|
|
||||||
});
|
|
||||||
setState(state => ({
|
|
||||||
...state,
|
|
||||||
workspaces,
|
|
||||||
}));
|
|
||||||
})();
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [state.workspacesMeta]);
|
|
||||||
|
|
||||||
const [loadWorkspaceHandler, _setLoadWorkspaceHandler] =
|
|
||||||
useState<LoadWorkspaceHandler>();
|
|
||||||
const setLoadWorkspaceHandler = useCallback(
|
|
||||||
(handler: LoadWorkspaceHandler) => {
|
|
||||||
_setLoadWorkspaceHandler(() => handler);
|
|
||||||
},
|
|
||||||
[_setLoadWorkspaceHandler]
|
|
||||||
);
|
|
||||||
|
|
||||||
const [createEditorHandler, _setCreateEditorHandler] =
|
const [createEditorHandler, _setCreateEditorHandler] =
|
||||||
useState<CreateEditorHandler>();
|
useState<CreateEditorHandler>();
|
||||||
|
|
||||||
@@ -85,7 +60,7 @@ export const AppStateProvider = ({ children }: { children?: ReactNode }) => {
|
|||||||
return state.currentWorkspace;
|
return state.currentWorkspace;
|
||||||
}
|
}
|
||||||
const workspace =
|
const workspace =
|
||||||
(await loadWorkspaceHandler?.(workspaceId, state.user)) || null;
|
(await loadWorkspaceHandler(workspaceId, state.user)) || null;
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
@@ -103,6 +78,7 @@ export const AppStateProvider = ({ children }: { children?: ReactNode }) => {
|
|||||||
}));
|
}));
|
||||||
return workspace;
|
return workspace;
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadPage = useRef<AppStateContext['loadPage']>(() =>
|
const loadPage = useRef<AppStateContext['loadPage']>(() =>
|
||||||
Promise.resolve(null)
|
Promise.resolve(null)
|
||||||
);
|
);
|
||||||
@@ -147,17 +123,6 @@ export const AppStateProvider = ({ children }: { children?: ReactNode }) => {
|
|||||||
setState(state => ({ ...state, editor }));
|
setState(state => ({ ...state, editor }));
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!loadWorkspaceHandler) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setState(state => ({
|
|
||||||
...state,
|
|
||||||
workspacesMeta: [],
|
|
||||||
synced: true,
|
|
||||||
}));
|
|
||||||
}, [loadWorkspaceHandler]);
|
|
||||||
|
|
||||||
const context = useMemo(
|
const context = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
...state,
|
...state,
|
||||||
@@ -172,10 +137,7 @@ export const AppStateProvider = ({ children }: { children?: ReactNode }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<AppState.Provider value={context}>
|
<AppState.Provider value={context}>
|
||||||
<DynamicBlocksuite
|
<DynamicBlocksuite setCreateEditorHandler={setCreateEditorHandler} />
|
||||||
setLoadWorkspaceHandler={setLoadWorkspaceHandler}
|
|
||||||
setCreateEditorHandler={setCreateEditorHandler}
|
|
||||||
/>
|
|
||||||
{children}
|
{children}
|
||||||
</AppState.Provider>
|
</AppState.Provider>
|
||||||
);
|
);
|
||||||
@@ -5,6 +5,7 @@ import type {
|
|||||||
Workspace as StoreWorkspace,
|
Workspace as StoreWorkspace,
|
||||||
} from '@blocksuite/store';
|
} from '@blocksuite/store';
|
||||||
import type { EditorContainer } from '@blocksuite/editor';
|
import type { EditorContainer } from '@blocksuite/editor';
|
||||||
|
|
||||||
export type LoadWorkspaceHandler = (
|
export type LoadWorkspaceHandler = (
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
user?: AccessTokenMessage | null
|
user?: AccessTokenMessage | null
|
||||||
@@ -20,7 +21,7 @@ export interface AppStateValue {
|
|||||||
|
|
||||||
currentPage: StorePage | null;
|
currentPage: StorePage | null;
|
||||||
|
|
||||||
workspaces: Record<string, StoreWorkspace | null>;
|
// workspaces: Record<string, StoreWorkspace | null>;
|
||||||
|
|
||||||
editor: EditorContainer | null;
|
editor: EditorContainer | null;
|
||||||
synced: boolean;
|
synced: boolean;
|
||||||
@@ -40,14 +41,10 @@ export interface AppStateContext extends AppStateValue {
|
|||||||
export const AppState = createContext<AppStateContext>({
|
export const AppState = createContext<AppStateContext>({
|
||||||
user: null,
|
user: null,
|
||||||
workspacesMeta: [],
|
workspacesMeta: [],
|
||||||
|
|
||||||
currentWorkspaceId: '',
|
currentWorkspaceId: '',
|
||||||
currentWorkspace: null,
|
currentWorkspace: null,
|
||||||
|
|
||||||
currentPage: null,
|
currentPage: null,
|
||||||
|
|
||||||
editor: null,
|
editor: null,
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||||
setState: () => {},
|
setState: () => {},
|
||||||
createEditor: undefined,
|
createEditor: undefined,
|
||||||
@@ -57,7 +54,6 @@ export const AppState = createContext<AppStateContext>({
|
|||||||
synced: false,
|
synced: false,
|
||||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||||
refreshWorkspacesMeta: () => {},
|
refreshWorkspacesMeta: () => {},
|
||||||
workspaces: {},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const useAppState = () => {
|
export const useAppState = () => {
|
||||||
|
|||||||
@@ -1,43 +0,0 @@
|
|||||||
import { useEffect } from 'react';
|
|
||||||
import type { Page } from '@blocksuite/store';
|
|
||||||
import '@blocksuite/blocks';
|
|
||||||
import { EditorContainer } from '@blocksuite/editor';
|
|
||||||
import type { LoadWorkspaceHandler, CreateEditorHandler } from './context';
|
|
||||||
import { getDataCenter } from '@affine/datacenter';
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
setLoadWorkspaceHandler: (handler: LoadWorkspaceHandler) => void;
|
|
||||||
setCreateEditorHandler: (handler: CreateEditorHandler) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const DynamicBlocksuite = ({
|
|
||||||
setLoadWorkspaceHandler,
|
|
||||||
setCreateEditorHandler,
|
|
||||||
}: Props) => {
|
|
||||||
useEffect(() => {
|
|
||||||
const openWorkspace: LoadWorkspaceHandler = async (workspaceId: string) => {
|
|
||||||
if (workspaceId) {
|
|
||||||
const dc = await getDataCenter();
|
|
||||||
return dc.load(workspaceId, { providerId: 'selfhosted' });
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
setLoadWorkspaceHandler(openWorkspace);
|
|
||||||
}, [setLoadWorkspaceHandler]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const createEditorHandler: CreateEditorHandler = (page: Page) => {
|
|
||||||
const editor = new EditorContainer();
|
|
||||||
editor.page = page;
|
|
||||||
return editor;
|
|
||||||
};
|
|
||||||
|
|
||||||
setCreateEditorHandler(createEditorHandler);
|
|
||||||
}, [setCreateEditorHandler]);
|
|
||||||
|
|
||||||
return <></>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default DynamicBlocksuite;
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
import { useEffect } from 'react';
|
|
||||||
import { useRouter } from 'next/router';
|
|
||||||
import { useAppState } from './context';
|
|
||||||
import { usePageHelper } from '@/hooks/use-page-helper';
|
|
||||||
export const useLoadWorkspace = () => {
|
|
||||||
const router = useRouter();
|
|
||||||
const { loadWorkspace, currentWorkspace, currentWorkspaceId } = useAppState();
|
|
||||||
|
|
||||||
const workspaceId = router.query.workspaceId as string;
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
loadWorkspace?.(workspaceId);
|
|
||||||
}, [workspaceId, loadWorkspace]);
|
|
||||||
|
|
||||||
return currentWorkspaceId === workspaceId ? currentWorkspace : null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useLoadPage = () => {
|
|
||||||
const router = useRouter();
|
|
||||||
const { loadPage, currentPage, currentWorkspaceId } = useAppState();
|
|
||||||
const { createPage } = usePageHelper();
|
|
||||||
const workspace = useLoadWorkspace();
|
|
||||||
|
|
||||||
const pageId = router.query.pageId as string;
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!workspace) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const page = pageId ? workspace?.getPage(pageId) : null;
|
|
||||||
if (page) {
|
|
||||||
loadPage?.(pageId);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const savedPageId = workspace.meta.pageMetas[0]?.id;
|
|
||||||
if (savedPageId) {
|
|
||||||
router.push(`/workspace/${currentWorkspaceId}/${savedPageId}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
createPage().then(async pageId => {
|
|
||||||
if (!pageId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
router.push(`/workspace/${currentWorkspaceId}/${pageId}`);
|
|
||||||
});
|
|
||||||
}, [workspace, pageId, loadPage, createPage, router, currentWorkspaceId]);
|
|
||||||
|
|
||||||
return currentPage?.id === pageId ? currentPage : null;
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,183 @@
|
|||||||
|
import { createContext, useContext, useEffect, useState } from 'react';
|
||||||
|
import type { PropsWithChildren } from 'react';
|
||||||
|
import { getWorkspaces, User, Workspace } from '@/hooks/mock-data/mock';
|
||||||
|
|
||||||
|
type TemporaryContextValue = {
|
||||||
|
workspaceMetaList: Workspace[];
|
||||||
|
currentWorkspace: Workspace | null;
|
||||||
|
user: User | null;
|
||||||
|
|
||||||
|
updateWorkspaceMeta: (
|
||||||
|
workspaceId: string,
|
||||||
|
workspaceData: {
|
||||||
|
name?: string;
|
||||||
|
avatar?: string;
|
||||||
|
isPublish?: boolean;
|
||||||
|
type?: 'local' | 'cloud' | 'join';
|
||||||
|
}
|
||||||
|
) => void;
|
||||||
|
|
||||||
|
createWorkspace: (workspaceName: string) => Workspace;
|
||||||
|
|
||||||
|
deleteWorkspace: (workspaceId: string) => void;
|
||||||
|
|
||||||
|
setWorkspacePublish: (id: string, isPublish: boolean) => void;
|
||||||
|
|
||||||
|
setActiveWorkspace: (workspaceData: Workspace) => void;
|
||||||
|
|
||||||
|
login: () => void;
|
||||||
|
|
||||||
|
signOut: () => void;
|
||||||
|
};
|
||||||
|
type TemporaryContextProps = PropsWithChildren<Record<string, unknown>>;
|
||||||
|
|
||||||
|
export const TemporaryContext = createContext<TemporaryContextValue>({
|
||||||
|
workspaceMetaList: [],
|
||||||
|
currentWorkspace: null,
|
||||||
|
user: null,
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||||
|
updateWorkspaceMeta: () => {},
|
||||||
|
|
||||||
|
createWorkspace: () => {
|
||||||
|
return {} as Workspace;
|
||||||
|
},
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||||
|
deleteWorkspace: () => {},
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||||
|
setWorkspacePublish: () => {},
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||||
|
setActiveWorkspace: () => {},
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||||
|
login: () => {},
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||||
|
signOut: () => {},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const useTemporaryHelper = () => useContext(TemporaryContext);
|
||||||
|
|
||||||
|
export const TemporaryHelperProvider = ({
|
||||||
|
children,
|
||||||
|
}: PropsWithChildren<TemporaryContextProps>) => {
|
||||||
|
const [workspaceMetaList, setWorkspaceMetaList] = useState<Workspace[]>([]);
|
||||||
|
const [currentWorkspace, setCurrentWorkspace] = useState<Workspace | null>(
|
||||||
|
null
|
||||||
|
);
|
||||||
|
const [user, setUser] = useState<User | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setWorkspaceMetaList(getWorkspaces());
|
||||||
|
setCurrentWorkspace(
|
||||||
|
JSON.parse(localStorage.getItem('affine-active-workspace') ?? '{}')
|
||||||
|
);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TemporaryContext.Provider
|
||||||
|
value={{
|
||||||
|
workspaceMetaList,
|
||||||
|
currentWorkspace,
|
||||||
|
user,
|
||||||
|
|
||||||
|
updateWorkspaceMeta: (workspaceId, workspaceData) => {
|
||||||
|
const workspacesMeta = getWorkspaces();
|
||||||
|
const newWorkspacesMeta = workspacesMeta.map(
|
||||||
|
(workspace: Workspace) => {
|
||||||
|
if (workspace.id === workspaceId) {
|
||||||
|
const workspaceObj = Object.assign(workspace, workspaceData);
|
||||||
|
return workspaceObj;
|
||||||
|
}
|
||||||
|
return workspace;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
localStorage.setItem(
|
||||||
|
'affine-workspace',
|
||||||
|
JSON.stringify(newWorkspacesMeta)
|
||||||
|
);
|
||||||
|
setWorkspaceMetaList(newWorkspacesMeta);
|
||||||
|
if (workspaceId === currentWorkspace?.id) {
|
||||||
|
setCurrentWorkspace(
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
newWorkspacesMeta.find(v => v.id === currentWorkspace.id)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
createWorkspace: workspaceName => {
|
||||||
|
const workspaceData = {
|
||||||
|
name: workspaceName,
|
||||||
|
id: 'workspace-' + Date.now(),
|
||||||
|
isPublish: false,
|
||||||
|
isLocal: true,
|
||||||
|
avatar: '',
|
||||||
|
type: 'local',
|
||||||
|
} as Workspace;
|
||||||
|
const workspacesMeta = getWorkspaces();
|
||||||
|
workspacesMeta.push(workspaceData);
|
||||||
|
localStorage.setItem(
|
||||||
|
'affine-workspace',
|
||||||
|
JSON.stringify(workspacesMeta)
|
||||||
|
);
|
||||||
|
|
||||||
|
setWorkspaceMetaList([...workspacesMeta]);
|
||||||
|
return workspaceData;
|
||||||
|
},
|
||||||
|
deleteWorkspace: workspaceId => {
|
||||||
|
const workspacesMeta = getWorkspaces();
|
||||||
|
const newWorkspacesMeta = workspacesMeta.filter(() => {
|
||||||
|
return workspaceId !== workspaceId;
|
||||||
|
});
|
||||||
|
localStorage.setItem(
|
||||||
|
'affine-workspace',
|
||||||
|
JSON.stringify(newWorkspacesMeta)
|
||||||
|
);
|
||||||
|
setWorkspaceMetaList(workspacesMeta);
|
||||||
|
},
|
||||||
|
|
||||||
|
setWorkspacePublish: (id, isPublish) => {
|
||||||
|
const workspacesMeta = getWorkspaces();
|
||||||
|
const newWorkspacesMeta = workspacesMeta.map(
|
||||||
|
(workspace: Workspace) => {
|
||||||
|
if (workspace.id === id) {
|
||||||
|
workspace.isPublish = isPublish;
|
||||||
|
}
|
||||||
|
return workspace;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
localStorage.setItem(
|
||||||
|
'affine-workspace',
|
||||||
|
JSON.stringify(newWorkspacesMeta)
|
||||||
|
);
|
||||||
|
setWorkspaceMetaList(workspacesMeta);
|
||||||
|
},
|
||||||
|
setActiveWorkspace(workspaceData) {
|
||||||
|
localStorage.setItem(
|
||||||
|
'affine-active-workspace',
|
||||||
|
JSON.stringify(workspaceData)
|
||||||
|
);
|
||||||
|
setCurrentWorkspace(workspaceData);
|
||||||
|
},
|
||||||
|
|
||||||
|
login: () => {
|
||||||
|
const userInfo = {
|
||||||
|
name: 'Diamond',
|
||||||
|
id: 'ttt',
|
||||||
|
email: 'diamond.shx@gmail.com',
|
||||||
|
avatar: 'string',
|
||||||
|
};
|
||||||
|
localStorage.setItem('affine-user', JSON.stringify(userInfo));
|
||||||
|
setUser(userInfo);
|
||||||
|
},
|
||||||
|
|
||||||
|
signOut(): void {
|
||||||
|
localStorage.removeItem('affine-user');
|
||||||
|
setUser(null);
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</TemporaryContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TemporaryHelperProvider;
|
||||||
@@ -13,7 +13,7 @@ Let us know what you think of this latest version.
|
|||||||
5. You can self-host locally with Docker.
|
5. You can self-host locally with Docker.
|
||||||
|
|
||||||
```basic
|
```basic
|
||||||
docker run -d -v [YOUR_PATH]:/app/data -p 3000:3000 ghcr.io/toeverything/affine-self-hosted:alpha-abbey-wood
|
docker run -it --name affine -d -v [YOUR_PATH]:/app/data -p 3000:3000 ghcr.io/toeverything/affine-self-hosted:alpha-abbey-wood
|
||||||
```
|
```
|
||||||
|
|
||||||
**Looking for Markdown syntax or keyboard shortcuts?**
|
**Looking for Markdown syntax or keyboard shortcuts?**
|
||||||
@@ -24,6 +24,9 @@ docker run -d -v [YOUR_PATH]:/app/data -p 3000:3000 ghcr.io/toeverything/affine-
|
|||||||
|
|
||||||
- Manage your pages from the collapsible **sidebar**, which allows you to add **favourites** and restore deleted files from the **trash**
|
- Manage your pages from the collapsible **sidebar**, which allows you to add **favourites** and restore deleted files from the **trash**
|
||||||
- Search through all your content with the quick search - activate with `Ctrl/⌘ + K`
|
- Search through all your content with the quick search - activate with `Ctrl/⌘ + K`
|
||||||
|
- A friendly Reminder:
|
||||||
|
- In the case of unselected text, `Ctrl/⌘ + K` activates quick search;
|
||||||
|
- In the case of selected text, `Ctrl/⌘ + K` will firstly ask to add a hyperlink, and then using `Ctrl/⌘ + K` again activates the quick search
|
||||||
- Quickly format text with the **pop-up toolbar** (highlight any text to give it a try)
|
- Quickly format text with the **pop-up toolbar** (highlight any text to give it a try)
|
||||||
- Copy and paste **images** into your pages, resize them and add captions
|
- Copy and paste **images** into your pages, resize them and add captions
|
||||||
- Add horizontal line dividers to your text with `---` and `***`
|
- Add horizontal line dividers to your text with `---` and `***`
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ export type ConfirmProps = {
|
|||||||
title?: string;
|
title?: string;
|
||||||
content?: string;
|
content?: string;
|
||||||
confirmText?: string;
|
confirmText?: string;
|
||||||
|
cancelText?: string;
|
||||||
// TODO: Confirm button's color should depend on confirm type
|
// TODO: Confirm button's color should depend on confirm type
|
||||||
confirmType?: 'primary' | 'warning' | 'danger';
|
confirmType?: 'primary' | 'warning' | 'danger';
|
||||||
onConfirm?: () => void;
|
onConfirm?: () => void;
|
||||||
@@ -25,6 +26,7 @@ export const Confirm = ({
|
|||||||
confirmType,
|
confirmType,
|
||||||
onConfirm,
|
onConfirm,
|
||||||
onCancel,
|
onCancel,
|
||||||
|
cancelText = 'Cancel',
|
||||||
}: ConfirmProps) => {
|
}: ConfirmProps) => {
|
||||||
const [open, setOpen] = useState(true);
|
const [open, setOpen] = useState(true);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -50,7 +52,7 @@ export const Confirm = ({
|
|||||||
}}
|
}}
|
||||||
style={{ marginRight: '24px' }}
|
style={{ marginRight: '24px' }}
|
||||||
>
|
>
|
||||||
{t('Cancel')}
|
{cancelText === 'Cancel' ? t('Cancel') : cancelText}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
type={confirmType}
|
type={confirmType}
|
||||||
|
|||||||
@@ -1,4 +1,10 @@
|
|||||||
import { InputHTMLAttributes, useEffect, useState } from 'react';
|
import {
|
||||||
|
InputHTMLAttributes,
|
||||||
|
useEffect,
|
||||||
|
useState,
|
||||||
|
FocusEventHandler,
|
||||||
|
KeyboardEventHandler,
|
||||||
|
} from 'react';
|
||||||
import { StyledInput } from './style';
|
import { StyledInput } from './style';
|
||||||
|
|
||||||
type inputProps = {
|
type inputProps = {
|
||||||
@@ -9,8 +15,8 @@ type inputProps = {
|
|||||||
maxLength?: number;
|
maxLength?: number;
|
||||||
minLength?: number;
|
minLength?: number;
|
||||||
onChange?: (value: string) => void;
|
onChange?: (value: string) => void;
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
onBlur?: FocusEventHandler<HTMLInputElement>;
|
||||||
onBlur?: (e: any) => void;
|
onKeyDown?: KeyboardEventHandler<HTMLInputElement>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Input = (props: inputProps) => {
|
export const Input = (props: inputProps) => {
|
||||||
@@ -23,6 +29,7 @@ export const Input = (props: inputProps) => {
|
|||||||
width = 260,
|
width = 260,
|
||||||
onChange,
|
onChange,
|
||||||
onBlur,
|
onBlur,
|
||||||
|
onKeyDown,
|
||||||
} = props;
|
} = props;
|
||||||
const [value, setValue] = useState<string>(valueProp || '');
|
const [value, setValue] = useState<string>(valueProp || '');
|
||||||
const handleChange: InputHTMLAttributes<HTMLInputElement>['onChange'] = e => {
|
const handleChange: InputHTMLAttributes<HTMLInputElement>['onChange'] = e => {
|
||||||
@@ -37,6 +44,10 @@ export const Input = (props: inputProps) => {
|
|||||||
const handleBlur: InputHTMLAttributes<HTMLInputElement>['onBlur'] = e => {
|
const handleBlur: InputHTMLAttributes<HTMLInputElement>['onBlur'] = e => {
|
||||||
onBlur && onBlur(e);
|
onBlur && onBlur(e);
|
||||||
};
|
};
|
||||||
|
const handleKeyDown: InputHTMLAttributes<HTMLInputElement>['onKeyDown'] =
|
||||||
|
e => {
|
||||||
|
onKeyDown && onKeyDown(e);
|
||||||
|
};
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setValue(valueProp || '');
|
setValue(valueProp || '');
|
||||||
}, [valueProp]);
|
}, [valueProp]);
|
||||||
@@ -50,6 +61,7 @@ export const Input = (props: inputProps) => {
|
|||||||
minLength={minLength}
|
minLength={minLength}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
onBlur={handleBlur}
|
onBlur={handleBlur}
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
></StyledInput>
|
></StyledInput>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
export function stringToColour(str: string) {
|
||||||
|
str = str || 'affine';
|
||||||
|
let colour = '#';
|
||||||
|
let hash = 0;
|
||||||
|
// str to hash
|
||||||
|
for (
|
||||||
|
let i = 0;
|
||||||
|
i < str.length;
|
||||||
|
hash = str.charCodeAt(i++) + ((hash << 5) - hash)
|
||||||
|
);
|
||||||
|
|
||||||
|
// int/hash to hex
|
||||||
|
for (
|
||||||
|
let i = 0;
|
||||||
|
i < 3;
|
||||||
|
colour += ('00' + ((hash >> (i++ * 8)) & 0xff).toString(16)).slice(-2)
|
||||||
|
);
|
||||||
|
|
||||||
|
return colour;
|
||||||
|
}
|
||||||
@@ -3,3 +3,5 @@ export { isDev } from './env';
|
|||||||
export * from './useragent';
|
export * from './useragent';
|
||||||
|
|
||||||
export * from './tools';
|
export * from './tools';
|
||||||
|
|
||||||
|
export * from './colors';
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user