mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-22 20:46:38 +08:00
feat: refactor provider
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
import type { EditorContainer } from '@blocksuite/editor';
|
||||
import { createContext, useContext, useEffect, useState } from 'react';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import dynamic from 'next/dynamic';
|
||||
import Loading from './loading';
|
||||
|
||||
const DynamicEditor = dynamic(() => import('./initial-editor'), {
|
||||
ssr: false,
|
||||
});
|
||||
|
||||
type EditorContextValue = {
|
||||
editor: EditorContainer | null;
|
||||
mode: EditorContainer['mode'];
|
||||
setMode: (mode: EditorContainer['mode']) => void;
|
||||
};
|
||||
|
||||
type EditorContextProps = PropsWithChildren<{}>;
|
||||
|
||||
export const EditorContext = createContext<EditorContextValue>({
|
||||
editor: null,
|
||||
mode: 'page',
|
||||
setMode: () => {},
|
||||
});
|
||||
|
||||
export const useEditor = () => useContext(EditorContext);
|
||||
|
||||
export const EditorProvider = ({
|
||||
children,
|
||||
}: PropsWithChildren<EditorContextProps>) => {
|
||||
const [editor, setEditor] = useState<EditorContainer | null>(null);
|
||||
const [mode, setMode] = useState<EditorContainer['mode']>('page');
|
||||
|
||||
useEffect(() => {
|
||||
const event = new CustomEvent('affine.switch-mode', { detail: mode });
|
||||
window.dispatchEvent(event);
|
||||
}, [mode]);
|
||||
|
||||
return (
|
||||
<EditorContext.Provider value={{ editor, mode, setMode }}>
|
||||
<DynamicEditor setEditor={setEditor} />
|
||||
{editor ? children : <Loading />}
|
||||
</EditorContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export default EditorProvider;
|
||||
@@ -0,0 +1,62 @@
|
||||
export const exampleMarkdown = `The AFFiNE Alpha is here! You can also view our [Official Website](https://affine.pro/)!
|
||||
|
||||
**What's different in AFFiNE Alpha?**
|
||||
|
||||
1. A much ~smoother editing~ experience, with much ~greater stability~;
|
||||
2. More complete ~Markdown~ support and improved ~keyboard shortcuts~;
|
||||
3. New features such as ~dark mode~;
|
||||
* **Switch between view styles using the** ☀ **and** 🌙.
|
||||
4. ~Clean and modern UI/UX~ design.
|
||||
|
||||
**Looking for Markdown syntax or keyboard shortcuts?**
|
||||
|
||||
* Find the (?) in the bottom right, then the ️⌨️, to view a full list of \`Keyboard Shortcuts\`
|
||||
|
||||
**Have an enjoyable editing experience !!!** 😃
|
||||
|
||||
Have some feedback or just want to get in touch? Use the (?), then 🎧 to get in touch and join our communities.
|
||||
|
||||
**Still in development**
|
||||
|
||||
There are also some things you should consider about this AFFiNE Alpha including some ~limitations~:
|
||||
|
||||
* Single page editing - currently editing multiple docs/pages is not supported;
|
||||
* Changes are not automatically stored, to save changes you should export your data. Options can be found by going to the top right and finding the \`⋮\` icon;
|
||||
* Without an import/open feature you are still able to access your data by copying it back in.
|
||||
|
||||
**Keyboard Shortcuts:**
|
||||
|
||||
1. Undo: \`⌘+Z\` or \`Ctrl+Z\`
|
||||
2. Redo: \`⌘+⇧+Z\` or \`Ctrl+Shift+Z\`
|
||||
3. Bold: \`⌘+B\` or \`Ctrl+B\`
|
||||
4. Italic: \`⌘+I\` or \`Ctrl+I\`
|
||||
5. Underline: \`⌘+U\` or \`Ctrl+U\`
|
||||
6. Strikethrough: \`⌘+⇧+S\` or \`Ctrl+Shift+S\`
|
||||
7. Inline code: \`⌘+E\` or \`Ctrl+E\`
|
||||
8. Link: \`⌘+K\` or \`Ctrl+K\`
|
||||
9. Body text: \`⌘+⌥+0\` or \`Ctrl+Shift+0\`
|
||||
10. Heading 1: \`⌘+⌥+1\` or \`Ctrl+Shift+1\`
|
||||
11. Heading 2: \`⌘+⌥+2\` or \`Ctrl+Shift+2\`
|
||||
12. Heading 3: \`⌘+⌥+3\` or \`Ctrl+Shift+3\`
|
||||
13. Heading 4: \`⌘+⌥+4\` or \`Ctrl+Shift+4\`
|
||||
14. Heading 5: \`⌘+⌥+5\` or \`Ctrl+Shift+5\`
|
||||
15. Heading 6: \`⌘+⌥+6\` or \`Ctrl+Shift+6\`
|
||||
16. Increase indent: \`Tab\`
|
||||
17. Reduce indent: \`⇧+Tab\` or \`Shift+Tab\`
|
||||
|
||||
**Markdown Syntax:**
|
||||
|
||||
1. Bold: \`**text**\`
|
||||
2. Italic: \`*text*\`
|
||||
3. Underline: \`~text~\`
|
||||
4. Strikethrough: \`~~text~~\`
|
||||
5. Inline code: \`\` \`text\` \`\`
|
||||
6. Heading 1: \`# text\`
|
||||
7. Heading 2: \`## text\`
|
||||
8. Heading 3: \`### text\`
|
||||
9. Heading 4: \`#### text\`
|
||||
10. Heading 5: \`##### text\`
|
||||
11. Heading 6: \`###### text\`
|
||||
`;
|
||||
|
||||
export default exampleMarkdown;
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from './editor-provider';
|
||||
export * from './example-markdown';
|
||||
export * from './utils';
|
||||
@@ -0,0 +1,51 @@
|
||||
import '@blocksuite/blocks';
|
||||
import '@blocksuite/blocks/style';
|
||||
import type { EditorContainer } from '@blocksuite/editor';
|
||||
import { BlockSchema, createEditor } from '@blocksuite/editor';
|
||||
import { useEffect } from 'react';
|
||||
import pkg from '../../../package.json';
|
||||
import { createWebsocketDocProvider, Store } from '@blocksuite/store';
|
||||
|
||||
const getEditorParams = () => {
|
||||
const providers = [];
|
||||
const params = new URLSearchParams(location.search);
|
||||
if (params.get('syncModes') === 'websocket') {
|
||||
const WebsocketDocProvider = createWebsocketDocProvider(
|
||||
'ws://127.0.0.1:3000/collaboration/AFFiNE'
|
||||
);
|
||||
providers.push(WebsocketDocProvider);
|
||||
}
|
||||
|
||||
return {
|
||||
providers,
|
||||
};
|
||||
};
|
||||
|
||||
const InitialEditor = ({
|
||||
setEditor,
|
||||
}: {
|
||||
setEditor: (editor: EditorContainer) => void;
|
||||
}) => {
|
||||
useEffect(() => {
|
||||
const store = new Store({
|
||||
...getEditorParams(),
|
||||
});
|
||||
const space = store.createSpace('page0').register(BlockSchema);
|
||||
const editor = createEditor(space);
|
||||
|
||||
setEditor(editor);
|
||||
|
||||
return () => {
|
||||
editor.remove();
|
||||
};
|
||||
}, [setEditor]);
|
||||
|
||||
useEffect(() => {
|
||||
const version = pkg.dependencies['@blocksuite/editor'].substring(1);
|
||||
console.log(`BlockSuite live demo ${version}`);
|
||||
}, []);
|
||||
|
||||
return <div />;
|
||||
};
|
||||
|
||||
export default InitialEditor;
|
||||
@@ -0,0 +1,30 @@
|
||||
import { styled } from '@/styles';
|
||||
import CommonLoading from '@/components/loading';
|
||||
|
||||
const StyledLoadingContainer = styled('div')(({ theme }) => {
|
||||
return {
|
||||
height: '100vh',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
color: '#6880FF',
|
||||
h1: {
|
||||
fontSize: '2em',
|
||||
marginTop: '150px',
|
||||
fontWeight: '600',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export const Loading = () => {
|
||||
return (
|
||||
<StyledLoadingContainer>
|
||||
<div className="wrapper">
|
||||
<CommonLoading />
|
||||
<h1>Loading...</h1>
|
||||
</div>
|
||||
</StyledLoadingContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default Loading;
|
||||
@@ -0,0 +1,13 @@
|
||||
import { EditorContainer } from '@blocksuite/editor';
|
||||
import exampleMarkdown from '@/providers/editor-provider/example-markdown';
|
||||
|
||||
export const initDefaultContent = (editor: EditorContainer) => {
|
||||
const { space } = editor;
|
||||
const pageId = space.addBlock({
|
||||
flavour: 'affine:page',
|
||||
title: 'Welcome to the AFFiNE Alpha',
|
||||
});
|
||||
const groupId = space.addBlock({ flavour: 'affine:group' }, pageId);
|
||||
editor.clipboard.importMarkdown(exampleMarkdown, `${groupId}`);
|
||||
space.resetHistory();
|
||||
};
|
||||
@@ -0,0 +1,53 @@
|
||||
import { createContext, useContext, useState } from 'react';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import ShortcutsModal from '@/components/shortcuts-modal';
|
||||
import ContactModal from '@/components/contact-modal';
|
||||
|
||||
type ModalContextValue = {
|
||||
shortcutsModalHandler: (visible: boolean) => void;
|
||||
contactModalHandler: (visible: boolean) => void;
|
||||
};
|
||||
type ModalContextProps = PropsWithChildren<{}>;
|
||||
|
||||
export const ModalContext = createContext<ModalContextValue>({
|
||||
shortcutsModalHandler: () => {},
|
||||
contactModalHandler: () => {},
|
||||
});
|
||||
|
||||
export const useModal = () => useContext(ModalContext);
|
||||
|
||||
export const ModalProvider = ({
|
||||
children,
|
||||
}: PropsWithChildren<ModalContextProps>) => {
|
||||
const [openContactModal, setOpenContactModal] = useState(false);
|
||||
const [openShortcutsModal, setOpenShortcutsModal] = useState(false);
|
||||
|
||||
return (
|
||||
<ModalContext.Provider
|
||||
value={{
|
||||
shortcutsModalHandler: visible => {
|
||||
setOpenShortcutsModal(visible);
|
||||
},
|
||||
contactModalHandler: visible => {
|
||||
setOpenContactModal(visible);
|
||||
},
|
||||
}}
|
||||
>
|
||||
<ContactModal
|
||||
open={openContactModal}
|
||||
onClose={() => {
|
||||
setOpenContactModal(false);
|
||||
}}
|
||||
></ContactModal>
|
||||
<ShortcutsModal
|
||||
open={openShortcutsModal}
|
||||
onClose={() => {
|
||||
setOpenShortcutsModal(false);
|
||||
}}
|
||||
></ShortcutsModal>
|
||||
{children}
|
||||
</ModalContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export default ModalProvider;
|
||||
@@ -0,0 +1,103 @@
|
||||
import {
|
||||
ThemeProvider as EmotionThemeProvider,
|
||||
Global,
|
||||
css,
|
||||
} from '@emotion/react';
|
||||
import { createContext, useContext, useEffect, useState } from 'react';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import {
|
||||
Theme,
|
||||
ThemeMode,
|
||||
ThemeProviderProps,
|
||||
ThemeProviderValue,
|
||||
} from '@/styles/types';
|
||||
import {
|
||||
getLightTheme,
|
||||
getDarkTheme,
|
||||
globalThemeVariables,
|
||||
} from '@/styles/theme';
|
||||
import { SystemThemeHelper, localStorageThemeHelper } from '@/styles/utils';
|
||||
import { useEditor } from '@/providers/editor-provider';
|
||||
|
||||
export const ThemeContext = createContext<ThemeProviderValue>({
|
||||
mode: 'light',
|
||||
changeMode: () => {},
|
||||
theme: getLightTheme('page'),
|
||||
});
|
||||
|
||||
export const useTheme = () => useContext(ThemeContext);
|
||||
|
||||
export const ThemeProvider = ({
|
||||
defaultTheme = 'light',
|
||||
children,
|
||||
}: PropsWithChildren<ThemeProviderProps>) => {
|
||||
const [theme, setTheme] = useState<Theme>(defaultTheme);
|
||||
const [mode, setMode] = useState<ThemeMode>('auto');
|
||||
const { mode: editorMode } = useEditor();
|
||||
const themeStyle =
|
||||
theme === 'light' ? getLightTheme(editorMode) : getDarkTheme(editorMode);
|
||||
const changeMode = (themeMode: ThemeMode) => {
|
||||
themeMode !== mode && setMode(themeMode);
|
||||
// Remember the theme mode which user selected for next time
|
||||
localStorageThemeHelper.set(themeMode);
|
||||
};
|
||||
|
||||
// ===================== A temporary solution, just use system theme and not remember the user selected ====================
|
||||
useEffect(() => {
|
||||
const systemThemeHelper = new SystemThemeHelper();
|
||||
const systemTheme = systemThemeHelper.get();
|
||||
setMode(systemTheme);
|
||||
|
||||
systemThemeHelper.onChange(() => {
|
||||
setMode(systemThemeHelper.get());
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setTheme(mode === 'auto' ? theme : mode);
|
||||
}, [mode, setTheme, theme]);
|
||||
// ===================== ====================
|
||||
|
||||
// useEffect(() => {
|
||||
// setMode(localStorageThemeHelper.get() || 'auto');
|
||||
// }, []);
|
||||
//
|
||||
// useEffect(() => {
|
||||
// const systemThemeHelper = new SystemThemeHelper();
|
||||
// const selectedThemeMode = localStorageThemeHelper.get();
|
||||
//
|
||||
// const themeMode = selectedThemeMode || mode;
|
||||
// if (themeMode === 'auto') {
|
||||
// setTheme(systemThemeHelper.get());
|
||||
// } else {
|
||||
// setTheme(themeMode);
|
||||
// }
|
||||
//
|
||||
// // When system theme changed, change the theme mode
|
||||
// systemThemeHelper.onChange(() => {
|
||||
// // TODO: There may be should be provided a way to let user choose whether to
|
||||
// if (mode === 'auto') {
|
||||
// setTheme(systemThemeHelper.get());
|
||||
// }
|
||||
// });
|
||||
//
|
||||
// return () => {
|
||||
// systemThemeHelper.dispose();
|
||||
// };
|
||||
// }, [mode]);
|
||||
|
||||
return (
|
||||
<ThemeContext.Provider value={{ mode, changeMode, theme: themeStyle }}>
|
||||
<Global
|
||||
styles={css`
|
||||
:root {
|
||||
${globalThemeVariables(mode, themeStyle) as {}}
|
||||
}
|
||||
`}
|
||||
/>
|
||||
<EmotionThemeProvider theme={themeStyle}>{children}</EmotionThemeProvider>
|
||||
</ThemeContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export default ThemeProvider;
|
||||
Reference in New Issue
Block a user