├── src ├── ui │ ├── tooltip │ │ ├── index.tsx │ │ └── Tooltip.tsx │ ├── popper │ │ ├── index.ts │ │ ├── interface.ts │ │ ├── PopoverArrow.tsx │ │ └── Popper.tsx │ ├── menu │ │ ├── index.ts │ │ ├── menu.tsx │ │ └── styles.ts │ ├── modal │ │ ├── index.tsx │ │ ├── style.ts │ │ └── modal.tsx │ └── shared │ │ └── Container.tsx ├── styles │ ├── utils │ │ ├── index.ts │ │ ├── localStorageThemeHelper.ts │ │ └── systemThemeHelper.ts │ ├── styled.ts │ ├── hooks.ts │ ├── index.ts │ ├── helper.ts │ ├── themeProvider.tsx │ ├── types.ts │ └── theme.ts ├── types.ts ├── components │ ├── mobile-modal │ │ ├── bg.png │ │ ├── index.tsx │ │ └── styles.ts │ ├── contact-modal │ │ ├── bg.png │ │ ├── affine-text-logo.png │ │ ├── index.tsx │ │ ├── style.ts │ │ └── icons.tsx │ ├── loading │ │ ├── index.tsx │ │ └── styled.ts │ ├── editor-mode-switch │ │ ├── type.ts │ │ ├── icons.tsx │ │ ├── index.tsx │ │ └── style.ts │ ├── edgeless-toolbar │ │ ├── reply.svg │ │ ├── style.ts │ │ ├── index.tsx │ │ └── icons.tsx │ ├── Header │ │ ├── utils.tsx │ │ ├── styles.ts │ │ ├── index.tsx │ │ └── icons.tsx │ ├── editor-provider.tsx │ ├── theme-mode-switch │ │ ├── index.tsx │ │ ├── style.ts │ │ └── icons.tsx │ ├── simple-counter │ │ └── index.ts │ ├── global-modal-provider.tsx │ ├── shortcuts-modal │ │ ├── config.ts │ │ ├── style.ts │ │ ├── index.tsx │ │ └── icons.tsx │ ├── faq │ │ ├── style.ts │ │ ├── index.tsx │ │ └── icons.tsx │ ├── example-markdown.ts │ └── editor.tsx ├── pages │ ├── temporary.css │ ├── _app.tsx │ ├── affine.tsx │ ├── _document.tsx │ └── index.tsx └── utils │ ├── __tests__ │ └── get-is-mobile.spec.ts │ ├── print-build-info.ts │ └── get-is-mobile.ts ├── tauri ├── build.rs ├── icons │ ├── 32x32.png │ ├── icon.icns │ ├── icon.ico │ ├── icon.png │ ├── 128x128.png │ ├── 128x128@2x.png │ ├── StoreLogo.png │ ├── playstore.icns │ ├── Square30x30Logo.png │ ├── Square44x44Logo.png │ ├── Square71x71Logo.png │ ├── Square89x89Logo.png │ ├── Square107x107Logo.png │ ├── Square142x142Logo.png │ ├── Square150x150Logo.png │ ├── Square284x284Logo.png │ └── Square310x310Logo.png ├── .gitignore ├── src │ └── main.rs ├── Cargo.toml └── tauri.conf.json ├── public ├── favicon.ico ├── variable.css └── globals.css ├── screenshots └── pathfinder.png ├── .vscode └── extensions.json ├── jest.config.js ├── next-env.d.ts ├── .gitignore ├── .eslintrc.js ├── next.config.js ├── tsconfig.json ├── scripts └── gitInfo.js ├── README.md ├── package.json └── .github └── workflows └── pre-release.yml /src/ui/tooltip/index.tsx: -------------------------------------------------------------------------------- 1 | export * from './Tooltip'; 2 | -------------------------------------------------------------------------------- /tauri/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | tauri_build::build() 3 | } 4 | -------------------------------------------------------------------------------- /src/ui/popper/index.ts: -------------------------------------------------------------------------------- 1 | export * from './interface'; 2 | export * from './Popper'; 3 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m1911star/affine-client/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /tauri/icons/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m1911star/affine-client/HEAD/tauri/icons/32x32.png -------------------------------------------------------------------------------- /tauri/icons/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m1911star/affine-client/HEAD/tauri/icons/icon.icns -------------------------------------------------------------------------------- /tauri/icons/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m1911star/affine-client/HEAD/tauri/icons/icon.ico -------------------------------------------------------------------------------- /tauri/icons/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m1911star/affine-client/HEAD/tauri/icons/icon.png -------------------------------------------------------------------------------- /src/ui/menu/index.ts: -------------------------------------------------------------------------------- 1 | export * from './menu'; 2 | export { StyledMenuItem as MenuItem } from './styles'; 3 | -------------------------------------------------------------------------------- /tauri/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | -------------------------------------------------------------------------------- /tauri/icons/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m1911star/affine-client/HEAD/tauri/icons/128x128.png -------------------------------------------------------------------------------- /screenshots/pathfinder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m1911star/affine-client/HEAD/screenshots/pathfinder.png -------------------------------------------------------------------------------- /src/styles/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './systemThemeHelper'; 2 | export * from './localStorageThemeHelper'; 3 | -------------------------------------------------------------------------------- /tauri/icons/128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m1911star/affine-client/HEAD/tauri/icons/128x128@2x.png -------------------------------------------------------------------------------- /tauri/icons/StoreLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m1911star/affine-client/HEAD/tauri/icons/StoreLogo.png -------------------------------------------------------------------------------- /tauri/icons/playstore.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m1911star/affine-client/HEAD/tauri/icons/playstore.icns -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["tauri-apps.tauri-vscode", "rust-lang.rust-analyzer"] 3 | } 4 | -------------------------------------------------------------------------------- /src/ui/modal/index.tsx: -------------------------------------------------------------------------------- 1 | import Modal from './modal'; 2 | 3 | export * from './modal'; 4 | export default Modal; 5 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | declare global { 2 | interface Window { 3 | CLIENT_APP?: boolean; 4 | } 5 | } 6 | 7 | export {}; 8 | -------------------------------------------------------------------------------- /tauri/icons/Square30x30Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m1911star/affine-client/HEAD/tauri/icons/Square30x30Logo.png -------------------------------------------------------------------------------- /tauri/icons/Square44x44Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m1911star/affine-client/HEAD/tauri/icons/Square44x44Logo.png -------------------------------------------------------------------------------- /tauri/icons/Square71x71Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m1911star/affine-client/HEAD/tauri/icons/Square71x71Logo.png -------------------------------------------------------------------------------- /tauri/icons/Square89x89Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m1911star/affine-client/HEAD/tauri/icons/Square89x89Logo.png -------------------------------------------------------------------------------- /src/components/mobile-modal/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m1911star/affine-client/HEAD/src/components/mobile-modal/bg.png -------------------------------------------------------------------------------- /tauri/icons/Square107x107Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m1911star/affine-client/HEAD/tauri/icons/Square107x107Logo.png -------------------------------------------------------------------------------- /tauri/icons/Square142x142Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m1911star/affine-client/HEAD/tauri/icons/Square142x142Logo.png -------------------------------------------------------------------------------- /tauri/icons/Square150x150Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m1911star/affine-client/HEAD/tauri/icons/Square150x150Logo.png -------------------------------------------------------------------------------- /tauri/icons/Square284x284Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m1911star/affine-client/HEAD/tauri/icons/Square284x284Logo.png -------------------------------------------------------------------------------- /tauri/icons/Square310x310Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m1911star/affine-client/HEAD/tauri/icons/Square310x310Logo.png -------------------------------------------------------------------------------- /src/components/contact-modal/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m1911star/affine-client/HEAD/src/components/contact-modal/bg.png -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('ts-jest').JestConfigWithTsJest} */ 2 | module.exports = { 3 | preset: 'ts-jest', 4 | testEnvironment: 'node', 5 | }; 6 | -------------------------------------------------------------------------------- /src/components/contact-modal/affine-text-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m1911star/affine-client/HEAD/src/components/contact-modal/affine-text-logo.png -------------------------------------------------------------------------------- /src/styles/styled.ts: -------------------------------------------------------------------------------- 1 | import emotionStyled from '@emotion/styled'; 2 | export { css, keyframes } from '@emotion/react'; 3 | export const styled = emotionStyled; 4 | -------------------------------------------------------------------------------- /src/styles/hooks.ts: -------------------------------------------------------------------------------- 1 | import { useContext } from 'react'; 2 | import { ThemeContext } from './themeProvider'; 3 | 4 | export const useTheme = () => useContext(ThemeContext); 5 | -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /src/styles/index.ts: -------------------------------------------------------------------------------- 1 | export type { ThemeMode, ThemeProviderProps, AffineTheme } from './types'; 2 | 3 | export * from './styled'; 4 | export { ThemeProvider } from './themeProvider'; 5 | export * from './theme'; 6 | export { useTheme } from './hooks'; 7 | export * from './helper'; 8 | -------------------------------------------------------------------------------- /src/pages/temporary.css: -------------------------------------------------------------------------------- 1 | debug-menu { 2 | display: none !important; 3 | } 4 | .affine-block-children-container.edgeless { 5 | background-color: #fff; 6 | } 7 | 8 | .affine-default-page-block-title-container { 9 | margin-top: 78px; 10 | margin-bottom: 40px; 11 | } 12 | .affine-editor-container { 13 | background: transparent !important; 14 | } 15 | -------------------------------------------------------------------------------- /src/components/loading/index.tsx: -------------------------------------------------------------------------------- 1 | import { StyledLoading, StyledLoadingItem } from './styled'; 2 | 3 | export const Loading = () => { 4 | return ( 5 | 6 | 7 | 8 | 9 | 10 | 11 | ); 12 | }; 13 | 14 | export default Loading; 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | .next 15 | 16 | # Editor directories and files 17 | .vscode/* 18 | !.vscode/extensions.json 19 | .idea 20 | .DS_Store 21 | *.suo 22 | *.ntvs* 23 | *.njsproj 24 | *.sln 25 | *.sw? 26 | 27 | src-tauri/target 28 | out/ -------------------------------------------------------------------------------- /src/styles/utils/localStorageThemeHelper.ts: -------------------------------------------------------------------------------- 1 | import { ThemeMode } from '../types'; 2 | 3 | export class LocalStorageThemeHelper { 4 | name = 'Affine-theme-mode'; 5 | get = (): ThemeMode | null => { 6 | return localStorage.getItem(this.name) as ThemeMode | null; 7 | }; 8 | set = (mode: ThemeMode) => { 9 | localStorage.setItem(this.name, mode); 10 | }; 11 | } 12 | 13 | export const localStorageThemeHelper = new LocalStorageThemeHelper(); 14 | -------------------------------------------------------------------------------- /src/utils/__tests__/get-is-mobile.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, test, expect } from '@jest/globals'; 2 | import { isMobile } from '../get-is-mobile'; 3 | 4 | describe('get-is-mobile', () => { 5 | test('get-is-mobile', () => { 6 | expect( 7 | isMobile( 8 | 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1' 9 | ) 10 | ).toBe(true); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /src/components/editor-mode-switch/type.ts: -------------------------------------------------------------------------------- 1 | import { CSSProperties, DOMAttributes, ReactElement } from 'react'; 2 | 3 | export type ItemStatus = 'normal' | 'stretch' | 'shrink' | 'hidden'; 4 | 5 | export type RadioItemStatus = { 6 | left: ItemStatus; 7 | right: ItemStatus; 8 | }; 9 | export type AnimateRadioProps = { 10 | isHover: boolean; 11 | style: CSSProperties; 12 | }; 13 | export type AnimateRadioItemProps = { 14 | active: boolean; 15 | status: ItemStatus; 16 | label: string; 17 | icon: ReactElement; 18 | isLeft: boolean; 19 | } & DOMAttributes; 20 | -------------------------------------------------------------------------------- /tauri/src/main.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr( 2 | all(not(debug_assertions), target_os = "windows"), 3 | windows_subsystem = "windows" 4 | )] 5 | 6 | // Learn more about Tauri commands at https://tauri.app/v1/guides/features/command 7 | #[tauri::command] 8 | fn greet(name: &str) -> String { 9 | format!("Hello, {}! You've been greeted from Rust!", name) 10 | } 11 | 12 | fn main() { 13 | tauri::Builder::default() 14 | .invoke_handler(tauri::generate_handler![greet]) 15 | .run(tauri::generate_context!()) 16 | .expect("error while running tauri application"); 17 | } 18 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | // https://eslint.org/docs/latest/user-guide/configuring 2 | // "off" or 0 - turn the rule off 3 | // "warn" or 1 - turn the rule on as a warning (doesn’t affect exit code) 4 | // "error" or 2 - turn the rule on as an error (exit code will be 1) 5 | 6 | /** @type { import('eslint').Linter.Config } */ 7 | module.exports = { 8 | extends: [ 9 | 'next/core-web-vitals', 10 | 'plugin:@next/next/recommended', 11 | 'plugin:prettier/recommended', 12 | ], 13 | rules: { 14 | 'prettier/prettier': 'warn', 15 | }, 16 | 17 | reportUnusedDisableDirectives: true, 18 | }; 19 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | const { getGitVersion, getCommitHash } = require('./scripts/gitInfo'); 3 | 4 | /** @type {import('next').NextConfig} */ 5 | const nextConfig = { 6 | productionBrowserSourceMaps: true, 7 | reactStrictMode: true, 8 | swcMinify: false, 9 | publicRuntimeConfig: { 10 | NODE_ENV: process.env.NODE_ENV, 11 | PROJECT_NAME: process.env.npm_package_name, 12 | BUILD_DATE: new Date().toISOString(), 13 | CI: process.env.CI || null, 14 | VERSION: getGitVersion(), 15 | COMMIT_HASH: getCommitHash(), 16 | }, 17 | basePath: process.env.BASE_PATH, 18 | }; 19 | 20 | module.exports = nextConfig; 21 | -------------------------------------------------------------------------------- /src/ui/menu/menu.tsx: -------------------------------------------------------------------------------- 1 | import { Popper, type PopperProps } from '../popper'; 2 | import { TooltipProps } from '@mui/material'; 3 | import { StyledMenuWrapper } from '@/ui/menu/styles'; 4 | 5 | export const Menu = (props: PopperProps & Omit) => { 6 | const { content, placement = 'bottom-start', children } = props; 7 | return content ? ( 8 | {content} 13 | } 14 | > 15 | {children} 16 | 17 | ) : null; 18 | }; 19 | 20 | export default Menu; 21 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true, 17 | "experimentalDecorators": true, 18 | "baseUrl": ".", 19 | "paths": { 20 | "@/*": ["src/*"] 21 | } 22 | }, 23 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], 24 | "exclude": ["node_modules"] 25 | } 26 | -------------------------------------------------------------------------------- /src/ui/modal/style.ts: -------------------------------------------------------------------------------- 1 | import { displayFlex, fixedCenter, styled } from '@/styles'; 2 | import ModalUnstyled from '@mui/base/ModalUnstyled'; 3 | 4 | export const StyledBackdrop = styled.div<{ open?: boolean }>(({ open }) => { 5 | return { 6 | zIndex: '-1', 7 | position: 'fixed', 8 | right: '0', 9 | bottom: '0', 10 | top: '0', 11 | left: '0', 12 | backgroundColor: 'rgba(58, 76, 92, 0.2)', 13 | }; 14 | }); 15 | 16 | export const StyledModal = styled(ModalUnstyled)(({ theme }) => { 17 | return { 18 | width: '100vw', 19 | height: '100vh', 20 | position: 'fixed', 21 | left: '0', 22 | top: '0', 23 | zIndex: theme.zIndex.modal, 24 | ...displayFlex('center', 'center'), 25 | '*': { 26 | WebkitTapHighlightColor: 'transparent', 27 | outline: 'none', 28 | }, 29 | }; 30 | }); 31 | -------------------------------------------------------------------------------- /src/styles/utils/systemThemeHelper.ts: -------------------------------------------------------------------------------- 1 | import { Theme } from '../types'; 2 | 3 | export class SystemThemeHelper { 4 | media: MediaQueryList = window.matchMedia('(prefers-color-scheme: light)'); 5 | eventList: Array<(e: Event) => void> = []; 6 | eventHandler = (e: Event) => { 7 | this.eventList.forEach(fn => fn(e)); 8 | }; 9 | 10 | constructor() { 11 | this.media.addEventListener('change', this.eventHandler); 12 | } 13 | 14 | get = (): Theme => { 15 | if (typeof window === 'undefined') { 16 | return 'light'; 17 | } 18 | return this.media.matches ? 'light' : 'dark'; 19 | }; 20 | 21 | onChange = (callback: () => void) => { 22 | this.eventList.push(callback); 23 | }; 24 | 25 | dispose = () => { 26 | this.eventList = []; 27 | this.media.removeEventListener('change', this.eventHandler); 28 | }; 29 | } 30 | -------------------------------------------------------------------------------- /src/ui/modal/modal.tsx: -------------------------------------------------------------------------------- 1 | import Fade from '@mui/material/Fade'; 2 | import { StyledModal, StyledBackdrop } from './style'; 3 | import { ModalUnstyledOwnProps } from '@mui/base/ModalUnstyled'; 4 | 5 | const Backdrop = ({ 6 | open, 7 | ...other 8 | }: { 9 | open?: boolean; 10 | className: string; 11 | }) => { 12 | return ( 13 | 14 | 15 | 16 | ); 17 | }; 18 | 19 | export type ModalProps = ModalUnstyledOwnProps; 20 | 21 | export const Modal = (props: ModalProps) => { 22 | const { components, open, children, ...otherProps } = props; 23 | return ( 24 |
25 | 26 | {children} 27 | 28 |
29 | ); 30 | }; 31 | 32 | export default Modal; 33 | -------------------------------------------------------------------------------- /src/ui/tooltip/Tooltip.tsx: -------------------------------------------------------------------------------- 1 | import StyledPopperContainer from '../shared/Container'; 2 | import { Popper, type PopperProps } from '../popper'; 3 | import { styled } from '@/styles'; 4 | import type { TooltipProps } from '@mui/material'; 5 | 6 | const StyledTooltip = styled(StyledPopperContainer)(({ theme }) => { 7 | return { 8 | boxShadow: theme.shadow.tooltip, 9 | padding: '4px 12px', 10 | backgroundColor: theme.colors.tooltipBackground, 11 | color: '#fff', 12 | fontSize: theme.font.xs, 13 | }; 14 | }); 15 | 16 | export const Tooltip = (props: PopperProps & Omit) => { 17 | const { content, placement = 'top-start', children } = props; 18 | return ( 19 | {content}} 23 | > 24 | {children} 25 | 26 | ); 27 | }; 28 | -------------------------------------------------------------------------------- /src/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import type { AppProps } from 'next/app'; 2 | import dynamic from 'next/dynamic'; 3 | import '../../public/globals.css'; 4 | import '../../public/variable.css'; 5 | import './temporary.css'; 6 | import { EditorProvider } from '@/components/editor-provider'; 7 | import { ModalProvider } from '@/components/global-modal-provider'; 8 | 9 | import '@fontsource/space-mono'; 10 | import '@fontsource/poppins'; 11 | import '../utils/print-build-info'; 12 | 13 | const ThemeProvider = dynamic(() => import('@/styles/themeProvider'), { 14 | ssr: false, 15 | }); 16 | 17 | function MyApp({ Component, pageProps }: AppProps) { 18 | return ( 19 | <> 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | ); 29 | } 30 | 31 | export default MyApp; 32 | -------------------------------------------------------------------------------- /public/variable.css: -------------------------------------------------------------------------------- 1 | /*:root {*/ 2 | /* --affine-primary-color: #3a4c5c;*/ 3 | /* --affine-muted-color: #a6abb7;*/ 4 | /* --affine-highlight-color: #6880ff;*/ 5 | /* --affine-placeholder-color: #c7c7c7;*/ 6 | /* --affine-selected-color: rgba(104, 128, 255, 0.1);*/ 7 | 8 | /* --affine-font-family: Avenir Next, apple-system, BlinkMacSystemFont,*/ 9 | /* Helvetica Neue, Tahoma, PingFang SC, Microsoft Yahei, Arial,*/ 10 | /* Hiragino Sans GB, sans-serif, Apple Color Emoji, Segoe UI Emoji,*/ 11 | /* Segoe UI Symbol, Noto Color Emoji;*/ 12 | 13 | /* --affine-font-family2: Roboto Mono, apple-system, BlinkMacSystemFont,*/ 14 | /* Helvetica Neue, Tahoma, PingFang SC, Microsoft Yahei, Arial,*/ 15 | /* Hiragino Sans GB, sans-serif, Apple Color Emoji, Segoe UI Emoji,*/ 16 | /* Segoe UI Symbol, Noto Color Emoji;*/ 17 | /*}*/ 18 | 19 | /*:root {*/ 20 | /* --page-background-color: #fff;*/ 21 | /* --page-text-color: #3a4c5c;*/ 22 | /*}*/ 23 | -------------------------------------------------------------------------------- /tauri/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tauri-blocksuite" 3 | version = "0.0.0" 4 | description = "A Tauri App" 5 | authors = ["you"] 6 | license = "" 7 | repository = "" 8 | edition = "2021" 9 | rust-version = "1.57" 10 | 11 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 12 | 13 | [build-dependencies] 14 | tauri-build = { version = "1.1", features = [] } 15 | 16 | [dependencies] 17 | serde_json = "1.0" 18 | serde = { version = "1.0", features = ["derive"] } 19 | tauri = { version = "1.2.2 ", features = ["api-all", "system-tray"] } 20 | 21 | [features] 22 | # by default Tauri runs in production mode 23 | # when `tauri dev` runs it is executed with `cargo run --no-default-features` if `devPath` is an URL 24 | default = [ "custom-protocol" ] 25 | # this feature is used used for production builds where `devPath` points to the filesystem 26 | # DO NOT remove this 27 | custom-protocol = [ "tauri/custom-protocol" ] 28 | -------------------------------------------------------------------------------- /src/components/edgeless-toolbar/reply.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/ui/menu/styles.ts: -------------------------------------------------------------------------------- 1 | import { styled } from '@/styles'; 2 | import StyledPopperContainer from '../shared/Container'; 3 | 4 | export const StyledMenuWrapper = styled(StyledPopperContainer)(({ theme }) => { 5 | return { 6 | background: theme.colors.popoverBackground, 7 | padding: '8px 4px', 8 | fontSize: '14px', 9 | backgroundColor: theme.colors.popoverBackground, 10 | boxShadow: theme.shadow.popover, 11 | color: theme.colors.popoverColor, 12 | }; 13 | }); 14 | 15 | export const StyledMenuItem = styled('div')<{ popperVisible?: boolean }>( 16 | ({ theme, popperVisible }) => { 17 | return { 18 | borderRadius: '5px', 19 | padding: '0 14px', 20 | 21 | color: popperVisible 22 | ? theme.colors.primaryColor 23 | : theme.colors.popoverColor, 24 | backgroundColor: popperVisible 25 | ? theme.colors.hoverBackground 26 | : 'transparent', 27 | 28 | ':hover': { 29 | color: theme.colors.primaryColor, 30 | backgroundColor: theme.colors.hoverBackground, 31 | }, 32 | }; 33 | } 34 | ); 35 | -------------------------------------------------------------------------------- /scripts/gitInfo.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | // import { execSync } from 'child_process' 4 | const { execSync } = require('child_process'); 5 | 6 | const hasGit = () => { 7 | try { 8 | execSync('git --version'); 9 | } catch { 10 | return false; 11 | } 12 | return true; 13 | }; 14 | 15 | const getTopLevel = () => execSync('git rev-parse --show-toplevel'); 16 | const isRepository = () => { 17 | try { 18 | getTopLevel(); 19 | } catch { 20 | return false; 21 | } 22 | return true; 23 | }; 24 | 25 | const getGitVersion = () => { 26 | if (!hasGit() || !isRepository()) { 27 | console.error( 28 | "You haven't installed git or it does not exist in your PATH." 29 | ); 30 | return null; 31 | } 32 | const VERSION = execSync('git describe --always --dirty') 33 | .toString() 34 | // remove empty line 35 | .replace(/[\s\r\n]+$/, ''); 36 | 37 | return VERSION; 38 | }; 39 | 40 | const getCommitHash = (rev = 'HEAD') => 41 | execSync(`git rev-parse --short ${rev}`).toString(); 42 | 43 | module.exports = { 44 | getGitVersion, 45 | getCommitHash, 46 | }; 47 | -------------------------------------------------------------------------------- /src/pages/affine.tsx: -------------------------------------------------------------------------------- 1 | import { displayFlex, styled } from '@/styles'; 2 | import { ThemeModeSwitch } from '@/components/theme-mode-switch'; 3 | import { Loading } from '@/components/loading'; 4 | import Modal from '@/ui/modal'; 5 | import { useState } from 'react'; 6 | export const StyledHeader = styled('div')({ 7 | height: '60px', 8 | width: '100vw', 9 | ...displayFlex('space-between', 'center'), 10 | position: 'relative', 11 | padding: '0 22px', 12 | borderBottom: '1px solid #e5e5e5', 13 | }); 14 | 15 | const Affine = () => { 16 | const [show, setShow] = useState(false); 17 | return ( 18 | <> 19 | 20 | 21 | 28 | 29 | { 32 | setShow(false); 33 | }} 34 | > 35 |
hi
36 |
37 | 38 | 39 | ); 40 | }; 41 | 42 | export default Affine; 43 | -------------------------------------------------------------------------------- /src/components/Header/utils.tsx: -------------------------------------------------------------------------------- 1 | import getIsMobile from '@/utils/get-is-mobile'; 2 | // Inspire by https://stackoverflow.com/a/4900484/8415727 3 | const getChromeVersion = () => { 4 | const raw = navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./); 5 | return raw ? parseInt(raw[2], 10) : false; 6 | }; 7 | const getIsChrome = () => { 8 | return ( 9 | /Chrome/.test(navigator.userAgent) && /Google Inc/.test(navigator.vendor) 10 | ); 11 | }; 12 | const minimumChromeVersion = 102; 13 | 14 | export const shouldShowWarning = () => { 15 | return ( 16 | !window.CLIENT_APP && 17 | !getIsMobile() && 18 | (!getIsChrome() || getChromeVersion() < minimumChromeVersion) 19 | ); 20 | }; 21 | 22 | export const getWarningMessage = () => { 23 | if (!getIsChrome()) { 24 | return ( 25 | 26 | We recommend the Chrome browser for optimal experience. 27 | 28 | ); 29 | } 30 | if (getChromeVersion() < minimumChromeVersion) { 31 | return ( 32 | 33 | Please upgrade to the latest version of Chrome for the best experience. 34 | 35 | ); 36 | } 37 | return ''; 38 | }; 39 | -------------------------------------------------------------------------------- /src/components/edgeless-toolbar/style.ts: -------------------------------------------------------------------------------- 1 | import { styled, displayFlex } from '@/styles'; 2 | 3 | export const StyledEdgelessToolbar = styled.div(({ theme }) => ({ 4 | height: '320px', 5 | position: 'fixed', 6 | left: '12px', 7 | top: 0, 8 | bottom: 0, 9 | margin: 'auto', 10 | zIndex: theme.zIndex.modal, 11 | })); 12 | 13 | export const StyledToolbarWrapper = styled.div(({ theme }) => ({ 14 | width: '44px', 15 | borderRadius: '10px', 16 | boxShadow: theme.shadow.modal, 17 | padding: '4px', 18 | background: theme.colors.popoverBackground, 19 | transition: 'background .5s', 20 | marginBottom: '12px', 21 | })); 22 | 23 | export const StyledToolbarItem = styled.div<{ 24 | disable?: boolean; 25 | }>(({ theme, disable = false }) => ({ 26 | width: '36px', 27 | height: '36px', 28 | ...displayFlex('center', 'center'), 29 | color: disable ? theme.colors.disableColor : theme.colors.iconColor, 30 | cursor: disable ? 'not-allowed' : 'pointer', 31 | svg: { 32 | width: '36px', 33 | height: '36px', 34 | }, 35 | ':hover': disable 36 | ? {} 37 | : { 38 | color: theme.colors.primaryColor, 39 | background: theme.colors.hoverBackground, 40 | }, 41 | })); 42 | -------------------------------------------------------------------------------- /src/utils/print-build-info.ts: -------------------------------------------------------------------------------- 1 | import getConfig from 'next/config'; 2 | 3 | type Config = { 4 | BUILD_DATE: string; 5 | NODE_ENV: string; 6 | PROJECT_NAME: string; 7 | VERSION: string; 8 | CI?: string; 9 | COMMIT_HASH: string; 10 | }; 11 | 12 | const nextConfig = getConfig(); 13 | const publicRuntimeConfig: Config = nextConfig.publicRuntimeConfig; 14 | 15 | const printBuildInfo = () => { 16 | if (process.env.NODE_ENV === 'development') { 17 | return; 18 | } 19 | console.group('Build info'); 20 | console.log('Project:', publicRuntimeConfig.PROJECT_NAME); 21 | console.log( 22 | 'Build date:', 23 | publicRuntimeConfig.BUILD_DATE 24 | ? new Date(publicRuntimeConfig.BUILD_DATE).toLocaleString() 25 | : 'Unknown' 26 | ); 27 | console.log( 28 | 'Environment:', 29 | `${publicRuntimeConfig.NODE_ENV}${publicRuntimeConfig.CI ? '(ci)' : ''}` 30 | ); 31 | console.log('Version:', publicRuntimeConfig.VERSION); 32 | console.log( 33 | 'AFFiNE is an open source project, you can view its source code on GitHub!' 34 | ); 35 | console.log( 36 | `https://github.com/toeverything/AFFiNE/tree/${publicRuntimeConfig.COMMIT_HASH}` 37 | ); 38 | console.groupEnd(); 39 | }; 40 | 41 | printBuildInfo(); 42 | 43 | export {}; 44 | -------------------------------------------------------------------------------- /src/components/editor-provider.tsx: -------------------------------------------------------------------------------- 1 | import type { EditorContainer } from '@blocksuite/editor'; 2 | 3 | import { createContext, useContext, useEffect, useState } from 'react'; 4 | import type { PropsWithChildren } from 'react'; 5 | 6 | type EditorContextValue = { 7 | editor: EditorContainer | null; 8 | mode: EditorContainer['mode']; 9 | setEditor: (editor: EditorContainer) => void; 10 | setMode: (mode: EditorContainer['mode']) => void; 11 | }; 12 | 13 | type EditorContextProps = PropsWithChildren<{}>; 14 | 15 | export const EditorContext = createContext({ 16 | editor: null, 17 | mode: 'page', 18 | setEditor: () => {}, 19 | setMode: () => {}, 20 | }); 21 | 22 | export const useEditor = () => useContext(EditorContext); 23 | 24 | export const EditorProvider = ({ 25 | children, 26 | }: PropsWithChildren) => { 27 | const [editor, setEditor] = useState(null); 28 | const [mode, setMode] = useState('page'); 29 | 30 | useEffect(() => { 31 | const event = new CustomEvent('affine.switch-mode', { detail: mode }); 32 | window.dispatchEvent(event); 33 | }, [mode]); 34 | 35 | return ( 36 | 37 | {children} 38 | 39 | ); 40 | }; 41 | 42 | export default EditorProvider; 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Affine Client 2 | 3 | > Update: AFFiNE official client is availble to be downloaded, the latest release link is https://github.com/toeverything/AFFiNE/releases 4 | 5 | affine-client is a client for [AFFINE](https://github.com/toeverything/AFFiNE) based on [Tauri](https://tauri.app/) 6 | 7 | ## Supported Platforms 8 | 9 | - Windows 10 | - Linux 11 | - MacOS 12 | 13 | ## Download 14 | 15 | `https://github.com/m1911star/affine-client/releases` 16 | 17 | ## Build 18 | 19 | ### System Requirements 20 | 21 | - [Rust stable & Cargo](https://www.rust-lang.org/) 22 | - [pnpm](https://pnpm.io/) 23 | 24 | ### How to build 25 | 26 | 1. install system requirements 27 | 2. git clone this repo (including submodule): git clone --recurse-submodules git@github.com:m1911star/affine-client.git 28 | 3. cd affine-client 29 | 4. `sh scripts/build.sh` 30 | 31 | Navigate to `affine-client/tauri/target/release/bundle/` for target file 32 | 33 | > It may fail when bundling, just retry 34 | 35 | ### Screenshot 36 | 37 | ![home](./screenshots/pathfinder.png) 38 | 39 | ## Limitations 40 | 41 | This client is only a wrapper without any native api intergration for now. 42 | 43 | ## TODO 44 | 45 | - [x] add build pipeline 46 | - [x] speed up build 47 | 48 | ## Q&A 49 | 50 | - if you come across an error `dlopen(): error loading libfuse.so.2 AppImages require FUSE to run.`, please install `fuse2` first 51 | -------------------------------------------------------------------------------- /src/components/mobile-modal/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import Modal from '@/ui/modal'; 3 | import getIsMobile from '@/utils/get-is-mobile'; 4 | import { 5 | ModalWrapper, 6 | StyledButton, 7 | StyledCloseButton, 8 | StyledContent, 9 | StyledTitle, 10 | } from './styles'; 11 | import CloseIcon from '@mui/icons-material/Close'; 12 | export const MobileModal = () => { 13 | const [showModal, setShowModal] = useState(getIsMobile()); 14 | return ( 15 | { 18 | setShowModal(false); 19 | }} 20 | > 21 | 22 | { 24 | setShowModal(false); 25 | }} 26 | > 27 | 28 | 29 | 30 | Ooops! 31 | 32 |

Looks like you are browsing on a mobile device.

33 |

34 | We are still working on mobile support and recommend you use a 35 | desktop device. 36 |

37 |
38 | { 40 | setShowModal(false); 41 | }} 42 | > 43 | Got it 44 | 45 |
46 |
47 | ); 48 | }; 49 | 50 | export default MobileModal; 51 | -------------------------------------------------------------------------------- /src/pages/_document.tsx: -------------------------------------------------------------------------------- 1 | import createEmotionServer from '@emotion/server/create-instance'; 2 | import { cache } from '@emotion/css'; 3 | 4 | import Document, { 5 | Html, 6 | Head, 7 | Main, 8 | NextScript, 9 | DocumentContext, 10 | } from 'next/document'; 11 | import * as React from 'react'; 12 | 13 | export const renderStatic = async (html: string) => { 14 | if (html === undefined) { 15 | throw new Error('did you forget to return html from renderToString?'); 16 | } 17 | const { extractCritical } = createEmotionServer(cache); 18 | const { ids, css } = extractCritical(html); 19 | 20 | return { html, ids, css }; 21 | }; 22 | 23 | export default class AppDocument extends Document { 24 | static async getInitialProps(ctx: DocumentContext) { 25 | const page = await ctx.renderPage(); 26 | const { css, ids } = await renderStatic(page.html); 27 | const initialProps = await Document.getInitialProps(ctx); 28 | return { 29 | ...initialProps, 30 | styles: ( 31 | 32 | {initialProps.styles} 33 |