init: the first public commit for AFFiNE

This commit is contained in:
DarkSky
2022-07-22 15:49:21 +08:00
commit e3e3741393
1451 changed files with 108124 additions and 0 deletions
+125
View File
@@ -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"
]
}
]
}
+66
View File
@@ -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,
};
+13
View File
@@ -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
+4
View File
@@ -0,0 +1,4 @@
# use for download icon from figma
FIGMA_TOKEN
NODE_ENV
AFFINE_FEATURE_FLAG_TOKEN
+4
View File
@@ -0,0 +1,4 @@
**/webpack.config.js
**/jest.config.js
**/node_modules/**
.github/**
+287
View File
@@ -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": {}
}
]
}
+1
View File
@@ -0,0 +1 @@
NX_FREE_LOGIN=true
+47
View File
@@ -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
+62
View File
@@ -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
+59
View File
@@ -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
+45
View File
@@ -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
+8
View File
@@ -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
+14
View File
@@ -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
+3
View File
@@ -0,0 +1,3 @@
registry=https://registry.npmjs.org
engine-strict=true
auto-install-peers=true
+15
View File
@@ -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/**
+6
View File
@@ -0,0 +1,6 @@
{
"singleQuote": true,
"trailingComma": "es5",
"tabWidth": 4,
"arrowParens": "avoid"
}
+11
View File
@@ -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"
]
}
+41
View File
@@ -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"
]
}
+39
View File
@@ -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
}
}
+21
View File
@@ -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"]
+21
View File
@@ -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.
+109
View File
@@ -0,0 +1,109 @@
# AFFiNE
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
[![All Contributors](https://img.shields.io/badge/all_contributors-11-orange.svg?style=flat-square)](#contributors-)
<!-- ALL-CONTRIBUTORS-BADGE:END -->
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
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
<!-- prettier-ignore-start -->
<!-- markdownlint-disable -->
<table>
<tr>
<td align="center"><a href="https://darksky.eu.org/"><img src="https://avatars.githubusercontent.com/u/25152247?v=4?s=100" width="100px;" alt=""/><br /><sub><b>DarkSky</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=darkskygit" title="Code">💻</a> <a href="https://github.com/toeverything/AFFiNE/commits?author=darkskygit" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/tzhangchi"><img src="https://avatars.githubusercontent.com/u/5910926?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Chi Zhang</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=tzhangchi" title="Code">💻</a> <a href="https://github.com/toeverything/AFFiNE/commits?author=tzhangchi" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/alt1o"><img src="https://avatars.githubusercontent.com/u/21084335?v=4?s=100" width="100px;" alt=""/><br /><sub><b>alt1o</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=alt1o" title="Code">💻</a> <a href="https://github.com/toeverything/AFFiNE/commits?author=alt1o" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/DiamondThree"><img src="https://avatars.githubusercontent.com/u/24630517?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Diamond</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=DiamondThree" title="Code">💻</a> <a href="https://github.com/toeverything/AFFiNE/commits?author=DiamondThree" title="Documentation">📖</a></td>
<td align="center"><a href="https://lawvs.github.io/profile/"><img src="https://avatars.githubusercontent.com/u/18554747?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Whitewater</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=lawvs" title="Code">💻</a> <a href="https://github.com/toeverything/AFFiNE/commits?author=lawvs" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/zuoxiaodong0815"><img src="https://avatars.githubusercontent.com/u/53252747?v=4?s=100" width="100px;" alt=""/><br /><sub><b>zuoxiaodong0815</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=zuoxiaodong0815" title="Code">💻</a> <a href="https://github.com/toeverything/AFFiNE/commits?author=zuoxiaodong0815" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/SaikaSakura"><img src="https://avatars.githubusercontent.com/u/11530942?v=4?s=100" width="100px;" alt=""/><br /><sub><b>SaikaSakura</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=SaikaSakura" title="Code">💻</a> <a href="https://github.com/toeverything/AFFiNE/commits?author=SaikaSakura" title="Documentation">📖</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/QiShaoXuan"><img src="https://avatars.githubusercontent.com/u/22772830?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Qi</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=QiShaoXuan" title="Code">💻</a> <a href="https://github.com/toeverything/AFFiNE/commits?author=QiShaoXuan" title="Documentation">📖</a></td>
<td align="center"><a href="https://tuluffy.github.io/angular.github.io/"><img src="https://avatars.githubusercontent.com/u/26808339?v=4?s=100" width="100px;" alt=""/><br /><sub><b>tuluffy</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=tuluffy" title="Code">💻</a> <a href="https://github.com/toeverything/AFFiNE/commits?author=tuluffy" title="Documentation">📖</a></td>
<td align="center"><a href="https://shockwave.me/"><img src="https://avatars.githubusercontent.com/u/15013925?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Austaras</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=Austaras" title="Code">💻</a> <a href="https://github.com/toeverything/AFFiNE/commits?author=Austaras" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/uptonking?tab=repositories&type=source"><img src="https://avatars.githubusercontent.com/u/11391549?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Jin Yao</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=uptonking" title="Code">💻</a> <a href="https://github.com/toeverything/AFFiNE/commits?author=uptonking" title="Documentation">📖</a></td>
</tr>
</table>
<!-- markdownlint-restore -->
<!-- prettier-ignore-end -->
<!-- ALL-CONTRIBUTORS-LIST:END -->
## License
AFFiNE is distributed under the terms of MIT license.
See LICENSE for details.
View File
+11
View File
@@ -0,0 +1,11 @@
{
"presets": [
[
"@nrwl/react/babel",
{
"runtime": "automatic"
}
]
],
"plugins": []
}
+16
View File
@@ -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'.
+18
View File
@@ -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": {}
}
]
}
+12
View File
@@ -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: [],
};
+20
View File
@@ -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 <developer@affine.pro>",
"dependencies": {
"@mui/icons-material": "^5.8.4"
},
"devDependencies": {
"firebase": "^9.8.4",
"mini-css-extract-plugin": "^2.6.1",
"webpack": "^5.73.0"
}
}
+75
View File
@@ -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"]
}
+13
View File
@@ -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
}
}
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

+60
View File
@@ -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();
@@ -0,0 +1,3 @@
export const environment = {
production: true,
};
@@ -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,
};
+16
View File
@@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="en">
<head>
<!-- local dev index.html -->
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="https://app.affine.pro/favicon.ico" />
<title>Affine | Local Dev Environment</title>
<script>
window.global = window;
</script>
</head>
<body>
<div id="root"></div>
</body>
</html>
+28
View File
@@ -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(
<StrictMode>
<BrowserRouter>
<ThemeProvider>
<FeatureFlagsProvider>
<LigoVirgoRoutes />
</FeatureFlagsProvider>
</ThemeProvider>
</BrowserRouter>
</StrictMode>
);
@@ -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 (
<StyledRootContainer id="idAppRoot">
<StyledContentContainer>
<LayoutHeader />
<StyledMainContainer>
<Outlet />
</StyledMainContainer>
</StyledContentContainer>
<SettingsSidebar />
</StyledRootContainer>
);
}
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',
});
+64
View File
@@ -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 (
<Routes>
<Route path="/" element={<LigoVirgoRootContainer />}>
<Route path="/error/404" element={<PageNotFound />} />
<Route
path="/error/workspace"
element={<WorkspaceNotFound />}
/>
<Route path="/agenda/*" element={<Agenda />} />
<Route path="/recent" element={<Recent />} />
<Route path="/search" element={<Search />} />
<Route path="/settings" element={<Settings />} />
<Route path="/shared" element={<Shared />} />
<Route path="/started" element={<Starred />} />
<Route path="/templates" element={<Templates />} />
<Route path="/ui" element={<UIPage />} />
<Route
path="/:workspace_id/*"
element={
<RoutePrivate>
<WorkspaceContainer />
</RoutePrivate>
}
/>
<Route path="/" element={<Navigate to="/login" replace />} />
</Route>
{/* put public routes here; header and sidebar are disabled here */}
<Route>
<Route path="/tools/*" element={<Tools />} />
<Route
path="/login"
element={
<RoutePublicAutoLogin>
<Login />
</RoutePublicAutoLogin>
}
/>
<Route path="/" element={<Navigate to="/login" replace />} />
</Route>
</Routes>
);
}
@@ -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 <PageLoading />;
}
if (!user) {
return (
<Navigate
to={unauthorizedRedirectTo}
state={{ from: pathname }}
replace={true}
/>
);
}
return children;
}
@@ -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 <PageLoading />;
}
if (currentSpaceId) {
return (
<Navigate
to={`/${currentSpaceId}`}
state={{ from: pathname }}
replace={true}
/>
);
}
return children;
}
@@ -0,0 +1,3 @@
import { Login } from '@toeverything/components/account';
export { Login };
@@ -0,0 +1,3 @@
export default function AgendaCalendar() {
return <span>AgendaCalendar</span>;
}
@@ -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 (
<Box className={styles('container')}>
<Outlet />
</Box>
);
}
@@ -0,0 +1,3 @@
export default function AgendaHome() {
return <span>AgendaHome</span>;
}
@@ -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 (
<Routes>
<Route path="/" element={<Container />}>
<Route path="/calendar" element={<Calendar />} />
<Route path="/tasks" element={<Tasks />} />
<Route path="/today" element={<Today />} />
<Route path="/" element={<Home />} />
</Route>
</Routes>
);
}
@@ -0,0 +1,3 @@
export default function AgendaTasks() {
return <span>AgendaTasks</span>;
}
@@ -0,0 +1,3 @@
export default function AgendaToday() {
return <span>AgendaToday</span>;
}
+1
View File
@@ -0,0 +1 @@
export { LigoVirgoRoutes } from './AppRoutes';
@@ -0,0 +1,3 @@
export default function Recent() {
return <span>Recent</span>;
}
@@ -0,0 +1,3 @@
export default function Search() {
return <span>Search</span>;
}
@@ -0,0 +1,3 @@
export default function Settings() {
return <span>Settings</span>;
}
@@ -0,0 +1,3 @@
export default function Shared() {
return <span>Shared</span>;
}
@@ -0,0 +1,3 @@
export default function Starred() {
return <span>Starred</span>;
}
@@ -0,0 +1,7 @@
import { Error } from '@toeverything/components/account';
export function PageNotFound() {
return <Error clearOnClick={true} />;
}
export default PageNotFound;
@@ -0,0 +1,13 @@
import { Error } from '@toeverything/components/account';
export function WorkspaceNotFound() {
return (
<Error
subTitle="No workspace is found, please contact the admin"
action1Text="Login or Register"
clearOnClick={true}
/>
);
}
export default WorkspaceNotFound;
@@ -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 (
<TemplatesContainer>
<div className="sidebar">
<div className="sidebar-title">
<ListButton
className="btn-use-this-template"
content="Use this template"
onClick={handle_click_use_this_template}
/>
</div>
<div className="sidebar-template-type">
{TemplateData.map((item, index) => {
return (
<div key={index}>
{item.name}
<ul>
{item.subList.map((item, index) => {
return <li key={index}>{item.name}</li>;
})}
</ul>
</div>
);
})}
</div>
</div>
<div className="preview-template">
{page_id && (
<AffineEditor
workspace={workspace_id}
rootBlockId={page_id}
/>
)}
</div>
</TemplatesContainer>
);
}
export { Templates };
@@ -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' },
],
},
];
@@ -0,0 +1,9 @@
import { Outlet } from 'react-router-dom';
export function Container() {
return (
<div>
<Outlet />
</div>
);
}
@@ -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<any> }> = ({ name, Icon }) => {
const on_click = () => {
copy(`<${name} />`);
message.success({
content: 'Copied.',
});
};
return (
<IconContainer title={name} onClick={on_click}>
<Icon />
<IconName>{name}</IconName>
</IconContainer>
);
};
const _icons = Object.entries(uiIcons).filter(([key]) => key !== 'timestamp');
export const Icons: FC = () => {
const ref = useRef<HTMLHeadingElement>(null);
return (
<Container>
<h3 ref={ref}>Example:</h3>
<div>
<code>
{`import { TextIcon } from '@toeverything/components/ui'`};
</code>
</div>
<h3>{`Total: ${_icons.length}`}</h3>
<blockquote>Click to copy.</blockquote>
<p>{`Last Updated: ${new Date(
uiIcons.timestamp
).toLocaleString()}`}</p>
<hr />
<IconsContainer>
{_icons.map(([key, icon]) => {
return <IconBooth key={key} name={key} Icon={icon as FC} />;
})}
</IconsContainer>
</Container>
);
};
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',
});
@@ -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();
};
@@ -0,0 +1 @@
export { Icons } from './Icons';
+14
View File
@@ -0,0 +1,14 @@
import { Routes, Route } from 'react-router-dom';
import { Container } from './container';
import { Icons } from './icons';
export function Tools() {
return (
<Routes>
<Route path="/" element={<Container />}>
<Route path="/icons" element={<Icons />} />
</Route>
</Routes>
);
}
+9
View File
@@ -0,0 +1,9 @@
import React from 'react';
export const UIPage = () => {
return (
<div className="">
This page is used to show ui components of Affine ~
</div>
);
};
@@ -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 (
<Box className={styles('container')}>
<Outlet />
</Box>
);
}
@@ -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;
}
@@ -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 (
<MemoAffineBoard workspace={props.workspace} rootBlockId={page_id} />
);
};
@@ -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<boolean>(false);
return (
<>
<Box
sx={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
paddingRight: 1,
}}
onMouseEnter={() => setNewPageBtnVisible(true)}
onMouseLeave={() => setNewPageBtnVisible(false)}
>
<Button
startIcon={
open ? <ArrowDropDownIcon /> : <ArrowRightIcon />
}
onClick={() => setOpen(prev => !prev)}
sx={{ color: '#566B7D', textTransform: 'none' }}
className={clsx(styles('ligoButton'), className)}
style={style}
disableElevation
disableRipple
>
{title}
</Button>
{newPageBtnVisible && (
<div
onClick={create_page}
className={clsx(styles('newPage'), className)}
>
+
</div>
)}
</Box>
{children ? (
<Collapse in={open} timeout="auto" unmountOnExit>
{children}
</Collapse>
) : null}
</>
);
}
export default CollapsiblePageTree;
@@ -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(<Page workspace="default" />);
expect(baseElement).toBeTruthy();
});
it('should have a greeting as the title', () => {
const { getByText } = render(<Page workspace="default" />);
expect(getByText(/Welcome ligo-virgo/gi)).toBeTruthy();
});
});
@@ -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 (
<LigoApp>
<LigoLeftContainer style={{ width: fixedDisplay ? '300px' : 0 }}>
<WorkspaceSidebar
style={{
opacity: !showSpaceSidebar && !fixedDisplay ? 0 : 1,
transform:
!showSpaceSidebar && !fixedDisplay
? 'translateX(-270px)'
: 'translateX(0px)',
}}
onMouseEnter={() => setSpaceSidebarVisible(true)}
onMouseLeave={() => setSpaceSidebarVisible(false)}
>
<WorkspaceName />
<Divider light={true} sx={{ my: 1, margin: '6px 0px' }} />
<WorkspaceSidebarContent>
<div>
{templatesPortalFlag && <TemplatesPortal />}
{dailyNotesFlag && (
<div>
<CollapsibleTitle title="Daily Notes">
<CalendarHeatmap />
</CollapsibleTitle>
</div>
)}
<div>
<CollapsibleTitle
title="Activities"
initialOpen={false}
>
<Activities></Activities>
</CollapsibleTitle>
</div>
<div>
<CollapsiblePageTree title="Page Tree">
{page_id ? <PageTree /> : null}
</CollapsiblePageTree>
</div>
</div>
</WorkspaceSidebarContent>
</WorkspaceSidebar>
</LigoLeftContainer>
<LigoRightContainer>
<LigoEditorOuterContainer>
{page_id ? (
<AffineEditor
workspace={props.workspace}
rootBlockId={page_id}
/>
) : (
<Box
sx={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
width: '100%',
}}
>
<CircularProgress />
</Box>
)}
</LigoEditorOuterContainer>
</LigoRightContainer>
</LigoApp>
);
}
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',
});
@@ -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 (
<QuickFindPortalContainer onClick={handle_search}>
<SearchIcon className="shortcutIcon" /> Quick Find
</QuickFindPortalContainer>
);
}
export default QuickFindPortal;
@@ -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 (
<>
<TemplatePortalContainer onClick={handle_open}>
<StarIcon className="shortcutIcon" /> Templates
</TemplatePortalContainer>
<Modal open={open} onClose={handle_close}>
<Box sx={style}>
<Templates
handleClickUseThisTemplate={handleClickUseThisTemplate}
/>
</Box>
</Modal>
</>
);
}
export default TemplatesPortal;
@@ -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<HTMLTextAreaElement>) => {
if (e.key === 'Enter') {
e.stopPropagation();
e.preventDefault();
setInRename(false);
}
},
[]
);
const handleChange = useCallback(
(e: React.ChangeEvent<HTMLTextAreaElement>) => {
services.api.userConfig.setWorkspaceName(
currentSpaceId,
e.currentTarget.value
);
},
[]
);
return (
<WorkspaceContainer>
<LeftContainer>
<LogoContainer>
<StyledLogoIcon />
</LogoContainer>
{inRename ? (
<WorkspaceReNameContainer>
<OutlinedInput
value={workspaceName}
onChange={handleChange}
onKeyDown={handleKeyDown}
onMouseLeave={() => setInRename(false)}
/>
</WorkspaceReNameContainer>
) : (
<WorkspaceNameContainer>
<span onClick={() => setInRename(true)}>
{workspaceName}
</span>
</WorkspaceNameContainer>
)}
</LeftContainer>
<ToggleDisplayContainer>
<span>{fixedDisplay ? 'ON' : 'OFF'}</span>
<Switch
checked={fixedDisplay}
onChange={toggleSpaceSidebar}
size="small"
/>
</ToggleDisplayContainer>
</WorkspaceContainer>
);
};
@@ -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 <Navigate to={`/${currentSpaceId}`} replace={true} />;
}
return (
<Routes>
<Route path="/" element={<WorkspaceRootContainer />}>
<Route path="/labels" element={<Labels />} />
<Route path="/pages" element={<Pages />} />
<Route
path="/:page_id/whiteboard"
element={<Whiteboard workspace={workspace_id} />}
/>
<Route
path="/:page_id"
element={<Page workspace={workspace_id} />}
/>
<Route path="/" element={<WorkspaceHome />} />
</Route>
</Routes>
);
}
@@ -0,0 +1,3 @@
export default function WorkspaceLabels() {
return <span>WorkspaceLabels</span>;
}
@@ -0,0 +1,3 @@
export default function WorkspacePages() {
return <span>WorkspacePages</span>;
}
@@ -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<TDPage['shapes']>({});
// const meta = useMemo<WhiteboardMeta>(() => {
// return {
// workspace: workspace_id,
// rootBlockId: page_id
// };
// }, [workspace_id, page_id]);
// return page_id ? <AffineWhiteboard meta={meta} shapes={shapes} /> : null;
// };
// export default Whiteboard;
+7
View File
@@ -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';
+285
View File
@@ -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;
}
+11
View File
@@ -0,0 +1,11 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<div id="root"></div>
</body>
</html>
+22
View File
@@ -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"]
}
+25
View File
@@ -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"
}
]
}
+23
View File
@@ -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"
]
}
+197
View File
@@ -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;
};
+3
View File
@@ -0,0 +1,3 @@
{
"babelrcRoots": ["*"]
}
+13
View File
@@ -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**!
+22
View File
@@ -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_ &#124; _tsconfig.xx_.json &#124; xx.spec .ts &#124; .env.xx &#124; 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() |
+91
View File
@@ -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**:
`<type>(<scope>): <subject>
<BLANK LINE>
<body>
<BLANK LINE>
<footer>`
The **header** is mandatory and the **scope** of the header is optional.
The **footer** can contain a [closing reference to an issue](https://help.github.com/articles/closing-issues-via-commit-messages).
### **Revert**
If the commit reverts a previous commit, it should begin with `revert:` , followed by the header of the reverted commit. In the body it should say: `This reverts commit <hash>.`, where the hash is the SHA of the commit being reverted.
### **Type**
The type must be one of the following:
| Type | Description |
| ----------- | ----------- |
| build | Changes that affect the build system or external | dependencies (example scopes: gulp, broccoli, npm) |
| ci | Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs) |
| docs | Documentation only changes |
| feat | A new feature |
| fix | A bug fix |
| perf | A code change that improves performance |
| refactor | A code change that neither fixes a bug nor adds a feature |
| style | Changes that do not affect the meaning of the code(white-space, formatting, missing semi-colons, etc) |
| test | Adding missing tests or correcting existing tests |
| chore | Changes to the build process or auxiliary tools | |
### **Subject**
The subject contains succinct description of the change:
- use the imperative, present tense: "change" not "changed" nor "changes"
- don't capitalize first letter
- no dot (.) at the end
### **Body**
Just as in the **subject**, use the imperative, present tense: "change" not "changed" nor "changes". The body should include the motivation for the change and contrast this with previous behavior.
### **Footer**
The footer should contain any information about **Breaking Changes** and is also the place to reference GitHub issues that this commit **Closes**.
**Breaking Changes** should start with the word `BREAKING CHANGE:` with a space or two newlines. The rest of the commit message is then used for this.
### **Examples**
`fix(pencil): stop graphite breaking when too much pressure applied`
``feat(pencil): add 'graphiteWidth' option`
Fix #42`
`perf(pencil): remove graphiteWidth option`
BREAKING CHANGE: The graphiteWidth option has been removed.
The default graphite width of 10mm is always used for performance reasons.`
# 3. tracking-your-work-with-issues
[https://docs.github.com/en/issues/tracking-your-work-with-issues/about-issues](https://docs.github.com/en/issues/tracking-your-work-with-issues/about-issues)
+7
View File
@@ -0,0 +1,7 @@
## Affine Icons
> Abundant and colorful icon resource for free free use
Website: [http://localhost:4200/tools/icons](http://localhost:4200/tools/icons)
Figma: [Figma Affine Icons](https://www.figma.com/file/7pyx5gMz6CN0qSRADmScQ7/AFFINE?node-id=665%3A1734)
+53
View File
@@ -0,0 +1,53 @@
# Tutorial
Affine defines a new component development specification in Figma, extends Affine UI Components based on MUI BASE and MUI SYSTEM, and supplements as needed https://github.com/toeverything/AFFiNE/tree/master/libs/components/ui , eg `src/libs/components/ui/src/button/base-button.ts`
```jsx
import ButtonUnstyled, {
buttonUnstyledClasses,
} from '@mui/base/ButtonUnstyled';
import { styled } from '../styled';
/* eslint-disable @typescript-eslint/naming-convention */
const blue = {
500: '#007FFF',
600: '#0072E5',
700: '#0059B2',
};
/* eslint-enable @typescript-eslint/naming-convention */
export const BaseButton = styled(ButtonUnstyled)`
font-family: IBM Plex Sans, sans-serif;
font-weight: bold;
font-size: 0.875rem;
background-color: ${blue[500]};
border-radius: 8px;
padding: 4px 8px;
color: white;
transition: all 150ms ease;
cursor: pointer;
border: none;
&:hover {
background-color: ${blue[600]};
}
&.${buttonUnstyledClasses.active} {
background-color: ${blue[700]};
}
&.${buttonUnstyledClasses.focusVisible} {
box-shadow: 0 4px 20px 0 rgba(61, 71, 82, 0.1), 0 0 0 5px rgba(0, 127, 255, 0.5);
outline: none;
}
&.${buttonUnstyledClasses.disabled} {
opacity: 0.5;
cursor: not-allowed;
}
`;
```
```jsx
export { BaseButton } from './base-button';
```
@@ -0,0 +1,13 @@
Create or edit the `.env.local` file in the project root directory, add `FIGMA_TOKEN`, you can refer to Generate ACCESS_TOKEN: https://www.figma.com/developers/api#access-tokens
```
FIGMA_TOKEN=your-figma-token
```
Execute the command `nx run components-icons:figmaRes` to synchronize figma resources
figma icon resource address: https://www.figma.com/file/7pyx5gMz6CN0qSRADmScQ7/AFFINE?node-id=665%3A1734
### Icon Supplementary Style
Some icons downloaded directly have incorrect styles. You can add supplementary styles in `tools/executors/figmaRes/patch-styles.js`. The key is the name of the icon kebab-case.
+3
View File
@@ -0,0 +1,3 @@
## Single Component Rollup Config
If styles are used in a Component, `rollupConfig` under `project.json` needs to be modified to `libs/rollup.config.cjs`
+103
View File
@@ -0,0 +1,103 @@
[toc]
# Tutorial
1. MUI styled
```jsx
import type { MouseEventHandler, ReactNode } from 'react';
import { styled } from '@toeverything/components/ui';
const CardContainer = styled('div')({
display: 'flex',
flexDirection: 'column',
backgroundColor: '#fff',
border: '1px solid #E2E7ED',
borderRadius: '5px',
});
const CardContent = styled('div')({
margin: '23px 52px 24px 19px',
});
const CardActions = styled('div')({
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
width: '100%',
height: '29px',
background: 'rgba(152, 172, 189, 0.1)',
borderRadius: '0px 0px 5px 5px',
padding: '6px 0 6px 19px',
fontSize: '12px',
fontWeight: '300',
color: '#98ACBD',
});
const PlusIcon = styled('div')({
marginRight: '9px',
fontWeight: '500',
lineHeight: 0,
'::before': {
content: '"+"',
},
});
export const Card = ({
children,
onAddItem,
}: {
children?: ReactNode,
onAddItem?: MouseEventHandler<HTMLDivElement>,
}) => {
return (
<CardContainer>
<CardContent>{children}</CardContent>
<CardActions onClick={onAddItem}>
<PlusIcon />
Add item
</CardActions>
</CardContainer>
);
};
```
## 2. import `*.scss`
```jsx
import styles from './tree-item.module.scss';
export const TreeItem = forwardRef<HTMLDivElement, TreeItemProps>(
() => {
return (
<li
ref={wrapperRef}
className={cx(
styles['Wrapper'],
clone && styles['clone'],
ghost && styles['ghost'],
indicator && styles['indicator']
)}
style={
{
'--spacing': `${indentationWidth * depth}px`
} as CSSProperties
}
{...props}
>
</li>
);
}
);
```
## 3. import `*.css`,
```js
import 'codemirror/lib/codemirror.css';
import 'codemirror/lib/codemirror';
```
+5
View File
@@ -0,0 +1,5 @@
const { getJestProjects } = require('@nrwl/jest');
module.exports = {
projects: getJestProjects(),
};
+3
View File
@@ -0,0 +1,3 @@
const nxPreset = require('@nrwl/jest/preset');
module.exports = { ...nxPreset };
View File
+12
View File
@@ -0,0 +1,12 @@
{
"presets": [
[
"@nrwl/react/babel",
{
"runtime": "automatic",
"useBuiltIns": "usage"
}
]
],
"plugins": []
}
+18
View File
@@ -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": {}
}
]
}
+7
View File
@@ -0,0 +1,7 @@
# components-account
- components about user accounts
## Running unit tests
Run `nx test components-account` to execute the unit tests via [Jest](https://jestjs.io).
+9
View File
@@ -0,0 +1,9 @@
module.exports = {
displayName: 'components-account',
preset: '../../../jest.preset.js',
transform: {
'^.+\\.[tj]sx?$': 'babel-jest',
},
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
coverageDirectory: '../../../coverage/libs/components/account',
};

Some files were not shown because too many files have changed in this diff Show More