commit e3e37413930eb0a52f1b030f2d04d049152d003f Author: DarkSky Date: Fri Jul 22 15:49:21 2022 +0800 init: the first public commit for AFFiNE diff --git a/.all-contributorsrc b/.all-contributorsrc new file mode 100644 index 000000000..0340f41c9 --- /dev/null +++ b/.all-contributorsrc @@ -0,0 +1,125 @@ +{ + "projectName": "Ligo-Virgo", + "projectOwner": "toeverything", + "repoType": "github", + "repoHost": "https://github.com", + "files": [ + "README.md" + ], + "imageSize": 100, + "commit": false, + "commitConvention": "angular", + "contributorsPerLine": 7, + "contributors": [ + { + "login": "darkskygit", + "name": "DarkSky", + "avatar_url": "https://avatars.githubusercontent.com/u/25152247?v=4", + "profile": "https://darksky.eu.org/", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "tzhangchi", + "name": "Chi Zhang", + "avatar_url": "https://avatars.githubusercontent.com/u/5910926?v=4", + "profile": "https://zhangchi.blog.csdn.net/", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "alt1o", + "name": "alt1o", + "avatar_url": "https://avatars.githubusercontent.com/u/21084335?v=4", + "profile": "https://github.com/alt1o", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "DiamondThree", + "name": "Diamond", + "avatar_url": "https://avatars.githubusercontent.com/u/24630517?v=4", + "profile": "https://github.com/DiamondThree", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "lawvs", + "name": "Whitewater", + "avatar_url": "https://avatars.githubusercontent.com/u/18554747?v=4", + "profile": "https://lawvs.github.io/profile/", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "zuoxiaodong0815", + "name": "zuoxiaodong0815", + "avatar_url": "https://avatars.githubusercontent.com/u/53252747?v=4", + "profile": "https://github.com/zuoxiaodong0815", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "SaikaSakura", + "name": "SaikaSakura", + "avatar_url": "https://avatars.githubusercontent.com/u/11530942?v=4", + "profile": "https://github.com/SaikaSakura", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "QiShaoXuan", + "name": "Qi", + "avatar_url": "https://avatars.githubusercontent.com/u/22772830?v=4", + "profile": "https://github.com/QiShaoXuan", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "tuluffy", + "name": "tuluffy", + "avatar_url": "https://avatars.githubusercontent.com/u/26808339?v=4", + "profile": "https://tuluffy.github.io/angular.github.io/", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "Austaras", + "name": "Austaras", + "avatar_url": "https://avatars.githubusercontent.com/u/15013925?v=4", + "profile": "https://shockwave.me/", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "uptonking", + "name": "Jin Yao", + "avatar_url": "https://avatars.githubusercontent.com/u/11391549?v=4", + "profile": "https://github.com/uptonking?tab=repositories&type=source", + "contributions": [ + "code", + "doc" + ] + } + ] +} diff --git a/.cz-config.js b/.cz-config.js new file mode 100644 index 000000000..e282d831e --- /dev/null +++ b/.cz-config.js @@ -0,0 +1,66 @@ +module.exports = { + types: [ + { value: 'feat', name: 'feat 🍄: add new features' }, + { value: 'fix', name: 'fix 🐛: fix bug' }, + { value: 'docs', name: 'docs 📄: modify documentation, comments' }, + { + value: 'refactor', + name: 'refactor 🎸: code refactoring, pay attention to distinguish it from features and fixes', + }, + { value: 'perf', name: 'perf ⚡: improve performance' }, + { value: 'test', name: 'test 👀: add a test' }, + { + value: 'tool', + name: 'tool 🚗: Development tool changes (build, scaffolding tools, etc.)', + }, + { + value: 'style', + name: 'style ✂: Modifications to code formatting do not affect logic', + }, + { value: 'revert', name: 'revert 🌝: version rollback' }, + { + value: 'editor', + name: 'editor 🔧: editor configuration modification', + }, + { value: 'update', name: 'update ⬆: third-party library upgrade' }, + ], + scopes: [ + { name: 'selection' }, + { name: 'whiteboard' }, + { name: 'point' }, + { name: 'group' }, + { name: 'page' }, + { name: 'component' }, + { name: 'config' }, + { name: 'others' }, + ], + + // it needs to match the value for field type. Eg.: 'fix' + /* +scopeOverrides: { + fix: [ + {name: 'merge'}, + {name: 'style'} + ] + +}, +*/ + // override the messages, de faults are as follows + messages: { + type: 'Choose a type of your submission:', + scope: 'Choose a scope (optional):', + // used if allowCustomScopes is true + customScope: 'Denote the SCOPE of this change:', + subject: 'Brief description:\n', + body: 'Detailed description, use "|" newline (optional):\n', + breaking: 'Incompatibility specification (optional):\n', + footer: 'Associate closed issues, for example: #31, #34 (optional):\n', + confirmCommit: 'Are you sure to commit?', + }, + + allowCustomScopes: true, + allowBreakingChanges: ['Added', 'Repair'], + + // limit subject length + subjectLimit: 100, +}; diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..9b7352176 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,13 @@ +# Editor configuration, see http://editorconfig.org +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 4 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +max_line_length = off +trim_trailing_whitespace = false diff --git a/.env b/.env new file mode 100644 index 000000000..35f39c15b --- /dev/null +++ b/.env @@ -0,0 +1,4 @@ +# use for download icon from figma +FIGMA_TOKEN +NODE_ENV +AFFINE_FEATURE_FLAG_TOKEN \ No newline at end of file diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 000000000..9809316cc --- /dev/null +++ b/.eslintignore @@ -0,0 +1,4 @@ +**/webpack.config.js +**/jest.config.js +**/node_modules/** +.github/** \ No newline at end of file diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 000000000..a277c50dc --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,287 @@ +{ + "root": true, + "ignorePatterns": ["**/*"], + "plugins": ["@nrwl/nx", "react", "filename-rules", "import", "prettier"], + "parserOptions": { + "project": ["./tsconfig.base.json"] + }, + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": { + "prettier/prettier": "warn", + "@nrwl/nx/enforce-module-boundaries": [ + "error", + { + "enforceBuildableLibDependency": true, + "allow": [], + "depConstraints": [ + { + "sourceTag": "library:utils", + "onlyDependOnLibsWithTags": ["library:utils"] + }, + { + "sourceTag": "datasource:jwt", + "onlyDependOnLibsWithTags": [ + "library:utils", + "datasource:remote-kv", + "datasource:jwt-rpc" + ] + }, + { + "sourceTag": "datasource:db-services", + "onlyDependOnLibsWithTags": [ + "library:utils", + "datasource:jwt" + ] + }, + { + "sourceTag": "datasource:hooks", + "onlyDependOnLibsWithTags": [ + "library:utils", + "datasource:jwt", + "datasource:db-services" + ] + }, + { + "sourceTag": "datasource:http", + "onlyDependOnLibsWithTags": ["library:utils"] + }, + { + "sourceTag": "datasource:state", + "onlyDependOnLibsWithTags": [ + "library:utils", + "library:feature-flags", + "datasource:http", + "datasource:jwt" + ] + }, + { + "sourceTag": "components:common", + "onlyDependOnLibsWithTags": [ + "library:utils", + "components:icons", + "components:ui" + ] + }, + { + "sourceTag": "components:editor-core", + "onlyDependOnLibsWithTags": [ + "library:utils", + "library:feature-flags", + "datasource:db-services", + "datasource:state", + "datasource:commands", + "datasource:jwt", + "components:ui", + "components:common", + "components:icons" + ] + }, + { + "sourceTag": "components:editor-blocks", + "onlyDependOnLibsWithTags": [ + "library:utils", + "library:feature-flags", + "components:common", + "components:editor-core", + "framework:editor", + "datasource:db-services", + "components:ui", + "components:icons" + ] + }, + { + "sourceTag": "components:editor-plugins", + "onlyDependOnLibsWithTags": [ + "library:utils", + "components:common", + "components:editor-core", + "framework:editor", + "components:editor-blocks", + "datasource:db-services", + "components:ui", + "components:icons", + "library:feature-flags" + ] + }, + { + "sourceTag": "components:ui", + "onlyDependOnLibsWithTags": [ + "components:icons", + "library:utils" + ] + }, + { + "sourceTag": "framework:editor", + "onlyDependOnLibsWithTags": [ + "components:editor-core" + ] + }, + { + "sourceTag": "*", + "onlyDependOnLibsWithTags": ["*"] + } + ], + "allowCircularSelfDependency": false + } + ], + "filename-rules/match": [ + "warn", + { + ".tsx": "PascalCase", + ".ts": "kebab-case", + ".json": "kebab-case", + "": "kebab-case" + } + ], + "no-restricted-imports": [ + "error", + { + "patterns": [ + { + "group": ["lodash"], + "message": "Forbid direct import of lodash, use @toeverything/utils" + }, + { + "group": ["lodash-es"], + "message": "Forbid direct import of lodash-es, use @toeverything/utils" + }, + { + "group": ["@mui/material", "@mui/material/*"], + "message": "Forbid direct import of @mui/material, use @toeverything/components/ui" + } + ] + } + ], + "@typescript-eslint/naming-convention": [ + "warn", + { + "selector": [ + "property", + "parameterProperty", + "accessor", + "enumMember" + ], + "format": ["strictCamelCase"] + }, + { + "selector": ["property", "accessor"], + "modifiers": ["private"], + "format": ["strictCamelCase"], + "leadingUnderscore": "require" + }, + { + "selector": ["method"], + "modifiers": ["public"], + "format": ["strictCamelCase"] + }, + // Private methods + { + "selector": ["method"], + "modifiers": ["private"], + "format": ["strictCamelCase"], + "leadingUnderscore": "require" + }, + { + "selector": ["method"], + "modifiers": ["protected"], + "format": ["strictCamelCase"], + "leadingUnderscore": "require" + }, + // Top Level Methods + // const func: Function + // allow PascalCase for react components + { + "selector": ["variable"], + "modifiers": ["global"], + "types": ["function"], + "format": ["strictCamelCase", "StrictPascalCase"], + "leadingUnderscore": "allow" + }, + // function something() { } + { + "selector": ["function"], + "modifiers": ["global"], + "format": ["strictCamelCase"], + "leadingUnderscore": "require" + }, + // export const func: Function + { + "selector": ["variable"], + "modifiers": ["exported"], + "types": ["function"], + "format": ["strictCamelCase", "StrictPascalCase"], + "leadingUnderscore": "forbid" + }, + // export function something() { } + { + "selector": ["function"], + "modifiers": ["exported"], + "format": ["strictCamelCase", "StrictPascalCase"], + "leadingUnderscore": "forbid" + }, + // Top Level Variables + { + "selector": ["variable"], + "modifiers": ["global", "const"], + "types": ["boolean", "string", "number"], + "format": ["UPPER_CASE"] + }, + { + "selector": ["variable"], + "modifiers": ["global"], + "format": ["strictCamelCase"], + "leadingUnderscore": "require" + }, + { + "selector": ["variable"], + "modifiers": ["exported"], + "format": ["strictCamelCase"] + }, + // types, enums + { + "selector": "typeLike", + "format": ["PascalCase"] + }, + { + "selector": "variableLike", + "format": ["strictCamelCase"] + } + ], + "react/self-closing-comp": "warn", + "no-restricted-syntax": [ + "warn", + { + "selector": ":matches(PropertyDefinition)[accessibility!='private'][accessibility!='protected'][key.name!='constructor']", + "message": "Use private instead, please implement getter or setXxx for external read and write requirements" + } + ] + } + }, + { + "files": ["*.ts", "*.tsx"], + "extends": ["plugin:@nrwl/nx/typescript"], + "rules": { + "prefer-const": "warn", + "no-console": ["warn", { "allow": ["warn", "error"] }], + "@typescript-eslint/ban-ts-comment": "warn", + "@typescript-eslint/no-empty-interface": "warn", + "@typescript-eslint/no-empty-function": "warn", + // https://github.com/nrwl/nx/issues/10445 + "react/jsx-key": "error", + "import/no-default-export": "warn", + "import/no-duplicates": "warn", + "max-lines": [ + "warn", + { "max": 300, "skipComments": true, "skipBlankLines": true } + ] + } + }, + { + "files": ["*.js", "*.jsx"], + "extends": ["plugin:@nrwl/nx/javascript"], + "rules": {} + } + ] +} diff --git a/.free.env b/.free.env new file mode 100644 index 000000000..24b1b20ec --- /dev/null +++ b/.free.env @@ -0,0 +1 @@ +NX_FREE_LOGIN=true diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 000000000..22f66396d --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,47 @@ +# About code owners + +# You can use a CODEOWNERS file to define individuals or teams that are responsible for code in a repository. + +# See https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners + + +# ================= +# configs +# ================= + +.* @darkskygit +babel.config.json @darkskygit +nx.json @darkskygit +tsconfig.base.json @darkskygit +**/project.json @darkskygit +**/tsconfig.json @darkskygit +**/tsconfig.*.json @darkskygit +**/.babelrc @darkskygit +**/babel.config.js @darkskygit +**/.* @darkskygit + +# ================= +# components +# ================= + +# editor-core +libs/components/editor-core @lawvs + +# editor-blocks + +# group block +libs/components/editor-blocks/src/blocks/group @lawvs +# todo block +libs/components/editor-blocks/src/blocks/todo @lawvs + +libs/framework/virgo @SaikaSakura + +# ================= +# datasource +# ================= + +# feature flags +libs/datasource/feature-flags @lawvs + +# jwt +libs/datasource/jwt @darkskygit diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 000000000..8266d460d --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,62 @@ +name: Lint + +on: + push: + branches: [master] + pull_request: + branches: [master] + +# Cancels all previous workflow runs for pull requests that have not completed. +# See https://docs.github.com/en/actions/using-jobs/using-concurrency +concurrency: + # The concurrency group contains the workflow name and the branch name for + # pull requests or the commit hash for any other events. + group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }} + cancel-in-progress: true + +jobs: + main: + strategy: + matrix: + node-version: [16] + os: [ubuntu-latest] + runs-on: ${{ matrix.os }} + # TODO Remove the next line after cleaning all errors + continue-on-error: true + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Use pnpm + uses: pnpm/action-setup@v2 + with: + version: 7 + + - name: Use Node.js ${{ matrix.node-version }} + # https://github.com/actions/setup-node + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + cache: 'pnpm' + + - name: Install node modules + run: pnpm install + + - name: Lint + if: always() + run: pnpm run lint:with-cache + + # - name: Check + # if: always() + # run: pnpm run check + + # - name: Format Check + # if: always() + # run: pnpm run format:ci + + # - name: Build + # run: pnpm run build + + # - name: Test + # run: pnpm run test diff --git a/.github/workflows/lisa.yml b/.github/workflows/lisa.yml new file mode 100644 index 000000000..878fad627 --- /dev/null +++ b/.github/workflows/lisa.yml @@ -0,0 +1,59 @@ +name: Build Lisa + +on: + push: + branches: [master] + pull_request: + branches: [master] + +# Cancels all previous workflow runs for pull requests that have not completed. +# See https://docs.github.com/en/actions/using-jobs/using-concurrency +concurrency: + # The concurrency group contains the workflow name and the branch name for + # pull requests or the commit hash for any other events. + group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }} + cancel-in-progress: true + +env: + REGISTRY: ghcr.io + NAMESPACE: toeverything + LISA_IMAGE_NAME: lisa + IMAGE_TAG: canary-${{ github.sha }} + IMAGE_TAG_LATEST: nightly-latest + +jobs: + ligo-virgo: + runs-on: self-hosted + environment: development + permissions: + contents: read + packages: write + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Log in to the Container registry + uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker (lisa) + id: meta_lisa + uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38 + with: + images: ${{ env.REGISTRY }}/${{ env.NAMESPACE }}/${{ env.LISA_IMAGE_NAME }} + tags: | + ${{ env.IMAGE_TAG }} + ${{ env.IMAGE_TAG_LATEST }} + + - name: Build and push Docker image (lisa) + uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc + with: + context: . + push: ${{ github.ref == 'refs/heads/master' && true || false }} + tags: ${{ steps.meta_lisa.outputs.tags }} + labels: ${{ steps.meta_lisa.outputs.labels }} + target: lisa diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..7b5e08b2c --- /dev/null +++ b/.gitignore @@ -0,0 +1,45 @@ +# See http://help.github.com/ignore-files/ for more about ignoring files. + +# compiled output +*dist +/tmp +/out-tsc + +# dependencies +node_modules + +# IDEs and editors +/.idea +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# IDE - VSCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +# misc +/.sass-cache +/connect.lock +/coverage +/libpeerconnection.log +npm-debug.log +yarn-error.log +testem.log +.pnpm-debug.log +/typings + +# System Files +.DS_Store +Thumbs.db + +# env +*.env.local +*.local.env +.history diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 000000000..deb953a84 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,8 @@ +#!/usr/bin/env sh +. "$(dirname -- "$0")/_/husky.sh" + +# npx lint-staged + +pnpm run lint:with-cache +pnpm run check +pnpm run format:ci diff --git a/.husky/pre-push b/.husky/pre-push new file mode 100755 index 000000000..741e9183e --- /dev/null +++ b/.husky/pre-push @@ -0,0 +1,14 @@ +#!/usr/bin/env sh +. "$(dirname -- "$0")/_/husky.sh" + +# Show just the current branch in Git +# See https://stackoverflow.com/questions/1417957/show-just-the-current-branch-in-git/1418022#1418022 +current_branch=$(git rev-parse --abbrev-ref HEAD) + +default_branch="master" + +if test $current_branch != $default_branch; then + exit 0 +fi + +npm run type:check diff --git a/.npmrc b/.npmrc new file mode 100644 index 000000000..c74aa6aa6 --- /dev/null +++ b/.npmrc @@ -0,0 +1,3 @@ +registry=https://registry.npmjs.org +engine-strict=true +auto-install-peers=true \ No newline at end of file diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 000000000..07db99c70 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,15 @@ +# Add files here to ignore them from prettier formatting + +/dist +/coverage +pnpm-lock.yaml + +# Editoe assets +apps/editoe/src/app/assets/svg + +# Automatically generated from Figma +libs/components/icons/src/auto-icons +libs/components/common/src/lib/icon + +# DevOps +.github/** diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 000000000..babd1d0f7 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,6 @@ +{ + "singleQuote": true, + "trailingComma": "es5", + "tabWidth": 4, + "arrowParens": "avoid" +} diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 000000000..1a9637994 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,11 @@ +{ + "recommendations": [ + "mikestead.dotenv", + "esbenp.prettier-vscode", + "visualstudioexptteam.vscodeintellicode", + "nrwl.angular-console", + "firsttris.vscode-jest-runner", + "dbaeumer.vscode-eslint", + "streetsidesoftware.code-spell-checker" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..2c3dd39eb --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,41 @@ +{ + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true, + "editor.formatOnSaveMode": "file", + "prettier.prettierPath": "./node_modules/prettier", + "cSpell.words": [ + "Backlinks", + "blockdb", + "booktitle", + "Cascader", + "clsx", + "cssmodule", + "datasource", + "fflate", + "groq", + "howpublished", + "immer", + "inbook", + "incollection", + "inproceedings", + "Kanban", + "keyval", + "ligo", + "lozad", + "mastersthesis", + "nrwl", + "phdthesis", + "pnpm", + "reindex", + "ROOTNODE", + "techreport", + "tldr", + "tldraw", + "tldtaw", + "toeverything", + "Unstyled", + "unversioned", + "uuidv", + "webm" + ] +} diff --git a/Caddyfile b/Caddyfile new file mode 100644 index 000000000..172d92f0a --- /dev/null +++ b/Caddyfile @@ -0,0 +1,39 @@ +:80 { + reverse_proxy /api/* keck:3001 + @websockets { + path /collaboration/* + } + reverse_proxy @websockets keck:3000 + reverse_proxy /* lisa:3001 { + header_up Host lisa:3001 + } +} + +http://lisa:3001 { + root /* ./dist + + file_server { + precompressed br + } + + encode { + zstd + gzip 9 + } + + @notStatic { + not path /*.css + not path /*.js + not path /*.png + not path /*.jpg + not path /*.svg + not path /*.ttf + not path /*.eot + not path /*.woff + not path /*.woff2 + } + + handle @notStatic { + try_files {path} /index.html + } +} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..f6e2249fa --- /dev/null +++ b/Dockerfile @@ -0,0 +1,21 @@ +FROM node:16-alpine as builder +WORKDIR /app +COPY . . +RUN apk add g++ make python3 git +RUN npm i -g pnpm@7 && pnpm i --frozen-lockfile --store=node_modules/.pnpm-store && pnpm run build + +FROM node:16-alpine as relocate +WORKDIR /app +COPY --from=builder /app/dist/apps/ligo-virgo ./dist +COPY --from=builder /app/Caddyfile ./ +RUN rm ./dist/*.txt + +# ============= +# lisa image +# ============= +FROM caddy:2.4.6-alpine as lisa +WORKDIR /app +COPY --from=relocate /app . + +EXPOSE 3000 +CMD ["caddy", "run"] \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..296f81d87 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) Toeverything Technology (Hangzhou) Co., Ltd. and its affiliates. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 000000000..147ed616e --- /dev/null +++ b/README.md @@ -0,0 +1,109 @@ +# AFFiNE + + + +[![All Contributors](https://img.shields.io/badge/all_contributors-11-orange.svg?style=flat-square)](#contributors-) + + + +Workspace for AFFiNE + +## Installation + +```sh +# Clone the repo +git clone git@github.com:toeverything/AFFiNE.git +``` + +Once cloned, switch to the master branch and navigate to the folder by typing `cd AFFiNE` and then running the following commands: + +```sh +# Install all project dependencies +npm i -g pnpm +pnpm i + +# Start the project +pnpm start +open http://localhost:4200/ +``` + +This project uses pnpm for package management and is built based on nx. It is recommended to install the [nx console](https://marketplace.visualstudio.com/items?itemName=nrwl.angular-console) plugin to create dependencies + +**If it is development, you can add environment variables in the project directory .env.local file** + +``` +NODE_ENV=development +``` + +## Scripts + +1. Create react dependency library: `pnpm run add:library` +2. Create react components: `pnpm run add:components` +3. Create a data source: `pnpm run add:datasource` +4. Unit testing: `pnpm test` +5. Compile specific components + - `pnpm build/test/lint `project name + - Project name reference workspace.json +6. Create react/node program: use nx console +7. If you need to use the git cz function, please install it globally first commitizen `npm install -g commitizen conventional-changelog conventional-changelog-cli` + +## Examples + +Have a look at [the examples to see AFFiNE in action](https://app.affine.pro/). + +## Contributing + +- Generic functional components (such as ui components) are placed in `libs/components/common` + - components within common are not allowed to reference _components_ except utils and dependencies + - Common components can reference each other +- Business components are placed in `libs/components` +- The data source component is placed in `libs/datasource` - api request code, schema, etc. belong to the data source + Please see [CONTRIBUTING](/docs/CONTRIBUTING.md) + +## Documentation + +- [how-to-write-css-in-affine.md](/docs/how-to-write-css-in-affine.md) +- [how-to-add-ui-component-in-affine.md](/docs/how-to-add-ui-component-in-affine.md) +- [how-to-customize-rollup-config.md](docs/how-to-customize-rollup-config.md) +- [how-to-auto-download-figma-assets-in-affine.md](docs/how-to-auto-download-figma-assets-in-affine.md) +- [affine-icons-user-guide.md](docs/affine-icons-user-guide.md) + +## Community + +For help, discussion about best practices, or any other conversation that would benefit from being searchable: + +[Discuss AFFiNE on GitHub](https://github.com/toeverything/AFFiNE/discussions) + +## Contributors + + + + + + + + + + + + + + + + + + + + +

DarkSky

💻 📖

Chi Zhang

💻 📖

alt1o

💻 📖

Diamond

💻 📖

Whitewater

💻 📖

zuoxiaodong0815

💻 📖

SaikaSakura

💻 📖

Qi

💻 📖

tuluffy

💻 📖

Austaras

💻 📖

Jin Yao

💻 📖
+ + + + + + +## License + +AFFiNE is distributed under the terms of MIT license. + +See LICENSE for details. diff --git a/apps/.gitkeep b/apps/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/apps/ligo-virgo/.babelrc b/apps/ligo-virgo/.babelrc new file mode 100644 index 000000000..f0aa1cb96 --- /dev/null +++ b/apps/ligo-virgo/.babelrc @@ -0,0 +1,11 @@ +{ + "presets": [ + [ + "@nrwl/react/babel", + { + "runtime": "automatic" + } + ] + ], + "plugins": [] +} diff --git a/apps/ligo-virgo/.browserslistrc b/apps/ligo-virgo/.browserslistrc new file mode 100644 index 000000000..f1d12df4f --- /dev/null +++ b/apps/ligo-virgo/.browserslistrc @@ -0,0 +1,16 @@ +# This file is used by: +# 1. autoprefixer to adjust CSS to support the below specified browsers +# 2. babel preset-env to adjust included polyfills +# +# For additional information regarding the format and rule options, please see: +# https://github.com/browserslist/browserslist#queries +# +# If you need to support different browsers in production, you may tweak the list below. + +last 1 Chrome version +last 1 Firefox version +last 2 Edge major versions +last 2 Safari major version +last 2 iOS major versions +Firefox ESR +not IE 9-11 # For IE 9-11 support, remove 'not'. \ No newline at end of file diff --git a/apps/ligo-virgo/.eslintrc.json b/apps/ligo-virgo/.eslintrc.json new file mode 100644 index 000000000..1f5ec76fb --- /dev/null +++ b/apps/ligo-virgo/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["plugin:@nrwl/nx/react", "../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/apps/ligo-virgo/jest.config.js b/apps/ligo-virgo/jest.config.js new file mode 100644 index 000000000..5b49bd23d --- /dev/null +++ b/apps/ligo-virgo/jest.config.js @@ -0,0 +1,12 @@ +module.exports = { + displayName: 'ligo-virgo', + preset: '../../jest.preset.js', + transform: { + 'node_modules\\/.+\\.js$': 'jest-esm-transformer', + '^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': '@nrwl/react/plugins/jest', + '^.+\\.[tj]sx?$': 'babel-jest', + }, + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], + coverageDirectory: '../../coverage/apps/ligo-virgo', + transformIgnorePatterns: [], +}; diff --git a/apps/ligo-virgo/package.json b/apps/ligo-virgo/package.json new file mode 100644 index 000000000..54c68924c --- /dev/null +++ b/apps/ligo-virgo/package.json @@ -0,0 +1,20 @@ +{ + "name": "ligo-virgo", + "version": "1.0.0", + "license": "MIT", + "description": "", + "main": "jest.config.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "AFFiNE ", + "dependencies": { + "@mui/icons-material": "^5.8.4" + }, + "devDependencies": { + "firebase": "^9.8.4", + "mini-css-extract-plugin": "^2.6.1", + "webpack": "^5.73.0" + } +} diff --git a/apps/ligo-virgo/project.json b/apps/ligo-virgo/project.json new file mode 100644 index 000000000..ad3444c4a --- /dev/null +++ b/apps/ligo-virgo/project.json @@ -0,0 +1,75 @@ +{ + "sourceRoot": "apps/ligo-virgo/src", + "projectType": "application", + "targets": { + "build": { + "executor": "@nrwl/web:webpack", + "outputs": ["{options.outputPath}"], + "defaultConfiguration": "production", + "options": { + "compiler": "babel", + "outputPath": "dist/apps/ligo-virgo", + "index": "apps/ligo-virgo/src/index.html", + "baseHref": "/", + "main": "apps/ligo-virgo/src/index.tsx", + "polyfills": "apps/ligo-virgo/src/polyfills.ts", + "tsConfig": "apps/ligo-virgo/tsconfig.app.json", + "assets": ["apps/ligo-virgo/src/assets"], + "styles": [], + "scripts": [], + "webpackConfig": "apps/ligo-virgo/webpack.config.js" + }, + "configurations": { + "production": { + "fileReplacements": [ + { + "replace": "apps/ligo-virgo/src/environments/environment.ts", + "with": "apps/ligo-virgo/src/environments/environment.prod.ts" + } + ], + "optimization": true, + "outputHashing": "all", + "sourceMap": false, + "namedChunks": true, + "extractLicenses": false, + "vendorChunk": false, + "generateIndexHtml": false + } + } + }, + "serve": { + "executor": "@nrwl/web:dev-server", + "options": { + "buildTarget": "ligo-virgo:build:development", + "hmr": true, + "proxyConfig": "apps/ligo-virgo/proxy.conf.json", + "open": true + }, + "configurations": { + "production": { + "buildTarget": "ligo-virgo:build:production", + "hmr": false + } + } + }, + "lint": { + "executor": "@nrwl/linter:eslint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": ["apps/ligo-virgo/**/*.{ts,tsx,js,jsx}"] + } + }, + "test": { + "executor": "@nrwl/jest:jest", + "outputs": ["coverage/apps/ligo-virgo"], + "options": { + "jestConfig": "apps/ligo-virgo/jest.config.js", + "passWithNoTests": true + } + }, + "check": { + "executor": "./tools/executors/tsCheck:tsCheck" + } + }, + "tags": ["app:ligo-virgo"] +} diff --git a/apps/ligo-virgo/proxy.conf.json b/apps/ligo-virgo/proxy.conf.json new file mode 100644 index 000000000..8dc733b87 --- /dev/null +++ b/apps/ligo-virgo/proxy.conf.json @@ -0,0 +1,13 @@ +{ + "/api": { + "target": "https://nightly.affine.pro/", + "secure": false, + "changeOrigin": true + }, + "/collaboration": { + "target": "https://canary.affine.pro", + "ws": true, + "changeOrigin": true, + "secure": false + } +} diff --git a/apps/ligo-virgo/src/assets/.gitkeep b/apps/ligo-virgo/src/assets/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/apps/ligo-virgo/src/assets/images/favicon.ico b/apps/ligo-virgo/src/assets/images/favicon.ico new file mode 100644 index 000000000..b972ff61c Binary files /dev/null and b/apps/ligo-virgo/src/assets/images/favicon.ico differ diff --git a/apps/ligo-virgo/src/custom-formatter.ts b/apps/ligo-virgo/src/custom-formatter.ts new file mode 100644 index 000000000..18bb39a22 --- /dev/null +++ b/apps/ligo-virgo/src/custom-formatter.ts @@ -0,0 +1,60 @@ +import { AsyncBlock } from '@toeverything/framework/virgo'; +import { isDev } from '@toeverything/utils'; + +/** + * Ported from https://github.com/vuejs/core/blob/main/packages/runtime-core/src/customFormatter.ts + * See [Custom Object Formatters in Chrome DevTools](https://docs.google.com/document/d/1FTascZXT9cxfetuPRT2eXPQKXui4nWFivUnS_335T3U) + */ + +const isAsyncBlock = (x: unknown): x is AsyncBlock => { + return x instanceof AsyncBlock; +}; + +export function initCustomFormatter() { + if (!isDev || typeof window === 'undefined') { + return; + } + + const bannerStyle = { + style: 'color: #eee; background: #3F6FDB; margin-right: 5px; padding: 2px; border-radius: 4px', + }; + const typeStyle = { + style: 'color: #eee; background: #DB6D56; margin-right: 5px; padding: 2px; border-radius: 4px', + }; + + // custom formatter for Chrome + // https://www.mattzeunert.com/2016/02/19/custom-chrome-devtools-object-formatters.html + const formatter = { + header(obj: unknown, config = { expand: false }) { + if (!isAsyncBlock(obj) || config.expand) { + return null; + } + + return [ + 'div', + {}, + ['span', bannerStyle, 'AsyncBlock'], + ['span', typeStyle, obj.type], + // @ts-expect-error Debug at development environment + `${JSON.stringify(obj.raw_data.properties)}`, + ]; + }, + hasBody(obj: unknown) { + return true; + }, + body(obj: unknown) { + return ['object', { object: obj, config: { expand: true } }]; + }, + }; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + if ((window as any).devtoolsFormatters) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (window as any).devtoolsFormatters.push(formatter); + } else { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (window as any).devtoolsFormatters = [formatter]; + } +} + +initCustomFormatter(); diff --git a/apps/ligo-virgo/src/environments/environment.prod.ts b/apps/ligo-virgo/src/environments/environment.prod.ts new file mode 100644 index 000000000..da7c84f63 --- /dev/null +++ b/apps/ligo-virgo/src/environments/environment.prod.ts @@ -0,0 +1,3 @@ +export const environment = { + production: true, +}; diff --git a/apps/ligo-virgo/src/environments/environment.ts b/apps/ligo-virgo/src/environments/environment.ts new file mode 100644 index 000000000..855830c23 --- /dev/null +++ b/apps/ligo-virgo/src/environments/environment.ts @@ -0,0 +1,6 @@ +// This file can be replaced during build by using the `fileReplacements` array. +// When building for production, this file is replaced with `environment.prod.ts`. + +export const environment = { + production: false, +}; diff --git a/apps/ligo-virgo/src/index.html b/apps/ligo-virgo/src/index.html new file mode 100644 index 000000000..1291a1091 --- /dev/null +++ b/apps/ligo-virgo/src/index.html @@ -0,0 +1,16 @@ + + + + + + + + Affine | Local Dev Environment + + + +
+ + diff --git a/apps/ligo-virgo/src/index.tsx b/apps/ligo-virgo/src/index.tsx new file mode 100644 index 000000000..9380c370b --- /dev/null +++ b/apps/ligo-virgo/src/index.tsx @@ -0,0 +1,28 @@ +/* eslint-disable filename-rules/match */ +import { StrictMode } from 'react'; +import { createRoot } from 'react-dom/client'; +import { BrowserRouter } from 'react-router-dom'; + +import { ThemeProvider } from '@toeverything/components/ui'; +import { FeatureFlagsProvider } from '@toeverything/datasource/feature-flags'; + +import './custom-formatter'; +import { LigoVirgoRoutes } from './pages'; +import './styles.css'; + +const container = document.getElementById('root'); +if (!container) { + throw new Error('No root container found'); +} +const root = createRoot(container); +root.render( + + + + + + + + + +); diff --git a/apps/ligo-virgo/src/pages/AppContainer.tsx b/apps/ligo-virgo/src/pages/AppContainer.tsx new file mode 100644 index 000000000..b554cd0fa --- /dev/null +++ b/apps/ligo-virgo/src/pages/AppContainer.tsx @@ -0,0 +1,36 @@ +import { Outlet } from 'react-router-dom'; + +import { styled } from '@toeverything/components/ui'; +import { SettingsSidebar, LayoutHeader } from '@toeverything/components/layout'; + +export function LigoVirgoRootContainer() { + return ( + + + + + + + + + + ); +} + +const StyledMainContainer = styled('div')({ + flex: 'auto', + display: 'flex', +}); + +const StyledRootContainer = styled('div')({ + display: 'flex', + flexDirection: 'row', + height: '100vh', +}); + +const StyledContentContainer = styled('div')({ + flex: 'auto', + display: 'flex', + flexDirection: 'column', + overflow: 'hidden', +}); diff --git a/apps/ligo-virgo/src/pages/AppRoutes.tsx b/apps/ligo-virgo/src/pages/AppRoutes.tsx new file mode 100644 index 000000000..8014a86a6 --- /dev/null +++ b/apps/ligo-virgo/src/pages/AppRoutes.tsx @@ -0,0 +1,64 @@ +import { Routes, Route, Navigate } from 'react-router-dom'; + +import Agenda from './agenda'; +import { WorkspaceContainer } from './workspace'; +import Recent from './recent'; +import Search from './search'; +import Settings from './settings'; +import Shared from './shared'; +import Starred from './starred'; +import { Login } from './account'; +import { PageNotFound } from './status/page-not-found'; +import { WorkspaceNotFound } from './status/workspace-not-found'; +import { RoutePrivate } from './RoutePrivate'; +import { RoutePublicAutoLogin } from './RoutePublicAutoLogin'; +import { Tools } from './tools'; +import { Templates } from './templates'; +import { LigoVirgoRootContainer } from './AppContainer'; +import { UIPage } from './ui'; + +export function LigoVirgoRoutes() { + return ( + + }> + } /> + } + /> + + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + + + } + /> + } /> + + + {/* put public routes here; header and sidebar are disabled here */} + + } /> + + + + } + /> + } /> + + + ); +} diff --git a/apps/ligo-virgo/src/pages/RoutePrivate.tsx b/apps/ligo-virgo/src/pages/RoutePrivate.tsx new file mode 100644 index 000000000..58dcbe3db --- /dev/null +++ b/apps/ligo-virgo/src/pages/RoutePrivate.tsx @@ -0,0 +1,37 @@ +import { Navigate, useLocation } from 'react-router-dom'; + +import { PageLoading, Error } from '@toeverything/components/account'; +import { useUserAndSpaces } from '@toeverything/datasource/state'; + +export type RoutePrivateProps = { + children: JSX.Element; + unauthorizedRedirectTo?: string; +}; + +/** + * A routing component that cannot be accessed without logging in, and can only be accessed after logging in. + */ +export function RoutePrivate({ + children, + unauthorizedRedirectTo = '/login', +}: RoutePrivateProps) { + const { pathname } = useLocation(); + + const { user, loading } = useUserAndSpaces(); + + if (user == null && loading) { + return ; + } + + if (!user) { + return ( + + ); + } + + return children; +} diff --git a/apps/ligo-virgo/src/pages/RoutePublicAutoLogin.tsx b/apps/ligo-virgo/src/pages/RoutePublicAutoLogin.tsx new file mode 100644 index 000000000..c200455ea --- /dev/null +++ b/apps/ligo-virgo/src/pages/RoutePublicAutoLogin.tsx @@ -0,0 +1,33 @@ +import { Navigate, useLocation } from 'react-router-dom'; + +import { PageLoading } from '@toeverything/components/account'; +import { useUserAndSpaces } from '@toeverything/datasource/state'; + +export type RouteUnauthorizedOnlyProps = { + children: JSX.Element; +}; + +/** + * Routing components that are accessible without logging in and inaccessible after logging in will automatically jump to the specified route authorizedRedirectTo + */ +export function RoutePublicAutoLogin({ children }: RouteUnauthorizedOnlyProps) { + const { pathname } = useLocation(); + + const { user, loading, currentSpaceId } = useUserAndSpaces(); + + if (user == null && loading) { + return ; + } + + if (currentSpaceId) { + return ( + + ); + } + + return children; +} diff --git a/apps/ligo-virgo/src/pages/account/index.ts b/apps/ligo-virgo/src/pages/account/index.ts new file mode 100644 index 000000000..42f6bc2be --- /dev/null +++ b/apps/ligo-virgo/src/pages/account/index.ts @@ -0,0 +1,3 @@ +import { Login } from '@toeverything/components/account'; + +export { Login }; diff --git a/apps/ligo-virgo/src/pages/agenda/calendar/index.tsx b/apps/ligo-virgo/src/pages/agenda/calendar/index.tsx new file mode 100644 index 000000000..e7b3d6f7a --- /dev/null +++ b/apps/ligo-virgo/src/pages/agenda/calendar/index.tsx @@ -0,0 +1,3 @@ +export default function AgendaCalendar() { + return AgendaCalendar; +} diff --git a/apps/ligo-virgo/src/pages/agenda/container/index.tsx b/apps/ligo-virgo/src/pages/agenda/container/index.tsx new file mode 100644 index 000000000..02d8378b6 --- /dev/null +++ b/apps/ligo-virgo/src/pages/agenda/container/index.tsx @@ -0,0 +1,18 @@ +import { Outlet } from 'react-router-dom'; +import style9 from 'style9'; + +import { MuiBox as Box } from '@toeverything/components/ui'; + +const styles = style9.create({ + container: { + display: 'flex', + }, +}); + +export default function AgendaRootContainer() { + return ( + + + + ); +} diff --git a/apps/ligo-virgo/src/pages/agenda/home/index.tsx b/apps/ligo-virgo/src/pages/agenda/home/index.tsx new file mode 100644 index 000000000..62b446f56 --- /dev/null +++ b/apps/ligo-virgo/src/pages/agenda/home/index.tsx @@ -0,0 +1,3 @@ +export default function AgendaHome() { + return AgendaHome; +} diff --git a/apps/ligo-virgo/src/pages/agenda/index.tsx b/apps/ligo-virgo/src/pages/agenda/index.tsx new file mode 100644 index 000000000..23f478ad5 --- /dev/null +++ b/apps/ligo-virgo/src/pages/agenda/index.tsx @@ -0,0 +1,20 @@ +import { Routes, Route } from 'react-router-dom'; + +import Container from './container'; +import Calendar from './calendar'; +import Tasks from './tasks'; +import Today from './today'; +import Home from './home'; + +export default function AgendaContainer() { + return ( + + }> + } /> + } /> + } /> + } /> + + + ); +} diff --git a/apps/ligo-virgo/src/pages/agenda/tasks/index.tsx b/apps/ligo-virgo/src/pages/agenda/tasks/index.tsx new file mode 100644 index 000000000..b51d40197 --- /dev/null +++ b/apps/ligo-virgo/src/pages/agenda/tasks/index.tsx @@ -0,0 +1,3 @@ +export default function AgendaTasks() { + return AgendaTasks; +} diff --git a/apps/ligo-virgo/src/pages/agenda/today/index.tsx b/apps/ligo-virgo/src/pages/agenda/today/index.tsx new file mode 100644 index 000000000..fa0f454e4 --- /dev/null +++ b/apps/ligo-virgo/src/pages/agenda/today/index.tsx @@ -0,0 +1,3 @@ +export default function AgendaToday() { + return AgendaToday; +} diff --git a/apps/ligo-virgo/src/pages/index.ts b/apps/ligo-virgo/src/pages/index.ts new file mode 100644 index 000000000..c5bf888a8 --- /dev/null +++ b/apps/ligo-virgo/src/pages/index.ts @@ -0,0 +1 @@ +export { LigoVirgoRoutes } from './AppRoutes'; diff --git a/apps/ligo-virgo/src/pages/recent/index.tsx b/apps/ligo-virgo/src/pages/recent/index.tsx new file mode 100644 index 000000000..9cf390d1e --- /dev/null +++ b/apps/ligo-virgo/src/pages/recent/index.tsx @@ -0,0 +1,3 @@ +export default function Recent() { + return Recent; +} diff --git a/apps/ligo-virgo/src/pages/search/index.tsx b/apps/ligo-virgo/src/pages/search/index.tsx new file mode 100644 index 000000000..135fd5818 --- /dev/null +++ b/apps/ligo-virgo/src/pages/search/index.tsx @@ -0,0 +1,3 @@ +export default function Search() { + return Search; +} diff --git a/apps/ligo-virgo/src/pages/settings/index.tsx b/apps/ligo-virgo/src/pages/settings/index.tsx new file mode 100644 index 000000000..803a6e29c --- /dev/null +++ b/apps/ligo-virgo/src/pages/settings/index.tsx @@ -0,0 +1,3 @@ +export default function Settings() { + return Settings; +} diff --git a/apps/ligo-virgo/src/pages/shared/index.tsx b/apps/ligo-virgo/src/pages/shared/index.tsx new file mode 100644 index 000000000..051e879ca --- /dev/null +++ b/apps/ligo-virgo/src/pages/shared/index.tsx @@ -0,0 +1,3 @@ +export default function Shared() { + return Shared; +} diff --git a/apps/ligo-virgo/src/pages/starred/index.tsx b/apps/ligo-virgo/src/pages/starred/index.tsx new file mode 100644 index 000000000..d2f36fe76 --- /dev/null +++ b/apps/ligo-virgo/src/pages/starred/index.tsx @@ -0,0 +1,3 @@ +export default function Starred() { + return Starred; +} diff --git a/apps/ligo-virgo/src/pages/status/page-not-found.tsx b/apps/ligo-virgo/src/pages/status/page-not-found.tsx new file mode 100644 index 000000000..940961070 --- /dev/null +++ b/apps/ligo-virgo/src/pages/status/page-not-found.tsx @@ -0,0 +1,7 @@ +import { Error } from '@toeverything/components/account'; + +export function PageNotFound() { + return ; +} + +export default PageNotFound; diff --git a/apps/ligo-virgo/src/pages/status/workspace-not-found.tsx b/apps/ligo-virgo/src/pages/status/workspace-not-found.tsx new file mode 100644 index 000000000..b2a8f7334 --- /dev/null +++ b/apps/ligo-virgo/src/pages/status/workspace-not-found.tsx @@ -0,0 +1,13 @@ +import { Error } from '@toeverything/components/account'; + +export function WorkspaceNotFound() { + return ( + + ); +} + +export default WorkspaceNotFound; diff --git a/apps/ligo-virgo/src/pages/templates/index.tsx b/apps/ligo-virgo/src/pages/templates/index.tsx new file mode 100644 index 000000000..523091c3e --- /dev/null +++ b/apps/ligo-virgo/src/pages/templates/index.tsx @@ -0,0 +1,98 @@ +import { styled, ListButton } from '@toeverything/components/ui'; +import { TemplateData } from './template-data'; +import { useParams } from 'react-router-dom'; +import { AffineEditor } from '@toeverything/components/affine-editor'; +const TemplatesContainer = styled('div')({ + display: 'flex', + flexDirection: 'row', + backgroundColor: '#fff', + border: '1px solid #E2E7ED', + borderRadius: '5px', + margin: '0 auto', + '.sidebar': { + width: '240px', + display: 'flex', + borderRight: '1px solid #E2E7ED', + flexDirection: 'column', + color: 'rgba(55, 53, 47, 0.65)', + background: 'rgb(247, 246, 243)', + padding: '12px', + }, + '.preview-template': { + display: 'flex', + }, + '.sidebar-title': { + borderBottom: '1px solid #E2E7ED', + }, + '.sidebar-template-type': { + height: '600px', + overflowY: 'scroll', + ul: {}, + 'ul li': { + paddingLeft: '10px', + height: '32px', + lineHeight: '32px', + listStyle: 'none', + fontWeight: 600, + fontSize: '14px', + cursor: 'pointer', + '&:hover': { + background: '#eee', + }, + }, + }, + '.btn-use-this-template': { + background: '#eee', + color: '#fff', + ':hover': { + background: '#ccc', + }, + }, +}); + +interface ITemplateProps { + handleClickUseThisTemplate?: () => void; +} +function Templates(props: ITemplateProps) { + const handle_click_use_this_template = () => { + props.handleClickUseThisTemplate(); + }; + const { workspace_id, page_id } = useParams(); + return ( + +
+
+ +
+
+ {TemplateData.map((item, index) => { + return ( +
+ {item.name} +
    + {item.subList.map((item, index) => { + return
  • {item.name}
  • ; + })} +
+
+ ); + })} +
+
+
+ {page_id && ( + + )} +
+
+ ); +} + +export { Templates }; diff --git a/apps/ligo-virgo/src/pages/templates/template-data.ts b/apps/ligo-virgo/src/pages/templates/template-data.ts new file mode 100644 index 000000000..7ca159a02 --- /dev/null +++ b/apps/ligo-virgo/src/pages/templates/template-data.ts @@ -0,0 +1,125 @@ +export const TemplateData = [ + { + name: 'Design', + subList: [ + { name: '🚘 Roadmap' }, + { name: '🔬 User Research Database' }, + { name: '🎒 Design Tasks' }, + { name: '✏️ Meeting Notes' }, + { name: '🖋️ Design System' }, + { name: '🎯 Company goals' }, + ], + }, + { + name: 'Student', + subList: [ + { name: '✏️ Class Notes' }, + { name: '🏗 Job Applications' }, + { name: '⚖️ Grade Calculator' }, + { name: '🏡 Club Homepage' }, + { name: '📚 Reading List' }, + { name: '📜 Thesis Planning' }, + { name: '📍 Cornell Notes System' }, + { name: '📇 Personal CRM' }, + { name: '✌️ Roommate Space' }, + { name: '💸 Simple Budget' }, + { name: '📄 Syllabus' }, + { name: '🏠 Classroom Home' }, + { name: '📋 Lesson Plans' }, + { name: '🗓 Course Schedule' }, + { name: '👋 Class Directory' }, + ], + }, + { + name: 'Engineering', + subList: [ + { name: '🎒 To-Do' }, + { name: '🚘 Roadmap' }, + { name: '📓 Engineering Wiki' }, + { name: '📎 Docs' }, + { name: '✏️ Meeting Notes' }, + { name: '🎯 Company goals' }, + ], + }, + { + name: 'Human resources', + subList: [ + { name: '💼 Job Board' }, + { name: '✏️ Meeting Notes' }, + { name: '🚂 New Hire Onboarding' }, + { name: '📮 Applicant Tracker' }, + { name: '🏠 Company Home' }, + ], + }, + { + name: 'Marketing', + subList: [ + { name: '🎨 Brand Assets' }, + { name: '✏️ Meeting Notes' }, + { name: '🎤 Media List' }, + { name: '📆 Content Calendar' }, + { name: '🎟️ Mood Board' }, + ], + }, + { + name: 'Personal', + subList: [ + { name: '📌 Quick Note' }, + { name: '🏠 Personal Home' }, + { name: '✔️ Task List' }, + { name: '🖊️ Journal' }, + { name: '📚 Reading List' }, + { name: '🏔️ Goals' }, + { name: '✈️ Travel Planner' }, + { name: '✏️ Blog Post' }, + { name: '📔 Simple Notebook' }, + { name: '👟 Habit Tracker' }, + { name: '🧭 Life Wiki' }, + { name: '👔 Resume' }, + { name: '📥 Job Applications' }, + { name: '📕 Weekly Agenda' }, + ], + }, + { + name: 'Other', + subList: [ + { name: '️📝 Meeting Notes' }, + { name: '📄 Docs' }, + { name: '🏠 Team Home' }, + { name: '☑️ Team Tasks' }, + { name: '✔️ Task List' }, + ], + }, + { + name: 'Product management', + subList: [ + { name: '🚘 Roadmap' }, + { name: ' User Research Database' }, + { name: '📎 Docs' }, + { name: '✏️ Meeting Notes' }, + { name: '🏗 Product Wiki' }, + { name: '🎯 Company goals' }, + ], + }, + { + name: 'Sales', + subList: [ + { name: '✏️ Meeting Notes' }, + { name: '👟 Sales CRM' }, + { name: '📕 Sales Wiki' }, + { name: '🎯 Competitive Analysis' }, + { name: '✌️ Sales Assets' }, + ], + }, + { + name: 'Support', + subList: [ + { name: '✌️ Team Directory' }, + { name: '❓ Product FAQs' }, + { name: '✏️ Meeting Notes' }, + { name: '🎒 Task List' }, + { name: '🚨 Help Center' }, + { name: '📎 Process Docs' }, + ], + }, +]; diff --git a/apps/ligo-virgo/src/pages/tools/container.tsx b/apps/ligo-virgo/src/pages/tools/container.tsx new file mode 100644 index 000000000..11005e9fe --- /dev/null +++ b/apps/ligo-virgo/src/pages/tools/container.tsx @@ -0,0 +1,9 @@ +import { Outlet } from 'react-router-dom'; + +export function Container() { + return ( +
+ +
+ ); +} diff --git a/apps/ligo-virgo/src/pages/tools/icons/Icons.tsx b/apps/ligo-virgo/src/pages/tools/icons/Icons.tsx new file mode 100644 index 000000000..55b66c5eb --- /dev/null +++ b/apps/ligo-virgo/src/pages/tools/icons/Icons.tsx @@ -0,0 +1,85 @@ +import { type FC, useRef } from 'react'; +import * as uiIcons from '@toeverything/components/icons'; +import { message, styled } from '@toeverything/components/ui'; +import { copy } from './copy'; + +const IconBooth: FC<{ name: string; Icon: FC }> = ({ name, Icon }) => { + const on_click = () => { + copy(`<${name} />`); + message.success({ + content: 'Copied.', + }); + }; + return ( + + + {name} + + ); +}; + +const _icons = Object.entries(uiIcons).filter(([key]) => key !== 'timestamp'); + +export const Icons: FC = () => { + const ref = useRef(null); + return ( + +

Example:

+
+ + {`import { TextIcon } from '@toeverything/components/ui'`}; + +
+

{`Total: ${_icons.length}`}

+
Click to copy.
+

{`Last Updated: ${new Date( + uiIcons.timestamp + ).toLocaleString()}`}

+
+ + {_icons.map(([key, icon]) => { + return ; + })} + +
+ ); +}; + +const Container = styled('div')({ + color: '#98ACBD', + padding: '20px', +}); + +const IconName = styled('div')({ + width: '100%', + marginTop: '8px', + wordBreak: 'break-all', +}); + +const IconContainer = styled('div')(({ theme }) => ({ + width: '112px', + borderRadius: '4px', + padding: '4px', + cursor: 'pointer', + textAlign: 'center', + '--color-0': theme.affine.palette.hover, + '--color-1': theme.affine.palette.icons, + + '& svg:first-of-type': { + boxShadow: '0 0 6px #e0e6eb', + }, + + '&:hover': { + backgroundColor: '#F5F7F8', + }, +})); + +const IconsContainer = styled('div')({ + display: 'grid', + gridTemplateColumns: 'repeat(auto-fill,112px)', + justifyContent: 'justify', + alignContent: 'start', + columnGap: '16px', + rowGap: '24px', + marginTop: '24px', +}); diff --git a/apps/ligo-virgo/src/pages/tools/icons/copy.ts b/apps/ligo-virgo/src/pages/tools/icons/copy.ts new file mode 100644 index 000000000..ed9e1b0b9 --- /dev/null +++ b/apps/ligo-virgo/src/pages/tools/icons/copy.ts @@ -0,0 +1,18 @@ +const create_fake_element = (value: string) => { + const fake_element = document.createElement('textarea'); + fake_element.style.position = 'fixed'; + fake_element.style.top = '0'; + fake_element.style.clipPath = "path('M0,0 L0,0')"; + fake_element.setAttribute('readonly', ''); + fake_element.value = value; + return fake_element; +}; + +export const copy = (value: string) => { + const fake_element = create_fake_element(value); + document.body.appendChild(fake_element); + fake_element.select(); + fake_element.setSelectionRange(0, fake_element.value.length); + document.execCommand('copy'); + fake_element.remove(); +}; diff --git a/apps/ligo-virgo/src/pages/tools/icons/index.ts b/apps/ligo-virgo/src/pages/tools/icons/index.ts new file mode 100644 index 000000000..9d2a7b7b2 --- /dev/null +++ b/apps/ligo-virgo/src/pages/tools/icons/index.ts @@ -0,0 +1 @@ +export { Icons } from './Icons'; diff --git a/apps/ligo-virgo/src/pages/tools/index.tsx b/apps/ligo-virgo/src/pages/tools/index.tsx new file mode 100644 index 000000000..984fae407 --- /dev/null +++ b/apps/ligo-virgo/src/pages/tools/index.tsx @@ -0,0 +1,14 @@ +import { Routes, Route } from 'react-router-dom'; + +import { Container } from './container'; +import { Icons } from './icons'; + +export function Tools() { + return ( + + }> + } /> + + + ); +} diff --git a/apps/ligo-virgo/src/pages/ui/index.tsx b/apps/ligo-virgo/src/pages/ui/index.tsx new file mode 100644 index 000000000..cc8273dbe --- /dev/null +++ b/apps/ligo-virgo/src/pages/ui/index.tsx @@ -0,0 +1,9 @@ +import React from 'react'; + +export const UIPage = () => { + return ( +
+ This page is used to show ui components of Affine ~ +
+ ); +}; diff --git a/apps/ligo-virgo/src/pages/workspace/Container.tsx b/apps/ligo-virgo/src/pages/workspace/Container.tsx new file mode 100644 index 000000000..92afc2248 --- /dev/null +++ b/apps/ligo-virgo/src/pages/workspace/Container.tsx @@ -0,0 +1,19 @@ +import { Outlet } from 'react-router-dom'; +import style9 from 'style9'; + +import { MuiBox as Box } from '@toeverything/components/ui'; + +const styles = style9.create({ + container: { + display: 'flex', + overflow: 'hidden', + }, +}); + +export function WorkspaceRootContainer() { + return ( + + + + ); +} diff --git a/apps/ligo-virgo/src/pages/workspace/Home.tsx b/apps/ligo-virgo/src/pages/workspace/Home.tsx new file mode 100644 index 000000000..c8ce89b6d --- /dev/null +++ b/apps/ligo-virgo/src/pages/workspace/Home.tsx @@ -0,0 +1,39 @@ +import { useEffect } from 'react'; +import { useNavigate, useParams } from 'react-router-dom'; +import { useUserAndSpaces } from '@toeverything/datasource/state'; +import { services, TemplateFactory } from '@toeverything/datasource/db-service'; + +export function WorkspaceHome() { + const navigate = useNavigate(); + const { workspace_id } = useParams(); + const { user } = useUserAndSpaces(); + + useEffect(() => { + const navigate_to_user_initial_page = async () => { + const recent_pages = await services.api.userConfig.getRecentPages( + workspace_id, + user.id + ); + + const user_initial_page_id = + await services.api.userConfig.getUserInitialPage( + workspace_id, + user.id + ); + if (recent_pages.length === 0) { + await services.api.editorBlock.copyTemplateToPage( + workspace_id, + user_initial_page_id, + TemplateFactory.generatePageTemplateByGroupKeys({ + name: null, + groupKeys: ['todolist'], + }) + ); + } + navigate(`/${workspace_id}/${user_initial_page_id}`); + }; + navigate_to_user_initial_page(); + }, [navigate, user.id, workspace_id]); + + return null; +} diff --git a/apps/ligo-virgo/src/pages/workspace/Whiteboard.tsx b/apps/ligo-virgo/src/pages/workspace/Whiteboard.tsx new file mode 100644 index 000000000..16bdf814f --- /dev/null +++ b/apps/ligo-virgo/src/pages/workspace/Whiteboard.tsx @@ -0,0 +1,37 @@ +import { memo, useEffect } from 'react'; +import { useParams } from 'react-router'; + +import { AffineBoard } from '@toeverything/components/affine-board'; +import { useUserAndSpaces } from '@toeverything/datasource/state'; +import { services } from '@toeverything/datasource/db-service'; + +const MemoAffineBoard = memo(AffineBoard, (prev, next) => { + return prev.rootBlockId === next.rootBlockId; +}); + +type WhiteboardProps = { + workspace: string; +}; + +export const Whiteboard = (props: WhiteboardProps) => { + const { page_id } = useParams(); + const { user } = useUserAndSpaces(); + + useEffect(() => { + if (!user?.id || !props.workspace) return; + const update_recent_pages = async () => { + // TODO: deal with it temporarily + await services.api.editorBlock.getWorkspaceDbBlock( + props.workspace, + { + userId: user.id, + } + ); + }; + update_recent_pages(); + }, [user, props.workspace]); + + return ( + + ); +}; diff --git a/apps/ligo-virgo/src/pages/workspace/docs/collapsible-page-tree.tsx b/apps/ligo-virgo/src/pages/workspace/docs/collapsible-page-tree.tsx new file mode 100644 index 000000000..069bc4885 --- /dev/null +++ b/apps/ligo-virgo/src/pages/workspace/docs/collapsible-page-tree.tsx @@ -0,0 +1,112 @@ +import { useCallback, useState } from 'react'; +import { useNavigate, useParams } from 'react-router-dom'; +import clsx from 'clsx'; +import style9 from 'style9'; +import { + MuiBox as Box, + MuiButton as Button, + MuiCollapse as Collapse, + MuiIconButton as IconButton, +} from '@toeverything/components/ui'; +import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown'; +import ArrowRightIcon from '@mui/icons-material/ArrowRight'; + +import { services } from '@toeverything/datasource/db-service'; +import { NewpageIcon } from '@toeverything/components/common'; +import { + usePageTree, + useCalendarHeatmap, +} from '@toeverything/components/layout'; + +const styles = style9.create({ + ligoButton: { + textTransform: 'none', + }, + newPage: { + color: '#B6C7D3', + width: '26px', + fontSize: '18px', + textAlign: 'center', + cursor: 'pointer', + }, +}); + +export type CollapsiblePageTreeProps = { + title?: string; + initialOpen?: boolean; + children?: React.ReactNode; + className?: string; + style?: React.CSSProperties; +}; + +export function CollapsiblePageTree(props: CollapsiblePageTreeProps) { + const { className, style, children, title, initialOpen = true } = props; + const navigate = useNavigate(); + const { workspace_id, page_id } = useParams(); + + const { handleAddPage } = usePageTree(); + const { addPageToday } = useCalendarHeatmap(); + + const [open, setOpen] = useState(initialOpen); + + const create_page = useCallback(async () => { + if (page_id) { + const newPage = await services.api.editorBlock.create({ + workspace: workspace_id, + type: 'page' as const, + }); + + await handleAddPage(newPage.id); + addPageToday(); + + navigate(`/${workspace_id}/${newPage.id}`); + } + }, [addPageToday, handleAddPage, navigate, page_id, workspace_id]); + + const [newPageBtnVisible, setNewPageBtnVisible] = useState(false); + + return ( + <> + setNewPageBtnVisible(true)} + onMouseLeave={() => setNewPageBtnVisible(false)} + > + + + {newPageBtnVisible && ( +
+ + +
+ )} +
+ {children ? ( + + {children} + + ) : null} + + ); +} + +export default CollapsiblePageTree; diff --git a/apps/ligo-virgo/src/pages/workspace/docs/index.spec.tsx b/apps/ligo-virgo/src/pages/workspace/docs/index.spec.tsx new file mode 100644 index 000000000..e17c51392 --- /dev/null +++ b/apps/ligo-virgo/src/pages/workspace/docs/index.spec.tsx @@ -0,0 +1,18 @@ +/* eslint-disable filename-rules/match */ +import { render } from '@testing-library/react'; + +import { Page } from './index'; + +describe('App', () => { + it('should render successfully', () => { + const { baseElement } = render(); + + expect(baseElement).toBeTruthy(); + }); + + it('should have a greeting as the title', () => { + const { getByText } = render(); + + expect(getByText(/Welcome ligo-virgo/gi)).toBeTruthy(); + }); +}); diff --git a/apps/ligo-virgo/src/pages/workspace/docs/index.tsx b/apps/ligo-virgo/src/pages/workspace/docs/index.tsx new file mode 100644 index 000000000..b24816952 --- /dev/null +++ b/apps/ligo-virgo/src/pages/workspace/docs/index.tsx @@ -0,0 +1,176 @@ +/* eslint-disable filename-rules/match */ +import { useEffect } from 'react'; +import { useParams } from 'react-router'; +import { + MuiBox as Box, + MuiCircularProgress as CircularProgress, + MuiDivider as Divider, + styled, +} from '@toeverything/components/ui'; +import { AffineEditor } from '@toeverything/components/affine-editor'; +import { + CalendarHeatmap, + PageTree, + Activities, +} from '@toeverything/components/layout'; +import { CollapsibleTitle } from '@toeverything/components/common'; +import { + useShowSpaceSidebar, + useUserAndSpaces, +} from '@toeverything/datasource/state'; +import { services } from '@toeverything/datasource/db-service'; + +import { WorkspaceName } from './workspace-name'; +import { CollapsiblePageTree } from './collapsible-page-tree'; +import TemplatesPortal from './templates-portal'; +import { useFlag } from '@toeverything/datasource/feature-flags'; + +type PageProps = { + workspace: string; +}; + +export function Page(props: PageProps) { + const { page_id } = useParams(); + const { showSpaceSidebar, fixedDisplay, setSpaceSidebarVisible } = + useShowSpaceSidebar(); + const { user } = useUserAndSpaces(); + const templatesPortalFlag = useFlag('BooleanTemplatesPortal', false); + const dailyNotesFlag = useFlag('BooleanDailyNotes', false); + + useEffect(() => { + if (!user?.id || !page_id) return; + const updateRecentPages = async () => { + // TODO: deal with it temporarily + await services.api.editorBlock.getWorkspaceDbBlock( + props.workspace, + { + userId: user.id, + } + ); + + await services.api.userConfig.addRecentPage( + props.workspace, + user.id, + page_id + ); + await services.api.editorBlock.clearUndoRedo(props.workspace); + }; + update_recent_pages(); + }, [user, props.workspace, page_id]); + + return ( + + + setSpaceSidebarVisible(true)} + onMouseLeave={() => setSpaceSidebarVisible(false)} + > + + + +
+ {templatesPortalFlag && } + {dailyNotesFlag && ( +
+ + + +
+ )} +
+ + + +
+
+ + {page_id ? : null} + +
+
+
+
+
+ + + {page_id ? ( + + ) : ( + + + + )} + + +
+ ); +} + +const LigoApp = styled('div')({ + width: '100vw', + position: 'relative', + display: 'flex', + flex: '1 1 0%', + backgroundColor: 'white', + margin: '10px 0', +}); + +const LigoRightContainer = styled('div')({ + position: 'relative', + width: '100%', + flex: 'auto', +}); + +const LigoEditorOuterContainer = styled('div')({ + position: 'absolute', + height: '100%', + width: '100%', + overflowX: 'hidden', + overflowY: 'hidden', +}); + +const LigoLeftContainer = styled('div')({ + flex: '0 0 auto', +}); + +const WorkspaceSidebar = styled('div')(({ hidden }) => ({ + position: 'absolute', + zIndex: 1, + display: 'flex', + flexDirection: 'column', + width: 300, + minWidth: 300, + height: '100%', + borderRadius: '0px 10px 10px 0px', + boxShadow: '0px 1px 10px rgba(152, 172, 189, 0.6)', + backgroundColor: '#FFFFFF', + transitionProperty: 'left', + transitionDuration: '0.35s', + transitionTimingFunction: 'ease', + padding: '16px 12px', +})); + +const WorkspaceSidebarContent = styled('div')({ + flex: 'auto', + overflow: 'hidden auto', +}); diff --git a/apps/ligo-virgo/src/pages/workspace/docs/quick-find-portal.tsx b/apps/ligo-virgo/src/pages/workspace/docs/quick-find-portal.tsx new file mode 100644 index 000000000..cb7f85f6d --- /dev/null +++ b/apps/ligo-virgo/src/pages/workspace/docs/quick-find-portal.tsx @@ -0,0 +1,39 @@ +import { styled } from '@toeverything/components/ui'; + +import SearchIcon from '@mui/icons-material/Search'; + +const handle_search = () => { + //@ts-ignore + virgo.plugins.plugins['search'].renderSearch(); +}; +const QuickFindPortalContainer = styled('div')({ + position: 'relative', + marginLeft: '10px', + height: '22px', + lineHeight: '22px', + width: '220px', + borderRadius: '8px', + color: '#4c6275', + fontSize: '14px', + paddingLeft: '20px', + cursor: 'pointer', + ':hover': { + backgroundColor: '#ccc', + }, + '.shortcutIcon': { + position: 'absolute', + top: '3px', + left: '0px', + fontSize: '16px!important', + }, +}); + +function QuickFindPortal() { + return ( + + Quick Find + + ); +} + +export default QuickFindPortal; diff --git a/apps/ligo-virgo/src/pages/workspace/docs/templates-portal.tsx b/apps/ligo-virgo/src/pages/workspace/docs/templates-portal.tsx new file mode 100644 index 000000000..237f47758 --- /dev/null +++ b/apps/ligo-virgo/src/pages/workspace/docs/templates-portal.tsx @@ -0,0 +1,90 @@ +import { + styled, + MuiBox as Box, + MuiModal as Modal, +} from '@toeverything/components/ui'; + +import * as React from 'react'; +import { Templates } from '../../templates'; +import StarIcon from '@mui/icons-material/Star'; +import { useNavigate } from 'react-router'; +import { AsyncBlock } from '@toeverything/framework/virgo'; +import { createEditor } from '@toeverything/components/affine-editor'; +const TemplatePortalContainer = styled('div')({ + position: 'relative', + marginLeft: '10px', + height: '22px', + lineHeight: '22px', + width: '220px', + borderRadius: '8px', + color: '#4c6275', + fontSize: '14px', + paddingLeft: '20px', + cursor: 'pointer', + ':hover': { + backgroundColor: '#ccc', + }, + '.shortcutIcon': { + position: 'absolute', + top: '3px', + left: '0px', + fontSize: '16px!important', + }, +}); + +const style = { + position: 'absolute', + top: '40%', + left: '50%', + transform: 'translate(-50%, -50%)', + width: '80%', + height: '70%', + boxShadow: 0, + p: 0, +}; +const maskStyle = { + background: 'rgba(0,0,0,0.5)', + width: '100%', + height: '100%', + position: 'fixed', +}; +function TemplatesPortal() { + const [open, set_open] = React.useState(false); + const handle_open = () => set_open(true); + const handle_close = () => set_open(false); + const navigate = useNavigate(); + + const get_default_workspace_id = () => { + return window.location.pathname.split('/')[1]; + }; + const handleClickUseThisTemplate = () => { + const block_editor = createEditor(get_default_workspace_id()); + //@ts-ignore + block_editor.plugins + .getPlugin('page-toolbar') + //@ts-ignore 泛型处理 + .addDailyNote() + .then((new_page: AsyncBlock) => { + handle_close(); + const new_state = + `/${get_default_workspace_id()}/` + new_page.id; + navigate(new_state); + }); + }; + return ( + <> + + Templates + + + + + + + + ); +} + +export default TemplatesPortal; diff --git a/apps/ligo-virgo/src/pages/workspace/docs/workspace-name.tsx b/apps/ligo-virgo/src/pages/workspace/docs/workspace-name.tsx new file mode 100644 index 000000000..dd898a860 --- /dev/null +++ b/apps/ligo-virgo/src/pages/workspace/docs/workspace-name.tsx @@ -0,0 +1,168 @@ +import { + MuiButton as Button, + MuiSwitch as Switch, + styled, + MuiOutlinedInput as OutlinedInput, +} from '@toeverything/components/ui'; +import { LogoIcon } from '@toeverything/components/icons'; +import { + useUserAndSpaces, + useShowSpaceSidebar, +} from '@toeverything/datasource/state'; +import { useCallback, useEffect, useState } from 'react'; +import { services } from '@toeverything/datasource/db-service'; + +const WorkspaceContainer = styled('div')({ + display: 'flex', + alignItems: 'center', + minHeight: 60, + padding: '12px 0px', + color: '#566B7D', +}); +const LeftContainer = styled('div')({ + flex: 'auto', + display: 'flex', +}); + +const LogoContainer = styled('div')({ + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + height: 24, + minWidth: 24, +}); + +const StyledLogoIcon = styled(LogoIcon)(({ theme }) => { + return { + color: theme.affine.palette.primary, + width: '16px !important', + height: '16px !important', + }; +}); + +const WorkspaceNameContainer = styled('div')({ + display: 'flex', + alignItems: 'center', + flex: 'auto', + width: '100px', + marginRight: '10px', + input: { + padding: '5px 10px', + }, + span: { + width: '100%', + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', + }, +}); + +const WorkspaceReNameContainer = styled('div')({ + marginRight: '10px', + input: { + padding: '5px 10px', + }, +}); + +const ToggleDisplayContainer = styled('div')({ + display: 'flex', + alignItems: 'center', + fontSize: 12, + color: '#3E6FDB', + padding: 6, + minWidth: 64, +}); + +export const WorkspaceName = () => { + const { currentSpaceId } = useUserAndSpaces(); + const { fixedDisplay, toggleSpaceSidebar } = useShowSpaceSidebar(); + const [inRename, setInRename] = useState(false); + const [workspaceName, setWorkspaceName] = useState(''); + + const fetchWorkspaceName = useCallback(async () => { + if (!currentSpaceId) { + return; + } + + const name = await services.api.userConfig.getWorkspaceName( + currentSpaceId + ); + setWorkspaceName(name); + }, [currentSpaceId]); + + useEffect(() => { + fetchWorkspaceName(); + }, [currentSpaceId]); + + useEffect(() => { + let unobserve: () => void; + const observe = async () => { + unobserve = await services.api.userConfig.observe( + { workspace: currentSpaceId }, + () => { + fetchWorkspaceName(); + } + ); + }; + observe(); + + return () => { + unobserve?.(); + }; + }, [currentSpaceId, fetchWorkspaceName]); + + const handleKeyDown = useCallback( + (e: React.KeyboardEvent) => { + if (e.key === 'Enter') { + e.stopPropagation(); + e.preventDefault(); + setInRename(false); + } + }, + [] + ); + const handleChange = useCallback( + (e: React.ChangeEvent) => { + services.api.userConfig.setWorkspaceName( + currentSpaceId, + e.currentTarget.value + ); + }, + [] + ); + + return ( + + + + + + + {inRename ? ( + + setInRename(false)} + /> + + ) : ( + + setInRename(true)}> + {workspaceName} + + + )} + + + {fixedDisplay ? 'ON' : 'OFF'} + + + + ); +}; diff --git a/apps/ligo-virgo/src/pages/workspace/index.tsx b/apps/ligo-virgo/src/pages/workspace/index.tsx new file mode 100644 index 000000000..a2e5c9297 --- /dev/null +++ b/apps/ligo-virgo/src/pages/workspace/index.tsx @@ -0,0 +1,41 @@ +/* eslint-disable filename-rules/match */ +import { Routes, Route, useParams, Navigate } from 'react-router'; + +import { useUserAndSpaces } from '@toeverything/datasource/state'; + +import { WorkspaceRootContainer } from './Container'; +import { Page } from './docs'; +import { WorkspaceHome } from './Home'; +import Labels from './labels'; +import Pages from './pages'; +import { Whiteboard } from './Whiteboard'; + +export function WorkspaceContainer() { + const { workspace_id } = useParams(); + const { user, currentSpaceId } = useUserAndSpaces(); + + if ( + user && + ![currentSpaceId, 'affine2vin277tcmafwq'].includes(workspace_id) + ) { + // return ; + } + + return ( + + }> + } /> + } /> + } + /> + } + /> + } /> + + + ); +} diff --git a/apps/ligo-virgo/src/pages/workspace/labels/index.tsx b/apps/ligo-virgo/src/pages/workspace/labels/index.tsx new file mode 100644 index 000000000..aad88dc3e --- /dev/null +++ b/apps/ligo-virgo/src/pages/workspace/labels/index.tsx @@ -0,0 +1,3 @@ +export default function WorkspaceLabels() { + return WorkspaceLabels; +} diff --git a/apps/ligo-virgo/src/pages/workspace/pages/index.tsx b/apps/ligo-virgo/src/pages/workspace/pages/index.tsx new file mode 100644 index 000000000..b1ee12e53 --- /dev/null +++ b/apps/ligo-virgo/src/pages/workspace/pages/index.tsx @@ -0,0 +1,3 @@ +export default function WorkspacePages() { + return WorkspacePages; +} diff --git a/apps/ligo-virgo/src/pages/workspace/whiteboard/index.bak.tsx b/apps/ligo-virgo/src/pages/workspace/whiteboard/index.bak.tsx new file mode 100644 index 000000000..fed971448 --- /dev/null +++ b/apps/ligo-virgo/src/pages/workspace/whiteboard/index.bak.tsx @@ -0,0 +1,53 @@ +// import { FC, useMemo, useState } from 'react'; +// import { useParams } from 'react-router-dom'; +// import { TDPage } from '@toeverything/framework/whiteboard'; +// import { +// AffineWhiteboard, +// WhiteboardMeta, +// AffineEditorShape +// } from '@toeverything/components/affine-whiteboard'; + +// interface EditorShapeProps { +// blockIds: string | string[]; +// point: [number, number]; +// } + +// const createEditorShape = (props: EditorShapeProps): AffineEditorShape => { +// const block_ids = Array.isArray(props.blockIds) +// ? props.blockIds +// : [props.blockIds]; +// return { +// id: block_ids.join('_'), +// label: '', +// childIndex: 1, +// name: 'affine_editor', +// parentId: 'page', +// point: props.point, +// rotation: 0, +// size: [400, 200], +// style: { +// color: 'black', +// size: 'small', +// isFilled: false, +// dash: 'draw', +// scale: 1 +// } as any, +// type: 'affineEditor', +// blockIds: block_ids +// }; +// }; + +// const Whiteboard: FC = () => { +// const { workspace_id, page_id } = useParams(); +// const [shapes, set_shapes] = useState({}); +// const meta = useMemo(() => { +// return { +// workspace: workspace_id, +// rootBlockId: page_id +// }; +// }, [workspace_id, page_id]); + +// return page_id ? : null; +// }; + +// export default Whiteboard; diff --git a/apps/ligo-virgo/src/polyfills.ts b/apps/ligo-virgo/src/polyfills.ts new file mode 100644 index 000000000..2adf3d05b --- /dev/null +++ b/apps/ligo-virgo/src/polyfills.ts @@ -0,0 +1,7 @@ +/** + * Polyfill stable language features. These imports will be optimized by `@babel/preset-env`. + * + * See: https://github.com/zloirock/core-js#babel + */ +import 'core-js/stable'; +import 'regenerator-runtime/runtime'; diff --git a/apps/ligo-virgo/src/styles.css b/apps/ligo-virgo/src/styles.css new file mode 100644 index 000000000..aaf71841a --- /dev/null +++ b/apps/ligo-virgo/src/styles.css @@ -0,0 +1,285 @@ +/* resset.dev • v5.0.2 */ + +/* # ================================================================= + # Global selectors + # ================================================================= */ + +html { + box-sizing: border-box; + -webkit-text-size-adjust: 100%; /* Prevent adjustments of font size after orientation changes in iOS */ + word-break: normal; + -moz-tab-size: 4; + tab-size: 4; + overflow: hidden; + font-family: -apple-system, BlinkMacSystemFont, Helvetica Neue, Tahoma, + PingFang SC, Microsoft Yahei, Arial, Hiragino Sans GB, sans-serif, + Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji; +} + +*, +::before, +::after { + background-repeat: no-repeat; /* Set `background-repeat: no-repeat` to all elements and pseudo elements */ + box-sizing: inherit; +} + +::before, +::after { + text-decoration: inherit; /* Inherit text-decoration and vertical align to ::before and ::after pseudo elements */ + vertical-align: inherit; +} + +* { + padding: 0; /* Reset `padding` and `margin` of all elements */ + margin: 0; +} + +/* # ================================================================= + # General elements + # ================================================================= */ + +hr { + overflow: visible; /* Show the overflow in Edge and IE */ + height: 0; /* Add the correct box sizing in Firefox */ + color: inherit; /* Correct border color in Firefox. */ +} + +details, +main { + display: block; /* Render the `main` element consistently in IE. */ +} + +summary { + display: list-item; /* Add the correct display in all browsers */ +} + +small { + font-size: 80%; /* Set font-size to 80% in `small` elements */ +} + +[hidden] { + display: none; /* Add the correct display in IE */ +} + +abbr[title] { + border-bottom: none; /* Remove the bottom border in Chrome 57 */ + /* Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari */ + text-decoration: underline; + text-decoration: underline dotted; +} + +a { + background-color: transparent; /* Remove the gray background on active links in IE 10 */ +} + +a:active, +a:hover { + outline-width: 0; /* Remove the outline when hovering in all browsers */ +} + +code, +kbd, +pre, +samp { + font-family: monospace, monospace; /* Specify the font family of code elements */ +} + +pre { + font-size: 1em; /* Correct the odd `em` font sizing in all browsers */ +} + +b, +strong { + font-weight: bolder; /* Add the correct font weight in Chrome, Edge, and Safari */ +} + +/* https://gist.github.com/unruthless/413930 */ +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sub { + bottom: -0.25em; +} + +sup { + top: -0.5em; +} + +table { + border-color: inherit; /* Correct border color in all Chrome, Edge, and Safari. */ + text-indent: 0; /* Remove text indentation in Chrome, Edge, and Safari */ +} + +iframe { + border-style: none; +} + +/* # ================================================================= + # Forms + # ================================================================= */ + +input { + border-radius: 0; +} + +[type='number']::-webkit-inner-spin-button, +[type='number']::-webkit-outer-spin-button { + height: auto; /* Correct the cursor style of increment and decrement buttons in Chrome */ +} + +[type='search'] { + -webkit-appearance: textfield; /* Correct the odd appearance in Chrome and Safari */ + outline-offset: -2px; /* Correct the outline style in Safari */ +} + +[type='search']::-webkit-search-decoration { + -webkit-appearance: none; /* Remove the inner padding in Chrome and Safari on macOS */ +} + +textarea { + overflow: auto; /* Internet Explorer 11+ */ + resize: vertical; /* Specify textarea resizability */ +} + +button, +input, +optgroup, +select, +textarea { + font: inherit; /* Specify font inheritance of form elements */ +} + +optgroup { + font-weight: bold; /* Restore the font weight unset by the previous rule */ +} + +button { + overflow: visible; /* Address `overflow` set to `hidden` in IE 8/9/10/11 */ +} + +button, +select { + text-transform: none; /* Firefox 40+, Internet Explorer 11- */ +} + +/* Apply cursor pointer to button elements */ +button, +[type='button'], +[type='reset'], +[type='submit'], +[role='button'] { + cursor: pointer; +} + +/* Remove inner padding and border in Firefox 4+ */ +button::-moz-focus-inner, +[type='button']::-moz-focus-inner, +[type='reset']::-moz-focus-inner, +[type='submit']::-moz-focus-inner { + border-style: none; + padding: 0; +} + +/* Replace focus style removed in the border reset above */ +button:-moz-focusring, +[type='button']::-moz-focus-inner, +[type='reset']::-moz-focus-inner, +[type='submit']::-moz-focus-inner { + outline: 1px dotted ButtonText; +} + +button, + html [type='button'], /* Prevent a WebKit bug where (2) destroys native `audio` and `video`controls in Android 4 */ + [type='reset'], + [type='submit'] { + -webkit-appearance: button; /* Correct the inability to style clickable types in iOS */ +} + +/* Remove the default button styling in all browsers */ +button, +input, +select, +textarea { + background-color: transparent; + border-style: none; +} + +a:focus, +button:focus, +input:focus, +select:focus, +textarea:focus { + outline-width: 0; +} + +/* Style select like a standard input */ +select { + -moz-appearance: none; /* Firefox 36+ */ + -webkit-appearance: none; /* Chrome 41+ */ +} + +select::-ms-expand { + display: none; /* Internet Explorer 11+ */ +} + +select::-ms-value { + color: currentColor; /* Internet Explorer 11+ */ +} + +legend { + border: 0; /* Correct `color` not being inherited in IE 8/9/10/11 */ + color: inherit; /* Correct the color inheritance from `fieldset` elements in IE */ + display: table; /* Correct the text wrapping in Edge and IE */ + max-width: 100%; /* Correct the text wrapping in Edge and IE */ + white-space: normal; /* Correct the text wrapping in Edge and IE */ + max-width: 100%; /* Correct the text wrapping in Edge 18- and IE */ +} + +::-webkit-file-upload-button { + /* Correct the inability to style clickable types in iOS and Safari */ + -webkit-appearance: button; + color: inherit; + font: inherit; /* Change font properties to `inherit` in Chrome and Safari */ +} + +/* Replace pointer cursor in disabled elements */ +[disabled] { + cursor: default; +} + +/* # ================================================================= + # Specify media element style + # ================================================================= */ + +img { + border-style: none; /* Remove border when inside `a` element in IE 8/9/10 */ +} + +/* Add the correct vertical alignment in Chrome, Firefox, and Opera */ +progress { + vertical-align: baseline; +} + +/* # ================================================================= + # Accessibility + # ================================================================= */ + +/* Specify the progress cursor of updating elements */ +[aria-busy='true'] { + cursor: progress; +} + +/* Specify the pointer cursor of trigger elements */ +[aria-controls] { + cursor: pointer; +} + +/* Specify the unstyled cursor of disabled, not-editable, or otherwise inoperable elements */ +[aria-disabled='true'] { + cursor: default; +} diff --git a/apps/ligo-virgo/src/template.html b/apps/ligo-virgo/src/template.html new file mode 100644 index 000000000..d0c4a84e0 --- /dev/null +++ b/apps/ligo-virgo/src/template.html @@ -0,0 +1,11 @@ + + + + + + <%= htmlWebpackPlugin.options.title %> + + +
+ + diff --git a/apps/ligo-virgo/tsconfig.app.json b/apps/ligo-virgo/tsconfig.app.json new file mode 100644 index 000000000..6d3f1cc6f --- /dev/null +++ b/apps/ligo-virgo/tsconfig.app.json @@ -0,0 +1,22 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "types": ["node"] + }, + "files": [ + "../../node_modules/@nrwl/react/typings/cssmodule.d.ts", + "../../node_modules/@nrwl/react/typings/image.d.ts" + ], + "exclude": [ + "**/*.spec.ts", + "**/*.test.ts", + "**/*.spec.tsx", + "**/*.test.tsx", + "**/*.spec.js", + "**/*.test.js", + "**/*.spec.jsx", + "**/*.test.jsx" + ], + "include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"] +} diff --git a/apps/ligo-virgo/tsconfig.json b/apps/ligo-virgo/tsconfig.json new file mode 100644 index 000000000..2105d4475 --- /dev/null +++ b/apps/ligo-virgo/tsconfig.json @@ -0,0 +1,25 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "jsx": "react-jsx", + "allowSyntheticDefaultImports": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + // "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noImplicitAny": false + // "strictNullChecks": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.app.json" + }, + { + "path": "./tsconfig.spec.json" + } + ] +} diff --git a/apps/ligo-virgo/tsconfig.spec.json b/apps/ligo-virgo/tsconfig.spec.json new file mode 100644 index 000000000..c37f7eb08 --- /dev/null +++ b/apps/ligo-virgo/tsconfig.spec.json @@ -0,0 +1,23 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "commonjs", + "types": ["jest", "node"] + }, + "include": [ + "**/*.test.ts", + "**/*.spec.ts", + "**/*.test.tsx", + "**/*.spec.tsx", + "**/*.test.js", + "**/*.spec.js", + "**/*.test.jsx", + "**/*.spec.jsx", + "**/*.d.ts" + ], + "files": [ + "../../node_modules/@nrwl/react/typings/cssmodule.d.ts", + "../../node_modules/@nrwl/react/typings/image.d.ts" + ] +} diff --git a/apps/ligo-virgo/webpack.config.js b/apps/ligo-virgo/webpack.config.js new file mode 100644 index 000000000..4d7500c60 --- /dev/null +++ b/apps/ligo-virgo/webpack.config.js @@ -0,0 +1,197 @@ +const path = require('path'); +const zlib = require('zlib'); +const webpack = require('webpack'); +const getNxWebpackConfig = require('@nrwl/react/plugins/webpack'); +const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer'); +const HtmlWebpackPlugin = require('html-webpack-plugin'); +const MiniCssExtractPlugin = require('mini-css-extract-plugin'); +const CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); +const CompressionPlugin = require('compression-webpack-plugin'); +const TerserPlugin = require('terser-webpack-plugin'); +const Style9Plugin = require('style9/webpack'); + +const enableBundleAnalyzer = process.env.BUNDLE_ANALYZER; + +module.exports = function (webpackConfig) { + const config = getNxWebpackConfig(webpackConfig); + + const isProd = config.mode === 'production'; + + const style9 = { + test: /\.(tsx|ts|js|mjs|jsx)$/, + use: [ + { + loader: Style9Plugin.loader, + options: { + minifyProperties: isProd, + incrementalClassnames: isProd, + }, + }, + ], + }; + + config.experiments.topLevelAwait = true; + + if (isProd) { + config.module.rules.unshift(style9); + } else { + config.module.rules.push(style9); + } + + if (isProd) { + config.entry = { + main: [...config.entry.main, ...config.entry.polyfills], + }; + config.devtool = false; + config.output = { + ...config.output, + filename: '[name].[contenthash:8].js', + chunkFilename: '[name].[chunkhash:8].js', + hashFunction: undefined, + }; + config.optimization = { + nodeEnv: 'production', + minimize: true, + minimizer: [ + new TerserPlugin({ + terserOptions: { + ecma: 2020, + }, + extractComments: true, + parallel: true, + }), + new CssMinimizerPlugin(), + ], + splitChunks: { + chunks: 'all', + cacheGroups: { + styles: { + name: 'styles', + type: 'css/mini-extract', + chunks: 'all', + enforce: true, + }, + auth: { + test: /[\\/]node_modules[\\/](@authing|@?firebase)/, + name: 'auth', + priority: -5, + chunks: 'all', + }, + whiteboard: { + test: /(libs\/components\/board-|[\\/]node_modules[\\/]@tldraw)/, + name: 'whiteboard', + priority: -7, + chunks: 'all', + }, + editor: { + test: /(libs\/framework\/(ligo|virgo|editor)|[\\/]node_modules[\\/](@codemirror|@lezer|slate))/, + name: 'editor', + priority: -8, + chunks: 'all', + }, + ui: { + test: /[\\/]node_modules[\\/](@mui|@emotion|react|katex)/, + name: 'ui', + priority: -9, + chunks: 'all', + }, + vender: { + test: /([\\/]node_modules[\\/]|polyfills|@nrwl)/, + name: 'vender', + priority: -10, + chunks: 'all', + }, + }, + }, + }; + config.module.rules.unshift({ + test: /\.css$/i, + use: [ + MiniCssExtractPlugin.loader, + { + loader: 'css-loader', + options: { + sourceMap: false, + }, + }, + ], + }); + config.module.rules.unshift({ + test: /\.scss$/i, + use: [ + 'style-loader', + { + loader: 'css-loader', + options: { + sourceMap: false, + }, + }, + { + loader: 'postcss-loader', + }, + ], + }); + config.module.rules.splice(6); + } else { + config.output = { + ...config.output, + publicPath: '/', + }; + + const babelLoader = config.module.rules.find( + rule => + typeof rule !== 'string' && + rule.loader?.toString().includes('babel-loader') + ); + if (babelLoader && typeof babelLoader !== 'string') { + babelLoader.options['plugins'] = [ + ...(babelLoader.options['plugins'] || []), + [require.resolve('babel-plugin-open-source')], + ]; + } + } + + config.plugins = [ + ...config.plugins.filter( + p => !(isProd && p instanceof MiniCssExtractPlugin) + ), + new webpack.DefinePlugin({ + JWT_DEV: !isProd, + global: {}, + }), + isProd && + new HtmlWebpackPlugin({ + title: 'Affine - All In One Workos', + favicon: path.resolve( + __dirname, + './src/assets/images/favicon.ico' + ), //favicon path + template: path.resolve(__dirname, './src/template.html'), + publicPath: '/', + }), + new Style9Plugin(), + isProd && new MiniCssExtractPlugin(), + isProd && + new CompressionPlugin({ + test: /\.(js|css|html|svg|ttf|woff)$/, + algorithm: 'brotliCompress', + filename: '[path][base].br', + compressionOptions: { + params: { + [zlib.constants.BROTLI_PARAM_QUALITY]: 11, + }, + }, + }), + isProd && + enableBundleAnalyzer && + new BundleAnalyzerPlugin({ analyzerMode: 'static' }), + ].filter(Boolean); + + // Workaround for webpack infinite recompile errors + config.watchOptions = { + // followSymlinks: false, + ignored: ['**/*.css'], + }; + + return config; +}; diff --git a/babel.config.json b/babel.config.json new file mode 100644 index 000000000..af8aa6b9d --- /dev/null +++ b/babel.config.json @@ -0,0 +1,3 @@ +{ + "babelrcRoots": ["*"] +} diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md new file mode 100644 index 000000000..dbf692845 --- /dev/null +++ b/docs/CONTRIBUTING.md @@ -0,0 +1,13 @@ +# AFFiNE CONTRIBUTING + +Contributions are **welcome** and will be fully **credited**. + +## **Requirements** + +If the project maintainer has any additional requirements, you will find them listed here. + +- Code Style [AFFiNE Code Guideline](./affine-code-guideline.md) +- Git Rules [AFFiNE Git Guideline ](./affine-git-guideline.md) +- • **One pull request per feature** - If you want to do more than one thing, send multiple pull requests. + +**Happy coding**! diff --git a/docs/affine-code-guideline.md b/docs/affine-code-guideline.md new file mode 100644 index 000000000..d98ba3cb5 --- /dev/null +++ b/docs/affine-code-guideline.md @@ -0,0 +1,22 @@ +# AFFiNE Code Guideline + +| Item | Specification | Example | +| ----------------------------------------------- | --------------------------------------------------- | ------------------------------------------------------------------------------------- | +| [Packages/Paths]() | aaa-bbb-ccc | ligo-virgo, editor-todo | +| [.tsx]() | PascalCase | AddPage.tsx | +| [.ts]() | kebab-case | file-export.ts | +| [.json]() | kebab-case | file-export.ts | +| [Domain File]() | OpenRules | xx._d.ts_ | _tsconfig.xx_.json | xx.spec .ts | .env.xx | yy-ds.ts | +| [Types]() | UpperCamelCase | WebEvent | +| [Enum variants]() | UpperCamelCase | Status{ Todo,Completed } | +| [Functions]() | lowerCamelCase | | +| [React Funciton Compoment]() | UpperCamelCase | function DocShare(){} | +| [React HOC]() | UpperCamelCase | function BussinessText(){} | +| [Function Parameter]() | lowerCamelCase | function searchByIdOrName(idOrname){ } | +| [Methods for external access]() | lowerCamelCase | public sayHello(){ }; | +| [Externally Accessible Variables (Variables)]() | lowerCamelCase | animal.sleepCount | +| [General constructors]() | constructor or with_more_details | | +| [Local variables]() | lowerCamelCase | const tableCollection = []; | +| [Statics]() | SCREAMING_SNAKE_CASE | GLOBAL_MESSAGES | +| [Constants](b) | SCREAMING_SNAKE_CASE | GLOBAL_CONFIG | +| [Type parameters]() | UpperCamelCase , usually a single capital letter: T | let a: Animal = new Animal() | diff --git a/docs/affine-git-guideline.md b/docs/affine-git-guideline.md new file mode 100644 index 000000000..adbc3b94c --- /dev/null +++ b/docs/affine-git-guideline.md @@ -0,0 +1,91 @@ +# AFFiNE Git Guideline + +# 1. Git Branch Name + +- fix/ +- feat/ + +# 2. **Commit message guidelines** + +Affine uses [semantic-release](https://github.com/semantic-release/semantic-release) for automated version management and package publishing. For that to work, commitmessages need to be in the right format. + +### **Atomic commits** + +If possible, make [atomic commits](https://en.wikipedia.org/wiki/Atomic_commit), which means: + +- a commit should contain exactly one self-contained functional change +- a functional change should be contained in exactly one commit +- a commit should not create an inconsistent state (such as test errors, linting errors, partial fix, feature with documentation etc...) + +A complex feature can be broken down into multiple commits as long as each one keep a consistent state and consist of a self-contained change. + +### **Commit message format** + +Each commit message consists of a **header**, a **body** and a **footer**. The header has a special format that includes a **type**, a **scope** and a **subject**: + +`(): + + + + +