Compare commits
402 Commits
0.9.0-beta
...
v0.10.3-be
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b20e91bee0 | ||
|
|
9a4e5ec8c3 | ||
|
|
9dc2d55a5a | ||
|
|
91efca107a | ||
|
|
cf65a5cd93 | ||
|
|
42f4045ad6 | ||
|
|
2019838ae7 | ||
|
|
30ff25f400 | ||
|
|
317ca7f4e7 | ||
|
|
e766208c18 | ||
|
|
8742f28148 | ||
|
|
4168551783 | ||
|
|
55c6477bcc | ||
|
|
ae8329c590 | ||
|
|
25eda22af6 | ||
|
|
23e0137ed8 | ||
|
|
1740e7efa1 | ||
|
|
7463e87742 | ||
|
|
9ded6afb4b | ||
|
|
ad2d3b9167 | ||
|
|
3499dbbb7f | ||
|
|
4c8d54b3a7 | ||
|
|
3710bcdc14 | ||
|
|
ca07b143ef | ||
|
|
e8616acfe4 | ||
|
|
06203498da | ||
|
|
d7d47853fe | ||
|
|
a3d880daa3 | ||
|
|
d1476495ae | ||
|
|
946b7b4004 | ||
|
|
525b196cae | ||
|
|
c69e542b98 | ||
|
|
85bee72e6b | ||
|
|
b7d6237c20 | ||
|
|
5f1a124b53 | ||
|
|
3839a9bd15 | ||
|
|
f33c49b27e | ||
|
|
615255706d | ||
|
|
5e8103adbd | ||
|
|
f06bdd9a39 | ||
|
|
00c11d40cf | ||
|
|
0f6b28fd06 | ||
|
|
90c130cf15 | ||
|
|
9370110cdc | ||
|
|
c9f1fd9649 | ||
|
|
70e71bd43e | ||
|
|
899e46b1fa | ||
|
|
c127d449a1 | ||
|
|
add20ec2f8 | ||
|
|
34c5e7d83d | ||
|
|
7f09652cca | ||
|
|
cd291bb60e | ||
|
|
57d42bf491 | ||
|
|
4ef1f4c046 | ||
|
|
9bab1b5dff | ||
|
|
f09c717413 | ||
|
|
134428f38d | ||
|
|
ce7a691eef | ||
|
|
5fea0102fb | ||
|
|
ce2eeeffbe | ||
|
|
62c0efcfd1 | ||
|
|
aa4c7407de | ||
|
|
9baad36e41 | ||
|
|
87248b3337 | ||
|
|
8b2c3d4c41 | ||
|
|
703fad6a0d | ||
|
|
791eb75ca8 | ||
|
|
00c940f7df | ||
|
|
931b459fbd | ||
|
|
51e71f4a0a | ||
|
|
9b631f2328 | ||
|
|
ddd7cab414 | ||
|
|
e7e617a791 | ||
|
|
cc2ade601c | ||
|
|
ea4f5ffc83 | ||
|
|
9ac8a32e00 | ||
|
|
8d55e5cdf9 | ||
|
|
01f481a9b6 | ||
|
|
0177ab5c87 | ||
|
|
4db35d341c | ||
|
|
3c4a803c97 | ||
|
|
8bcc886b46 | ||
|
|
f9971ba922 | ||
|
|
5b0b8cf216 | ||
|
|
16488d594c | ||
|
|
c44a9a4903 | ||
|
|
05154dc7ca | ||
|
|
c90b477f60 | ||
|
|
76b585d1ef | ||
|
|
6f18ddbe85 | ||
|
|
dde779a71d | ||
|
|
bd9f66fbc7 | ||
|
|
92f1f40bfa | ||
|
|
993974d20d | ||
|
|
f17c0e1268 | ||
|
|
eded501123 | ||
|
|
ac3756ea23 | ||
|
|
dc8e84df31 | ||
|
|
48dc1049b3 | ||
|
|
a8d89254ce | ||
|
|
9add530370 | ||
|
|
b77460d871 | ||
|
|
42db41776b | ||
|
|
7525126d89 | ||
|
|
075439c74f | ||
|
|
30bac7dce2 | ||
|
|
b98a258083 | ||
|
|
28177657ef | ||
|
|
fc6c553ece | ||
|
|
59cb3d5df1 | ||
|
|
5c2d958e2b | ||
|
|
2117d6b232 | ||
|
|
5c48c83301 | ||
|
|
af7d331610 | ||
|
|
1fe5a0fffa | ||
|
|
f1e32aab66 | ||
|
|
7698a6ac8e | ||
|
|
063f5a683e | ||
|
|
fd74776abe | ||
|
|
9d89e4f7f5 | ||
|
|
7182b85bd0 | ||
|
|
91f1d0081b | ||
|
|
09c3a8828f | ||
|
|
927a6489f9 | ||
|
|
075eead9fa | ||
|
|
15b0c127f5 | ||
|
|
d0b014543c | ||
|
|
839f500979 | ||
|
|
af72bf0f69 | ||
|
|
405167854b | ||
|
|
f9654bb1f8 | ||
|
|
e3e0553c56 | ||
|
|
248fb1fa69 | ||
|
|
12a2ccf1a5 | ||
|
|
227b8b0061 | ||
|
|
442115632b | ||
|
|
ad82376890 | ||
|
|
a8bff81a7e | ||
|
|
da2821eaac | ||
|
|
c55565ee71 | ||
|
|
ecd5db2952 | ||
|
|
744cd47481 | ||
|
|
12c72e63b1 | ||
|
|
b7edaab387 | ||
|
|
83472cc682 | ||
|
|
08e7fa3486 | ||
|
|
7305530d97 | ||
|
|
b99ac51624 | ||
|
|
01d7fe4597 | ||
|
|
ac0a3aab3e | ||
|
|
7fe23a31b2 | ||
|
|
f50b8002b3 | ||
|
|
7a4669a6aa | ||
|
|
8554d5d791 | ||
|
|
da9934fbdc | ||
|
|
cb6974f263 | ||
|
|
f75684d6f6 | ||
|
|
acf0734c95 | ||
|
|
cfffcad1b8 | ||
|
|
3b74ff2b92 | ||
|
|
2b34e1a9cd | ||
|
|
9664d142ad | ||
|
|
e7106b7393 | ||
|
|
f491ff94cc | ||
|
|
e8987457ab | ||
|
|
7a8150398c | ||
|
|
3c4dbed16b | ||
|
|
7e381e830a | ||
|
|
61dc4a56f9 | ||
|
|
788c445f2b | ||
|
|
97db941749 | ||
|
|
f6cfe7c8a1 | ||
|
|
fb0aaabe53 | ||
|
|
de33967a73 | ||
|
|
65321e39cc | ||
|
|
a3906bf92b | ||
|
|
0a88be7771 | ||
|
|
7068d5f38a | ||
|
|
bf17b4789b | ||
|
|
5e9efbffa3 | ||
|
|
7e516236f5 | ||
|
|
15024c6c8a | ||
|
|
6a93203d68 | ||
|
|
af9663d3e7 | ||
|
|
1d7e3dd570 | ||
|
|
6ef02fbc38 | ||
|
|
604c3da9fe | ||
|
|
75c8dd75e3 | ||
|
|
e5c86a9249 | ||
|
|
e5be570f54 | ||
|
|
6aaf550241 | ||
|
|
2d62ec72a7 | ||
|
|
0273ea8b00 | ||
|
|
97d189f1c8 | ||
|
|
db36f81d24 | ||
|
|
c30d2550ff | ||
|
|
93e286177f | ||
|
|
8ca53326a7 | ||
|
|
7d6c096462 | ||
|
|
f08408ebe5 | ||
|
|
0ad0ab50d0 | ||
|
|
563863005f | ||
|
|
57d71ad6cf | ||
|
|
37ec552f74 | ||
|
|
9e3c79526c | ||
|
|
a015dc42bb | ||
|
|
17afe218fe | ||
|
|
7b204cc611 | ||
|
|
9102f1f9a9 | ||
|
|
f6b53a1167 | ||
|
|
6fcdb05925 | ||
|
|
99b35c7a93 | ||
|
|
fc27a2e906 | ||
|
|
b4d8f1428c | ||
|
|
1b0c604c02 | ||
|
|
581635f40b | ||
|
|
d752086846 | ||
|
|
f23ec9063c | ||
|
|
26b953ce57 | ||
|
|
72babe9157 | ||
|
|
b6ca81821e | ||
|
|
f11cc40ae3 | ||
|
|
95c1a44a0d | ||
|
|
198befb006 | ||
|
|
fc3516acfb | ||
|
|
de9e7f97a4 | ||
|
|
05ad6eb450 | ||
|
|
98d0ac3c90 | ||
|
|
2aa4b4c1f3 | ||
|
|
3798293d3e | ||
|
|
fd76d33421 | ||
|
|
2a4495f7ee | ||
|
|
1775138228 | ||
|
|
8c194ab8b0 | ||
|
|
5ba1c0dbdb | ||
|
|
59ec122940 | ||
|
|
588f63505d | ||
|
|
ef8024c657 | ||
|
|
abbd8235aa | ||
|
|
385de7d33b | ||
|
|
87571a0879 | ||
|
|
af24334264 | ||
|
|
9fc0152cb1 | ||
|
|
35dbbe561a | ||
|
|
50563dcb6e | ||
|
|
edb6e0fd69 | ||
|
|
9334a363c7 | ||
|
|
e0f7ac426c | ||
|
|
1deb6bffd3 | ||
|
|
ae6376edee | ||
|
|
780c164cc8 | ||
|
|
df69c908fe | ||
|
|
eaa90c9fb6 | ||
|
|
e8a88da9e4 | ||
|
|
559ec3956f | ||
|
|
3749125907 | ||
|
|
97d06432f0 | ||
|
|
b13705ba3d | ||
|
|
df77ffde9a | ||
|
|
ef1228dcb4 | ||
|
|
551287ab44 | ||
|
|
113105181d | ||
|
|
627e5dfbb5 | ||
|
|
21604a2cad | ||
|
|
fd6536ea90 | ||
|
|
a42d218962 | ||
|
|
5226d6c568 | ||
|
|
14bee1811c | ||
|
|
7ecee01d20 | ||
|
|
2e4f6ef2ed | ||
|
|
9b43380b05 | ||
|
|
303dade2ef | ||
|
|
113b20f669 | ||
|
|
95d37fc63f | ||
|
|
858a1da35f | ||
|
|
1d62133f4f | ||
|
|
df054ac7f6 | ||
|
|
493b815b7b | ||
|
|
e75a0743f8 | ||
|
|
5573afc8d5 | ||
|
|
8b703fd9ad | ||
|
|
2a97194c75 | ||
|
|
13b6bb7ee3 | ||
|
|
43220f6db6 | ||
|
|
b52e006bfe | ||
|
|
a8b10c64b8 | ||
|
|
9d6b335829 | ||
|
|
9820c80ee2 | ||
|
|
1dd17c6334 | ||
|
|
b6d21c0945 | ||
|
|
aba771e99c | ||
|
|
d9e2d17a26 | ||
|
|
779ac39b36 | ||
|
|
817463c40e | ||
|
|
890905ed0e | ||
|
|
54aad58388 | ||
|
|
37c6560dd6 | ||
|
|
352420b881 | ||
|
|
61d9958a4c | ||
|
|
7275d417b2 | ||
|
|
d835be90cb | ||
|
|
c54489ba6e | ||
|
|
9958baa843 | ||
|
|
97d8660a54 | ||
|
|
b14a6bc29e | ||
|
|
1d29c26284 | ||
|
|
bed9310519 | ||
|
|
814d552be8 | ||
|
|
63ca9671be | ||
|
|
524e48c8e6 | ||
|
|
9b3e6bf1f5 | ||
|
|
4135cfd243 | ||
|
|
be6bcfdb9a | ||
|
|
bb046a12dc | ||
|
|
a430266389 | ||
|
|
62d2b09e3c | ||
|
|
e831f612e8 | ||
|
|
01987990ee | ||
|
|
b3dc4dce9c | ||
|
|
5e9eeaddbd | ||
|
|
a0095496d7 | ||
|
|
77efcad89d | ||
|
|
1d06114f00 | ||
|
|
579fa1ae4c | ||
|
|
fea6b81a53 | ||
|
|
7911d67439 | ||
|
|
efca651429 | ||
|
|
07b5d18441 | ||
|
|
c1d386d932 | ||
|
|
710a2f2c97 | ||
|
|
2e1e486bc6 | ||
|
|
227017625d | ||
|
|
6ea10860b4 | ||
|
|
286347420d | ||
|
|
daa976ca62 | ||
|
|
d05897b724 | ||
|
|
5ebd82dc04 | ||
|
|
ae4322b75f | ||
|
|
a0e6ff9bd1 | ||
|
|
491cd75fe0 | ||
|
|
5be5863693 | ||
|
|
23abb97ccb | ||
|
|
b09d5f3e18 | ||
|
|
a731499024 | ||
|
|
8f5ee9234c | ||
|
|
1f6a105e5c | ||
|
|
b1eb926b7b | ||
|
|
c8d1de3a59 | ||
|
|
aa7e0dd85b | ||
|
|
0092a19812 | ||
|
|
4a6cfedc4a | ||
|
|
8c97fd1d28 | ||
|
|
d9fe3e73d5 | ||
|
|
59a4b3bc31 | ||
|
|
0161c98d65 | ||
|
|
d3ffa2c5f2 | ||
|
|
0a6859a1d7 | ||
|
|
69db99636b | ||
|
|
aab1a1e50a | ||
|
|
19646a97af | ||
|
|
f59a35d8d2 | ||
|
|
c911806062 | ||
|
|
b440c3a820 | ||
|
|
98cabc44e4 | ||
|
|
dd94ea5b45 | ||
|
|
b012e615ba | ||
|
|
603f82ffc2 | ||
|
|
56f75160f3 | ||
|
|
a860cf8e43 | ||
|
|
504dda2092 | ||
|
|
1df8b6edfb | ||
|
|
ddfa5d394d | ||
|
|
a69820e4ca | ||
|
|
369db3fea5 | ||
|
|
4a03fa65d1 | ||
|
|
a633fb6dea | ||
|
|
1b6cd70247 | ||
|
|
29fa237dfb | ||
|
|
61044d91a8 | ||
|
|
eb728f7ef2 | ||
|
|
1bdc402b7b | ||
|
|
127a84b4e1 | ||
|
|
2e1acec3c0 | ||
|
|
672a01b385 | ||
|
|
ad63c5a525 | ||
|
|
3a79346ce0 | ||
|
|
bf729df7fe | ||
|
|
b785840d91 | ||
|
|
e8410b948d | ||
|
|
3f09ba92bc | ||
|
|
35dc6d6687 | ||
|
|
5b4ce75e13 | ||
|
|
dc6b66c32f | ||
|
|
5f7f5b74ca | ||
|
|
7b5157aa89 | ||
|
|
bd0ed7f474 | ||
|
|
2da6702991 | ||
|
|
56d8fa5d29 | ||
|
|
4e5e48ce9f | ||
|
|
e0063ebc9b | ||
|
|
27e4599c94 | ||
|
|
edd7d00104 |
9
.devcontainer/Dockerfile
Normal file
@@ -0,0 +1,9 @@
|
||||
FROM mcr.microsoft.com/devcontainers/base:bookworm
|
||||
|
||||
# Install Homebrew For Linux
|
||||
RUN /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" && \
|
||||
eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" && \
|
||||
echo "eval \"\$($(brew --prefix)/bin/brew shellenv)\"" >> /home/vscode/.zshrc && \
|
||||
echo "eval \"\$($(brew --prefix)/bin/brew shellenv)\"" >> /home/vscode/.bashrc && \
|
||||
# Install Graphite
|
||||
brew install withgraphite/tap/graphite && gt --version
|
||||
12
.devcontainer/build.sh
Normal file
@@ -0,0 +1,12 @@
|
||||
#!/bin/bash
|
||||
# This is a script used by the devcontainer to build the project
|
||||
|
||||
#Enable yarn
|
||||
corepack enable
|
||||
corepack prepare yarn@stable --activate
|
||||
|
||||
# install dependencies
|
||||
yarn install
|
||||
|
||||
# Create database
|
||||
yarn workspace @affine/server prisma db push
|
||||
25
.devcontainer/devcontainer.json
Normal file
@@ -0,0 +1,25 @@
|
||||
// For format details, see https://aka.ms/devcontainer.json.
|
||||
{
|
||||
"name": "Debian",
|
||||
"dockerComposeFile": "docker-compose.yml",
|
||||
"service": "app",
|
||||
"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/node:1": {
|
||||
"version": "18"
|
||||
},
|
||||
"ghcr.io/devcontainers/features/rust:1": {}
|
||||
},
|
||||
// Configure tool-specific properties.
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"extensions": [
|
||||
"ms-playwright.playwright",
|
||||
"esbenp.prettier-vscode",
|
||||
"streetsidesoftware.code-spell-checker"
|
||||
]
|
||||
}
|
||||
},
|
||||
"updateContentCommand": "bash ./.devcontainer/build.sh",
|
||||
"postCreateCommand": "bash ./.devcontainer/setup-user.sh"
|
||||
}
|
||||
26
.devcontainer/docker-compose.yml
Normal file
@@ -0,0 +1,26 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
app:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
volumes:
|
||||
- ../..:/workspaces:cached
|
||||
command: sleep infinity
|
||||
network_mode: service:db
|
||||
environment:
|
||||
DATABASE_URL: postgresql://affine:affine@db:5432/affine
|
||||
|
||||
db:
|
||||
image: postgres:latest
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- postgres-data:/var/lib/postgresql/data
|
||||
environment:
|
||||
POSTGRES_PASSWORD: affine
|
||||
POSTGRES_USER: affine
|
||||
POSTGRES_DB: affine
|
||||
|
||||
volumes:
|
||||
postgres-data:
|
||||
7
.devcontainer/setup-user.sh
Executable file
@@ -0,0 +1,7 @@
|
||||
if [ -v GRAPHITE_TOKEN ];then
|
||||
gt auth --token $GRAPHITE_TOKEN
|
||||
fi
|
||||
|
||||
git fetch
|
||||
git branch canary -t origin/canary
|
||||
gt init --trunk canary
|
||||
@@ -11,3 +11,4 @@ ENABLE_CLOUD=
|
||||
ENABLE_MOVE_DATABASE=
|
||||
SHOULD_REPORT_TRACE=
|
||||
TRACE_REPORT_ENDPOINT=
|
||||
CAPTCHA_SITE_KEY=
|
||||
@@ -7,10 +7,10 @@ affine-out
|
||||
_next
|
||||
lib
|
||||
.eslintrc.js
|
||||
packages/i18n/src/i18n-generated.ts
|
||||
e2e-dist-*
|
||||
static
|
||||
web-static
|
||||
public
|
||||
packages/sdk/src/*.d.ts
|
||||
packages/sdk/src/*.js
|
||||
packages/common/sdk/src/*.d.ts
|
||||
packages/common/sdk/src/*.js
|
||||
packages/frontend/i18n/src/i18n-generated.ts
|
||||
|
||||
53
.eslintrc.js
@@ -56,26 +56,25 @@ const createPattern = packageName => [
|
||||
];
|
||||
|
||||
const allPackages = [
|
||||
'packages/cli',
|
||||
'packages/component',
|
||||
'packages/debug',
|
||||
'packages/env',
|
||||
'packages/graphql',
|
||||
'packages/hooks',
|
||||
'packages/i18n',
|
||||
'packages/native',
|
||||
'packages/infra',
|
||||
'packages/sdk',
|
||||
'packages/templates',
|
||||
'packages/theme',
|
||||
'packages/workspace',
|
||||
'packages/y-indexeddb',
|
||||
'apps/web',
|
||||
'apps/server',
|
||||
'apps/electron',
|
||||
'apps/storybook',
|
||||
'plugins/copilot',
|
||||
'plugins/bookmark-block',
|
||||
'packages/backend/server',
|
||||
'packages/frontend/component',
|
||||
'packages/frontend/core',
|
||||
'packages/frontend/electron',
|
||||
'packages/frontend/graphql',
|
||||
'packages/frontend/hooks',
|
||||
'packages/frontend/i18n',
|
||||
'packages/frontend/native',
|
||||
'packages/frontend/templates',
|
||||
'packages/frontend/workspace',
|
||||
'packages/common/debug',
|
||||
'packages/common/env',
|
||||
'packages/common/infra',
|
||||
'packages/common/sdk',
|
||||
'packages/common/theme',
|
||||
'packages/common/y-indexeddb',
|
||||
'packages/plugins/copilot',
|
||||
'tools/cli',
|
||||
'tests/storybook',
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -88,7 +87,7 @@ const config = {
|
||||
version: 'detect',
|
||||
},
|
||||
next: {
|
||||
rootDir: 'apps/web',
|
||||
rootDir: 'packages/frontend/core',
|
||||
},
|
||||
},
|
||||
extends: [
|
||||
@@ -128,10 +127,12 @@ const config = {
|
||||
'no-constant-binary-expression': 'error',
|
||||
'no-constructor-return': 'error',
|
||||
'react/prop-types': 'off',
|
||||
'react/jsx-no-useless-fragment': 'error',
|
||||
'@typescript-eslint/consistent-type-imports': 'error',
|
||||
'@typescript-eslint/no-non-null-assertion': 'error',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@typescript-eslint/no-empty-function': 'off',
|
||||
'@typescript-eslint/await-thenable': 'error',
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
'error',
|
||||
{
|
||||
@@ -202,6 +203,7 @@ const config = {
|
||||
ignore: ['^\\[[a-zA-Z0-9-_]+\\]\\.tsx$'],
|
||||
},
|
||||
],
|
||||
'unicorn/no-unnecessary-await': 'error',
|
||||
'sonarjs/no-all-duplicated-branches': 'error',
|
||||
'sonarjs/no-element-overwrite': 'error',
|
||||
'sonarjs/no-empty-collection': 'error',
|
||||
@@ -221,7 +223,7 @@ const config = {
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: 'apps/server/**/*.ts',
|
||||
files: 'packages/backend/server/**/*.ts',
|
||||
rules: {
|
||||
'@typescript-eslint/consistent-type-imports': 0,
|
||||
},
|
||||
@@ -252,6 +254,13 @@ const config = {
|
||||
},
|
||||
],
|
||||
'@typescript-eslint/no-misused-promises': ['error'],
|
||||
'i/no-extraneous-dependencies': ['error'],
|
||||
'react-hooks/exhaustive-deps': [
|
||||
'warn',
|
||||
{
|
||||
additionalHooks: 'useAsyncCallback',
|
||||
},
|
||||
],
|
||||
},
|
||||
})),
|
||||
{
|
||||
|
||||
1
.github/CLA.md
vendored
@@ -61,3 +61,4 @@ Example:
|
||||
- Shishu, @shishudesu, 2023/05/19
|
||||
- Kushagra Singh, @kush002, 2023/06/28
|
||||
- Sarvesh Kumar, @sarvesh521 2023/08/25
|
||||
- 微扰理论 Qinghao Huang, @wfnuser 2023/09/29
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/BUG-REPORT.yml
vendored
@@ -58,6 +58,6 @@ body:
|
||||
label: Are you willing to submit a PR?
|
||||
description: >
|
||||
(Optional) We encourage you to submit a [Pull Request](https://github.com/toeverything/affine/pulls) (PR) to help improve AFFiNE for everyone, especially if you have a good understanding of how to implement a fix or feature.
|
||||
See the AFFiNE [Contributing Guide](https://github.com/toeverything/affine/blob/master/CONTRIBUTING.md) to get started.
|
||||
See the AFFiNE [Contributing Guide](https://github.com/toeverything/affine/blob/canary/CONTRIBUTING.md) to get started.
|
||||
options:
|
||||
- label: Yes I'd like to help by submitting a PR!
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/FEATURE-REQUEST.yml
vendored
@@ -31,6 +31,6 @@ body:
|
||||
label: Are you willing to submit a PR?
|
||||
description: >
|
||||
(Optional) We encourage you to submit a [Pull Request](https://github.com/toeverything/affine/pulls) (PR) to help improve AFFiNE for everyone, especially if you have a good understanding of how to implement a fix or feature.
|
||||
See the AFFiNE [Contributing Guide](https://github.com/toeverything/affine/blob/master/CONTRIBUTING.md) to get started.
|
||||
See the AFFiNE [Contributing Guide](https://github.com/toeverything/affine/blob/canary/CONTRIBUTING.md) to get started.
|
||||
options:
|
||||
- label: Yes I'd like to help by submitting a PR!
|
||||
|
||||
24
.github/actions/build-rust/action.yml
vendored
@@ -19,6 +19,8 @@ runs:
|
||||
with:
|
||||
toolchain: stable
|
||||
targets: ${{ inputs.target }}
|
||||
env:
|
||||
CARGO_INCREMENTAL: '1'
|
||||
|
||||
- name: Cache cargo
|
||||
uses: actions/cache@v3
|
||||
@@ -34,7 +36,7 @@ runs:
|
||||
if: ${{ inputs.target != 'x86_64-unknown-linux-gnu' && inputs.target != 'aarch64-unknown-linux-gnu' }}
|
||||
shell: bash
|
||||
run: |
|
||||
yarn nx build ${{ inputs.package }} --target ${{ inputs.target }}
|
||||
yarn workspace ${{ inputs.package }} nx build ${{ inputs.package }} --target ${{ inputs.target }}
|
||||
env:
|
||||
NX_CLOUD_ACCESS_TOKEN: ${{ inputs.nx_token }}
|
||||
|
||||
@@ -48,9 +50,13 @@ runs:
|
||||
export CC=x86_64-unknown-linux-gnu-gcc
|
||||
export CC_x86_64_unknown_linux_gnu=x86_64-unknown-linux-gnu-gcc
|
||||
export RUSTFLAGS="-C debuginfo=1"
|
||||
yarn nx build ${{ inputs.package }} --target ${{ inputs.target }}
|
||||
chmod -R 777 node_modules/.cache
|
||||
chmod -R 777 target
|
||||
yarn workspace ${{ inputs.package }} nx build ${{ inputs.package }} --target ${{ inputs.target }}
|
||||
if [ -d "node_modules/.cache" ]; then
|
||||
chmod -R 777 node_modules/.cache
|
||||
fi
|
||||
if [ -d "target" ]; then
|
||||
chmod -R 777 target;
|
||||
fi
|
||||
|
||||
- name: Build
|
||||
if: ${{ inputs.target == 'aarch64-unknown-linux-gnu' }}
|
||||
@@ -60,6 +66,10 @@ runs:
|
||||
options: --user 0:0 -v ${{ github.workspace }}/.cargo-cache/git/db:/usr/local/cargo/git/db -v ${{ github.workspace }}/.cargo/registry/cache:/usr/local/cargo/registry/cache -v ${{ github.workspace }}/.cargo/registry/index:/usr/local/cargo/registry/index -v ${{ github.workspace }}:/build -w /build -e NX_CLOUD_ACCESS_TOKEN=${{ inputs.nx_token }}
|
||||
run: |
|
||||
export RUSTFLAGS="-C debuginfo=1"
|
||||
yarn nx build ${{ inputs.package }} --target ${{ inputs.target }}
|
||||
chmod -R 777 node_modules/.cache
|
||||
chmod -R 777 target
|
||||
yarn workspace ${{ inputs.package }} nx build ${{ inputs.package }} --target ${{ inputs.target }}
|
||||
if [ -d "node_modules/.cache" ]; then
|
||||
chmod -R 777 node_modules/.cache
|
||||
fi
|
||||
if [ -d "target" ]; then
|
||||
chmod -R 777 target;
|
||||
fi
|
||||
|
||||
17
.github/actions/deploy/deploy.mjs
vendored
@@ -13,6 +13,8 @@ const {
|
||||
R2_ACCESS_KEY_ID,
|
||||
R2_SECRET_ACCESS_KEY,
|
||||
R2_BUCKET,
|
||||
ENABLE_CAPTCHA,
|
||||
CAPTCHA_TURNSTILE_SECRET,
|
||||
OAUTH_EMAIL_SENDER,
|
||||
OAUTH_EMAIL_LOGIN,
|
||||
OAUTH_EMAIL_PASSWORD,
|
||||
@@ -23,6 +25,8 @@ const {
|
||||
GCLOUD_CLOUD_SQL_INTERNAL_ENDPOINT,
|
||||
REDIS_HOST,
|
||||
REDIS_PASSWORD,
|
||||
STRIPE_API_KEY,
|
||||
STRIPE_WEBHOOK_KEY,
|
||||
} = process.env;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
@@ -37,8 +41,8 @@ const createHelmCommand = ({ isDryRun }) => {
|
||||
const staticIpName = isProduction
|
||||
? 'affine-cluster-production'
|
||||
: isBeta
|
||||
? 'affine-cluster-beta'
|
||||
: 'affine-cluster-dev';
|
||||
? 'affine-cluster-beta'
|
||||
: 'affine-cluster-dev';
|
||||
const redisAndPostgres =
|
||||
isProduction || isBeta
|
||||
? [
|
||||
@@ -64,8 +68,8 @@ const createHelmCommand = ({ isDryRun }) => {
|
||||
]
|
||||
: [];
|
||||
const webReplicaCount = isProduction ? 3 : isBeta ? 2 : 2;
|
||||
const graphqlReplicaCount = isProduction ? 3 : isBeta ? 2 : 2;
|
||||
const syncReplicaCount = isProduction ? 6 : isBeta ? 3 : 2;
|
||||
const graphqlReplicaCount = isProduction ? 10 : isBeta ? 5 : 2;
|
||||
const syncReplicaCount = isProduction ? 10 : isBeta ? 5 : 2;
|
||||
const namespace = isProduction ? 'production' : isBeta ? 'beta' : 'dev';
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const host = DEPLOY_HOST || CANARY_DEPLOY_HOST;
|
||||
@@ -81,6 +85,8 @@ const createHelmCommand = ({ isDryRun }) => {
|
||||
`--set graphql.replicaCount=${graphqlReplicaCount}`,
|
||||
`--set-string graphql.image.tag="${imageTag}"`,
|
||||
`--set graphql.app.host=${host}`,
|
||||
`--set graphql.app.captcha.enabled=${ENABLE_CAPTCHA}`,
|
||||
`--set-string graphql.app.captcha.turnstile.secret="${CAPTCHA_TURNSTILE_SECRET}"`,
|
||||
`--set graphql.app.objectStorage.r2.enabled=true`,
|
||||
`--set-string graphql.app.objectStorage.r2.accountId="${R2_ACCOUNT_ID}"`,
|
||||
`--set-string graphql.app.objectStorage.r2.accessKeyId="${R2_ACCESS_KEY_ID}"`,
|
||||
@@ -92,7 +98,10 @@ const createHelmCommand = ({ isDryRun }) => {
|
||||
`--set-string graphql.app.oauth.google.enabled=true`,
|
||||
`--set-string graphql.app.oauth.google.clientId="${AFFINE_GOOGLE_CLIENT_ID}"`,
|
||||
`--set-string graphql.app.oauth.google.clientSecret="${AFFINE_GOOGLE_CLIENT_SECRET}"`,
|
||||
`--set-string graphql.app.payment.stripe.apiKey="${STRIPE_API_KEY}"`,
|
||||
`--set-string graphql.app.payment.stripe.webhookKey="${STRIPE_WEBHOOK_KEY}"`,
|
||||
`--set graphql.app.experimental.enableJwstCodec=true`,
|
||||
`--set graphql.app.features.earlyAccessPreview=false`,
|
||||
`--set sync.replicaCount=${syncReplicaCount}`,
|
||||
`--set-string sync.image.tag="${imageTag}"`,
|
||||
...serviceAnnotations,
|
||||
|
||||
16
.github/actions/setup-maker/action.yml
vendored
@@ -1,16 +0,0 @@
|
||||
name: Setup maker
|
||||
description: 'Setup maker dmg for electron'
|
||||
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: 'Install @electron-forge/maker-dmg'
|
||||
if: runner.os == 'macos'
|
||||
shell: bash
|
||||
working-directory: ./apps/electron
|
||||
run: yarn add @electron-forge/maker-dmg --dev
|
||||
env:
|
||||
HUSKY: '0'
|
||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: '1'
|
||||
ELECTRON_SKIP_BINARY_DOWNLOAD: '1'
|
||||
SENTRYCLI_SKIP_DOWNLOAD: '1'
|
||||
45
.github/actions/setup-node/action.yml
vendored
@@ -21,6 +21,21 @@ inputs:
|
||||
description: 'set nmMode to hardlinks-local in .yarnrc.yml'
|
||||
required: false
|
||||
default: 'true'
|
||||
build-infra:
|
||||
description: 'Build infra'
|
||||
required: false
|
||||
default: 'true'
|
||||
build-plugins:
|
||||
description: 'Build plugins'
|
||||
required: false
|
||||
default: 'true'
|
||||
nmHoistingLimits:
|
||||
description: 'Set nmHoistingLimits in .yarnrc.yml'
|
||||
required: false
|
||||
enableScripts:
|
||||
description: 'Set enableScripts in .yarnrc.yml'
|
||||
required: false
|
||||
default: 'true'
|
||||
|
||||
runs:
|
||||
using: 'composite'
|
||||
@@ -34,30 +49,42 @@ runs:
|
||||
cache: 'yarn'
|
||||
|
||||
- name: Set nmMode
|
||||
if: ${{ inputs.hard-link-nm == 'true' }}
|
||||
if: ${{ inputs.hard-link-nm == 'false' }}
|
||||
shell: bash
|
||||
run: yarn config set nmMode hardlinks-local
|
||||
run: yarn config set nmMode classic
|
||||
|
||||
- name: Set nmHoistingLimits
|
||||
if: ${{ inputs.nmHoistingLimits }}
|
||||
shell: bash
|
||||
run: yarn config set nmHoistingLimits ${{ inputs.nmHoistingLimits }}
|
||||
|
||||
- name: Set enableScripts
|
||||
if: ${{ inputs.enableScripts == 'false' }}
|
||||
shell: bash
|
||||
run: yarn config set enableScripts false
|
||||
|
||||
- name: yarn install
|
||||
if: ${{ inputs.package-install == 'true' }}
|
||||
continue-on-error: true
|
||||
shell: bash
|
||||
run: yarn install ${{ inputs.extra-flags }}
|
||||
run: yarn ${{ inputs.extra-flags }}
|
||||
env:
|
||||
HUSKY: '0'
|
||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: '1'
|
||||
ELECTRON_SKIP_BINARY_DOWNLOAD: '1'
|
||||
SENTRYCLI_SKIP_DOWNLOAD: '1'
|
||||
DEBUG: '*'
|
||||
|
||||
- name: yarn install (try again)
|
||||
if: ${{ steps.install.outcome == 'failure' }}
|
||||
shell: bash
|
||||
run: yarn install ${{ inputs.extra-flags }}
|
||||
run: yarn ${{ inputs.extra-flags }}
|
||||
env:
|
||||
HUSKY: '0'
|
||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: '1'
|
||||
ELECTRON_SKIP_BINARY_DOWNLOAD: '1'
|
||||
SENTRYCLI_SKIP_DOWNLOAD: '1'
|
||||
DEBUG: '*'
|
||||
|
||||
- name: Get installed Playwright version
|
||||
id: playwright-version
|
||||
@@ -89,11 +116,11 @@ runs:
|
||||
${{ runner.os }}-${{ runner.arch }}-playwright-
|
||||
|
||||
# If the Playwright browser binaries weren't able to be restored, we tell
|
||||
# paywright to install everything for us.
|
||||
# playwright to install everything for us.
|
||||
- name: Install Playwright's dependencies
|
||||
shell: bash
|
||||
if: inputs.playwright-install == 'true' && steps.playwright-cache.outputs.cache-hit != 'true'
|
||||
run: yarn playwright install --with-deps
|
||||
if: inputs.playwright-install == 'true'
|
||||
run: yarn playwright install --with-deps chromium
|
||||
|
||||
- name: Get installed Electron version
|
||||
id: electron-version
|
||||
@@ -114,14 +141,16 @@ runs:
|
||||
- name: Install Electron binary
|
||||
shell: bash
|
||||
if: inputs.electron-install == 'true'
|
||||
run: node apps/electron/node_modules/electron/install.js
|
||||
run: node ./node_modules/electron/install.js
|
||||
env:
|
||||
ELECTRON_OVERRIDE_DIST_PATH: ./node_modules/.cache/electron
|
||||
|
||||
- name: Build Infra
|
||||
shell: bash
|
||||
if: inputs.build-infra == 'true'
|
||||
run: yarn run build:infra
|
||||
|
||||
- name: Build Plugins
|
||||
if: inputs.build-plugins == 'true'
|
||||
shell: bash
|
||||
run: yarn run build:plugins
|
||||
|
||||
22
.github/dependabot.yml
vendored
@@ -2,8 +2,30 @@ version: 2
|
||||
updates:
|
||||
- package-ecosystem: 'npm'
|
||||
directory: '/'
|
||||
groups:
|
||||
all-npm-dependencies:
|
||||
patterns:
|
||||
- '*'
|
||||
schedule:
|
||||
interval: 'weekly'
|
||||
versioning-strategy: increase
|
||||
commit-message:
|
||||
prefix: 'chore'
|
||||
- package-ecosystem: 'cargo'
|
||||
directory: '/'
|
||||
schedule:
|
||||
interval: 'weekly'
|
||||
versioning-strategy: auto
|
||||
commit-message:
|
||||
prefix: 'chore'
|
||||
groups:
|
||||
all-cargo-dependencies:
|
||||
patterns:
|
||||
- '*'
|
||||
|
||||
- package-ecosystem: 'github-actions'
|
||||
directory: '/'
|
||||
schedule:
|
||||
interval: 'daily'
|
||||
commit-message:
|
||||
prefix: 'ci'
|
||||
|
||||
2
.github/deployment/front/Dockerfile
vendored
@@ -1,6 +1,6 @@
|
||||
FROM openresty/openresty:1.21.4.1-0-buster
|
||||
WORKDIR /app
|
||||
COPY ./apps/core/dist ./dist
|
||||
COPY ./packages/frontend/core/dist ./dist
|
||||
COPY ./.github/deployment/front/nginx.conf /usr/local/openresty/nginx/conf/nginx.conf
|
||||
COPY ./.github/deployment/front/affine.nginx.conf /etc/nginx/conf.d/affine.nginx.conf
|
||||
|
||||
|
||||
2
.github/deployment/node/Dockerfile
vendored
@@ -1,6 +1,6 @@
|
||||
FROM node:18-bookworm-slim
|
||||
|
||||
COPY ./apps/server /app
|
||||
COPY ./packages/backend/server /app
|
||||
WORKDIR /app
|
||||
|
||||
RUN apt-get update && \
|
||||
|
||||
9
.github/helm/affine/charts/graphql/templates/captcha-secret.yaml
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
{{- if .Values.app.captcha.enabled -}}
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: "{{ .Values.app.captcha.secretName }}"
|
||||
type: Opaque
|
||||
data:
|
||||
turnstileSecret: {{ .Values.app.captcha.turnstile.secret | b64enc }}
|
||||
{{- end }}
|
||||
@@ -35,6 +35,8 @@ spec:
|
||||
key: key
|
||||
- name: NODE_ENV
|
||||
value: "{{ .Values.env }}"
|
||||
- name: NODE_OPTIONS
|
||||
value: "--max-old-space-size=4096"
|
||||
- name: NO_COLOR
|
||||
value: "1"
|
||||
- name: SERVER_FLAVOR
|
||||
@@ -73,6 +75,10 @@ spec:
|
||||
value: "{{ .Values.app.host }}"
|
||||
- name: ENABLE_R2_OBJECT_STORAGE
|
||||
value: "{{ .Values.app.objectStorage.r2.enabled }}"
|
||||
- name: ENABLE_CAPTCHA
|
||||
value: "{{ .Values.app.captcha.enabled }}"
|
||||
- name: FEATURES_EARLY_ACCESS_PREVIEW
|
||||
value: "{{ .Values.app.features.earlyAccessPreview }}"
|
||||
- name: OAUTH_EMAIL_SENDER
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
@@ -98,6 +104,16 @@ spec:
|
||||
secretKeyRef:
|
||||
name: "{{ .Values.app.oauth.email.secretName }}"
|
||||
key: password
|
||||
- name: STRIPE_API_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: "{{ .Values.app.payment.stripe.secretName }}"
|
||||
key: stripeAPIKey
|
||||
- name: STRIPE_WEBHOOK_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: "{{ .Values.app.payment.stripe.secretName }}"
|
||||
key: stripeWebhookKey
|
||||
- name: DOC_MERGE_INTERVAL
|
||||
value: "{{ .Values.app.doc.mergeInterval }}"
|
||||
{{ if .Values.app.experimental.enableJwstCodec }}
|
||||
@@ -126,6 +142,13 @@ spec:
|
||||
name: "{{ .Values.app.objectStorage.r2.secretName }}"
|
||||
key: bucket
|
||||
{{ end }}
|
||||
{{ if .Values.app.captcha.enabled }}
|
||||
- name: CAPTCHA_TURNSTILE_SECRET
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: "{{ .Values.app.captcha.secretName }}"
|
||||
key: turnstileSecret
|
||||
{{ end }}
|
||||
{{ if .Values.app.oauth.google.enabled }}
|
||||
- name: OAUTH_GOOGLE_CLIENT_ID
|
||||
valueFrom:
|
||||
|
||||
@@ -16,7 +16,7 @@ spec:
|
||||
containers:
|
||||
- name: {{ .Chart.Name }}
|
||||
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
|
||||
command: ["yarn", "prisma", "migrate", "deploy"]
|
||||
command: ["yarn", "predeploy"]
|
||||
env:
|
||||
- name: NODE_ENV
|
||||
value: "{{ .Values.env }}"
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
{{- if .Values.global.gke.enabled -}}
|
||||
apiVersion: monitoring.googleapis.com/v1
|
||||
kind: PodMonitoring
|
||||
metadata:
|
||||
name: "{{ .Chart.Name }}-monitoring"
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: "{{ include "graphql.name" . }}"
|
||||
endpoints:
|
||||
- port: {{ .Values.service.port }}
|
||||
interval: 30s
|
||||
{{- end }}
|
||||
8
.github/helm/affine/charts/graphql/templates/payment.yml
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: "{{ .Values.app.payment.stripe.secretName }}"
|
||||
type: Opaque
|
||||
data:
|
||||
stripeAPIKey: "{{ .Values.app.payment.stripe.apiKey | b64enc }}"
|
||||
stripeWebhookKey: "{{ .Values.app.payment.stripe.webhookKey | b64enc }}"
|
||||
17
.github/helm/affine/charts/graphql/values.yaml
vendored
@@ -22,6 +22,11 @@ app:
|
||||
secretName: jwt-private-key
|
||||
# base64 encoded ecdsa private key
|
||||
privateKey: ''
|
||||
captcha:
|
||||
enable: false
|
||||
secretName: captcha
|
||||
turnstile:
|
||||
secret: ''
|
||||
objectStorage:
|
||||
r2:
|
||||
enabled: false
|
||||
@@ -48,6 +53,13 @@ app:
|
||||
secretName: oauth-github
|
||||
clientId: ''
|
||||
clientSecret: ''
|
||||
payment:
|
||||
stripe:
|
||||
secretName: 'stripe'
|
||||
apiKey: ''
|
||||
webhookKey: ''
|
||||
features:
|
||||
earlyAccessPreview: false
|
||||
|
||||
serviceAccount:
|
||||
create: true
|
||||
@@ -60,11 +72,8 @@ podSecurityContext:
|
||||
fsGroup: 2000
|
||||
|
||||
resources:
|
||||
limits:
|
||||
cpu: '4'
|
||||
memory: 8Gi
|
||||
requests:
|
||||
cpu: '2'
|
||||
cpu: '4'
|
||||
memory: 4Gi
|
||||
|
||||
probe:
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
{{- if .Values.global.gke.enabled -}}
|
||||
apiVersion: monitoring.googleapis.com/v1
|
||||
kind: PodMonitoring
|
||||
metadata:
|
||||
name: "{{ .Chart.Name }}-monitoring"
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: "{{ include "sync.name" . }}"
|
||||
endpoints:
|
||||
- port: {{ .Values.service.port }}
|
||||
interval: 30s
|
||||
{{- end }}
|
||||
43
.github/labeler.yml
vendored
@@ -1,7 +1,7 @@
|
||||
docs:
|
||||
- 'docs/**/*'
|
||||
- '**/README.md'
|
||||
- 'packages/templates/**/*'
|
||||
- 'packages/frontend/templates/**/*'
|
||||
|
||||
test:
|
||||
- 'tests/**/*'
|
||||
@@ -10,40 +10,37 @@ test:
|
||||
|
||||
mod:dev:
|
||||
- 'scripts/**/*'
|
||||
- 'packages/cli/**/*'
|
||||
- 'packages/debug/**/*'
|
||||
- 'tools/cli/**/*'
|
||||
- 'packages/common/debug/**/*'
|
||||
|
||||
mod:plugin:
|
||||
- 'plugins/**/*'
|
||||
|
||||
plugin:bookmark-block:
|
||||
- 'plugins/bookmark-block/**/*'
|
||||
- 'packages/plugins/**/*'
|
||||
|
||||
plugin:copilot:
|
||||
- 'plugins/copilot/**/*'
|
||||
- 'packages/plugins/copilot/**/*'
|
||||
|
||||
mod:infra:
|
||||
- 'packages/infra/**/*'
|
||||
- 'packages/common/infra/**/*'
|
||||
|
||||
mod:sdk:
|
||||
- 'packages/sdk/**/*'
|
||||
- 'packages/common/sdk/**/*'
|
||||
|
||||
mod:plugin-cli:
|
||||
- 'packages/plugin-cli/**/*'
|
||||
- 'tools/plugin-cli/**/*'
|
||||
|
||||
mod:workspace: 'packages/workspace/**/*'
|
||||
mod:workspace: 'packages/frontend/workspace/**/*'
|
||||
|
||||
mod:i18n: 'packages/i18n/**/*'
|
||||
mod:i18n: 'packages/frontend/i18n/**/*'
|
||||
|
||||
mod:env: 'packages/env/**/*'
|
||||
mod:env: 'packages/common/env/**/*'
|
||||
|
||||
mod:hooks: 'packages/hooks/**/*'
|
||||
mod:hooks: 'packages/frontend/hooks/**/*'
|
||||
|
||||
mod:component: 'packages/component/**/*'
|
||||
mod:component: 'packages/frontend/component/**/*'
|
||||
|
||||
mod:storage: 'packages/storage/**/*'
|
||||
mod:storage: 'packages/backend/storage/**/*'
|
||||
|
||||
mod:native: 'packages/native/**/*'
|
||||
mod:native: 'packages/frontend/native/**/*'
|
||||
|
||||
mod:store:
|
||||
- '**/atoms/**/*'
|
||||
@@ -56,12 +53,10 @@ rust:
|
||||
- '**/rust-toolchain.toml'
|
||||
- '**/rustfmt.toml'
|
||||
|
||||
package:y-indexeddb: 'packages/y-indexeddb/**/*'
|
||||
package:y-indexeddb: 'packages/common/y-indexeddb/**/*'
|
||||
|
||||
app:core: 'apps/core/**/*'
|
||||
app:core: 'packages/frontend/core/**/*'
|
||||
|
||||
app:electron: 'apps/electron/**/*'
|
||||
app:electron: 'packages/frontend/electron/**/*'
|
||||
|
||||
app:server: 'apps/server/**/*'
|
||||
|
||||
app:docs: 'apps/docs/**/*'
|
||||
app:server: 'packages/backend/server/**/*'
|
||||
|
||||
51
.github/workflows/build-desktop.yml
vendored
@@ -3,7 +3,7 @@ name: Build(Desktop) & Test
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- canary
|
||||
- v[0-9]+.[0-9]+.x-staging
|
||||
- v[0-9]+.[0-9]+.x
|
||||
paths-ignore:
|
||||
@@ -15,7 +15,7 @@ on:
|
||||
pull_request:
|
||||
merge_group:
|
||||
branches:
|
||||
- master
|
||||
- canary
|
||||
- v[0-9]+.[0-9]+.x-staging
|
||||
- v[0-9]+.[0-9]+.x
|
||||
paths-ignore:
|
||||
@@ -38,27 +38,43 @@ jobs:
|
||||
build-core:
|
||||
name: Build @affine/core
|
||||
runs-on: ubuntu-latest
|
||||
environment: development
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
- name: Build Plugins
|
||||
run: yarn run build:plugins
|
||||
with:
|
||||
electron-install: false
|
||||
- name: Build Core
|
||||
run: yarn nx build @affine/core
|
||||
- name: Upload core artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: core
|
||||
path: ./apps/core/dist
|
||||
path: ./packages/frontend/core/dist
|
||||
if-no-files-found: error
|
||||
|
||||
build-native:
|
||||
name: Build Native
|
||||
runs-on: ubuntu-latest
|
||||
needs: build-core
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
- name: Build AFFiNE native
|
||||
uses: ./.github/actions/build-rust
|
||||
with:
|
||||
target: x86_64-unknown-linux-gnu
|
||||
package: '@affine/native'
|
||||
nx_token: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }}
|
||||
- name: Run tests
|
||||
run: yarn test
|
||||
working-directory: ./packages/frontend/native
|
||||
|
||||
desktop-test:
|
||||
name: Desktop Test
|
||||
runs-on: ${{ matrix.spec.os }}
|
||||
environment: development
|
||||
strategy:
|
||||
fail-fast: false
|
||||
# all combinations: macos-latest x64, macos-latest arm64, windows-latest x64, ubuntu-latest x64
|
||||
@@ -94,13 +110,15 @@ jobs:
|
||||
}
|
||||
needs: build-core
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
timeout-minutes: 10
|
||||
with:
|
||||
extra-flags: workspaces focus @affine/electron @affine/monorepo @affine-test/affine-desktop
|
||||
playwright-install: true
|
||||
hard-link-nm: false
|
||||
enableScripts: false
|
||||
|
||||
- name: Build AFFiNE native
|
||||
uses: ./.github/actions/build-rust
|
||||
@@ -108,20 +126,18 @@ jobs:
|
||||
target: ${{ matrix.spec.target }}
|
||||
package: '@affine/native'
|
||||
nx_token: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }}
|
||||
|
||||
- name: Run unit tests
|
||||
if: ${{ matrix.spec.test }}
|
||||
shell: bash
|
||||
run: yarn vitest
|
||||
working-directory: ./apps/electron
|
||||
working-directory: packages/frontend/electron
|
||||
|
||||
- name: Download core artifact
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: core
|
||||
path: apps/electron/resources/web-static
|
||||
|
||||
- name: Build Plugins
|
||||
run: yarn run build:plugins
|
||||
path: packages/frontend/electron/resources/web-static
|
||||
|
||||
- name: Build Desktop Layers
|
||||
run: yarn workspace @affine/electron build
|
||||
@@ -142,13 +158,14 @@ jobs:
|
||||
if: ${{ matrix.spec.os == 'macos-latest' && matrix.spec.arch == 'arm64' }}
|
||||
env:
|
||||
SKIP_BUNDLE: true
|
||||
run: yarn workspace @affine/electron make --platform=darwin --arch=arm64
|
||||
SKIP_WEB_BUILD: true
|
||||
HOIST_NODE_MODULES: 1
|
||||
run: yarn workspace @affine/electron package --platform=darwin --arch=arm64
|
||||
|
||||
- name: Output check
|
||||
if: ${{ matrix.spec.os == 'macos-latest' && matrix.spec.arch == 'arm64' }}
|
||||
run: |
|
||||
yarn ts-node-esm ./scripts/macos-arm64-output-check.mts
|
||||
working-directory: apps/electron
|
||||
yarn workspace @affine/electron ts-node ./scripts/macos-arm64-output-check.ts
|
||||
|
||||
- name: Collect code coverage report
|
||||
if: ${{ matrix.spec.test }}
|
||||
|
||||
47
.github/workflows/build-server.yml
vendored
@@ -3,7 +3,7 @@ name: Build(Server) & Test
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- canary
|
||||
- v[0-9]+.[0-9]+.x-staging
|
||||
- v[0-9]+.[0-9]+.x
|
||||
paths-ignore:
|
||||
@@ -15,7 +15,7 @@ on:
|
||||
pull_request:
|
||||
merge_group:
|
||||
branches:
|
||||
- master
|
||||
- canary
|
||||
- v[0-9]+.[0-9]+.x-staging
|
||||
- v[0-9]+.[0-9]+.x
|
||||
paths-ignore:
|
||||
@@ -39,13 +39,17 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
RUSTFLAGS: '-C debuginfo=1'
|
||||
environment: development
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
- name: Setup Rust
|
||||
with:
|
||||
extra-flags: workspaces focus @affine/storage
|
||||
electron-install: false
|
||||
build-infra: false
|
||||
build-plugins: false
|
||||
- name: Build Rust
|
||||
uses: ./.github/actions/build-rust
|
||||
with:
|
||||
target: 'x86_64-unknown-linux-gnu'
|
||||
@@ -55,13 +59,12 @@ jobs:
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: storage.node
|
||||
path: ./packages/storage/storage.node
|
||||
path: ./packages/backend/storage/storage.node
|
||||
if-no-files-found: error
|
||||
|
||||
server-test:
|
||||
name: Server Test
|
||||
runs-on: ubuntu-latest
|
||||
environment: development
|
||||
needs: build-storage
|
||||
services:
|
||||
postgres:
|
||||
@@ -81,10 +84,12 @@ jobs:
|
||||
- 1025:1025
|
||||
- 8025:8025
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
electron-install: false
|
||||
|
||||
- name: Initialize database
|
||||
run: |
|
||||
@@ -102,7 +107,7 @@ jobs:
|
||||
DATABASE_URL: postgresql://affine:affine@localhost:5432/affine
|
||||
|
||||
- name: Run init-db script
|
||||
run: yarn workspace @affine/server exec ts-node-esm ./scripts/init-db.ts
|
||||
run: yarn workspace @affine/server exec ts-node ./scripts/init-db.ts
|
||||
env:
|
||||
DATABASE_URL: postgresql://affine:affine@localhost:5432/affine
|
||||
|
||||
@@ -110,7 +115,7 @@ jobs:
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: storage.node
|
||||
path: ./apps/server
|
||||
path: ./packages/backend/server
|
||||
|
||||
- name: Run server tests
|
||||
run: yarn workspace @affine/server test:coverage
|
||||
@@ -122,7 +127,7 @@ jobs:
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
files: ./apps/server/.coverage/lcov.info
|
||||
files: ./packages/backend/server/.coverage/lcov.info
|
||||
flags: server-test
|
||||
name: affine
|
||||
fail_ci_if_error: false
|
||||
@@ -130,7 +135,6 @@ jobs:
|
||||
server-e2e-test:
|
||||
name: Server E2E Test
|
||||
runs-on: ubuntu-latest
|
||||
environment: development
|
||||
needs: build-storage
|
||||
services:
|
||||
postgres:
|
||||
@@ -150,7 +154,7 @@ jobs:
|
||||
- 1025:1025
|
||||
- 8025:8025
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
@@ -173,7 +177,7 @@ jobs:
|
||||
DATABASE_URL: postgresql://affine:affine@localhost:5432/affine
|
||||
|
||||
- name: Run init-db script
|
||||
run: yarn workspace @affine/server exec ts-node-esm ./scripts/init-db.ts
|
||||
run: yarn workspace @affine/server exec ts-node ./scripts/init-db.ts
|
||||
env:
|
||||
DATABASE_URL: postgresql://affine:affine@localhost:5432/affine
|
||||
|
||||
@@ -181,7 +185,7 @@ jobs:
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: storage.node
|
||||
path: ./apps/server
|
||||
path: ./packages/backend/server
|
||||
|
||||
- name: Run playwright tests
|
||||
run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- yarn workspace @affine-test/affine-cloud e2e --forbid-only
|
||||
@@ -212,7 +216,6 @@ jobs:
|
||||
server-desktop-e2e-test:
|
||||
name: Server Desktop E2E Test
|
||||
runs-on: ubuntu-latest
|
||||
environment: development
|
||||
needs: build-storage
|
||||
services:
|
||||
postgres:
|
||||
@@ -232,7 +235,7 @@ jobs:
|
||||
- 1025:1025
|
||||
- 8025:8025
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
@@ -257,15 +260,13 @@ jobs:
|
||||
|
||||
- name: Generate prisma client
|
||||
run: |
|
||||
yarn exec prisma generate
|
||||
yarn exec prisma db push
|
||||
working-directory: apps/server
|
||||
yarn workspace @affine/server exec prisma generate
|
||||
yarn workspace @affine/server prisma db push
|
||||
env:
|
||||
DATABASE_URL: postgresql://affine:affine@localhost:5432/affine
|
||||
|
||||
- name: Run init-db script
|
||||
run: yarn exec ts-node-esm ./scripts/init-db.ts
|
||||
working-directory: apps/server
|
||||
run: yarn workspace @affine/server exec ts-node ./scripts/init-db.ts
|
||||
env:
|
||||
DATABASE_URL: postgresql://affine:affine@localhost:5432/affine
|
||||
|
||||
@@ -273,7 +274,7 @@ jobs:
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: storage.node
|
||||
path: ./apps/server
|
||||
path: ./packages/backend/server
|
||||
|
||||
- name: Build Plugins
|
||||
run: yarn run build:plugins
|
||||
|
||||
126
.github/workflows/build.yml
vendored
@@ -3,7 +3,7 @@ name: Build & Test
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- canary
|
||||
- v[0-9]+.[0-9]+.x-staging
|
||||
- v[0-9]+.[0-9]+.x
|
||||
paths-ignore:
|
||||
@@ -15,7 +15,7 @@ on:
|
||||
pull_request:
|
||||
merge_group:
|
||||
branches:
|
||||
- master
|
||||
- canary
|
||||
- v[0-9]+.[0-9]+.x-staging
|
||||
- v[0-9]+.[0-9]+.x
|
||||
paths-ignore:
|
||||
@@ -39,10 +39,12 @@ jobs:
|
||||
lint:
|
||||
name: Lint
|
||||
runs-on: ubuntu-latest
|
||||
environment: development
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Run oxlint
|
||||
# oxlint is fast, so wrong code will fail quickly
|
||||
run: yarn dlx oxlint@latest .
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
@@ -64,54 +66,18 @@ jobs:
|
||||
check-yarn-binary:
|
||||
name: Check yarn binary
|
||||
runs-on: ubuntu-latest
|
||||
environment: development
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Run check
|
||||
run: |
|
||||
yarn set version $(node -e "console.log(require('./package.json').packageManager.split('@')[1])")
|
||||
git diff --exit-code
|
||||
|
||||
build-prototype:
|
||||
name: Build Prototype
|
||||
runs-on: ubuntu-latest
|
||||
environment: development
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
electron-install: false
|
||||
- name: Build Prototype
|
||||
run: yarn nx build prototype
|
||||
- name: Upload prototype artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: prototype
|
||||
path: ./apps/prototype/dist
|
||||
if-no-files-found: error
|
||||
|
||||
build-docs:
|
||||
name: Build Docs
|
||||
runs-on: ubuntu-latest
|
||||
environment: development
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
electron-install: false
|
||||
- run: yarn nx build @affine/docs
|
||||
env:
|
||||
NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }}
|
||||
|
||||
e2e-plugin-test:
|
||||
name: E2E Plugin Test
|
||||
runs-on: ubuntu-latest
|
||||
environment: development
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
@@ -142,49 +108,6 @@ jobs:
|
||||
path: ./test-results
|
||||
if-no-files-found: ignore
|
||||
|
||||
e2e-prototype-test:
|
||||
name: E2E Prototype Test
|
||||
runs-on: ubuntu-latest
|
||||
environment: development
|
||||
needs: build-prototype
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
playwright-install: true
|
||||
electron-install: false
|
||||
- name: Download prototype artifact
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: prototype
|
||||
path: ./apps/prototype/dist
|
||||
- name: Run playwright tests
|
||||
run: yarn e2e --forbid-only
|
||||
working-directory: tests/affine-prototype
|
||||
env:
|
||||
COVERAGE: true
|
||||
|
||||
# - name: Collect code coverage report
|
||||
# run: yarn exec nyc report -t .nyc_output --report-dir .coverage --reporter=lcov
|
||||
|
||||
# - name: Upload e2e test coverage results
|
||||
# uses: codecov/codecov-action@v3
|
||||
# with:
|
||||
# token: ${{ secrets.CODECOV_TOKEN }}
|
||||
# files: ./.coverage/lcov.info
|
||||
# flags: e2etest-prototype
|
||||
# name: affine
|
||||
# fail_ci_if_error: false
|
||||
|
||||
- name: Upload test results
|
||||
if: ${{ failure() }}
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: test-results-e2e-prototype
|
||||
path: ./test-results
|
||||
if-no-files-found: ignore
|
||||
|
||||
e2e-test:
|
||||
name: E2E Test
|
||||
runs-on: ubuntu-latest
|
||||
@@ -192,9 +115,8 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
shard: [1, 2, 3, 4, 5]
|
||||
environment: development
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
@@ -230,48 +152,42 @@ jobs:
|
||||
e2e-migration-test:
|
||||
name: E2E Migration Test
|
||||
runs-on: ubuntu-latest
|
||||
environment: development
|
||||
strategy:
|
||||
matrix:
|
||||
spec:
|
||||
- { package: 0.7.0-canary.18 }
|
||||
- { package: 0.8.0-canary.7 }
|
||||
- { package: 0.8.3 }
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
playwright-install: true
|
||||
electron-install: false
|
||||
|
||||
- name: Unzip
|
||||
run: yarn unzip
|
||||
working-directory: ./tests/affine-legacy/${{ matrix.spec.package }}
|
||||
|
||||
- name: Run playwright tests
|
||||
run: yarn e2e --forbid-only
|
||||
working-directory: ./tests/affine-legacy/${{ matrix.spec.package }}
|
||||
run: yarn workspace @affine-test/affine-migration e2e --forbid-only
|
||||
|
||||
- name: Upload test results
|
||||
if: ${{ failure() }}
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: test-results-e2e-migration-${{ matrix.spec.package }}
|
||||
path: ./tests/affine-legacy/${{ matrix.spec.package }}/test-results
|
||||
name: test-results-e2e-migration
|
||||
path: ./tests/affine-migration/test-results
|
||||
if-no-files-found: ignore
|
||||
|
||||
unit-test:
|
||||
name: Unit Test
|
||||
runs-on: ubuntu-latest
|
||||
environment: development
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
electron-install: false
|
||||
|
||||
- name: Build AFFiNE native
|
||||
uses: ./.github/actions/build-rust
|
||||
with:
|
||||
target: x86_64-unknown-linux-gnu
|
||||
package: '@affine/native'
|
||||
nx_token: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }}
|
||||
|
||||
- name: Unit Test
|
||||
run: yarn nx test:coverage @affine/monorepo
|
||||
|
||||
|
||||
2
.github/workflows/cache-cleanup.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Cleanup
|
||||
run: |
|
||||
|
||||
2
.github/workflows/cancel.yml
vendored
@@ -11,7 +11,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 2
|
||||
steps:
|
||||
- uses: styfle/cancel-workflow-action@0.11.0
|
||||
- uses: styfle/cancel-workflow-action@0.12.0
|
||||
with:
|
||||
# See https://api.github.com/repos/toeverything/AFFiNE/actions/workflows
|
||||
workflow_id: 44038251, 61883931, 65188160, 66789140
|
||||
|
||||
6
.github/workflows/codeql.yml
vendored
@@ -13,11 +13,11 @@ name: 'CodeQL'
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
branches: [canary]
|
||||
pull_request:
|
||||
merge_group:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [master]
|
||||
branches: [canary]
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
@@ -37,7 +37,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
|
||||
86
.github/workflows/deploy.yml
vendored
@@ -1,15 +1,10 @@
|
||||
name: Deploy
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
tags:
|
||||
- 'v[0-9]+.[0-9]+.[0-9]+-canary.[0-9]+'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
flavor:
|
||||
description: 'Build type (canary, beta, internal or stable)'
|
||||
description: 'Build type (canary, beta, or stable)'
|
||||
type: string
|
||||
default: canary
|
||||
|
||||
@@ -24,7 +19,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
environment: ${{ github.event.inputs.flavor }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
@@ -35,15 +30,14 @@ jobs:
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: server-dist
|
||||
path: ./apps/server/dist
|
||||
path: ./packages/backend/server/dist
|
||||
if-no-files-found: error
|
||||
build-core:
|
||||
name: Build @affine/core
|
||||
runs-on: ubuntu-latest
|
||||
environment: production
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
- name: Build Plugins
|
||||
@@ -57,20 +51,20 @@ jobs:
|
||||
BUILD_TYPE_OVERRIDE: ${{ github.event.inputs.flavor }}
|
||||
SHOULD_REPORT_TRACE: true
|
||||
TRACE_REPORT_ENDPOINT: ${{ secrets.TRACE_REPORT_ENDPOINT }}
|
||||
CAPTCHA_SITE_KEY: ${{ secrets.CAPTCHA_SITE_KEY }}
|
||||
- name: Upload core artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: core
|
||||
path: ./apps/core/dist
|
||||
path: ./packages/frontend/core/dist
|
||||
if-no-files-found: error
|
||||
|
||||
build-storage:
|
||||
name: Build Storage
|
||||
runs-on: ubuntu-latest
|
||||
environment: ${{ github.event.inputs.flavor }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
- name: Setup Rust
|
||||
@@ -83,34 +77,62 @@ jobs:
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: storage.node
|
||||
path: ./packages/storage/storage.node
|
||||
path: ./packages/backend/storage/storage.node
|
||||
if-no-files-found: error
|
||||
|
||||
build-storage-arm64:
|
||||
name: Build Storage arm64
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
- name: Setup Rust
|
||||
uses: ./.github/actions/build-rust
|
||||
with:
|
||||
target: 'aarch64-unknown-linux-gnu'
|
||||
package: '@affine/storage'
|
||||
nx_token: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }}
|
||||
- name: Upload storage.node
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: storage.arm64.node
|
||||
path: ./packages/backend/storage/storage.node
|
||||
if-no-files-found: error
|
||||
|
||||
build-docker:
|
||||
name: Build Docker
|
||||
runs-on: ubuntu-latest
|
||||
environment: ${{ github.event.inputs.flavor }}
|
||||
needs:
|
||||
- build-server
|
||||
- build-core
|
||||
- build-storage
|
||||
- build-storage-arm64
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Download core artifact
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: core
|
||||
path: ./apps/core/dist
|
||||
path: ./packages/frontend/core/dist
|
||||
- name: Download server dist
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: server-dist
|
||||
path: ./apps/server/dist
|
||||
path: ./packages/backend/server/dist
|
||||
- name: Download storage.node
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: storage.node
|
||||
path: ./apps/server
|
||||
path: ./packages/backend/server
|
||||
- name: Download storage.node arm64
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: storage.arm64.node
|
||||
path: ./packages/backend/storage
|
||||
- name: move storage.arm64.node
|
||||
run: mv ./packages/backend/storage/storage.node ./packages/backend/server/storage.arm64.node
|
||||
- name: Setup env
|
||||
run: |
|
||||
echo "GIT_SHORT_HASH=$(git rev-parse --short HEAD)" >> "$GITHUB_ENV"
|
||||
@@ -122,18 +144,18 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v2
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
logout: false
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
uses: docker/setup-qemu-action@v3
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Build front Dockerfile
|
||||
uses: docker/build-push-action@v4
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
@@ -146,20 +168,22 @@ jobs:
|
||||
# setup node without cache configuration
|
||||
# Prisma cache is not compatible with docker build cache
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
registry-url: https://npm.pkg.github.com
|
||||
scope: '@toeverything'
|
||||
|
||||
- name: Install Node.js dependencies
|
||||
run: yarn workspaces focus @affine/server --production
|
||||
run: |
|
||||
yarn config set --json supportedArchitectures.cpu '["x64", "arm64"]'
|
||||
yarn workspaces focus @affine/server --production
|
||||
|
||||
- name: Generate Prisma client
|
||||
run: yarn workspace @affine/server prisma generate
|
||||
|
||||
- name: Build graphql Dockerfile
|
||||
uses: docker/build-push-action@v4
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
@@ -167,11 +191,11 @@ jobs:
|
||||
platforms: linux/amd64,linux/arm64
|
||||
provenance: true
|
||||
file: .github/deployment/node/Dockerfile
|
||||
tags: ghcr.io/toeverything/affine-graphql:${{env.RELEASE_FLAVOR}}-${{ env.GIT_SHORT_HASH }},ghcr.io/toeverything/affine-front:${{env.RELEASE_FLAVOR}}
|
||||
tags: ghcr.io/toeverything/affine-graphql:${{env.RELEASE_FLAVOR}}-${{ env.GIT_SHORT_HASH }},ghcr.io/toeverything/affine-graphql:${{env.RELEASE_FLAVOR}}
|
||||
|
||||
deploy:
|
||||
name: Deploy to cluster
|
||||
if: ${{ github.event_name == 'workflow_dispatch' || github.ref_type == 'tag' }}
|
||||
if: ${{ github.event_name == 'workflow_dispatch' }}
|
||||
environment: ${{ github.event.inputs.flavor }}
|
||||
permissions:
|
||||
contents: 'write'
|
||||
@@ -180,7 +204,7 @@ jobs:
|
||||
- build-docker
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Deploy to dev
|
||||
uses: ./.github/actions/deploy
|
||||
with:
|
||||
@@ -197,6 +221,8 @@ jobs:
|
||||
R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
|
||||
R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
|
||||
R2_BUCKET: ${{ secrets.R2_BUCKET }}
|
||||
ENABLE_CAPTCHA: true
|
||||
CAPTCHA_TURNSTILE_SECRET: ${{ secrets.CAPTCHA_TURNSTILE_SECRET }}
|
||||
OAUTH_EMAIL_SENDER: ${{ secrets.OAUTH_EMAIL_SENDER }}
|
||||
OAUTH_EMAIL_LOGIN: ${{ secrets.OAUTH_EMAIL_LOGIN }}
|
||||
OAUTH_EMAIL_PASSWORD: ${{ secrets.OAUTH_EMAIL_PASSWORD }}
|
||||
@@ -212,3 +238,5 @@ jobs:
|
||||
REDIS_HOST: ${{ secrets.REDIS_HOST }}
|
||||
REDIS_PASSWORD: ${{ secrets.REDIS_PASSWORD }}
|
||||
CLOUD_SQL_IAM_ACCOUNT: ${{ secrets.CLOUD_SQL_IAM_ACCOUNT }}
|
||||
STRIPE_API_KEY: ${{ secrets.STRIPE_API_KEY }}
|
||||
STRIPE_WEBHOOK_KEY: ${{ secrets.STRIPE_WEBHOOK_KEY }}
|
||||
|
||||
30
.github/workflows/dispatch-deploy.yml
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
name: Dispatch Deploy by tag
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v[0-9]+.[0-9]+.[0-9]+-canary.[0-9]+'
|
||||
|
||||
jobs:
|
||||
dispatch-deploy-by-tag:
|
||||
runs-on: ubuntu-latest
|
||||
name: Setup deploy environment
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
extra-flags: 'workspaces focus @affine/monorepo'
|
||||
hard-link-nm: false
|
||||
electron-install: false
|
||||
build-infra: false
|
||||
build-plugins: false
|
||||
- name: Setup output value
|
||||
id: flavor
|
||||
run: |
|
||||
node -e "const env = require('semver').parse('${{ github.ref_name }}').prerelease[0] ?? 'stable'; console.log(`flavor=${env}`)" >> "$GITHUB_OUTPUT"
|
||||
- name: dispatch deploy
|
||||
uses: benc-uk/workflow-dispatch@v1
|
||||
with:
|
||||
workflow: deploy.yml
|
||||
inputs: '{ "flavor": "${{ steps.flavor.outputs.flavor }}" }'
|
||||
6
.github/workflows/helm-releaser.yml
vendored
@@ -2,7 +2,7 @@ name: Release Charts
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
branches: [canary]
|
||||
paths:
|
||||
- '.github/helm/**/Chart.yml'
|
||||
|
||||
@@ -11,12 +11,12 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Checkout Helm chart repo
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: toeverything/helm-charts
|
||||
path: .helm-chart-repo
|
||||
|
||||
2
.github/workflows/label-checker.yml
vendored
@@ -6,7 +6,7 @@ on:
|
||||
- labeled
|
||||
- unlabeled
|
||||
branches:
|
||||
- master
|
||||
- canary
|
||||
|
||||
jobs:
|
||||
check_labels:
|
||||
|
||||
14
.github/workflows/languages-sync.yml
vendored
@@ -2,15 +2,15 @@ name: Languages Sync
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ['master']
|
||||
branches: ['canary']
|
||||
paths:
|
||||
- 'packages/i18n/**'
|
||||
- 'packages/frontend/i18n/**'
|
||||
- '.github/workflows/languages-sync.yml'
|
||||
- '!.github/actions/setup-node/action.yml'
|
||||
pull_request_target:
|
||||
branches: ['master']
|
||||
branches: ['canary']
|
||||
paths:
|
||||
- 'packages/i18n/**'
|
||||
- 'packages/frontend/i18n/**'
|
||||
- '.github/workflows/languages-sync.yml'
|
||||
- '!.github/actions/setup-node/action.yml'
|
||||
workflow_dispatch:
|
||||
@@ -19,17 +19,17 @@ jobs:
|
||||
main:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
- name: Check Language Key
|
||||
if: github.ref != 'refs/heads/master'
|
||||
if: github.ref != 'refs/heads/canary'
|
||||
run: yarn workspace @affine/i18n run sync-languages:check
|
||||
env:
|
||||
TOLGEE_API_KEY: ${{ secrets.TOLGEE_API_KEY }}
|
||||
|
||||
- name: Sync Languages
|
||||
if: github.ref == 'refs/heads/master'
|
||||
if: github.ref == 'refs/heads/canary'
|
||||
run: yarn workspace @affine/i18n run sync-languages
|
||||
env:
|
||||
TOLGEE_API_KEY: ${{ secrets.TOLGEE_API_KEY }}
|
||||
|
||||
71
.github/workflows/nightly-build.yml
vendored
@@ -2,6 +2,15 @@ name: Build Canary Desktop App on Staging Branch
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
channel_override:
|
||||
description: 'channel type (canary, beta, or stable)'
|
||||
type: choice
|
||||
default: beta
|
||||
options:
|
||||
- canary
|
||||
- beta
|
||||
- stable
|
||||
push:
|
||||
branches:
|
||||
# 0.6.x-staging
|
||||
@@ -27,27 +36,28 @@ concurrency:
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
# BUILD_TYPE => app icon, app name, etc
|
||||
BUILD_TYPE: internal
|
||||
# BUILD_TYPE_OVERRIDE => channel type (canary, beta, or stable) - get the channel type (the api configs)
|
||||
BUILD_TYPE_OVERRIDE: ${{ github.event.inputs.channel_override || 'beta' }}
|
||||
|
||||
jobs:
|
||||
set-build-version:
|
||||
runs-on: ubuntu-latest
|
||||
environment: production
|
||||
outputs:
|
||||
version: 0.0.0-internal.${{ steps.version.outputs.version }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: toeverything/set-build-version@latest
|
||||
- id: version
|
||||
run: echo ::set-output name=version::${{ env.BUILD_VERSION }}
|
||||
|
||||
before-make:
|
||||
runs-on: ubuntu-latest
|
||||
environment: production
|
||||
needs:
|
||||
- set-build-version
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
- name: Setup @sentry/cli
|
||||
@@ -55,7 +65,7 @@ jobs:
|
||||
- name: Replace Version
|
||||
run: ./scripts/set-version.sh ${{ needs.set-build-version.outputs.version }}
|
||||
- name: generate-assets
|
||||
working-directory: apps/electron
|
||||
working-directory: packages/frontend/electron
|
||||
run: yarn generate-assets
|
||||
env:
|
||||
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
|
||||
@@ -68,10 +78,9 @@ jobs:
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: core
|
||||
path: apps/electron/resources/web-static
|
||||
path: packages/frontend/electron/resources/web-static
|
||||
|
||||
make-distribution:
|
||||
environment: production
|
||||
strategy:
|
||||
# all combinations: macos-latest x64, macos-latest arm64, ubuntu-latest x64
|
||||
# For windows, we need a separate approach
|
||||
@@ -103,13 +112,26 @@ jobs:
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
SKIP_GENERATE_ASSETS: 1
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Node.js
|
||||
timeout-minutes: 10
|
||||
if: ${{ matrix.spec.platform == 'darwin' }}
|
||||
uses: ./.github/actions/setup-node
|
||||
- name: Setup Maker
|
||||
with:
|
||||
extra-flags: workspaces focus @affine/electron @affine/monorepo
|
||||
hard-link-nm: false
|
||||
build-plugins: false
|
||||
nmHoistingLimits: workspaces
|
||||
enableScripts: false
|
||||
- name: Setup Node.js
|
||||
timeout-minutes: 10
|
||||
uses: ./.github/actions/setup-maker
|
||||
if: ${{ matrix.spec.platform != 'darwin' }}
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
extra-flags: workspaces focus @affine/electron @affine/monorepo
|
||||
hard-link-nm: false
|
||||
build-plugins: false
|
||||
nmHoistingLimits: workspaces
|
||||
- name: Build AFFiNE native
|
||||
uses: ./.github/actions/build-rust
|
||||
with:
|
||||
@@ -121,7 +143,7 @@ jobs:
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: core
|
||||
path: apps/electron/resources/web-static
|
||||
path: packages/frontend/electron/resources/web-static
|
||||
|
||||
- name: Build Plugins
|
||||
run: yarn run build:plugins
|
||||
@@ -138,28 +160,32 @@ jobs:
|
||||
|
||||
- name: make
|
||||
run: yarn workspace @affine/electron make --platform=${{ matrix.spec.platform }} --arch=${{ matrix.spec.arch }}
|
||||
env:
|
||||
SKIP_PLUGIN_BUILD: 1
|
||||
SKIP_WEB_BUILD: 1
|
||||
HOIST_NODE_MODULES: 1
|
||||
|
||||
- name: Save artifacts (mac)
|
||||
if: ${{ matrix.spec.platform == 'darwin' }}
|
||||
run: |
|
||||
mkdir -p builds
|
||||
mv apps/electron/out/*/make/*.dmg ./builds/affine-${{ env.BUILD_TYPE }}-macos-${{ matrix.spec.arch }}.dmg
|
||||
mv apps/electron/out/*/make/zip/darwin/${{ matrix.spec.arch }}/*.zip ./builds/affine-${{ env.BUILD_TYPE }}-macos-${{ matrix.spec.arch }}.zip
|
||||
mv packages/frontend/electron/out/*/make/*.dmg ./builds/affine-${{ env.BUILD_TYPE }}-macos-${{ matrix.spec.arch }}.dmg
|
||||
mv packages/frontend/electron/out/*/make/zip/darwin/${{ matrix.spec.arch }}/*.zip ./builds/affine-${{ env.BUILD_TYPE }}-macos-${{ matrix.spec.arch }}.zip
|
||||
- name: Save artifacts (windows)
|
||||
if: ${{ matrix.spec.platform == 'win32' }}
|
||||
run: |
|
||||
mkdir -p builds
|
||||
mv apps/electron/out/*/make/zip/win32/x64/AFFiNE*-win32-x64-*.zip ./builds/affine-${{ env.BUILD_TYPE }}-windows-x64.zip
|
||||
mv apps/electron/out/*/make/squirrel.windows/x64/*.exe ./builds/affine-${{ env.BUILD_TYPE }}-windows-x64.exe
|
||||
mv apps/electron/out/*/make/squirrel.windows/x64/*.msi ./builds/affine-${{ env.BUILD_TYPE }}-windows-x64.msi
|
||||
mv apps/electron/out/*/make/squirrel.windows/x64/*.nupkg ./builds/affine-${{ env.BUILD_TYPE }}-windows-x64.nupkg
|
||||
mv packages/frontend/electron/out/*/make/zip/win32/x64/AFFiNE*-win32-x64-*.zip ./builds/affine-${{ env.BUILD_TYPE }}-windows-x64.zip
|
||||
mv packages/frontend/electron/out/*/make/squirrel.windows/x64/*.exe ./builds/affine-${{ env.BUILD_TYPE }}-windows-x64.exe
|
||||
mv packages/frontend/electron/out/*/make/squirrel.windows/x64/*.msi ./builds/affine-${{ env.BUILD_TYPE }}-windows-x64.msi
|
||||
mv packages/frontend/electron/out/*/make/squirrel.windows/x64/*.nupkg ./builds/affine-${{ env.BUILD_TYPE }}-windows-x64.nupkg
|
||||
|
||||
- name: Save artifacts (linux)
|
||||
if: ${{ matrix.spec.platform == 'linux' }}
|
||||
run: |
|
||||
mkdir -p builds
|
||||
mv apps/electron/out/*/make/zip/linux/x64/*.zip ./builds/affine-${{ env.BUILD_TYPE }}-linux-x64.zip
|
||||
mv apps/electron/out/*/make/AppImage/x64/*.AppImage ./builds/affine-${{ env.BUILD_TYPE }}-linux-x64.AppImage
|
||||
mv packages/frontend/electron/out/*/make/zip/linux/x64/*.zip ./builds/affine-${{ env.BUILD_TYPE }}-linux-x64.zip
|
||||
mv packages/frontend/electron/out/*/make/AppImage/x64/*.AppImage ./builds/affine-${{ env.BUILD_TYPE }}-linux-x64.AppImage
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
@@ -174,7 +200,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Download Artifacts (macos-x64)
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
@@ -196,13 +222,12 @@ jobs:
|
||||
name: affine-linux-x64-builds
|
||||
path: ./
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
- name: Generate Release yml
|
||||
run: |
|
||||
cp ./apps/electron/scripts/generate-yml.js .
|
||||
node generate-yml.js
|
||||
node ./packages/frontend/electron/scripts/generate-yml.js
|
||||
env:
|
||||
RELEASE_VERSION: ${{ needs.set-build-version.outputs.version }}
|
||||
- name: Create Release Draft
|
||||
|
||||
54
.github/workflows/nx.yml
vendored
@@ -1,54 +0,0 @@
|
||||
name: NX
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- v[0-9]+.[0-9]+.x-staging
|
||||
- v[0-9]+.[0-9]+.x
|
||||
paths-ignore:
|
||||
- README.md
|
||||
- .github/**
|
||||
- '!.github/workflows/nx.yml'
|
||||
- '!.github/actions/build-rust/action.yml'
|
||||
- '!.github/actions/setup-node/action.yml'
|
||||
pull_request:
|
||||
merge_group:
|
||||
branches:
|
||||
- master
|
||||
- v[0-9]+.[0-9]+.x-staging
|
||||
- v[0-9]+.[0-9]+.x
|
||||
paths-ignore:
|
||||
- README.md
|
||||
- .github/**
|
||||
- '!.github/workflows/nx.yml'
|
||||
- '!.github/actions/build-rust/action.yml'
|
||||
- '!.github/actions/setup-node/action.yml'
|
||||
|
||||
jobs:
|
||||
main:
|
||||
name: Nx Cloud - Main Job
|
||||
uses: nrwl/ci/.github/workflows/nx-cloud-main.yml@v0.13.0
|
||||
with:
|
||||
runs-on: macos-latest
|
||||
main-branch-name: master
|
||||
number-of-agents: 5
|
||||
init-commands: |
|
||||
yarn exec nx-cloud start-ci-run --stop-agents-after="build" --agent-count=5
|
||||
environment-variables: |
|
||||
BUILD_TYPE=canary
|
||||
# parallel-commands: |
|
||||
# yarn exec nx-cloud record -- yarn exec nx format:check
|
||||
parallel-commands-on-agents: |
|
||||
yarn exec nx affected --target=build --parallel=5
|
||||
timeout: 60
|
||||
|
||||
agents:
|
||||
name: Nx Cloud - Agents
|
||||
uses: nrwl/ci/.github/workflows/nx-cloud-agents.yml@v0.13.0
|
||||
with:
|
||||
runs-on: macos-latest
|
||||
number-of-agents: 5
|
||||
environment-variables: |
|
||||
BUILD_TYPE=canary
|
||||
timeout: 60
|
||||
2
.github/workflows/pr-auto-assign.yml
vendored
@@ -9,4 +9,4 @@ jobs:
|
||||
add-reviews:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: kentaro-m/auto-assign-action@v1.2.4
|
||||
- uses: kentaro-m/auto-assign-action@v1.2.5
|
||||
|
||||
8
.github/workflows/pr-title-lint.yml
vendored
@@ -7,7 +7,7 @@ on:
|
||||
- edited
|
||||
- synchronize
|
||||
branches:
|
||||
- master
|
||||
- canary
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -17,7 +17,9 @@ jobs:
|
||||
name: Check pull request title
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
- run: echo "${{ github.event.pull_request.title }}" | npx commitlint -g ./.commitlintrc.json
|
||||
with:
|
||||
electron-install: false
|
||||
- run: echo "${{ github.event.pull_request.title }}" | yarn dlx commitlint -g ./.commitlintrc.json
|
||||
|
||||
16
.github/workflows/publish-storybook.yml
vendored
@@ -7,25 +7,23 @@ on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- canary
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
- canary
|
||||
paths-ignore:
|
||||
- README.md
|
||||
- .github/**
|
||||
- apps/server
|
||||
- apps/docs
|
||||
- apps/electron
|
||||
- packages/backend/server
|
||||
- packages/frontend/electron
|
||||
- '!.github/workflows/publish-storybook.yml'
|
||||
|
||||
jobs:
|
||||
publish-storybook:
|
||||
name: Publish Storybook
|
||||
runs-on: ubuntu-latest
|
||||
environment: development
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.merge_commit_sha }}
|
||||
# This is required to fetch all commits for chromatic
|
||||
@@ -38,7 +36,7 @@ jobs:
|
||||
run: yarn run build:plugins
|
||||
- uses: chromaui/action-next@v1
|
||||
with:
|
||||
workingDir: apps/storybook
|
||||
workingDir: tests/storybook
|
||||
buildScriptName: build
|
||||
exitOnceUploaded: true
|
||||
onlyChanged: false
|
||||
@@ -46,7 +44,7 @@ jobs:
|
||||
env:
|
||||
CHROMATIC_PROJECT_TOKEN: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
|
||||
NODE_OPTIONS: ${{ env.NODE_OPTIONS }}
|
||||
- uses: actions/upload-artifact@v2
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: always()
|
||||
with:
|
||||
name: chromatic-build-artifacts-${{ github.run_id }}
|
||||
|
||||
93
.github/workflows/release-desktop-app.yml
vendored
@@ -40,11 +40,10 @@ env:
|
||||
jobs:
|
||||
before-make:
|
||||
runs-on: ubuntu-latest
|
||||
environment: production
|
||||
outputs:
|
||||
RELEASE_VERSION: ${{ steps.get-canary-version.outputs.RELEASE_VERSION }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
- name: Setup @sentry/cli
|
||||
@@ -54,12 +53,12 @@ jobs:
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
run: |
|
||||
TAG_VERSION=${GITHUB_REF#refs/tags/v}
|
||||
PACKAGE_VERSION=$(node -p "require('./apps/electron/package.json').version")
|
||||
PACKAGE_VERSION=$(node -p "require('./packages/frontend/electron/package.json').version")
|
||||
if [ "$TAG_VERSION" != "$PACKAGE_VERSION" ]; then
|
||||
echo "Tag version ($TAG_VERSION) does not match package.json version ($PACKAGE_VERSION)"
|
||||
exit 1
|
||||
fi
|
||||
echo "RELEASE_VERSION=$(node -p "require('./apps/electron/package.json').version")" >> $GITHUB_OUTPUT
|
||||
echo "RELEASE_VERSION=$(node -p "require('./packages/frontend/electron/package.json').version")" >> $GITHUB_OUTPUT
|
||||
- name: generate-assets
|
||||
run: yarn workspace @affine/electron generate-assets
|
||||
env:
|
||||
@@ -67,15 +66,15 @@ jobs:
|
||||
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
RELEASE_VERSION: ${{ github.event.inputs.version || steps.get-canary-version.outputs.RELEASE_VERSION }}
|
||||
SKIP_PLUGIN_BUILD: 'true'
|
||||
|
||||
- name: Upload core artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: core
|
||||
path: apps/electron/resources/web-static
|
||||
path: packages/frontend/electron/resources/web-static
|
||||
|
||||
make-distribution:
|
||||
environment: production
|
||||
strategy:
|
||||
# all combinations: macos-latest x64, macos-latest arm64, ubuntu-latest x64
|
||||
# For windows, we need a separate approach
|
||||
@@ -101,13 +100,16 @@ jobs:
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
SKIP_GENERATE_ASSETS: 1
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Node.js
|
||||
timeout-minutes: 10
|
||||
uses: ./.github/actions/setup-node
|
||||
- name: Setup Maker
|
||||
timeout-minutes: 10
|
||||
uses: ./.github/actions/setup-maker
|
||||
with:
|
||||
extra-flags: workspaces focus @affine/electron @affine/monorepo
|
||||
hard-link-nm: false
|
||||
build-plugins: false
|
||||
nmHoistingLimits: workspaces
|
||||
enableScripts: false
|
||||
- name: Build AFFiNE native
|
||||
uses: ./.github/actions/build-rust
|
||||
with:
|
||||
@@ -117,10 +119,7 @@ jobs:
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: core
|
||||
path: apps/electron/resources/web-static
|
||||
|
||||
- name: Build Plugins
|
||||
run: yarn run build:plugins
|
||||
path: packages/frontend/electron/resources/web-static
|
||||
|
||||
- name: Build Desktop Layers
|
||||
run: yarn workspace @affine/electron build
|
||||
@@ -134,19 +133,23 @@ jobs:
|
||||
|
||||
- name: make
|
||||
run: yarn workspace @affine/electron make --platform=${{ matrix.spec.platform }} --arch=${{ matrix.spec.arch }}
|
||||
env:
|
||||
SKIP_PLUGIN_BUILD: 1
|
||||
SKIP_WEB_BUILD: 1
|
||||
HOIST_NODE_MODULES: 1
|
||||
|
||||
- name: Save artifacts (mac)
|
||||
if: ${{ matrix.spec.platform == 'darwin' }}
|
||||
run: |
|
||||
mkdir -p builds
|
||||
mv apps/electron/out/*/make/*.dmg ./builds/affine-${{ env.BUILD_TYPE }}-macos-${{ matrix.spec.arch }}.dmg
|
||||
mv apps/electron/out/*/make/zip/darwin/${{ matrix.spec.arch }}/*.zip ./builds/affine-${{ env.BUILD_TYPE }}-macos-${{ matrix.spec.arch }}.zip
|
||||
mv packages/frontend/electron/out/*/make/*.dmg ./builds/affine-${{ env.BUILD_TYPE }}-macos-${{ matrix.spec.arch }}.dmg
|
||||
mv packages/frontend/electron/out/*/make/zip/darwin/${{ matrix.spec.arch }}/*.zip ./builds/affine-${{ env.BUILD_TYPE }}-macos-${{ matrix.spec.arch }}.zip
|
||||
- name: Save artifacts (linux)
|
||||
if: ${{ matrix.spec.platform == 'linux' }}
|
||||
run: |
|
||||
mkdir -p builds
|
||||
mv apps/electron/out/*/make/zip/linux/x64/*.zip ./builds/affine-${{ env.BUILD_TYPE }}-linux-x64.zip
|
||||
mv apps/electron/out/*/make/AppImage/x64/*.AppImage ./builds/affine-${{ env.BUILD_TYPE }}-linux-x64.AppImage
|
||||
mv packages/frontend/electron/out/*/make/zip/linux/x64/*.zip ./builds/affine-${{ env.BUILD_TYPE }}-linux-x64.zip
|
||||
mv packages/frontend/electron/out/*/make/AppImage/x64/*.AppImage ./builds/affine-${{ env.BUILD_TYPE }}-linux-x64.AppImage
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
@@ -155,7 +158,6 @@ jobs:
|
||||
path: builds
|
||||
|
||||
package-distribution-windows:
|
||||
environment: production
|
||||
strategy:
|
||||
# all combinations: macos-latest x64, macos-latest arm64, ubuntu-latest x64
|
||||
# For windows, we need a separate approach
|
||||
@@ -172,13 +174,15 @@ jobs:
|
||||
env:
|
||||
SKIP_GENERATE_ASSETS: 1
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Node.js
|
||||
timeout-minutes: 10
|
||||
uses: ./.github/actions/setup-node
|
||||
- name: Setup Maker
|
||||
timeout-minutes: 10
|
||||
uses: ./.github/actions/setup-maker
|
||||
with:
|
||||
extra-flags: workspaces focus @affine/electron @affine/monorepo
|
||||
hard-link-nm: false
|
||||
build-plugins: false
|
||||
nmHoistingLimits: workspaces
|
||||
- name: Build AFFiNE native
|
||||
uses: ./.github/actions/build-rust
|
||||
with:
|
||||
@@ -188,7 +192,7 @@ jobs:
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: core
|
||||
path: apps/electron/resources/web-static
|
||||
path: packages/frontend/electron/resources/web-static
|
||||
|
||||
- name: Build Plugins
|
||||
run: yarn run build:plugins
|
||||
@@ -198,16 +202,20 @@ jobs:
|
||||
|
||||
- name: package
|
||||
run: yarn workspace @affine/electron package --platform=${{ matrix.spec.platform }} --arch=${{ matrix.spec.arch }}
|
||||
env:
|
||||
SKIP_PLUGIN_BUILD: 1
|
||||
SKIP_WEB_BUILD: 1
|
||||
HOIST_NODE_MODULES: 1
|
||||
|
||||
- name: get all files to be signed
|
||||
id: get_files_to_be_signed
|
||||
run: |
|
||||
Set-Variable -Name FILES_TO_BE_SIGNED -Value ((Get-ChildItem -Path apps/electron/out -Recurse -File | Where-Object { $_.Extension -in @(".exe", ".node", ".dll", ".msi") } | ForEach-Object { '"' + $_.FullName.Replace((Get-Location).Path + '\apps\electron\out\', '') + '"' }) -join ' ')
|
||||
Set-Variable -Name FILES_TO_BE_SIGNED -Value ((Get-ChildItem -Path packages/frontend/electron/out -Recurse -File | Where-Object { $_.Extension -in @(".exe", ".node", ".dll", ".msi") } | ForEach-Object { '"' + $_.FullName.Replace((Get-Location).Path + '\packages\frontend\electron\out\', '') + '"' }) -join ' ')
|
||||
"FILES_TO_BE_SIGNED=$FILES_TO_BE_SIGNED" >> $env:GITHUB_OUTPUT
|
||||
echo $FILES_TO_BE_SIGNED
|
||||
|
||||
- name: Zip artifacts for faster upload
|
||||
run: Compress-Archive -CompressionLevel Fastest -Path apps/electron/out/* -DestinationPath archive.zip
|
||||
run: Compress-Archive -CompressionLevel Fastest -Path packages/frontend/electron/out/* -DestinationPath archive.zip
|
||||
|
||||
- name: Save packaged artifacts for signing
|
||||
uses: actions/upload-artifact@v3
|
||||
@@ -225,7 +233,6 @@ jobs:
|
||||
artifact-name: packaged-win32-x64
|
||||
|
||||
make-windows-installer:
|
||||
environment: production
|
||||
needs: sign-packaged-artifacts-windows
|
||||
strategy:
|
||||
# all combinations: macos-latest x64, macos-latest arm64, ubuntu-latest x64
|
||||
@@ -240,7 +247,7 @@ jobs:
|
||||
outputs:
|
||||
FILES_TO_BE_SIGNED: ${{ steps.get_files_to_be_signed.outputs.FILES_TO_BE_SIGNED }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Node.js
|
||||
timeout-minutes: 10
|
||||
uses: ./.github/actions/setup-node
|
||||
@@ -250,18 +257,18 @@ jobs:
|
||||
name: signed-packaged-${{ matrix.spec.platform }}-${{ matrix.spec.arch }}
|
||||
path: .
|
||||
- name: unzip file
|
||||
run: Expand-Archive -Path signed.zip -DestinationPath apps/electron/out
|
||||
run: Expand-Archive -Path signed.zip -DestinationPath packages/frontend/electron/out
|
||||
|
||||
- name: Make squirrel.windows installer
|
||||
run: yarn workspace @affine/electron make-squirrel --platform=${{ matrix.spec.platform }} --arch=${{ matrix.spec.arch }}
|
||||
|
||||
- name: Zip artifacts for faster upload
|
||||
run: Compress-Archive -CompressionLevel Fastest -Path apps/electron/out/${{ env.BUILD_TYPE }}/make/* -DestinationPath archive.zip
|
||||
run: Compress-Archive -CompressionLevel Fastest -Path packages/frontend/electron/out/${{ env.BUILD_TYPE }}/make/* -DestinationPath archive.zip
|
||||
|
||||
- name: get all files to be signed
|
||||
id: get_files_to_be_signed
|
||||
run: |
|
||||
Set-Variable -Name FILES_TO_BE_SIGNED -Value ((Get-ChildItem -Path apps/electron/out/${{ env.BUILD_TYPE }}/make -Recurse -File | Where-Object { $_.Extension -in @(".exe", ".node", ".dll", ".msi") } | ForEach-Object { '"' + $_.FullName.Replace((Get-Location).Path + '\apps\electron\out\${{ env.BUILD_TYPE }}\make\', '') + '"' }) -join ' ')
|
||||
Set-Variable -Name FILES_TO_BE_SIGNED -Value ((Get-ChildItem -Path packages/frontend/electron/out/${{ env.BUILD_TYPE }}/make -Recurse -File | Where-Object { $_.Extension -in @(".exe", ".node", ".dll", ".msi") } | ForEach-Object { '"' + $_.FullName.Replace((Get-Location).Path + '\packages\frontend\electron\out\${{ env.BUILD_TYPE }}\make\', '') + '"' }) -join ' ')
|
||||
"FILES_TO_BE_SIGNED=$FILES_TO_BE_SIGNED" >> $env:GITHUB_OUTPUT
|
||||
echo $FILES_TO_BE_SIGNED
|
||||
|
||||
@@ -279,7 +286,6 @@ jobs:
|
||||
artifact-name: installer-win32-x64
|
||||
|
||||
finalize-installer-windows:
|
||||
environment: production
|
||||
needs: sign-installer-artifacts-windows
|
||||
strategy:
|
||||
# all combinations: macos-latest x64, macos-latest arm64, ubuntu-latest x64
|
||||
@@ -298,14 +304,14 @@ jobs:
|
||||
name: signed-installer-${{ matrix.spec.platform }}-${{ matrix.spec.arch }}
|
||||
path: .
|
||||
- name: unzip file
|
||||
run: Expand-Archive -Path signed.zip -DestinationPath apps/electron/out/${{ env.BUILD_TYPE }}/make
|
||||
run: Expand-Archive -Path signed.zip -DestinationPath packages/frontend/electron/out/${{ env.BUILD_TYPE }}/make
|
||||
|
||||
- name: Save artifacts
|
||||
run: |
|
||||
mkdir -p builds
|
||||
mv apps/electron/out/*/make/zip/win32/x64/AFFiNE*-win32-x64-*.zip ./builds/affine-${{ env.BUILD_TYPE }}-windows-x64.zip
|
||||
mv apps/electron/out/*/make/squirrel.windows/x64/*.exe ./builds/affine-${{ env.BUILD_TYPE }}-windows-x64.exe
|
||||
mv apps/electron/out/*/make/squirrel.windows/x64/*.msi ./builds/affine-${{ env.BUILD_TYPE }}-windows-x64.msi
|
||||
mv packages/frontend/electron/out/*/make/zip/win32/x64/AFFiNE*-win32-x64-*.zip ./builds/affine-${{ env.BUILD_TYPE }}-windows-x64.zip
|
||||
mv packages/frontend/electron/out/*/make/squirrel.windows/x64/*.exe ./builds/affine-${{ env.BUILD_TYPE }}-windows-x64.exe
|
||||
mv packages/frontend/electron/out/*/make/squirrel.windows/x64/*.msi ./builds/affine-${{ env.BUILD_TYPE }}-windows-x64.msi
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
@@ -318,7 +324,13 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: core
|
||||
path: web-static
|
||||
- name: Zip web-static
|
||||
run: zip -r web-static.zip web-static
|
||||
- name: Download Artifacts (macos-x64)
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
@@ -339,19 +351,16 @@ jobs:
|
||||
with:
|
||||
name: affine-linux-x64-builds
|
||||
path: ./
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
- name: Generate Release yml
|
||||
run: |
|
||||
cp ./apps/electron/scripts/generate-yml.js .
|
||||
node generate-yml.js
|
||||
node ./packages/frontend/electron/scripts/generate-yml.js
|
||||
env:
|
||||
RELEASE_VERSION: ${{ github.event.inputs.version || needs.before-make.outputs.RELEASE_VERSION }}
|
||||
- name: Create Release Draft
|
||||
uses: softprops/action-gh-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||
with:
|
||||
name: ${{ github.event.inputs.version || needs.before-make.outputs.RELEASE_VERSION }}
|
||||
body: ''
|
||||
|
||||
38
.github/workflows/release.yml
vendored
@@ -3,7 +3,7 @@ name: Release
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- canary
|
||||
|
||||
env:
|
||||
BUILD_TYPE: stable
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
name: Try publishing npm@latest release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
- name: Try publishing to NPM
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
environment: development
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
- name: Build Plugins
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: core
|
||||
path: ./apps/core/dist
|
||||
path: ./packages/frontend/core/dist
|
||||
if-no-files-found: error
|
||||
|
||||
build-server:
|
||||
@@ -50,7 +50,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
environment: development
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
@@ -61,7 +61,7 @@ jobs:
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: server-dist
|
||||
path: ./apps/server/dist
|
||||
path: ./packages/backend/server/dist
|
||||
if-no-files-found: error
|
||||
|
||||
build-storage:
|
||||
@@ -72,7 +72,7 @@ jobs:
|
||||
environment: development
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
- name: Setup Rust
|
||||
@@ -85,11 +85,11 @@ jobs:
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: storage.node
|
||||
path: ./packages/storage/storage.node
|
||||
path: ./packages/backend/storage/storage.node
|
||||
if-no-files-found: error
|
||||
|
||||
build-docker:
|
||||
if: github.ref == 'refs/heads/master'
|
||||
if: github.ref == 'refs/heads/canary'
|
||||
name: Build Docker
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
@@ -97,38 +97,38 @@ jobs:
|
||||
- build-core
|
||||
- build-storage
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Download core artifact
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: core
|
||||
path: ./apps/core/dist
|
||||
path: ./packages/frontend/core/dist
|
||||
- name: Download server dist
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: server-dist
|
||||
path: ./apps/server/dist
|
||||
path: ./packages/backend/server/dist
|
||||
- name: Download storage.node
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: storage.node
|
||||
path: ./apps/server
|
||||
path: ./packages/backend/server
|
||||
- name: Setup Git short hash
|
||||
run: |
|
||||
echo "GIT_SHORT_HASH=$(git rev-parse --short HEAD)" >> "$GITHUB_ENV"
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v2
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
logout: false
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
uses: docker/setup-qemu-action@v3
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Build front Dockerfile
|
||||
uses: docker/build-push-action@v4
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
@@ -141,7 +141,7 @@ jobs:
|
||||
# setup node without cache configuration
|
||||
# Prisma cache is not compatible with docker build cache
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
registry-url: https://npm.pkg.github.com
|
||||
@@ -154,7 +154,7 @@ jobs:
|
||||
run: yarn workspace @affine/server prisma generate
|
||||
|
||||
- name: Build graphql Dockerfile
|
||||
uses: docker/build-push-action@v4
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
|
||||
11
.github/workflows/workers.yml
vendored
@@ -3,9 +3,9 @@ name: Deploy Cloudflare Worker
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- canary
|
||||
paths:
|
||||
- packages/workers/**
|
||||
- tools/workers/**
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
@@ -13,10 +13,11 @@ jobs:
|
||||
name: Deploy
|
||||
environment: production
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
- name: Publish
|
||||
uses: cloudflare/wrangler-action@2.0.0
|
||||
uses: cloudflare/wrangler-action@v3.3.2
|
||||
with:
|
||||
apiToken: ${{ secrets.CF_API_TOKEN }}
|
||||
accountId: ${{ secrets.CF_ACCOUNT_ID }}
|
||||
workingDirectory: 'packages/workers'
|
||||
workingDirectory: 'tools/workers'
|
||||
packageManager: 'yarn'
|
||||
|
||||
1
.gitignore
vendored
@@ -78,3 +78,4 @@ tsconfig.node.tsbuildinfo
|
||||
lib
|
||||
affine.db
|
||||
apps/web/next-routes.conf
|
||||
.nx
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
"version": 1,
|
||||
"list": [
|
||||
{
|
||||
"input": "./packages/i18n/src/resources/en.json",
|
||||
"output": "./packages/i18n/src/i18n-generated",
|
||||
"input": "./packages/frontend/i18n/src/resources/en.json",
|
||||
"output": "./packages/frontend/i18n/src/i18n-generated",
|
||||
"parser": {
|
||||
"type": "i18next",
|
||||
"contextSeparator": "$",
|
||||
|
||||
@@ -2,16 +2,22 @@ yarn.lock
|
||||
target
|
||||
lib
|
||||
test-results
|
||||
packages/i18n/src/i18n-generated.ts
|
||||
packages/graphql/src/graphql/index.ts
|
||||
.next
|
||||
out
|
||||
dist
|
||||
.yarn
|
||||
tests/affine-legacy/**/static
|
||||
.github/helm
|
||||
_next
|
||||
storybook-static
|
||||
web-static
|
||||
public
|
||||
apps/server/src/schema.gql
|
||||
packages/backend/server/src/schema.gql
|
||||
packages/frontend/i18n/src/i18n-generated.ts
|
||||
packages/frontend/graphql/src/graphql/index.ts
|
||||
tests/affine-legacy/**/static
|
||||
|
||||
# auto-generated by NAPI-RS
|
||||
# fixme(@joooye34): need script to check and generate ignore list here
|
||||
packages/backend/storage/index.d.ts
|
||||
packages/frontend/native/index.d.ts
|
||||
packages/frontend/native/index.js
|
||||
|
||||
10
.vscode/settings.template.json
vendored
@@ -29,15 +29,7 @@
|
||||
"[typescriptreact]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"vitest.include": [
|
||||
"packages/**/*.spec.ts",
|
||||
"packages/**/*.spec.tsx",
|
||||
"apps/web/**/*.spec.ts",
|
||||
"apps/web/**/*.spec.tsx",
|
||||
"apps/electron/src/**/*.spec.ts",
|
||||
"tests/unit/**/*.spec.ts",
|
||||
"tests/unit/**/*.spec.tsx"
|
||||
],
|
||||
"vitest.include": ["packages/**/*.spec.ts", "packages/**/*.spec.tsx"],
|
||||
"rust-analyzer.check.extraEnv": {
|
||||
"DATABASE_URL": "sqlite:affine.db"
|
||||
}
|
||||
|
||||
15
.yarn/patches/next-auth-npm-4.24.5-8428e11927.patch
Normal file
@@ -0,0 +1,15 @@
|
||||
diff --git a/package.json b/package.json
|
||||
index ca30bca63196b923fa5a27eb85ce2ee890222d36..39e9d08dea40f25568a39bfbc0154458d32c8a66 100644
|
||||
--- a/package.json
|
||||
+++ b/package.json
|
||||
@@ -31,6 +31,10 @@
|
||||
"types": "./index.d.ts",
|
||||
"default": "./index.js"
|
||||
},
|
||||
+ "./core": {
|
||||
+ "types": "./core/index.d.ts",
|
||||
+ "default": "./core/index.js"
|
||||
+ },
|
||||
"./adapters": {
|
||||
"types": "./adapters.d.ts"
|
||||
},
|
||||
541
.yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
vendored
550
.yarn/plugins/@yarnpkg/plugin-version.cjs
vendored
874
.yarn/releases/yarn-3.6.3.cjs
vendored
893
.yarn/releases/yarn-4.0.1.cjs
vendored
Executable file
14
.yarnrc.yml
@@ -1,3 +1,7 @@
|
||||
compressionLevel: mixed
|
||||
|
||||
enableGlobalCache: true
|
||||
|
||||
nmMode: hardlinks-local
|
||||
|
||||
nodeLinker: node-modules
|
||||
@@ -8,12 +12,4 @@ npmPublishAccess: public
|
||||
|
||||
npmPublishRegistry: 'https://registry.npmjs.org'
|
||||
|
||||
plugins:
|
||||
- path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
|
||||
spec: '@yarnpkg/plugin-interactive-tools'
|
||||
- path: .yarn/plugins/@yarnpkg/plugin-version.cjs
|
||||
spec: '@yarnpkg/plugin-version'
|
||||
- path: .yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs
|
||||
spec: '@yarnpkg/plugin-workspace-tools'
|
||||
|
||||
yarnPath: .yarn/releases/yarn-3.6.3.cjs
|
||||
yarnPath: .yarn/releases/yarn-4.0.1.cjs
|
||||
|
||||
660
Cargo.lock
generated
@@ -1,9 +1,9 @@
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
members = [
|
||||
"./packages/native",
|
||||
"./packages/native/schema",
|
||||
"./packages/storage",
|
||||
"./packages/frontend/native",
|
||||
"./packages/frontend/native/schema",
|
||||
"./packages/backend/storage",
|
||||
]
|
||||
|
||||
[profile.dev.package.sqlx-macros]
|
||||
|
||||
2
LICENSE
@@ -2,7 +2,7 @@ Copyright (c) 2022-present TOEVERYTHING PTE. LTD. and its affiliates.
|
||||
|
||||
Portions of this software are licensed as follows:
|
||||
|
||||
- All content that resides under the "apps/server" directory of this repository, if that directory exists, is licensed under the license defined in "apps/server/LICENSE".
|
||||
- All content that resides under the "packages/backend/server" directory of this repository, if that directory exists, is licensed under the license defined in "packages/backend/server/LICENSE".
|
||||
- All third party components incorporated into the AFFiNE Software are licensed under the original license provided by the owner of the applicable component.
|
||||
- Content outside of the above mentioned directories or restrictions above is available under the "MIT" license as defined in "LICENSE-MIT".
|
||||
|
||||
|
||||
49
README.md
@@ -110,9 +110,9 @@ If you have questions, you are welcome to contact us. One of the best places to
|
||||
| Name | | |
|
||||
| ----------------------------------------------------------------------------------------------- | --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| [@toeverything/component](https://github.com/toeverything/design/tree/main/packages/components) | Toeverything Shared Component Resources | |
|
||||
| [@affine/component](packages/component) | AFFiNE Component Resources | [](https://affine-storybook.vercel.app/) |
|
||||
| [@toeverything/y-indexeddb](packages/y-indexeddb) | IndexedDB database adapter for Yjs | [](https://www.npmjs.com/package/@toeverything/y-indexeddb) |
|
||||
| [@toeverything/theme](packages/theme) | AFFiNE theme | [](https://www.npmjs.com/package/@toeverything/theme) |
|
||||
| [@affine/component](packages/frontend/component) | AFFiNE Component Resources | [](https://affine-storybook.vercel.app/) |
|
||||
| [@toeverything/y-indexeddb](packages/common/y-indexeddb) | IndexedDB database adapter for Yjs | [](https://www.npmjs.com/package/@toeverything/y-indexeddb) |
|
||||
| [@toeverything/theme](packages/common/theme) | AFFiNE theme | [](https://www.npmjs.com/package/@toeverything/theme) |
|
||||
|
||||
## Plugins
|
||||
|
||||
@@ -120,15 +120,14 @@ If you have questions, you are welcome to contact us. One of the best places to
|
||||
>
|
||||
> (Currently, the plugin system is under heavy development. You will see the plugin system in the canary release.)
|
||||
|
||||
- [@affine/sdk](./packages/sdk) - SDK for developing plugins
|
||||
- [@affine/plugin-cli](./packages/plugin-cli) - CLI for developing plugins
|
||||
- [@affine/sdk](./packages/common/sdk) - SDK for developing plugins
|
||||
- [@affine/plugin-cli](./tools/plugin-cli) - CLI for developing plugins
|
||||
|
||||
| Official Plugin | Description | Status |
|
||||
| ----------------------------------------------------- | ----------------------------------------- | ------ |
|
||||
| [@affine/bookmark-plugin](plugins/bookmark) | A block for bookmarking a website | ✅ |
|
||||
| [@affine/copilot-plugin](plugins/copilot) | AI Copilot that help you document writing | 🚧 |
|
||||
| [@affine/image-preview-plugin](plugins/image-preview) | Component for previewing an image | ✅ |
|
||||
| [@affine/outline](plugins/outline) | Outline for your document | ✅ |
|
||||
| Official Plugin | Description | Status |
|
||||
| ---------------------------------------------------------------- | ----------------------------------------- | ------ |
|
||||
| [@affine/copilot-plugin](./packages/plugins/copilot) | AI Copilot that help you document writing | 🚧 |
|
||||
| [@affine/image-preview-plugin](./packages/plugins/image-preview) | Component for previewing an image | ✅ |
|
||||
| [@affine/outline](./packages/plugins/outline) | Outline for your document | ✅ |
|
||||
|
||||
## Upstreams
|
||||
|
||||
@@ -147,18 +146,7 @@ We would also like to give thanks to open-source projects that make AFFiNE possi
|
||||
|
||||
Thanks a lot to the community for providing such powerful and simple libraries, so that we can focus more on the implementation of the product logic, and we hope that in the future our projects will also provide a more easy-to-use knowledge base for everyone.
|
||||
|
||||
# Contributors
|
||||
|
||||
## Current Core members
|
||||
|
||||
Team members who are currently maintaining the project:
|
||||
|
||||
- [JimmFly](https://github.com/JimmFly) - Jinfei Yang <yangjinfei001@gmail.com> (he/him)
|
||||
- [pengx17](https://github.com/pengx17) - Peng Xiao <pengxiao@outlook.com> (he/him)
|
||||
- [QiShaoXuan](https://github.com/QiSHaoXuan) - Shaoxuan Qi <qishaoxuan777@gmail.com> (he/him)
|
||||
- [himself65](https://github.com/himself65) - Zeyu "Alex" Yang <himself65@outlook.com> (he/him)
|
||||
|
||||
## All Contributors
|
||||
## Contributors
|
||||
|
||||
We would like to express our gratitude to all the individuals who have already contributed to AFFiNE! If you have any AFFiNE-related project, documentation, tool or template, please feel free to contribute it by submitting a pull request to our curated list on GitHub: [awesome-affine](https://github.com/toeverything/awesome-affine).
|
||||
|
||||
@@ -207,6 +195,13 @@ For feature request, please see [community.affine.pro](https://community.affine.
|
||||
|
||||
## Building
|
||||
|
||||
### Codespaces
|
||||
|
||||
From the GitHub repo main page, click the green "Code" button and select "Create codespace on master". This will open a new Codespace with the (supposedly auto-forked
|
||||
AFFiNE repo cloned, built, and ready to go.
|
||||
|
||||
### Local
|
||||
|
||||
See [BUILDING.md] for instructions on how to build AFFiNE from source code.
|
||||
|
||||
## Contributing
|
||||
@@ -232,11 +227,11 @@ See [LICENSE] for details.
|
||||
[update page]: https://affine.pro/blog?tag=Release%20Note
|
||||
[jobs available]: ./docs/jobs.md
|
||||
[latest packages]: https://github.com/toeverything/AFFiNE/pkgs/container/affine-self-hosted
|
||||
[contributor license agreement]: https://github.com/toeverything/affine/edit/master/.github/CLA.md
|
||||
[contributor license agreement]: https://github.com/toeverything/affine/edit/canary/.github/CLA.md
|
||||
[rust-version-icon]: https://img.shields.io/badge/Rust-1.71.0-dea584
|
||||
[stars-icon]: https://img.shields.io/github/stars/toeverything/AFFiNE.svg?style=flat&logo=github&colorB=red&label=stars
|
||||
[codecov]: https://codecov.io/gh/toeverything/affine/branch/master/graphs/badge.svg?branch=master
|
||||
[codecov]: https://codecov.io/gh/toeverything/affine/branch/canary/graphs/badge.svg?branch=canary
|
||||
[node-version-icon]: https://img.shields.io/badge/node-%3E=18.16.1-success
|
||||
[typescript-version-icon]: https://img.shields.io/github/package-json/dependency-version/toeverything/affine/dev/typescript
|
||||
[react-version-icon]: https://img.shields.io/github/package-json/dependency-version/toeverything/AFFiNE/react?filename=apps%2Fcore%2Fpackage.json&color=rgb(97%2C228%2C251)
|
||||
[blocksuite-icon]: https://img.shields.io/github/package-json/dependency-version/toeverything/AFFiNE/@blocksuite/store?color=6880ff&filename=apps%2Fcore%2Fpackage.json&label=blocksuite
|
||||
[react-version-icon]: https://img.shields.io/github/package-json/dependency-version/toeverything/AFFiNE/react?filename=packages%2Ffrontend%2Fcore%2Fpackage.json&color=rgb(97%2C228%2C251)
|
||||
[blocksuite-icon]: https://img.shields.io/github/package-json/dependency-version/toeverything/AFFiNE/@blocksuite/store?color=6880ff&filename=packages%2Ffrontend%2Fcore%2Fpackage.json&label=blocksuite
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
# Apps structure
|
||||
|
||||
> This is the structure of the `apps` directory.
|
||||
|
||||
## docs
|
||||
|
||||
AFFiNE Developer Documentation using [waku](https://github.com/dai-shi/waku).
|
||||
|
||||
## electron
|
||||
|
||||
> `core` needs to be built before electron.
|
||||
|
||||
AFFiNE Desktop (macOS, Linux and Windows Distribution) using [Electron](https://www.electronjs.org/).
|
||||
|
||||
## server
|
||||
|
||||
Server using [Nest.js](https://nestjs.com/).
|
||||
|
||||
## storybook
|
||||
|
||||
Storybook using [Storybook](https://storybook.js.org/).
|
||||
|
||||
## prototype
|
||||
|
||||
AFFiNE Prototype using [React.js](https://reactjs.org/) + [Vite](https://vitejs.dev/).
|
||||
|
||||
## core
|
||||
|
||||
AFFiNE Core Application using [React.js](https://reactjs.org/) + [Webpack](https://webpack.js.org/).
|
||||
@@ -1,12 +0,0 @@
|
||||
import type { BuildFlags } from '@affine/cli/config';
|
||||
|
||||
export function computeCacheKey(buildFlags: BuildFlags) {
|
||||
return [
|
||||
'1',
|
||||
'node' + process.version,
|
||||
buildFlags.mode,
|
||||
buildFlags.distribution,
|
||||
buildFlags.channel,
|
||||
...(buildFlags.localBlockSuite ? [buildFlags.localBlockSuite] : []),
|
||||
].join('-');
|
||||
}
|
||||
|
Before Width: | Height: | Size: 7.5 KiB |
|
Before Width: | Height: | Size: 6.0 KiB |
|
Before Width: | Height: | Size: 8.5 KiB |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 5.4 KiB |
|
Before Width: | Height: | Size: 84 KiB |
|
Before Width: | Height: | Size: 88 KiB |
|
Before Width: | Height: | Size: 85 KiB |
|
Before Width: | Height: | Size: 84 KiB |
@@ -1,166 +0,0 @@
|
||||
import { setupGlobal } from '@affine/env/global';
|
||||
import type { WorkspaceAdapter } from '@affine/env/workspace';
|
||||
import { WorkspaceFlavour } from '@affine/env/workspace';
|
||||
import type { RootWorkspaceMetadata } from '@affine/workspace/atom';
|
||||
import {
|
||||
type RootWorkspaceMetadataV2,
|
||||
rootWorkspacesMetadataAtom,
|
||||
workspaceAdaptersAtom,
|
||||
} from '@affine/workspace/atom';
|
||||
import {
|
||||
getOrCreateWorkspace,
|
||||
globalBlockSuiteSchema,
|
||||
} from '@affine/workspace/manager';
|
||||
import { assertExists } from '@blocksuite/global/utils';
|
||||
import { nanoid } from '@blocksuite/store';
|
||||
import {
|
||||
migrateLocalBlobStorage,
|
||||
migrateWorkspace,
|
||||
WorkspaceVersion,
|
||||
} from '@toeverything/infra/blocksuite';
|
||||
import { downloadBinary, overwriteBinary } from '@toeverything/y-indexeddb';
|
||||
import type { createStore } from 'jotai/vanilla';
|
||||
import { applyUpdate, Doc as YDoc, encodeStateAsUpdate } from 'yjs';
|
||||
|
||||
import { WorkspaceAdapters } from '../adapters/workspace';
|
||||
|
||||
async function tryMigration() {
|
||||
const value = localStorage.getItem('jotai-workspaces');
|
||||
if (value) {
|
||||
try {
|
||||
const metadata = JSON.parse(value) as RootWorkspaceMetadata[];
|
||||
const promises: Promise<void>[] = [];
|
||||
const newMetadata = [...metadata];
|
||||
metadata.forEach(oldMeta => {
|
||||
if (oldMeta.flavour === WorkspaceFlavour.LOCAL) {
|
||||
let doc: YDoc;
|
||||
const options = {
|
||||
getCurrentRootDoc: async () => {
|
||||
doc = new YDoc({
|
||||
guid: oldMeta.id,
|
||||
});
|
||||
const downloadWorkspace = async (doc: YDoc): Promise<void> => {
|
||||
const binary = await downloadBinary(doc.guid);
|
||||
if (binary) {
|
||||
applyUpdate(doc, binary);
|
||||
}
|
||||
await Promise.all(
|
||||
[...doc.subdocs.values()].map(subdoc =>
|
||||
downloadWorkspace(subdoc)
|
||||
)
|
||||
);
|
||||
};
|
||||
await downloadWorkspace(doc);
|
||||
return doc;
|
||||
},
|
||||
createWorkspace: async () =>
|
||||
getOrCreateWorkspace(nanoid(), WorkspaceFlavour.LOCAL),
|
||||
getSchema: () => globalBlockSuiteSchema,
|
||||
};
|
||||
promises.push(
|
||||
migrateWorkspace(
|
||||
'version' in oldMeta ? oldMeta.version : undefined,
|
||||
options
|
||||
).then(async status => {
|
||||
if (typeof status !== 'boolean') {
|
||||
const adapter = WorkspaceAdapters[oldMeta.flavour];
|
||||
const oldWorkspace = await adapter.CRUD.get(oldMeta.id);
|
||||
const newId = await adapter.CRUD.create(status);
|
||||
assertExists(
|
||||
oldWorkspace,
|
||||
'workspace should exist after migrate'
|
||||
);
|
||||
await adapter.CRUD.delete(oldWorkspace.blockSuiteWorkspace);
|
||||
const index = newMetadata.findIndex(
|
||||
meta => meta.id === oldMeta.id
|
||||
);
|
||||
newMetadata[index] = {
|
||||
...oldMeta,
|
||||
id: newId,
|
||||
version: WorkspaceVersion.Surface,
|
||||
};
|
||||
await migrateLocalBlobStorage(status.id, newId);
|
||||
console.log('workspace migrated', oldMeta.id, newId);
|
||||
} else if (status) {
|
||||
const index = newMetadata.findIndex(
|
||||
meta => meta.id === oldMeta.id
|
||||
);
|
||||
newMetadata[index] = {
|
||||
...oldMeta,
|
||||
version: WorkspaceVersion.Surface,
|
||||
};
|
||||
const overWrite = async (doc: YDoc): Promise<void> => {
|
||||
await overwriteBinary(doc.guid, encodeStateAsUpdate(doc));
|
||||
return Promise.all(
|
||||
[...doc.subdocs.values()].map(subdoc => overWrite(subdoc))
|
||||
).then();
|
||||
};
|
||||
await overWrite(doc);
|
||||
console.log('workspace migrated', oldMeta.id);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
await Promise.all(promises)
|
||||
.then(() => {
|
||||
console.log('migration done');
|
||||
})
|
||||
.catch(e => {
|
||||
console.error('migration failed', e);
|
||||
})
|
||||
.finally(() => {
|
||||
localStorage.setItem('jotai-workspaces', JSON.stringify(newMetadata));
|
||||
window.dispatchEvent(new CustomEvent('migration-done'));
|
||||
window.$migrationDone = true;
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('error when migrating data', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function createFirstAppData(store: ReturnType<typeof createStore>) {
|
||||
const createFirst = (): RootWorkspaceMetadataV2[] => {
|
||||
const Plugins = Object.values(WorkspaceAdapters).sort(
|
||||
(a, b) => a.loadPriority - b.loadPriority
|
||||
);
|
||||
|
||||
return Plugins.flatMap(Plugin => {
|
||||
return Plugin.Events['app:init']?.().map(
|
||||
id =>
|
||||
<RootWorkspaceMetadataV2>{
|
||||
id,
|
||||
flavour: Plugin.flavour,
|
||||
version: WorkspaceVersion.DatabaseV3,
|
||||
}
|
||||
);
|
||||
}).filter((ids): ids is RootWorkspaceMetadataV2 => !!ids);
|
||||
};
|
||||
if (localStorage.getItem('is-first-open') !== null) {
|
||||
return;
|
||||
}
|
||||
const result = createFirst();
|
||||
console.info('create first workspace', result);
|
||||
localStorage.setItem('is-first-open', 'false');
|
||||
store.set(rootWorkspacesMetadataAtom, result);
|
||||
}
|
||||
|
||||
export async function setup(store: ReturnType<typeof createStore>) {
|
||||
store.set(
|
||||
workspaceAdaptersAtom,
|
||||
WorkspaceAdapters as Record<
|
||||
WorkspaceFlavour,
|
||||
WorkspaceAdapter<WorkspaceFlavour>
|
||||
>
|
||||
);
|
||||
|
||||
console.log('setup global');
|
||||
setupGlobal();
|
||||
|
||||
await tryMigration();
|
||||
// do not read `rootWorkspacesMetadataAtom` before migration
|
||||
await store.get(rootWorkspacesMetadataAtom);
|
||||
console.log('setup done');
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
import type {
|
||||
QueryParamError,
|
||||
Unreachable,
|
||||
WorkspaceNotFoundError,
|
||||
} from '@affine/env/constant';
|
||||
import { PageNotFoundError } from '@affine/env/constant';
|
||||
import { rootWorkspacesMetadataAtom } from '@affine/workspace/atom';
|
||||
import {
|
||||
currentPageIdAtom,
|
||||
currentWorkspaceIdAtom,
|
||||
getCurrentStore,
|
||||
} from '@toeverything/infra/atom';
|
||||
import { useAtomValue } from 'jotai/react';
|
||||
import { Provider } from 'jotai/react';
|
||||
import type { ErrorInfo, ReactElement, ReactNode } from 'react';
|
||||
import type React from 'react';
|
||||
import { Component } from 'react';
|
||||
import { useLocation, useParams } from 'react-router-dom';
|
||||
export type AffineErrorBoundaryProps = React.PropsWithChildren;
|
||||
|
||||
type AffineError =
|
||||
| QueryParamError
|
||||
| Unreachable
|
||||
| WorkspaceNotFoundError
|
||||
| PageNotFoundError
|
||||
| Error;
|
||||
|
||||
interface AffineErrorBoundaryState {
|
||||
error: AffineError | null;
|
||||
}
|
||||
|
||||
export const DumpInfo = () => {
|
||||
const location = useLocation();
|
||||
const metadata = useAtomValue(rootWorkspacesMetadataAtom);
|
||||
const currentWorkspaceId = useAtomValue(currentWorkspaceIdAtom);
|
||||
const currentPageId = useAtomValue(currentPageIdAtom);
|
||||
const path = location.pathname;
|
||||
const query = useParams();
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
Please copy the following information and send it to the developer.
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
border: '1px solid red',
|
||||
}}
|
||||
>
|
||||
<div>path: {path}</div>
|
||||
<div>query: {JSON.stringify(query)}</div>
|
||||
<div>currentWorkspaceId: {currentWorkspaceId}</div>
|
||||
<div>currentPageId: {currentPageId}</div>
|
||||
<div>metadata: {JSON.stringify(metadata)}</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export class AffineErrorBoundary extends Component<
|
||||
AffineErrorBoundaryProps,
|
||||
AffineErrorBoundaryState
|
||||
> {
|
||||
public override state: AffineErrorBoundaryState = {
|
||||
error: null,
|
||||
};
|
||||
|
||||
public static getDerivedStateFromError(
|
||||
error: AffineError
|
||||
): AffineErrorBoundaryState {
|
||||
return { error };
|
||||
}
|
||||
|
||||
public override componentDidCatch(error: AffineError, errorInfo: ErrorInfo) {
|
||||
console.error('Uncaught error:', error, errorInfo);
|
||||
}
|
||||
|
||||
public override render(): ReactNode {
|
||||
if (this.state.error) {
|
||||
let errorDetail: ReactElement | null = null;
|
||||
const error = this.state.error;
|
||||
if (error instanceof PageNotFoundError) {
|
||||
errorDetail = (
|
||||
<>
|
||||
<h1>Sorry.. there was an error</h1>
|
||||
<>
|
||||
<span> Page error </span>
|
||||
<span>
|
||||
Cannot find page {error.pageId} in workspace{' '}
|
||||
{error.workspace.id}
|
||||
</span>
|
||||
</>
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
errorDetail = (
|
||||
<>
|
||||
<h1>Sorry.. there was an error</h1>
|
||||
{error.message ?? error.toString()}
|
||||
</>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<>
|
||||
{errorDetail}
|
||||
<Provider key="JotaiProvider" store={getCurrentStore()}>
|
||||
<DumpInfo />
|
||||
</Provider>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
import {
|
||||
AuthContent,
|
||||
BackButton,
|
||||
CountDownRender,
|
||||
ModalHeader,
|
||||
} from '@affine/component/auth-components';
|
||||
import { Trans } from '@affine/i18n';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { Button } from '@toeverything/components/button';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { useCurrentLoginStatus } from '../../../hooks/affine/use-current-login-status';
|
||||
import type { AuthPanelProps } from './index';
|
||||
import * as style from './style.css';
|
||||
import { useAuth } from './use-auth';
|
||||
|
||||
export const AfterSignInSendEmail = ({
|
||||
setAuthState,
|
||||
email,
|
||||
onSignedIn,
|
||||
}: AuthPanelProps) => {
|
||||
const t = useAFFiNEI18N();
|
||||
const loginStatus = useCurrentLoginStatus();
|
||||
|
||||
const { resendCountDown, allowSendEmail, signIn } = useAuth();
|
||||
if (loginStatus === 'authenticated') {
|
||||
onSignedIn?.();
|
||||
}
|
||||
|
||||
const onResendClick = useCallback(async () => {
|
||||
await signIn(email);
|
||||
}, [email, signIn]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<ModalHeader
|
||||
title={t['com.affine.auth.sign.in']()}
|
||||
subTitle={t['com.affine.auth.sign.in.sent.email.subtitle']()}
|
||||
/>
|
||||
<AuthContent style={{ height: 162 }}>
|
||||
{t['com.affine.auth.sign.sent.email.message.start']()}
|
||||
<a href={`mailto:${email}`}>{email}</a>
|
||||
{t['com.affine.auth.sign.sent.email.message.end']()}
|
||||
</AuthContent>
|
||||
|
||||
<div className={style.resendWrapper}>
|
||||
{allowSendEmail ? (
|
||||
<Button type="plain" size="large" onClick={onResendClick}>
|
||||
{t['com.affine.auth.sign.auth.code.resend.hint']()}
|
||||
</Button>
|
||||
) : (
|
||||
<>
|
||||
<span className="resend-code-hint">
|
||||
{t['com.affine.auth.sign.auth.code.on.resend.hint']()}
|
||||
</span>
|
||||
<CountDownRender
|
||||
className={style.resendCountdown}
|
||||
timeLeft={resendCountDown}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className={style.authMessage} style={{ marginTop: 20 }}>
|
||||
{/*prettier-ignore*/}
|
||||
<Trans i18nKey="com.affine.auth.sign.auth.code.message.password">
|
||||
If you haven't received the email, please check your spam folder.
|
||||
Or <span
|
||||
className="link"
|
||||
data-testid='sign-in-with-password'
|
||||
onClick={useCallback(() => {
|
||||
setAuthState('signInWithPassword');
|
||||
}, [setAuthState])}
|
||||
>
|
||||
sign in with password
|
||||
</span> instead.
|
||||
</Trans>
|
||||
</div>
|
||||
|
||||
<BackButton
|
||||
onClick={useCallback(() => {
|
||||
setAuthState('signIn');
|
||||
}, [setAuthState])}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,71 +0,0 @@
|
||||
import { LOCALES } from '@affine/i18n';
|
||||
import { useI18N } from '@affine/i18n';
|
||||
import { Menu, MenuItem, MenuTrigger } from '@toeverything/components/menu';
|
||||
import type { ReactElement } from 'react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
// Fixme: keyboard focus should be supported by Menu component
|
||||
const LanguageMenuContent = ({
|
||||
currentLanguage,
|
||||
onSelect,
|
||||
}: {
|
||||
currentLanguage?: string;
|
||||
onSelect: (value: string) => void;
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
{LOCALES.map(option => {
|
||||
return (
|
||||
<MenuItem
|
||||
key={option.name}
|
||||
selected={currentLanguage === option.originalName}
|
||||
title={option.name}
|
||||
onSelect={() => onSelect(option.tag)}
|
||||
>
|
||||
{option.originalName}
|
||||
</MenuItem>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const LanguageMenu = () => {
|
||||
const i18n = useI18N();
|
||||
const currentLanguage = useMemo(
|
||||
() => LOCALES.find(item => item.tag === i18n.language),
|
||||
[i18n.language]
|
||||
);
|
||||
const onSelect = useCallback(
|
||||
(event: string) => {
|
||||
return i18n.changeLanguage(event);
|
||||
},
|
||||
[i18n]
|
||||
);
|
||||
return (
|
||||
<Menu
|
||||
items={
|
||||
(
|
||||
<LanguageMenuContent
|
||||
currentLanguage={currentLanguage?.originalName}
|
||||
onSelect={onSelect}
|
||||
/>
|
||||
) as ReactElement
|
||||
}
|
||||
contentOptions={{
|
||||
style: {
|
||||
background: 'var(--affine-white)',
|
||||
},
|
||||
align: 'end',
|
||||
}}
|
||||
>
|
||||
<MenuTrigger
|
||||
data-testid="language-menu-button"
|
||||
style={{ textTransform: 'capitalize', fontWeight: 600 }}
|
||||
block={true}
|
||||
>
|
||||
{currentLanguage?.originalName || ''}
|
||||
</MenuTrigger>
|
||||
</Menu>
|
||||
);
|
||||
};
|
||||
@@ -1,38 +0,0 @@
|
||||
import { styled } from '@affine/component';
|
||||
|
||||
export const StyledModalWrapper = styled('div')(() => {
|
||||
return {
|
||||
position: 'relative',
|
||||
padding: '0px',
|
||||
width: '560px',
|
||||
background: 'var(--affine-background-overlay-panel-color)',
|
||||
borderRadius: '12px',
|
||||
// height: '312px',
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledModalHeader = styled('div')(() => {
|
||||
return {
|
||||
margin: '44px 0px 12px 0px',
|
||||
width: '560px',
|
||||
fontWeight: '600',
|
||||
fontSize: '20px;',
|
||||
textAlign: 'center',
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledInputContent = styled('div')(() => {
|
||||
return {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
margin: '24px 0',
|
||||
fontSize: 'var(--affine-font-base)',
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledWorkspaceName = styled('span')(() => {
|
||||
return {
|
||||
fontWeight: '600',
|
||||
};
|
||||
});
|
||||
@@ -1,45 +0,0 @@
|
||||
import { globalStyle, style } from '@vanilla-extract/css';
|
||||
|
||||
export const settingContent = style({
|
||||
flexGrow: '1',
|
||||
height: '100%',
|
||||
padding: '40px 15px',
|
||||
overflow: 'hidden',
|
||||
});
|
||||
|
||||
globalStyle(`${settingContent} .wrapper`, {
|
||||
padding: '0 15px',
|
||||
height: '100%',
|
||||
maxWidth: '560px',
|
||||
margin: '0 auto',
|
||||
overflowY: 'auto',
|
||||
});
|
||||
|
||||
globalStyle(`${settingContent} .wrapper::-webkit-scrollbar`, {
|
||||
display: 'none',
|
||||
});
|
||||
globalStyle(`${settingContent} .content`, {
|
||||
minHeight: '100%',
|
||||
paddingBottom: '80px',
|
||||
});
|
||||
globalStyle(`${settingContent} .footer`, {
|
||||
cursor: 'pointer',
|
||||
paddingTop: '40px',
|
||||
marginTop: '-80px',
|
||||
fontSize: 'var(--affine-font-sm)',
|
||||
display: 'flex',
|
||||
minHeight: '100px',
|
||||
});
|
||||
|
||||
globalStyle(`${settingContent} .footer a`, {
|
||||
color: 'var(--affine-text-primary-color)',
|
||||
lineHeight: 'normal',
|
||||
});
|
||||
export const footerIconWrapper = style({
|
||||
fontSize: 'var(--affine-font-base)',
|
||||
color: 'var(--affine-icon-color)',
|
||||
marginRight: '12px',
|
||||
height: '19px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
});
|
||||
@@ -1,110 +0,0 @@
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { assertExists } from '@blocksuite/global/utils';
|
||||
import { Tooltip } from '@toeverything/components/tooltip';
|
||||
import { useBlockSuitePageMeta } from '@toeverything/hooks/use-block-suite-page-meta';
|
||||
import { useAtom } from 'jotai';
|
||||
import type { CSSProperties } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { pageSettingFamily } from '../../../atoms';
|
||||
import type { BlockSuiteWorkspace } from '../../../shared';
|
||||
import { toast } from '../../../utils';
|
||||
import { StyledEditorModeSwitch, StyledKeyboardItem } from './style';
|
||||
import { EdgelessSwitchItem, PageSwitchItem } from './switch-items';
|
||||
|
||||
export type EditorModeSwitchProps = {
|
||||
// todo(himself65): combine these two properties
|
||||
blockSuiteWorkspace: BlockSuiteWorkspace;
|
||||
pageId: string;
|
||||
style?: CSSProperties;
|
||||
};
|
||||
const TooltipContent = () => {
|
||||
const t = useAFFiNEI18N();
|
||||
return (
|
||||
<>
|
||||
{t['Switch']()}
|
||||
<StyledKeyboardItem>
|
||||
{!environment.isServer && environment.isMacOs ? '⌥ + S' : 'Alt + S'}
|
||||
</StyledKeyboardItem>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export const EditorModeSwitch = ({
|
||||
style,
|
||||
blockSuiteWorkspace,
|
||||
pageId,
|
||||
}: EditorModeSwitchProps) => {
|
||||
const [setting, setSetting] = useAtom(pageSettingFamily(pageId));
|
||||
const currentMode = setting?.mode ?? 'page';
|
||||
const pageMeta = useBlockSuitePageMeta(blockSuiteWorkspace).find(
|
||||
meta => meta.id === pageId
|
||||
);
|
||||
const t = useAFFiNEI18N();
|
||||
assertExists(pageMeta);
|
||||
const { trash } = pageMeta;
|
||||
useEffect(() => {
|
||||
if (trash) {
|
||||
return;
|
||||
}
|
||||
const keydown = (e: KeyboardEvent) => {
|
||||
if (
|
||||
!environment.isServer && environment.isMacOs
|
||||
? e.key === 'ß'
|
||||
: e.key === 's' && e.altKey
|
||||
) {
|
||||
e.preventDefault();
|
||||
setSetting(setting => {
|
||||
if (setting?.mode !== 'page') {
|
||||
toast(t['com.affine.toastMessage.pageMode']());
|
||||
return { ...setting, mode: 'page' };
|
||||
} else {
|
||||
toast(t['com.affine.toastMessage.edgelessMode']());
|
||||
return { ...setting, mode: 'edgeless' };
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
document.addEventListener('keydown', keydown, { capture: true });
|
||||
return () =>
|
||||
document.removeEventListener('keydown', keydown, { capture: true });
|
||||
}, [setSetting, t, trash]);
|
||||
|
||||
return (
|
||||
<Tooltip content={<TooltipContent />}>
|
||||
<StyledEditorModeSwitch
|
||||
style={style}
|
||||
switchLeft={currentMode === 'page'}
|
||||
showAlone={trash}
|
||||
>
|
||||
<PageSwitchItem
|
||||
data-testid="switch-page-mode-button"
|
||||
active={currentMode === 'page'}
|
||||
hide={trash && currentMode !== 'page'}
|
||||
trash={trash}
|
||||
onClick={() => {
|
||||
setSetting(setting => {
|
||||
if (setting?.mode !== 'page') {
|
||||
toast(t['com.affine.toastMessage.pageMode']());
|
||||
}
|
||||
return { ...setting, mode: 'page' };
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<EdgelessSwitchItem
|
||||
data-testid="switch-edgeless-mode-button"
|
||||
active={currentMode === 'edgeless'}
|
||||
hide={trash && currentMode !== 'edgeless'}
|
||||
trash={trash}
|
||||
onClick={() => {
|
||||
setSetting(setting => {
|
||||
if (setting?.mode !== 'edgeless') {
|
||||
toast(t['com.affine.toastMessage.edgelessMode']());
|
||||
}
|
||||
return { ...setting, mode: 'edgeless' };
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</StyledEditorModeSwitch>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
@@ -1,292 +0,0 @@
|
||||
import { Empty } from '@affine/component';
|
||||
import type { ListData, TrashListData } from '@affine/component/page-list';
|
||||
import { PageList, PageListTrashView } from '@affine/component/page-list';
|
||||
import type { Collection } from '@affine/env/filter';
|
||||
import { Trans } from '@affine/i18n';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { assertExists } from '@blocksuite/global/utils';
|
||||
import { EdgelessIcon, PageIcon } from '@blocksuite/icons';
|
||||
import { type PageMeta, type Workspace } from '@blocksuite/store';
|
||||
import { useBlockSuitePageMeta } from '@toeverything/hooks/use-block-suite-page-meta';
|
||||
import { useBlockSuitePagePreview } from '@toeverything/hooks/use-block-suite-page-preview';
|
||||
import { useBlockSuiteWorkspacePage } from '@toeverything/hooks/use-block-suite-workspace-page';
|
||||
import { useAtom, useAtomValue } from 'jotai';
|
||||
import { Suspense, useCallback, useMemo } from 'react';
|
||||
|
||||
import { allPageModeSelectAtom } from '../../../atoms';
|
||||
import { useBlockSuiteMetaHelper } from '../../../hooks/affine/use-block-suite-meta-helper';
|
||||
import { useGetPageInfoById } from '../../../hooks/use-get-page-info';
|
||||
import type { BlockSuiteWorkspace } from '../../../shared';
|
||||
import { toast } from '../../../utils';
|
||||
import { filterPage } from '../../../utils/filter';
|
||||
import { currentCollectionsAtom } from '../../../utils/user-setting';
|
||||
import { emptyDescButton, emptyDescKbd, pageListEmptyStyle } from './index.css';
|
||||
import { usePageHelper } from './utils';
|
||||
|
||||
export interface BlockSuitePageListProps {
|
||||
blockSuiteWorkspace: BlockSuiteWorkspace;
|
||||
listType: 'all' | 'trash' | 'shared' | 'public';
|
||||
isPublic?: boolean;
|
||||
onOpenPage: (pageId: string, newTab?: boolean) => void;
|
||||
collection?: Collection;
|
||||
}
|
||||
|
||||
const filter = {
|
||||
all: (pageMeta: PageMeta) => !pageMeta.trash,
|
||||
public: (pageMeta: PageMeta) => !pageMeta.trash,
|
||||
trash: (pageMeta: PageMeta, allMetas: PageMeta[]) => {
|
||||
const parentMeta = allMetas.find(m => m.subpageIds?.includes(pageMeta.id));
|
||||
return !parentMeta?.trash && pageMeta.trash;
|
||||
},
|
||||
shared: (pageMeta: PageMeta) => pageMeta.isPublic && !pageMeta.trash,
|
||||
};
|
||||
|
||||
interface PagePreviewInnerProps {
|
||||
workspace: Workspace;
|
||||
pageId: string;
|
||||
}
|
||||
|
||||
const PagePreviewInner = ({ workspace, pageId }: PagePreviewInnerProps) => {
|
||||
const page = useBlockSuiteWorkspacePage(workspace, pageId);
|
||||
assertExists(page);
|
||||
const previewAtom = useBlockSuitePagePreview(page);
|
||||
const preview = useAtomValue(previewAtom);
|
||||
return preview;
|
||||
};
|
||||
|
||||
interface PagePreviewProps {
|
||||
workspace: Workspace;
|
||||
pageId: string;
|
||||
}
|
||||
|
||||
const PagePreview = ({ workspace, pageId }: PagePreviewProps) => {
|
||||
return (
|
||||
<Suspense>
|
||||
<PagePreviewInner workspace={workspace} pageId={pageId} />
|
||||
</Suspense>
|
||||
);
|
||||
};
|
||||
|
||||
interface PageListEmptyProps {
|
||||
createPage?: ReturnType<typeof usePageHelper>['createPage'];
|
||||
listType: BlockSuitePageListProps['listType'];
|
||||
}
|
||||
|
||||
const PageListEmpty = (props: PageListEmptyProps) => {
|
||||
const { listType, createPage } = props;
|
||||
const t = useAFFiNEI18N();
|
||||
|
||||
const onCreatePage = useCallback(() => {
|
||||
createPage?.();
|
||||
}, [createPage]);
|
||||
|
||||
const getEmptyDescription = () => {
|
||||
if (listType === 'all') {
|
||||
const createNewPageButton = (
|
||||
<button className={emptyDescButton} onClick={onCreatePage}>
|
||||
New Page
|
||||
</button>
|
||||
);
|
||||
if (environment.isDesktop) {
|
||||
const shortcut = environment.isMacOs ? '⌘ + N' : 'Ctrl + N';
|
||||
return (
|
||||
<Trans i18nKey="emptyAllPagesClient">
|
||||
Click on the {createNewPageButton} button Or press
|
||||
<kbd className={emptyDescKbd}>{{ shortcut } as any}</kbd> to create
|
||||
your first page.
|
||||
</Trans>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Trans i18nKey="emptyAllPages">
|
||||
Click on the
|
||||
{createNewPageButton}
|
||||
button to create your first page.
|
||||
</Trans>
|
||||
);
|
||||
}
|
||||
if (listType === 'trash') {
|
||||
return t['emptyTrash']();
|
||||
}
|
||||
if (listType === 'shared') {
|
||||
return t['emptySharedPages']();
|
||||
}
|
||||
return;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={pageListEmptyStyle}>
|
||||
<Empty
|
||||
title={t['com.affine.emptyDesc']()}
|
||||
description={getEmptyDescription()}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const BlockSuitePageList = ({
|
||||
blockSuiteWorkspace,
|
||||
onOpenPage,
|
||||
listType,
|
||||
isPublic = false,
|
||||
collection,
|
||||
}: BlockSuitePageListProps) => {
|
||||
const pageMetas = useBlockSuitePageMeta(blockSuiteWorkspace);
|
||||
const {
|
||||
toggleFavorite,
|
||||
removeToTrash,
|
||||
restoreFromTrash,
|
||||
permanentlyDeletePage,
|
||||
cancelPublicPage,
|
||||
} = useBlockSuiteMetaHelper(blockSuiteWorkspace);
|
||||
const [filterMode] = useAtom(allPageModeSelectAtom);
|
||||
const { createPage, createEdgeless, importFile, isPreferredEdgeless } =
|
||||
usePageHelper(blockSuiteWorkspace);
|
||||
const t = useAFFiNEI18N();
|
||||
const getPageInfo = useGetPageInfoById(blockSuiteWorkspace);
|
||||
const tagOptionMap = useMemo(
|
||||
() =>
|
||||
Object.fromEntries(
|
||||
(blockSuiteWorkspace.meta.properties.tags?.options ?? []).map(v => [
|
||||
v.id,
|
||||
v,
|
||||
])
|
||||
),
|
||||
[blockSuiteWorkspace.meta.properties.tags?.options]
|
||||
);
|
||||
const list = useMemo(
|
||||
() =>
|
||||
pageMetas
|
||||
.filter(pageMeta => {
|
||||
if (filterMode === 'all') {
|
||||
return true;
|
||||
}
|
||||
if (filterMode === 'edgeless') {
|
||||
return isPreferredEdgeless(pageMeta.id);
|
||||
}
|
||||
if (filterMode === 'page') {
|
||||
return !isPreferredEdgeless(pageMeta.id);
|
||||
}
|
||||
console.error('unknown filter mode', pageMeta, filterMode);
|
||||
return true;
|
||||
})
|
||||
.filter(pageMeta => {
|
||||
if (!filter[listType](pageMeta, pageMetas)) {
|
||||
return false;
|
||||
}
|
||||
if (!collection) {
|
||||
return true;
|
||||
}
|
||||
return filterPage(collection, pageMeta);
|
||||
}),
|
||||
[pageMetas, filterMode, isPreferredEdgeless, listType, collection]
|
||||
);
|
||||
|
||||
if (listType === 'trash') {
|
||||
const pageList: TrashListData[] = list.map(pageMeta => {
|
||||
return {
|
||||
icon: isPreferredEdgeless(pageMeta.id) ? (
|
||||
<EdgelessIcon />
|
||||
) : (
|
||||
<PageIcon />
|
||||
),
|
||||
pageId: pageMeta.id,
|
||||
title: pageMeta.title,
|
||||
preview: (
|
||||
<PagePreview workspace={blockSuiteWorkspace} pageId={pageMeta.id} />
|
||||
),
|
||||
createDate: new Date(pageMeta.createDate),
|
||||
trashDate: pageMeta.trashDate
|
||||
? new Date(pageMeta.trashDate)
|
||||
: undefined,
|
||||
onClickPage: () => onOpenPage(pageMeta.id),
|
||||
onClickRestore: () => {
|
||||
restoreFromTrash(pageMeta.id);
|
||||
},
|
||||
onRestorePage: () => {
|
||||
restoreFromTrash(pageMeta.id);
|
||||
toast(
|
||||
t['com.affine.toastMessage.restored']({
|
||||
title: pageMeta.title || 'Untitled',
|
||||
})
|
||||
);
|
||||
},
|
||||
onPermanentlyDeletePage: () => {
|
||||
permanentlyDeletePage(pageMeta.id);
|
||||
toast(t['com.affine.toastMessage.permanentlyDeleted']());
|
||||
},
|
||||
};
|
||||
});
|
||||
return (
|
||||
<PageListTrashView
|
||||
list={pageList}
|
||||
fallback={<PageListEmpty listType={listType} />}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const pageList: ListData[] = list.map(pageMeta => {
|
||||
const page = blockSuiteWorkspace.getPage(pageMeta.id);
|
||||
return {
|
||||
icon: isPreferredEdgeless(pageMeta.id) ? <EdgelessIcon /> : <PageIcon />,
|
||||
pageId: pageMeta.id,
|
||||
title: pageMeta.title,
|
||||
preview: (
|
||||
<PagePreview workspace={blockSuiteWorkspace} pageId={pageMeta.id} />
|
||||
),
|
||||
tags:
|
||||
page?.meta.tags?.map(id => tagOptionMap[id]).filter(v => v != null) ??
|
||||
[],
|
||||
favorite: !!pageMeta.favorite,
|
||||
isPublicPage: !!pageMeta.isPublic,
|
||||
createDate: new Date(pageMeta.createDate),
|
||||
updatedDate: new Date(pageMeta.updatedDate ?? pageMeta.createDate),
|
||||
onClickPage: () => onOpenPage(pageMeta.id),
|
||||
onOpenPageInNewTab: () => onOpenPage(pageMeta.id, true),
|
||||
onClickRestore: () => {
|
||||
restoreFromTrash(pageMeta.id);
|
||||
},
|
||||
removeToTrash: () => {
|
||||
removeToTrash(pageMeta.id);
|
||||
toast(t['com.affine.toastMessage.successfullyDeleted']());
|
||||
},
|
||||
onRestorePage: () => {
|
||||
restoreFromTrash(pageMeta.id);
|
||||
toast(
|
||||
t['com.affine.toastMessage.restored']({
|
||||
title: pageMeta.title || 'Untitled',
|
||||
})
|
||||
);
|
||||
},
|
||||
bookmarkPage: () => {
|
||||
const status = pageMeta.favorite;
|
||||
toggleFavorite(pageMeta.id);
|
||||
toast(
|
||||
status
|
||||
? t['com.affine.toastMessage.removedFavorites']()
|
||||
: t['com.affine.toastMessage.addedFavorites']()
|
||||
);
|
||||
},
|
||||
onDisablePublicSharing: () => {
|
||||
cancelPublicPage(pageMeta.id);
|
||||
toast('Successfully disabled', {
|
||||
portal: document.body,
|
||||
});
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<PageList
|
||||
collectionsAtom={currentCollectionsAtom}
|
||||
propertiesMeta={blockSuiteWorkspace.meta.properties}
|
||||
getPageInfo={getPageInfo}
|
||||
onCreateNewPage={createPage}
|
||||
onCreateNewEdgeless={createEdgeless}
|
||||
onImportFile={importFile}
|
||||
isPublicWorkspace={isPublic}
|
||||
list={pageList}
|
||||
fallback={<PageListEmpty createPage={createPage} listType={listType} />}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -1,18 +0,0 @@
|
||||
import { style } from '@vanilla-extract/css';
|
||||
|
||||
export const filterContainerStyle = style({
|
||||
padding: '12px',
|
||||
display: 'flex',
|
||||
position: 'relative',
|
||||
'::after': {
|
||||
content: '""',
|
||||
display: 'block',
|
||||
width: '100%',
|
||||
height: '1px',
|
||||
background: 'var(--affine-border-color)',
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
margin: '0 1px',
|
||||
},
|
||||
});
|
||||
@@ -1,81 +0,0 @@
|
||||
import type {
|
||||
AffineSocketIOProvider,
|
||||
LocalIndexedDBBackgroundProvider,
|
||||
SQLiteProvider,
|
||||
} from '@affine/env/workspace';
|
||||
import {
|
||||
syncDataSourceFromDoc,
|
||||
syncDocFromDataSource,
|
||||
} from '@affine/y-provider';
|
||||
import { assertExists } from '@blocksuite/global/utils';
|
||||
import { Button } from '@toeverything/components/button';
|
||||
import { forceUpgradePages } from '@toeverything/infra/blocksuite';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
|
||||
import { useCurrentWorkspace } from '../hooks/current/use-current-workspace';
|
||||
|
||||
export const MigrationFallback = function MigrationFallback() {
|
||||
const [done, setDone] = useState(false);
|
||||
const [workspace] = useCurrentWorkspace();
|
||||
const providers = workspace.blockSuiteWorkspace.providers;
|
||||
const remoteProvider: AffineSocketIOProvider | undefined = useMemo(() => {
|
||||
return providers.find(
|
||||
(provider): provider is AffineSocketIOProvider =>
|
||||
provider.flavour === 'affine-socket-io'
|
||||
);
|
||||
}, [providers]);
|
||||
const localProvider = useMemo(() => {
|
||||
const sqliteProvider = providers.find(
|
||||
(provider): provider is SQLiteProvider => provider.flavour === 'sqlite'
|
||||
);
|
||||
const indexedDbProvider = providers.find(
|
||||
(provider): provider is LocalIndexedDBBackgroundProvider =>
|
||||
provider.flavour === 'local-indexeddb-background'
|
||||
);
|
||||
const provider = sqliteProvider || indexedDbProvider;
|
||||
assertExists(provider, 'no local provider');
|
||||
return provider;
|
||||
}, [providers]);
|
||||
const handleClick = useCallback(async () => {
|
||||
setDone(false);
|
||||
await syncDocFromDataSource(
|
||||
workspace.blockSuiteWorkspace.doc,
|
||||
localProvider.datasource
|
||||
);
|
||||
if (remoteProvider) {
|
||||
await syncDocFromDataSource(
|
||||
workspace.blockSuiteWorkspace.doc,
|
||||
remoteProvider.datasource
|
||||
);
|
||||
}
|
||||
|
||||
await forceUpgradePages({
|
||||
getCurrentRootDoc: async () => workspace.blockSuiteWorkspace.doc,
|
||||
getSchema: () => workspace.blockSuiteWorkspace.schema,
|
||||
});
|
||||
await syncDataSourceFromDoc(
|
||||
workspace.blockSuiteWorkspace.doc,
|
||||
localProvider.datasource
|
||||
);
|
||||
if (remoteProvider) {
|
||||
await syncDataSourceFromDoc(
|
||||
workspace.blockSuiteWorkspace.doc,
|
||||
remoteProvider.datasource
|
||||
);
|
||||
}
|
||||
setDone(true);
|
||||
}, [
|
||||
localProvider.datasource,
|
||||
remoteProvider,
|
||||
workspace.blockSuiteWorkspace.doc,
|
||||
workspace.blockSuiteWorkspace.schema,
|
||||
]);
|
||||
if (done) {
|
||||
return <div>Done, please refresh the page.</div>;
|
||||
}
|
||||
return (
|
||||
<Button data-testid="upgrade-workspace" onClick={handleClick}>
|
||||
Upgrade Workspace
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
@@ -1,86 +0,0 @@
|
||||
import { BrowserWarning } from '@affine/component/affine-banner';
|
||||
import { DownloadTips } from '@affine/component/affine-banner';
|
||||
import { Trans } from '@affine/i18n';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { useAtom } from 'jotai';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { guideDownloadClientTipAtom } from '../../../atoms/guide';
|
||||
|
||||
const minimumChromeVersion = 102;
|
||||
|
||||
const shouldShowWarning = () => {
|
||||
if (environment.isDesktop) {
|
||||
// even though desktop has compatibility issues,
|
||||
// we don't want to show the warning
|
||||
return false;
|
||||
}
|
||||
if (!environment.isBrowser) {
|
||||
// disable in SSR
|
||||
return false;
|
||||
}
|
||||
if (environment.isChrome) {
|
||||
return environment.chromeVersion < minimumChromeVersion;
|
||||
} else {
|
||||
return !environment.isMobile;
|
||||
}
|
||||
};
|
||||
|
||||
const OSWarningMessage = () => {
|
||||
const t = useAFFiNEI18N();
|
||||
const [notChrome, setNotChrome] = useState(false);
|
||||
const [notGoodVersion, setNotGoodVersion] = useState(false);
|
||||
useEffect(() => {
|
||||
setNotChrome(environment.isBrowser && !environment.isChrome);
|
||||
setNotGoodVersion(
|
||||
environment.isBrowser &&
|
||||
environment.isChrome &&
|
||||
environment.chromeVersion < minimumChromeVersion
|
||||
);
|
||||
}, []);
|
||||
|
||||
if (notChrome) {
|
||||
return (
|
||||
<span>
|
||||
<Trans i18nKey="recommendBrowser">
|
||||
We recommend the <strong>Chrome</strong> browser for an optimal
|
||||
experience.
|
||||
</Trans>
|
||||
</span>
|
||||
);
|
||||
} else if (notGoodVersion) {
|
||||
return <span>{t['upgradeBrowser']()}</span>;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
export const TopTip = () => {
|
||||
const [showWarning, setShowWarning] = useState(false);
|
||||
const [showDownloadTip, setShowDownloadTip] = useAtom(
|
||||
guideDownloadClientTipAtom
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setShowWarning(shouldShowWarning());
|
||||
}, []);
|
||||
|
||||
if (showDownloadTip && environment.isDesktop) {
|
||||
return (
|
||||
<DownloadTips
|
||||
onClose={() => {
|
||||
setShowDownloadTip(false);
|
||||
localStorage.setItem('affine-is-dt-hide', '1');
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<BrowserWarning
|
||||
show={showWarning}
|
||||
message={<OSWarningMessage />}
|
||||
onClose={() => {
|
||||
setShowWarning(false);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -1,148 +0,0 @@
|
||||
import { MuiFade } from '@affine/component';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { CloseIcon, NewIcon, UserGuideIcon } from '@blocksuite/icons';
|
||||
import { Tooltip } from '@toeverything/components/tooltip';
|
||||
import { useSetAtom } from 'jotai/react';
|
||||
import { useAtomValue } from 'jotai/react';
|
||||
import { useCallback, useState } from 'react';
|
||||
|
||||
import { openOnboardingModalAtom, openSettingModalAtom } from '../../../atoms';
|
||||
import { currentModeAtom } from '../../../atoms/mode';
|
||||
import { ShortcutsModal } from '../shortcuts-modal';
|
||||
import { ContactIcon, HelpIcon, KeyboardIcon } from './icons';
|
||||
import {
|
||||
StyledAnimateWrapper,
|
||||
StyledIconWrapper,
|
||||
StyledIsland,
|
||||
StyledTriggerWrapper,
|
||||
} from './style';
|
||||
|
||||
const DEFAULT_SHOW_LIST: IslandItemNames[] = [
|
||||
'whatNew',
|
||||
'contact',
|
||||
'shortcuts',
|
||||
];
|
||||
const DESKTOP_SHOW_LIST: IslandItemNames[] = [...DEFAULT_SHOW_LIST, 'guide'];
|
||||
export type IslandItemNames = 'whatNew' | 'contact' | 'shortcuts' | 'guide';
|
||||
|
||||
export const HelpIsland = ({
|
||||
showList = environment.isDesktop ? DESKTOP_SHOW_LIST : DEFAULT_SHOW_LIST,
|
||||
}: {
|
||||
showList?: IslandItemNames[];
|
||||
}) => {
|
||||
const mode = useAtomValue(currentModeAtom);
|
||||
const setOpenOnboarding = useSetAtom(openOnboardingModalAtom);
|
||||
const setOpenSettingModalAtom = useSetAtom(openSettingModalAtom);
|
||||
const [spread, setShowSpread] = useState(false);
|
||||
const t = useAFFiNEI18N();
|
||||
|
||||
const [openShortCut, setOpenShortCut] = useState(false);
|
||||
|
||||
const openAbout = useCallback(() => {
|
||||
setShowSpread(false);
|
||||
|
||||
setOpenSettingModalAtom({
|
||||
open: true,
|
||||
activeTab: 'about',
|
||||
workspaceId: null,
|
||||
});
|
||||
}, [setOpenSettingModalAtom]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<StyledIsland
|
||||
spread={spread}
|
||||
data-testid="help-island"
|
||||
onClick={() => {
|
||||
setShowSpread(!spread);
|
||||
}}
|
||||
inEdgelessPage={mode === 'edgeless'}
|
||||
>
|
||||
<StyledAnimateWrapper
|
||||
style={{ height: spread ? `${showList.length * 40 + 4}px` : 0 }}
|
||||
>
|
||||
{showList.includes('whatNew') && (
|
||||
<Tooltip
|
||||
content={t['com.affine.appUpdater.whatsNew']()}
|
||||
side="left"
|
||||
>
|
||||
<StyledIconWrapper
|
||||
data-testid="right-bottom-change-log-icon"
|
||||
onClick={() => {
|
||||
window.open(runtimeConfig.changelogUrl, '_blank');
|
||||
}}
|
||||
>
|
||||
<NewIcon />
|
||||
</StyledIconWrapper>
|
||||
</Tooltip>
|
||||
)}
|
||||
{showList.includes('contact') && (
|
||||
<Tooltip
|
||||
content={t['com.affine.helpIsland.contactUs']()}
|
||||
side="left"
|
||||
>
|
||||
<StyledIconWrapper
|
||||
data-testid="right-bottom-contact-us-icon"
|
||||
onClick={openAbout}
|
||||
>
|
||||
<ContactIcon />
|
||||
</StyledIconWrapper>
|
||||
</Tooltip>
|
||||
)}
|
||||
{showList.includes('shortcuts') && (
|
||||
<Tooltip
|
||||
content={t['com.affine.keyboardShortcuts.title']()}
|
||||
side="left"
|
||||
>
|
||||
<StyledIconWrapper
|
||||
data-testid="shortcuts-icon"
|
||||
onClick={() => {
|
||||
setShowSpread(false);
|
||||
setOpenShortCut(true);
|
||||
}}
|
||||
>
|
||||
<KeyboardIcon />
|
||||
</StyledIconWrapper>
|
||||
</Tooltip>
|
||||
)}
|
||||
{showList.includes('guide') && (
|
||||
<Tooltip
|
||||
content={t['com.affine.helpIsland.gettingStarted']()}
|
||||
side="left"
|
||||
>
|
||||
<StyledIconWrapper
|
||||
data-testid="easy-guide"
|
||||
onClick={() => {
|
||||
setShowSpread(false);
|
||||
setOpenOnboarding(true);
|
||||
}}
|
||||
>
|
||||
<UserGuideIcon />
|
||||
</StyledIconWrapper>
|
||||
</Tooltip>
|
||||
)}
|
||||
</StyledAnimateWrapper>
|
||||
|
||||
<Tooltip
|
||||
content={t['com.affine.helpIsland.helpAndFeedback']()}
|
||||
side="left"
|
||||
>
|
||||
<MuiFade in={!spread} data-testid="faq-icon">
|
||||
<StyledTriggerWrapper>
|
||||
<HelpIcon />
|
||||
</StyledTriggerWrapper>
|
||||
</MuiFade>
|
||||
</Tooltip>
|
||||
<MuiFade in={spread}>
|
||||
<StyledTriggerWrapper spread>
|
||||
<CloseIcon />
|
||||
</StyledTriggerWrapper>
|
||||
</MuiFade>
|
||||
</StyledIsland>
|
||||
<ShortcutsModal
|
||||
open={openShortCut}
|
||||
onClose={() => setOpenShortCut(false)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,60 +0,0 @@
|
||||
import { WorkspaceSubPath } from '@affine/env/workspace';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import {
|
||||
DeleteTemporarilyIcon,
|
||||
FolderIcon,
|
||||
SettingsIcon,
|
||||
} from '@blocksuite/icons';
|
||||
import { useAtom } from 'jotai';
|
||||
import type { ReactElement, SVGProps } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { openSettingModalAtom } from '../../../atoms';
|
||||
|
||||
type IconComponent = (props: SVGProps<SVGSVGElement>) => ReactElement;
|
||||
|
||||
interface ConfigItem {
|
||||
title: string;
|
||||
icon: IconComponent;
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
interface ConfigPathItem {
|
||||
title: string;
|
||||
icon: IconComponent;
|
||||
subPath: WorkspaceSubPath;
|
||||
}
|
||||
|
||||
export type Config = ConfigItem | ConfigPathItem;
|
||||
|
||||
export const useSwitchToConfig = (workspaceId: string): Config[] => {
|
||||
const t = useAFFiNEI18N();
|
||||
const [, setOpenSettingModalAtom] = useAtom(openSettingModalAtom);
|
||||
|
||||
return useMemo(
|
||||
() => [
|
||||
{
|
||||
title: t['com.affine.workspaceSubPath.all'](),
|
||||
subPath: WorkspaceSubPath.ALL,
|
||||
icon: FolderIcon,
|
||||
},
|
||||
{
|
||||
title: t['Workspace Settings'](),
|
||||
onClick: () => {
|
||||
setOpenSettingModalAtom({
|
||||
open: true,
|
||||
activeTab: 'workspace',
|
||||
workspaceId,
|
||||
});
|
||||
},
|
||||
icon: SettingsIcon,
|
||||
},
|
||||
{
|
||||
title: t['com.affine.workspaceSubPath.trash'](),
|
||||
subPath: WorkspaceSubPath.TRASH,
|
||||
icon: DeleteTemporarilyIcon,
|
||||
},
|
||||
],
|
||||
[t, workspaceId, setOpenSettingModalAtom]
|
||||
);
|
||||
};
|
||||
@@ -1,59 +0,0 @@
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { assertEquals } from '@blocksuite/global/utils';
|
||||
import { PlusIcon } from '@blocksuite/icons';
|
||||
import { nanoid } from '@blocksuite/store';
|
||||
import { useBlockSuiteWorkspaceHelper } from '@toeverything/hooks/use-block-suite-workspace-helper';
|
||||
import { initEmptyPage } from '@toeverything/infra/blocksuite';
|
||||
import { Command } from 'cmdk';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { useNavigateHelper } from '../../../hooks/use-navigate-helper';
|
||||
import type { BlockSuiteWorkspace } from '../../../shared';
|
||||
import { StyledModalFooterContent } from './style';
|
||||
|
||||
export interface FooterProps {
|
||||
query: string;
|
||||
onClose: () => void;
|
||||
blockSuiteWorkspace: BlockSuiteWorkspace;
|
||||
}
|
||||
|
||||
export const Footer = ({
|
||||
query,
|
||||
onClose,
|
||||
blockSuiteWorkspace,
|
||||
}: FooterProps) => {
|
||||
const { createPage } = useBlockSuiteWorkspaceHelper(blockSuiteWorkspace);
|
||||
const t = useAFFiNEI18N();
|
||||
const { jumpToPage } = useNavigateHelper();
|
||||
const MAX_QUERY_SHOW_LENGTH = 20;
|
||||
const normalizedQuery =
|
||||
query.length > MAX_QUERY_SHOW_LENGTH
|
||||
? query.slice(0, MAX_QUERY_SHOW_LENGTH) + '...'
|
||||
: query;
|
||||
|
||||
return (
|
||||
<Command.Item
|
||||
data-testid="quick-search-add-new-page"
|
||||
onSelect={useCallback(async () => {
|
||||
const id = nanoid();
|
||||
const page = createPage(id);
|
||||
assertEquals(page.id, id);
|
||||
await initEmptyPage(page, query);
|
||||
blockSuiteWorkspace.setPageMeta(page.id, {
|
||||
title: query,
|
||||
});
|
||||
onClose();
|
||||
jumpToPage(blockSuiteWorkspace.id, page.id);
|
||||
}, [blockSuiteWorkspace, createPage, jumpToPage, onClose, query])}
|
||||
>
|
||||
<StyledModalFooterContent>
|
||||
<PlusIcon />
|
||||
{query ? (
|
||||
<span>{t['New Keyword Page']({ query: normalizedQuery })}</span>
|
||||
) : (
|
||||
<span>{t['New Page']()}</span>
|
||||
)}
|
||||
</StyledModalFooterContent>
|
||||
</Command.Item>
|
||||
);
|
||||
};
|
||||
@@ -1,164 +0,0 @@
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { Modal } from '@toeverything/components/modal';
|
||||
import { Command } from 'cmdk';
|
||||
import { startTransition, Suspense } from 'react';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
|
||||
import type { AllWorkspace } from '../../../shared';
|
||||
import { Footer } from './footer';
|
||||
import { Results } from './results';
|
||||
import { SearchInput } from './search-input';
|
||||
import {
|
||||
StyledContent,
|
||||
StyledModalDivider,
|
||||
StyledModalFooter,
|
||||
StyledModalHeader,
|
||||
StyledNotFound,
|
||||
StyledShortcut,
|
||||
} from './style';
|
||||
|
||||
export interface QuickSearchModalProps {
|
||||
workspace: AllWorkspace;
|
||||
open: boolean;
|
||||
setOpen: (value: boolean) => void;
|
||||
}
|
||||
|
||||
export const QuickSearchModal = ({
|
||||
open,
|
||||
setOpen,
|
||||
workspace,
|
||||
}: QuickSearchModalProps) => {
|
||||
const blockSuiteWorkspace = workspace?.blockSuiteWorkspace;
|
||||
const t = useAFFiNEI18N();
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const [query, _setQuery] = useState('');
|
||||
const setQuery = useCallback((query: string) => {
|
||||
startTransition(() => {
|
||||
_setQuery(query);
|
||||
});
|
||||
}, []);
|
||||
const [showCreatePage, setShowCreatePage] = useState(true);
|
||||
const handleClose = useCallback(() => {
|
||||
setOpen(false);
|
||||
}, [setOpen]);
|
||||
|
||||
// Add ‘⌘+K’ shortcut keys as switches
|
||||
useEffect(() => {
|
||||
const keydown = (e: KeyboardEvent) => {
|
||||
if ((e.key === 'k' && e.metaKey) || (e.key === 'k' && e.ctrlKey)) {
|
||||
const selection = window.getSelection();
|
||||
// prevent search bar focus in firefox
|
||||
e.preventDefault();
|
||||
setQuery('');
|
||||
if (selection?.toString()) {
|
||||
setOpen(false);
|
||||
return;
|
||||
}
|
||||
setOpen(!open);
|
||||
}
|
||||
};
|
||||
document.addEventListener('keydown', keydown, { capture: true });
|
||||
return () =>
|
||||
document.removeEventListener('keydown', keydown, { capture: true });
|
||||
}, [open, setOpen, setQuery]);
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
// Waiting for DOM rendering
|
||||
requestAnimationFrame(() => {
|
||||
const inputElement = inputRef.current;
|
||||
inputElement?.focus();
|
||||
});
|
||||
}
|
||||
}, [open]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
width={608}
|
||||
withoutCloseButton
|
||||
contentOptions={{
|
||||
['data-testid' as string]: 'quickSearch',
|
||||
style: {
|
||||
maxHeight: '80vh',
|
||||
minHeight: '412px',
|
||||
top: '80px',
|
||||
overflow: 'hidden',
|
||||
transform: 'translateX(-50%)',
|
||||
padding: 0,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Command
|
||||
shouldFilter={false}
|
||||
//Handle KeyboardEvent conflicts with blocksuite
|
||||
onKeyDown={(e: React.KeyboardEvent) => {
|
||||
if (
|
||||
e.key === 'ArrowDown' ||
|
||||
e.key === 'ArrowUp' ||
|
||||
e.key === 'ArrowLeft' ||
|
||||
e.key === 'ArrowRight'
|
||||
) {
|
||||
e.stopPropagation();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<StyledModalHeader>
|
||||
<SearchInput
|
||||
ref={inputRef}
|
||||
onValueChange={value => {
|
||||
setQuery(value);
|
||||
}}
|
||||
onKeyDown={e => {
|
||||
// Avoid triggering the cmdk onSelect event when the input method is in use
|
||||
if (e.nativeEvent.isComposing) {
|
||||
e.stopPropagation();
|
||||
return;
|
||||
}
|
||||
}}
|
||||
placeholder={t['Quick search placeholder']()}
|
||||
/>
|
||||
<StyledShortcut>
|
||||
{environment.isBrowser && environment.isMacOs
|
||||
? '⌘ + K'
|
||||
: 'Ctrl + K'}
|
||||
</StyledShortcut>
|
||||
</StyledModalHeader>
|
||||
<StyledModalDivider />
|
||||
<Command.List>
|
||||
<StyledContent>
|
||||
<Suspense
|
||||
fallback={
|
||||
<StyledNotFound>
|
||||
<span>{t['com.affine.loading']()}</span>
|
||||
</StyledNotFound>
|
||||
}
|
||||
>
|
||||
<Results
|
||||
query={query}
|
||||
onClose={handleClose}
|
||||
workspace={workspace}
|
||||
setShowCreatePage={setShowCreatePage}
|
||||
/>
|
||||
</Suspense>
|
||||
</StyledContent>
|
||||
{showCreatePage ? (
|
||||
<>
|
||||
<StyledModalDivider />
|
||||
<StyledModalFooter>
|
||||
<Footer
|
||||
query={query}
|
||||
onClose={handleClose}
|
||||
blockSuiteWorkspace={blockSuiteWorkspace}
|
||||
/>
|
||||
</StyledModalFooter>
|
||||
</>
|
||||
) : null}
|
||||
</Command.List>
|
||||
</Command>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default QuickSearchModal;
|
||||
@@ -1,188 +0,0 @@
|
||||
import { UNTITLED_WORKSPACE_NAME } from '@affine/env/constant';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { assertExists } from '@blocksuite/global/utils';
|
||||
import { EdgelessIcon, PageIcon } from '@blocksuite/icons';
|
||||
import type { Workspace } from '@blocksuite/store';
|
||||
import { useBlockSuitePageMeta } from '@toeverything/hooks/use-block-suite-page-meta';
|
||||
import { useBlockSuiteWorkspaceHelper } from '@toeverything/hooks/use-block-suite-workspace-helper';
|
||||
import { Command } from 'cmdk';
|
||||
import { type Atom, atom, useAtomValue } from 'jotai';
|
||||
import type { Dispatch, SetStateAction } from 'react';
|
||||
import { startTransition, useEffect } from 'react';
|
||||
|
||||
import { recentPageSettingsAtom } from '../../../atoms';
|
||||
import { useNavigateHelper } from '../../../hooks/use-navigate-helper';
|
||||
import type { AllWorkspace } from '../../../shared';
|
||||
import { useSwitchToConfig } from './config';
|
||||
import { StyledListItem, StyledNotFound } from './style';
|
||||
|
||||
export interface ResultsProps {
|
||||
workspace: AllWorkspace;
|
||||
query: string;
|
||||
onClose: () => void;
|
||||
setShowCreatePage: Dispatch<SetStateAction<boolean>>;
|
||||
}
|
||||
|
||||
const loadAllPageWeakMap = new WeakMap<Workspace, Atom<Promise<void>>>();
|
||||
|
||||
function getLoadAllPage(workspace: Workspace) {
|
||||
if (loadAllPageWeakMap.has(workspace)) {
|
||||
return loadAllPageWeakMap.get(workspace) as Atom<Promise<void>>;
|
||||
} else {
|
||||
const aAtom = atom(async () => {
|
||||
// fixme: we have to load all pages here and re-index them
|
||||
// there might have performance issue
|
||||
await Promise.all(
|
||||
[...workspace.pages.values()].map(page =>
|
||||
page.waitForLoaded().then(() => {
|
||||
workspace.indexer.search.refreshPageIndex(page.id, page.spaceDoc);
|
||||
})
|
||||
)
|
||||
);
|
||||
});
|
||||
loadAllPageWeakMap.set(workspace, aAtom);
|
||||
return aAtom;
|
||||
}
|
||||
}
|
||||
|
||||
export const Results = ({
|
||||
query,
|
||||
workspace,
|
||||
setShowCreatePage,
|
||||
onClose,
|
||||
}: ResultsProps) => {
|
||||
const blockSuiteWorkspace = workspace.blockSuiteWorkspace;
|
||||
useBlockSuiteWorkspaceHelper(blockSuiteWorkspace);
|
||||
const pageList = useBlockSuitePageMeta(blockSuiteWorkspace);
|
||||
assertExists(blockSuiteWorkspace.id);
|
||||
const list = useSwitchToConfig(workspace.id);
|
||||
useAtomValue(getLoadAllPage(blockSuiteWorkspace));
|
||||
|
||||
const recentPageSetting = useAtomValue(recentPageSettingsAtom);
|
||||
const t = useAFFiNEI18N();
|
||||
const { jumpToPage, jumpToSubPath } = useNavigateHelper();
|
||||
const pageIds = [...blockSuiteWorkspace.search({ query }).values()].map(
|
||||
id => {
|
||||
if (id.startsWith('space:')) {
|
||||
return id.slice(6);
|
||||
} else {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const resultsPageMeta = pageList.filter(
|
||||
page => pageIds.indexOf(page.id) > -1 && !page.trash
|
||||
);
|
||||
|
||||
const recentlyViewedItem = recentPageSetting.filter(recent => {
|
||||
const page = pageList.find(page => recent.id === page.id);
|
||||
if (!page) {
|
||||
return false;
|
||||
} else {
|
||||
return page.trash !== true;
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
startTransition(() => {
|
||||
setShowCreatePage(resultsPageMeta.length === 0);
|
||||
});
|
||||
}, [resultsPageMeta.length, setShowCreatePage]);
|
||||
|
||||
if (!query) {
|
||||
return (
|
||||
<>
|
||||
{recentlyViewedItem.length > 0 && (
|
||||
<Command.Group heading={t['Recent']()}>
|
||||
{recentlyViewedItem.map(recent => {
|
||||
const page = pageList.find(page => recent.id === page.id);
|
||||
assertExists(page);
|
||||
return (
|
||||
<Command.Item
|
||||
key={page.id}
|
||||
value={page.id}
|
||||
onSelect={() => {
|
||||
onClose();
|
||||
jumpToPage(blockSuiteWorkspace.id, page.id);
|
||||
}}
|
||||
>
|
||||
<StyledListItem>
|
||||
{recent.mode === 'edgeless' ? (
|
||||
<EdgelessIcon />
|
||||
) : (
|
||||
<PageIcon />
|
||||
)}
|
||||
<span>{page.title || UNTITLED_WORKSPACE_NAME}</span>
|
||||
</StyledListItem>
|
||||
</Command.Item>
|
||||
);
|
||||
})}
|
||||
</Command.Group>
|
||||
)}
|
||||
<Command.Group heading={t['Jump to']()}>
|
||||
{list.map(link => {
|
||||
return (
|
||||
<Command.Item
|
||||
key={link.title}
|
||||
value={link.title}
|
||||
onSelect={() => {
|
||||
onClose();
|
||||
if ('subPath' in link) {
|
||||
jumpToSubPath(blockSuiteWorkspace.id, link.subPath);
|
||||
} else if ('onClick' in link) {
|
||||
link.onClick();
|
||||
} else {
|
||||
throw new Error('Invalid link');
|
||||
}
|
||||
}}
|
||||
>
|
||||
<StyledListItem>
|
||||
<link.icon />
|
||||
<span>{link.title}</span>
|
||||
</StyledListItem>
|
||||
</Command.Item>
|
||||
);
|
||||
})}
|
||||
</Command.Group>
|
||||
</>
|
||||
);
|
||||
}
|
||||
if (!resultsPageMeta.length) {
|
||||
return (
|
||||
<StyledNotFound>
|
||||
<span>{t['Find 0 result']()}</span>
|
||||
<img
|
||||
alt="no result"
|
||||
src="/imgs/no-result.svg"
|
||||
width={200}
|
||||
height={200}
|
||||
/>
|
||||
</StyledNotFound>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Command.Group
|
||||
heading={t['Find results']({ number: `${resultsPageMeta.length}` })}
|
||||
>
|
||||
{resultsPageMeta.map(result => {
|
||||
return (
|
||||
<Command.Item
|
||||
key={result.id}
|
||||
onSelect={() => {
|
||||
onClose();
|
||||
assertExists(blockSuiteWorkspace.id);
|
||||
jumpToPage(blockSuiteWorkspace.id, result.id);
|
||||
}}
|
||||
value={result.id}
|
||||
>
|
||||
<StyledListItem>
|
||||
{result.mode === 'edgeless' ? <EdgelessIcon /> : <PageIcon />}
|
||||
<span>{result.title || UNTITLED_WORKSPACE_NAME}</span>
|
||||
</StyledListItem>
|
||||
</Command.Item>
|
||||
);
|
||||
})}
|
||||
</Command.Group>
|
||||
);
|
||||
};
|
||||
@@ -1,33 +0,0 @@
|
||||
import { SearchIcon } from '@blocksuite/icons';
|
||||
import { Command } from 'cmdk';
|
||||
import { forwardRef } from 'react';
|
||||
|
||||
import { StyledInputContent, StyledLabel } from './style';
|
||||
|
||||
export const SearchInput = forwardRef<
|
||||
HTMLInputElement,
|
||||
Omit<
|
||||
React.InputHTMLAttributes<HTMLInputElement>,
|
||||
'value' | 'onChange' | 'type'
|
||||
> & {
|
||||
/**
|
||||
* Optional controlled state for the value of the search input.
|
||||
*/
|
||||
value?: string;
|
||||
/**
|
||||
* Event handler called when the search value changes.
|
||||
*/
|
||||
onValueChange?: (search: string) => void;
|
||||
} & React.RefAttributes<HTMLInputElement>
|
||||
>((props, ref) => {
|
||||
return (
|
||||
<StyledInputContent>
|
||||
<StyledLabel htmlFor=":r5:">
|
||||
<SearchIcon />
|
||||
</StyledLabel>
|
||||
<Command.Input ref={ref} {...props} />
|
||||
</StyledInputContent>
|
||||
);
|
||||
});
|
||||
|
||||
SearchInput.displayName = 'SearchInput';
|
||||
@@ -1,180 +0,0 @@
|
||||
import { displayFlex, styled, textEllipsis } from '@affine/component';
|
||||
|
||||
export const StyledContent = styled('div')(() => {
|
||||
return {
|
||||
minHeight: '290px',
|
||||
maxHeight: '70vh',
|
||||
width: '100%',
|
||||
overflow: 'auto',
|
||||
marginBottom: '10px',
|
||||
...displayFlex('flex-start', 'flex-start'),
|
||||
flexDirection: 'column',
|
||||
color: 'var(--affine-text-primary-color)',
|
||||
transition: 'all 0.15s',
|
||||
letterSpacing: '0.06em',
|
||||
'[cmdk-group]': {
|
||||
width: '100%',
|
||||
},
|
||||
'[cmdk-group-heading]': {
|
||||
...displayFlex('start', 'center'),
|
||||
margin: '0 16px',
|
||||
height: '36px',
|
||||
lineHeight: '22px',
|
||||
fontSize: 'var(--affine-font-sm)',
|
||||
color: 'var(--affine-text-secondary-color)',
|
||||
},
|
||||
'[cmdk-item]': {
|
||||
margin: '0 4px',
|
||||
},
|
||||
'[aria-selected="true"]': {
|
||||
transition: 'all 0.15s',
|
||||
borderRadius: '4px',
|
||||
color: 'var(--affine-primary-color)',
|
||||
backgroundColor: 'var(--affine-hover-color)',
|
||||
padding: '0 2px',
|
||||
},
|
||||
};
|
||||
});
|
||||
export const StyledJumpTo = styled('div')(() => {
|
||||
return {
|
||||
...displayFlex('center', 'start'),
|
||||
flexDirection: 'column',
|
||||
padding: '10px 10px 10px 0',
|
||||
fontSize: 'var(--affine-font-base)',
|
||||
strong: {
|
||||
fontWeight: '500',
|
||||
marginBottom: '10px',
|
||||
},
|
||||
};
|
||||
});
|
||||
export const StyledNotFound = styled('div')(() => {
|
||||
return {
|
||||
width: '612px',
|
||||
...displayFlex('center', 'center'),
|
||||
flexDirection: 'column',
|
||||
padding: '0 16px',
|
||||
fontSize: 'var(--affine-font-sm)',
|
||||
lineHeight: '22px',
|
||||
color: 'var(--affine-text-secondary-color)',
|
||||
span: {
|
||||
...displayFlex('flex-start', 'center'),
|
||||
width: '100%',
|
||||
fontWeight: '400',
|
||||
height: '36px',
|
||||
},
|
||||
|
||||
img: {
|
||||
marginTop: '10px',
|
||||
},
|
||||
};
|
||||
});
|
||||
export const StyledInputContent = styled('div')(() => {
|
||||
return {
|
||||
...displayFlex('space-between', 'center'),
|
||||
input: {
|
||||
width: '492px',
|
||||
height: '22px',
|
||||
padding: '0 12px',
|
||||
fontSize: 'var(--affine-font-base)',
|
||||
...displayFlex('space-between', 'center'),
|
||||
letterSpacing: '0.06em',
|
||||
color: 'var(--affine-text-primary-color)',
|
||||
'::placeholder': {
|
||||
color: 'var(--affine-placeholder-color)',
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
export const StyledShortcut = styled('div')(() => {
|
||||
return {
|
||||
color: 'var(--affine-placeholder-color)',
|
||||
fontSize: 'var(--affine-font-sm)',
|
||||
whiteSpace: 'nowrap',
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledLabel = styled('label')(() => {
|
||||
return {
|
||||
width: '20px',
|
||||
height: '20px',
|
||||
color: 'var(--affine-icon-color)',
|
||||
fontSize: '20px',
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledModalHeader = styled('div')(() => {
|
||||
return {
|
||||
height: '36px',
|
||||
margin: '12px 16px 0px 16px',
|
||||
...displayFlex('space-between', 'center'),
|
||||
};
|
||||
});
|
||||
export const StyledModalDivider = styled('div')(() => {
|
||||
return {
|
||||
width: 'auto',
|
||||
height: '0',
|
||||
margin: '6px 16px',
|
||||
borderTop: '0.5px solid var(--affine-border-color)',
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledModalFooter = styled('div')(() => {
|
||||
return {
|
||||
fontSize: 'inherit',
|
||||
lineHeight: '22px',
|
||||
marginBottom: '8px',
|
||||
textAlign: 'center',
|
||||
color: 'var(--affine-text-primary-color)',
|
||||
...displayFlex('center', 'center'),
|
||||
transition: 'all .15s',
|
||||
'[cmdk-item]': {
|
||||
margin: '0 4px',
|
||||
},
|
||||
'[aria-selected="true"]': {
|
||||
transition: 'all 0.15s',
|
||||
borderRadius: '4px',
|
||||
color: 'var(--affine-primary-color)',
|
||||
backgroundColor: 'var(--affine-hover-color)',
|
||||
'span,svg': {
|
||||
transition: 'all 0.15s',
|
||||
transform: 'scale(1.02)',
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
export const StyledModalFooterContent = styled('button')(() => {
|
||||
return {
|
||||
width: '600px',
|
||||
height: '32px',
|
||||
fontSize: 'var(--affine-font-base)',
|
||||
lineHeight: '22px',
|
||||
textAlign: 'center',
|
||||
...displayFlex('center', 'center'),
|
||||
color: 'inherit',
|
||||
borderRadius: '4px',
|
||||
transition: 'background .15s, color .15s',
|
||||
'>svg': {
|
||||
fontSize: '20px',
|
||||
marginRight: '12px',
|
||||
},
|
||||
};
|
||||
});
|
||||
export const StyledListItem = styled('button')(() => {
|
||||
return {
|
||||
width: '100%',
|
||||
height: '32px',
|
||||
fontSize: 'inherit',
|
||||
color: 'inherit',
|
||||
padding: '0 12px',
|
||||
borderRadius: '4px',
|
||||
transition: 'all .15s',
|
||||
...displayFlex('flex-start', 'center'),
|
||||
span: {
|
||||
...textEllipsis(1),
|
||||
},
|
||||
'> svg': {
|
||||
fontSize: '20px',
|
||||
marginRight: '12px',
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -1,27 +0,0 @@
|
||||
export const CloseIcon = () => {
|
||||
return (
|
||||
<svg
|
||||
width="14"
|
||||
height="14"
|
||||
viewBox="0 0 14 14"
|
||||
fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M7.94 7.00014L13.4667 1.47348C13.5759 1.34594 13.633 1.18189 13.6265 1.01411C13.62 0.846324 13.5504 0.687165 13.4317 0.568435C13.313 0.449706 13.1538 0.38015 12.986 0.37367C12.8183 0.367189 12.6542 0.42426 12.5267 0.533477L7 6.06014L1.47334 0.526811C1.3478 0.401275 1.17754 0.33075 1 0.33075C0.822468 0.33075 0.652205 0.401275 0.526669 0.526811C0.401133 0.652346 0.330608 0.82261 0.330608 1.00014C0.330608 1.17768 0.401133 1.34794 0.526669 1.47348L6.06 7.00014L0.526669 12.5268C0.456881 12.5866 0.400201 12.6601 0.360186 12.7428C0.32017 12.8255 0.297683 12.9156 0.294137 13.0074C0.290591 13.0993 0.306061 13.1908 0.339577 13.2764C0.373094 13.3619 0.423932 13.4396 0.488902 13.5046C0.553872 13.5695 0.63157 13.6204 0.71712 13.6539C0.80267 13.6874 0.894225 13.7029 0.986038 13.6993C1.07785 13.6958 1.16794 13.6733 1.25065 13.6333C1.33336 13.5933 1.4069 13.5366 1.46667 13.4668L7 7.94014L12.5267 13.4668C12.6542 13.576 12.8183 13.6331 12.986 13.6266C13.1538 13.6201 13.313 13.5506 13.4317 13.4319C13.5504 13.3131 13.62 13.154 13.6265 12.9862C13.633 12.8184 13.5759 12.6543 13.4667 12.5268L7.94 7.00014Z" />
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export const KeyboardIcon = () => {
|
||||
return (
|
||||
<svg
|
||||
width="20"
|
||||
height="15"
|
||||
viewBox="0 0 20 15"
|
||||
fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M17.745 0C18.3417 0 18.914 0.237053 19.336 0.65901C19.7579 1.08097 19.995 1.65326 19.995 2.25V11.755C19.995 12.3517 19.7579 12.924 19.336 13.346C18.914 13.7679 18.3417 14.005 17.745 14.005H2.25C1.95453 14.005 1.66194 13.9468 1.38896 13.8337C1.11598 13.7207 0.867941 13.5549 0.65901 13.346C0.450078 13.1371 0.284344 12.889 0.171271 12.616C0.058198 12.3431 0 12.0505 0 11.755V2.25C0 1.65326 0.237053 1.08097 0.65901 0.65901C1.08097 0.237053 1.65326 0 2.25 0H17.745ZM17.745 1.5H2.25C2.05109 1.5 1.86032 1.57902 1.71967 1.71967C1.57902 1.86032 1.5 2.05109 1.5 2.25V11.755C1.5 12.169 1.836 12.505 2.25 12.505H17.745C17.9439 12.505 18.1347 12.426 18.2753 12.2853C18.416 12.1447 18.495 11.9539 18.495 11.755V2.25C18.495 2.05109 18.416 1.86032 18.2753 1.71967C18.1347 1.57902 17.9439 1.5 17.745 1.5ZM4.75 9.5H15.25C15.44 9.50006 15.6229 9.57224 15.7618 9.70197C15.9006 9.8317 15.9851 10.0093 15.998 10.1989C16.011 10.3885 15.9515 10.5759 15.8316 10.7233C15.7117 10.8707 15.5402 10.9671 15.352 10.993L15.25 11H4.75C4.55998 10.9999 4.37706 10.9278 4.23821 10.798C4.09936 10.6683 4.01493 10.4907 4.00197 10.3011C3.98902 10.1115 4.04852 9.92411 4.16843 9.7767C4.28835 9.62929 4.45975 9.5329 4.648 9.507L4.75 9.5H15.25H4.75ZM14.5 6C14.7652 6 15.0196 6.10536 15.2071 6.29289C15.3946 6.48043 15.5 6.73478 15.5 7C15.5 7.26522 15.3946 7.51957 15.2071 7.70711C15.0196 7.89464 14.7652 8 14.5 8C14.2348 8 13.9804 7.89464 13.7929 7.70711C13.6054 7.51957 13.5 7.26522 13.5 7C13.5 6.73478 13.6054 6.48043 13.7929 6.29289C13.9804 6.10536 14.2348 6 14.5 6ZM8.505 6C8.77022 6 9.02457 6.10536 9.21211 6.29289C9.39964 6.48043 9.505 6.73478 9.505 7C9.505 7.26522 9.39964 7.51957 9.21211 7.70711C9.02457 7.89464 8.77022 8 8.505 8C8.23978 8 7.98543 7.89464 7.79789 7.70711C7.61036 7.51957 7.505 7.26522 7.505 7C7.505 6.73478 7.61036 6.48043 7.79789 6.29289C7.98543 6.10536 8.23978 6 8.505 6ZM5.505 6C5.77022 6 6.02457 6.10536 6.21211 6.29289C6.39964 6.48043 6.505 6.73478 6.505 7C6.505 7.26522 6.39964 7.51957 6.21211 7.70711C6.02457 7.89464 5.77022 8 5.505 8C5.23978 8 4.98543 7.89464 4.79789 7.70711C4.61036 7.51957 4.505 7.26522 4.505 7C4.505 6.73478 4.61036 6.48043 4.79789 6.29289C4.98543 6.10536 5.23978 6 5.505 6ZM11.505 6C11.7702 6 12.0246 6.10536 12.2121 6.29289C12.3996 6.48043 12.505 6.73478 12.505 7C12.505 7.26522 12.3996 7.51957 12.2121 7.70711C12.0246 7.89464 11.7702 8 11.505 8C11.2398 8 10.9854 7.89464 10.7979 7.70711C10.6104 7.51957 10.505 7.26522 10.505 7C10.505 6.73478 10.6104 6.48043 10.7979 6.29289C10.9854 6.10536 11.2398 6 11.505 6ZM4 3C4.26522 3 4.51957 3.10536 4.70711 3.29289C4.89464 3.48043 5 3.73478 5 4C5 4.26522 4.89464 4.51957 4.70711 4.70711C4.51957 4.89464 4.26522 5 4 5C3.73478 5 3.48043 4.89464 3.29289 4.70711C3.10536 4.51957 3 4.26522 3 4C3 3.73478 3.10536 3.48043 3.29289 3.29289C3.48043 3.10536 3.73478 3 4 3ZM6.995 3C7.26022 3 7.51457 3.10536 7.70211 3.29289C7.88964 3.48043 7.995 3.73478 7.995 4C7.995 4.26522 7.88964 4.51957 7.70211 4.70711C7.51457 4.89464 7.26022 5 6.995 5C6.72978 5 6.47543 4.89464 6.28789 4.70711C6.10036 4.51957 5.995 4.26522 5.995 4C5.995 3.73478 6.10036 3.48043 6.28789 3.29289C6.47543 3.10536 6.72978 3 6.995 3ZM9.995 3C10.2602 3 10.5146 3.10536 10.7021 3.29289C10.8896 3.48043 10.995 3.73478 10.995 4C10.995 4.26522 10.8896 4.51957 10.7021 4.70711C10.5146 4.89464 10.2602 5 9.995 5C9.72978 5 9.47543 4.89464 9.28789 4.70711C9.10036 4.51957 8.995 4.26522 8.995 4C8.995 3.73478 9.10036 3.48043 9.28789 3.29289C9.47543 3.10536 9.72978 3 9.995 3ZM12.995 3C13.2602 3 13.5146 3.10536 13.7021 3.29289C13.8896 3.48043 13.995 3.73478 13.995 4C13.995 4.26522 13.8896 4.51957 13.7021 4.70711C13.5146 4.89464 13.2602 5 12.995 5C12.7298 5 12.4754 4.89464 12.2879 4.70711C12.1004 4.51957 11.995 4.26522 11.995 4C11.995 3.73478 12.1004 3.48043 12.2879 3.29289C12.4754 3.10536 12.7298 3 12.995 3ZM15.995 3C16.2602 3 16.5146 3.10536 16.7021 3.29289C16.8896 3.48043 16.995 3.73478 16.995 4C16.995 4.26522 16.8896 4.51957 16.7021 4.70711C16.5146 4.89464 16.2602 5 15.995 5C15.7298 5 15.4754 4.89464 15.2879 4.70711C15.1004 4.51957 14.995 4.26522 14.995 4C14.995 3.73478 15.1004 3.48043 15.2879 3.29289C15.4754 3.10536 15.7298 3 15.995 3Z" />
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
@@ -1,97 +0,0 @@
|
||||
import { MuiClickAwayListener, MuiSlide } from '@affine/component';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { CloseIcon } from '@blocksuite/icons';
|
||||
import { IconButton } from '@toeverything/components/button';
|
||||
|
||||
import {
|
||||
type ShortcutsInfo,
|
||||
useEdgelessShortcuts,
|
||||
useGeneralShortcuts,
|
||||
useMarkdownShortcuts,
|
||||
usePageShortcuts,
|
||||
} from '../../../hooks/affine/use-shortcuts';
|
||||
import { KeyboardIcon } from './icons';
|
||||
import * as styles from './style.css';
|
||||
|
||||
type ModalProps = {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
const ShortcutsPanel = ({
|
||||
shortcutsInfo,
|
||||
}: {
|
||||
shortcutsInfo: ShortcutsInfo;
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<div className={styles.subtitle}>{shortcutsInfo.title}</div>
|
||||
|
||||
{Object.entries(shortcutsInfo.shortcuts).map(([title, shortcuts]) => {
|
||||
return (
|
||||
<div className={styles.listItem} key={title}>
|
||||
<span>{title}</span>
|
||||
<div className={styles.keyContainer}>
|
||||
{shortcuts.map(key => {
|
||||
return (
|
||||
<span className={styles.key} key={key}>
|
||||
{key}
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const ShortcutsModal = ({ open, onClose }: ModalProps) => {
|
||||
const t = useAFFiNEI18N();
|
||||
|
||||
const markdownShortcutsInfo = useMarkdownShortcuts();
|
||||
const pageShortcutsInfo = usePageShortcuts();
|
||||
const edgelessShortcutsInfo = useEdgelessShortcuts();
|
||||
const generalShortcutsInfo = useGeneralShortcuts();
|
||||
|
||||
return (
|
||||
<MuiSlide direction="left" in={open} mountOnEnter unmountOnExit>
|
||||
<div className={styles.shortcutsModal} data-testid="shortcuts-modal">
|
||||
<MuiClickAwayListener
|
||||
onClickAway={() => {
|
||||
onClose();
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
className={styles.modalHeader}
|
||||
style={{ marginBottom: '-28px' }}
|
||||
>
|
||||
<div className={styles.title}>
|
||||
<KeyboardIcon />
|
||||
{t['Shortcuts']()}
|
||||
</div>
|
||||
|
||||
<IconButton
|
||||
style={{
|
||||
position: 'absolute',
|
||||
right: 6,
|
||||
top: 6,
|
||||
}}
|
||||
onClick={() => {
|
||||
onClose();
|
||||
}}
|
||||
icon={<CloseIcon />}
|
||||
/>
|
||||
</div>
|
||||
<ShortcutsPanel shortcutsInfo={generalShortcutsInfo} />
|
||||
<ShortcutsPanel shortcutsInfo={pageShortcutsInfo} />
|
||||
<ShortcutsPanel shortcutsInfo={edgelessShortcutsInfo} />
|
||||
<ShortcutsPanel shortcutsInfo={markdownShortcutsInfo} />
|
||||
</div>
|
||||
</MuiClickAwayListener>
|
||||
</div>
|
||||
</MuiSlide>
|
||||
);
|
||||
};
|
||||
@@ -1,89 +0,0 @@
|
||||
import { globalStyle, style } from '@vanilla-extract/css';
|
||||
|
||||
export const shortcutsModal = style({
|
||||
width: '288px',
|
||||
height: '74vh',
|
||||
paddingBottom: '28px',
|
||||
backgroundColor: 'var(--affine-white)',
|
||||
boxShadow: 'var(--affine-popover-shadow)',
|
||||
borderRadius: `var(--affine-popover-radius)`,
|
||||
overflow: 'auto',
|
||||
position: 'fixed',
|
||||
right: '12px',
|
||||
top: '0',
|
||||
bottom: '0',
|
||||
margin: 'auto',
|
||||
zIndex: 'var(--affine-z-index-modal)',
|
||||
});
|
||||
// export const shortcutsModal = style({
|
||||
// color: 'var(--affine-text-primary-color)',
|
||||
// fontWeight: '500',
|
||||
// fontSize: 'var(--affine-font-sm)',
|
||||
// height: '44px',
|
||||
// display: 'flex',
|
||||
// justifyContent: 'center',
|
||||
// alignItems: 'center',
|
||||
// svg: {
|
||||
// width: '20px',
|
||||
// marginRight: '14px',
|
||||
// color: 'var(--affine-primary-color)',
|
||||
// },
|
||||
// });
|
||||
export const title = style({
|
||||
color: 'var(--affine-text-primary-color)',
|
||||
fontWeight: '500',
|
||||
fontSize: 'var(--affine-font-sm)',
|
||||
height: '44px',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
});
|
||||
|
||||
globalStyle(`${title} svg`, {
|
||||
width: '20px',
|
||||
marginRight: '14px',
|
||||
color: 'var(--affine-primary-color)',
|
||||
});
|
||||
|
||||
export const subtitle = style({
|
||||
fontWeight: '500',
|
||||
fontSize: 'var(--affine-font-sm)',
|
||||
height: '34px',
|
||||
lineHeight: '36px',
|
||||
marginTop: '28px',
|
||||
padding: '0 16px',
|
||||
});
|
||||
export const modalHeader = style({
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
paddingTop: '8px 4px 0 4px',
|
||||
width: '100%',
|
||||
padding: '8px 16px 0 16px',
|
||||
position: 'sticky',
|
||||
left: '0',
|
||||
top: '0',
|
||||
background: 'var(--affine-white)',
|
||||
transition: 'background-color 0.5s',
|
||||
});
|
||||
|
||||
export const listItem = style({
|
||||
height: '34px',
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
fontSize: 'var(--affine-font-sm)',
|
||||
padding: '0 16px',
|
||||
});
|
||||
export const keyContainer = style({
|
||||
display: 'flex',
|
||||
});
|
||||
|
||||
export const key = style({
|
||||
selectors: {
|
||||
'&:not(:last-child)::after': {
|
||||
content: '+',
|
||||
margin: '0 4px',
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -1,60 +0,0 @@
|
||||
import {
|
||||
EditCollectionModal,
|
||||
useCollectionManager,
|
||||
} from '@affine/component/page-list';
|
||||
import type { Collection } from '@affine/env/filter';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { PlusIcon } from '@blocksuite/icons';
|
||||
import type { Workspace } from '@blocksuite/store';
|
||||
import { uuidv4 } from '@blocksuite/store';
|
||||
import { IconButton } from '@toeverything/components/button';
|
||||
import { useCallback, useState } from 'react';
|
||||
|
||||
import { useGetPageInfoById } from '../../../../hooks/use-get-page-info';
|
||||
import { currentCollectionsAtom } from '../../../../utils/user-setting';
|
||||
|
||||
type AddCollectionButtonProps = {
|
||||
workspace: Workspace;
|
||||
};
|
||||
|
||||
export const AddCollectionButton = ({
|
||||
workspace,
|
||||
}: AddCollectionButtonProps) => {
|
||||
const getPageInfo = useGetPageInfoById(workspace);
|
||||
const setting = useCollectionManager(currentCollectionsAtom);
|
||||
const t = useAFFiNEI18N();
|
||||
const [show, showUpdateCollection] = useState(false);
|
||||
const [defaultCollection, setDefaultCollection] = useState<Collection>();
|
||||
const handleClick = useCallback(() => {
|
||||
showUpdateCollection(true);
|
||||
setDefaultCollection({
|
||||
id: uuidv4(),
|
||||
name: '',
|
||||
pinned: true,
|
||||
filterList: [],
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
}, [showUpdateCollection, workspace.id]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<IconButton
|
||||
data-testid="slider-bar-add-collection-button"
|
||||
onClick={handleClick}
|
||||
size="small"
|
||||
>
|
||||
<PlusIcon />
|
||||
</IconButton>
|
||||
|
||||
<EditCollectionModal
|
||||
propertiesMeta={workspace.meta.properties}
|
||||
getPageInfo={getPageInfo}
|
||||
onConfirm={setting.saveCollection}
|
||||
open={show}
|
||||
onOpenChange={showUpdateCollection}
|
||||
title={t['com.affine.editCollection.saveCollection']()}
|
||||
init={defaultCollection}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,309 +0,0 @@
|
||||
import { MenuItem as CollectionItem } from '@affine/component/app-sidebar';
|
||||
import {
|
||||
EditCollectionModal,
|
||||
useCollectionManager,
|
||||
useSavedCollections,
|
||||
} from '@affine/component/page-list';
|
||||
import type { Collection } from '@affine/env/filter';
|
||||
import type { GetPageInfoById } from '@affine/env/page-info';
|
||||
import { WorkspaceSubPath } from '@affine/env/workspace';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import {
|
||||
DeleteIcon,
|
||||
FilterIcon,
|
||||
InformationIcon,
|
||||
MoreHorizontalIcon,
|
||||
UnpinIcon,
|
||||
ViewLayersIcon,
|
||||
} from '@blocksuite/icons';
|
||||
import type { PageMeta, Workspace } from '@blocksuite/store';
|
||||
import type { DragEndEvent } from '@dnd-kit/core';
|
||||
import { useDroppable } from '@dnd-kit/core';
|
||||
import * as Collapsible from '@radix-ui/react-collapsible';
|
||||
import { IconButton } from '@toeverything/components/button';
|
||||
import {
|
||||
Menu,
|
||||
MenuIcon,
|
||||
MenuItem,
|
||||
type MenuItemProps,
|
||||
} from '@toeverything/components/menu';
|
||||
import { useBlockSuitePageMeta } from '@toeverything/hooks/use-block-suite-page-meta';
|
||||
import type { ReactElement } from 'react';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
|
||||
import { useGetPageInfoById } from '../../../../hooks/use-get-page-info';
|
||||
import { useNavigateHelper } from '../../../../hooks/use-navigate-helper';
|
||||
import { filterPage } from '../../../../utils/filter';
|
||||
import { currentCollectionsAtom } from '../../../../utils/user-setting';
|
||||
import type { CollectionsListProps } from '../index';
|
||||
import { Page } from './page';
|
||||
import * as styles from './styles.css';
|
||||
|
||||
const Collections_DROP_AREA_PREFIX = 'collections-';
|
||||
const isCollectionsDropArea = (id?: string | number) => {
|
||||
return typeof id === 'string' && id.startsWith(Collections_DROP_AREA_PREFIX);
|
||||
};
|
||||
export const processCollectionsDrag = (e: DragEndEvent) => {
|
||||
if (
|
||||
isCollectionsDropArea(e.over?.id) &&
|
||||
String(e.active.id).startsWith('page-list-item-')
|
||||
) {
|
||||
e.over?.data.current?.addToCollection?.(e.active.data.current?.pageId);
|
||||
}
|
||||
};
|
||||
const CollectionOperations = ({
|
||||
view,
|
||||
showUpdateCollection,
|
||||
setting,
|
||||
}: {
|
||||
view: Collection;
|
||||
showUpdateCollection: () => void;
|
||||
setting: ReturnType<typeof useCollectionManager>;
|
||||
}) => {
|
||||
const t = useAFFiNEI18N();
|
||||
const actions = useMemo<
|
||||
Array<
|
||||
| {
|
||||
icon: ReactElement;
|
||||
name: string;
|
||||
click: () => void;
|
||||
type?: MenuItemProps['type'];
|
||||
element?: undefined;
|
||||
}
|
||||
| {
|
||||
element: ReactElement;
|
||||
}
|
||||
>
|
||||
>(
|
||||
() => [
|
||||
{
|
||||
icon: (
|
||||
<MenuIcon>
|
||||
<FilterIcon />
|
||||
</MenuIcon>
|
||||
),
|
||||
name: t['Edit Filter'](),
|
||||
click: showUpdateCollection,
|
||||
},
|
||||
{
|
||||
icon: (
|
||||
<MenuIcon>
|
||||
<UnpinIcon />
|
||||
</MenuIcon>
|
||||
),
|
||||
name: t['Unpin'](),
|
||||
click: () => {
|
||||
return setting.updateCollection({
|
||||
...view,
|
||||
pinned: false,
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
element: <div key="divider" className={styles.menuDividerStyle}></div>,
|
||||
},
|
||||
{
|
||||
icon: (
|
||||
<MenuIcon>
|
||||
<DeleteIcon />
|
||||
</MenuIcon>
|
||||
),
|
||||
name: t['Delete'](),
|
||||
click: () => {
|
||||
return setting.deleteCollection(view.id);
|
||||
},
|
||||
type: 'danger',
|
||||
},
|
||||
],
|
||||
[setting, showUpdateCollection, t, view]
|
||||
);
|
||||
return (
|
||||
<div style={{ minWidth: 150 }}>
|
||||
{actions.map(action => {
|
||||
if (action.element) {
|
||||
return action.element;
|
||||
}
|
||||
return (
|
||||
<MenuItem
|
||||
data-testid="collection-option"
|
||||
key={action.name}
|
||||
type={action.type}
|
||||
preFix={action.icon}
|
||||
onClick={action.click}
|
||||
>
|
||||
{action.name}
|
||||
</MenuItem>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
const CollectionRenderer = ({
|
||||
collection,
|
||||
pages,
|
||||
workspace,
|
||||
getPageInfo,
|
||||
}: {
|
||||
collection: Collection;
|
||||
pages: PageMeta[];
|
||||
workspace: Workspace;
|
||||
getPageInfo: GetPageInfoById;
|
||||
}) => {
|
||||
const [collapsed, setCollapsed] = useState(true);
|
||||
const setting = useCollectionManager(currentCollectionsAtom);
|
||||
const { jumpToSubPath } = useNavigateHelper();
|
||||
const clickCollection = useCallback(() => {
|
||||
jumpToSubPath(workspace.id, WorkspaceSubPath.ALL);
|
||||
setting.selectCollection(collection.id);
|
||||
}, [jumpToSubPath, workspace.id, setting, collection.id]);
|
||||
const { setNodeRef, isOver } = useDroppable({
|
||||
id: `${Collections_DROP_AREA_PREFIX}${collection.id}`,
|
||||
data: {
|
||||
addToCollection: (id: string) => {
|
||||
setting.addPage(collection.id, id).catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
const allPagesMeta = useMemo(
|
||||
() => Object.fromEntries(pages.map(v => [v.id, v])),
|
||||
[pages]
|
||||
);
|
||||
const [show, showUpdateCollection] = useState(false);
|
||||
const allowList = useMemo(
|
||||
() => new Set(collection.allowList),
|
||||
[collection.allowList]
|
||||
);
|
||||
const excludeList = useMemo(
|
||||
() => new Set(collection.excludeList),
|
||||
[collection.excludeList]
|
||||
);
|
||||
const removeFromAllowList = useCallback(
|
||||
(id: string) => {
|
||||
return setting.updateCollection({
|
||||
...collection,
|
||||
allowList: collection.allowList?.filter(v => v != id),
|
||||
});
|
||||
},
|
||||
[collection, setting]
|
||||
);
|
||||
const addToExcludeList = useCallback(
|
||||
(id: string) => {
|
||||
return setting.updateCollection({
|
||||
...collection,
|
||||
excludeList: [id, ...(collection.excludeList ?? [])],
|
||||
});
|
||||
},
|
||||
[collection, setting]
|
||||
);
|
||||
const pagesToRender = pages.filter(
|
||||
page => filterPage(collection, page) && !page.trash
|
||||
);
|
||||
|
||||
return (
|
||||
<Collapsible.Root open={!collapsed}>
|
||||
<EditCollectionModal
|
||||
propertiesMeta={workspace.meta.properties}
|
||||
getPageInfo={getPageInfo}
|
||||
init={collection}
|
||||
onConfirm={setting.saveCollection}
|
||||
open={show}
|
||||
onOpenChange={showUpdateCollection}
|
||||
/>
|
||||
<CollectionItem
|
||||
data-testid="collection-item"
|
||||
data-type="collection-list-item"
|
||||
ref={setNodeRef}
|
||||
onCollapsedChange={setCollapsed}
|
||||
active={isOver}
|
||||
icon={<ViewLayersIcon />}
|
||||
postfix={
|
||||
<Menu
|
||||
items={
|
||||
<CollectionOperations
|
||||
view={collection}
|
||||
showUpdateCollection={() => showUpdateCollection(true)}
|
||||
setting={setting}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<IconButton
|
||||
data-testid="collection-options"
|
||||
type="plain"
|
||||
withoutHoverStyle
|
||||
>
|
||||
<MoreHorizontalIcon />
|
||||
</IconButton>
|
||||
</Menu>
|
||||
}
|
||||
collapsed={pagesToRender.length > 0 ? collapsed : undefined}
|
||||
onClick={clickCollection}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
}}
|
||||
>
|
||||
<div>{collection.name}</div>
|
||||
</div>
|
||||
</CollectionItem>
|
||||
<Collapsible.Content className={styles.collapsibleContent}>
|
||||
<div style={{ marginLeft: 20, marginTop: -4 }}>
|
||||
{pagesToRender.map(page => {
|
||||
return (
|
||||
<Page
|
||||
inAllowList={allowList.has(page.id)}
|
||||
removeFromAllowList={removeFromAllowList}
|
||||
inExcludeList={excludeList.has(page.id)}
|
||||
addToExcludeList={addToExcludeList}
|
||||
allPageMeta={allPagesMeta}
|
||||
page={page}
|
||||
key={page.id}
|
||||
workspace={workspace}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</Collapsible.Content>
|
||||
</Collapsible.Root>
|
||||
);
|
||||
};
|
||||
export const CollectionsList = ({ workspace }: CollectionsListProps) => {
|
||||
const metas = useBlockSuitePageMeta(workspace);
|
||||
const { savedCollections } = useSavedCollections(currentCollectionsAtom);
|
||||
const getPageInfo = useGetPageInfoById(workspace);
|
||||
const pinedCollections = useMemo(
|
||||
() => savedCollections.filter(v => v.pinned),
|
||||
[savedCollections]
|
||||
);
|
||||
const t = useAFFiNEI18N();
|
||||
if (pinedCollections.length === 0) {
|
||||
return (
|
||||
<CollectionItem
|
||||
data-testid="slider-bar-collection-null-description"
|
||||
icon={<InformationIcon />}
|
||||
disabled
|
||||
>
|
||||
<span>{t['Create a collection']()}</span>
|
||||
</CollectionItem>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div data-testid="collections" className={styles.wrapper}>
|
||||
{pinedCollections.map(view => {
|
||||
return (
|
||||
<CollectionRenderer
|
||||
getPageInfo={getPageInfo}
|
||||
key={view.id}
|
||||
collection={view}
|
||||
pages={metas}
|
||||
workspace={workspace}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,130 +0,0 @@
|
||||
import { alpha, displayFlex, styled, textEllipsis } from '@affine/component';
|
||||
|
||||
export const StyledListItem = styled('div')<{
|
||||
active?: boolean;
|
||||
disabled?: boolean;
|
||||
}>(({ active, disabled }) => {
|
||||
return {
|
||||
height: '32px',
|
||||
color: active
|
||||
? 'var(--affine-primary-color)'
|
||||
: 'var(--affine-text-primary-color)',
|
||||
paddingLeft: '2px',
|
||||
paddingRight: '2px',
|
||||
borderRadius: '8px',
|
||||
cursor: 'pointer',
|
||||
marginBottom: '4px',
|
||||
position: 'relative',
|
||||
flexShrink: 0,
|
||||
userSelect: 'none',
|
||||
...displayFlex('flex-start', 'stretch'),
|
||||
...(disabled
|
||||
? {
|
||||
cursor: 'not-allowed',
|
||||
color: 'var(--affine-border-color)',
|
||||
}
|
||||
: {}),
|
||||
|
||||
'a > svg, div > svg': {
|
||||
fontSize: '20px',
|
||||
marginLeft: '14px',
|
||||
marginRight: '12px',
|
||||
color: active
|
||||
? 'var(--affine-primary-color)'
|
||||
: 'var(--affine-icon-color)',
|
||||
},
|
||||
':hover:not([disabled])': {
|
||||
backgroundColor: 'var(--affine-hover-color)',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledCollapseButton = styled('button')<{
|
||||
collapse: boolean;
|
||||
show?: boolean;
|
||||
}>(({ collapse, show = true }) => {
|
||||
return {
|
||||
width: '16px',
|
||||
height: '100%',
|
||||
...displayFlex('center', 'center'),
|
||||
fontSize: '16px',
|
||||
position: 'absolute',
|
||||
left: '0',
|
||||
top: '0',
|
||||
bottom: '0',
|
||||
margin: 'auto',
|
||||
color: 'var(--affine-icon-color)',
|
||||
opacity: '.6',
|
||||
transition: 'opacity .15s ease-in-out',
|
||||
display: show ? 'flex' : 'none',
|
||||
svg: {
|
||||
transform: `rotate(${collapse ? '0' : '-90'}deg)`,
|
||||
},
|
||||
':hover': {
|
||||
opacity: '1',
|
||||
},
|
||||
':focus-visible': {
|
||||
outline: '-webkit-focus-ring-color auto 1px',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledCollapseItem = styled('div')<{
|
||||
disable?: boolean;
|
||||
active?: boolean;
|
||||
isOver?: boolean;
|
||||
textWrap?: boolean;
|
||||
}>(({ disable = false, active = false, isOver, textWrap = false }) => {
|
||||
return {
|
||||
width: '100%',
|
||||
lineHeight: '1.5',
|
||||
minHeight: '32px',
|
||||
borderRadius: '8px',
|
||||
...displayFlex('flex-start', 'center'),
|
||||
paddingRight: '2px',
|
||||
position: 'relative',
|
||||
color: disable
|
||||
? 'var(--affine-text-disable-color)'
|
||||
: active
|
||||
? 'var(--affine-primary-color)'
|
||||
: 'var(--affine-text-primary-color)',
|
||||
cursor: disable ? 'not-allowed' : 'pointer',
|
||||
background: isOver ? alpha('var(--affine-primary-color)', 0.06) : '',
|
||||
userSelect: 'none',
|
||||
...(textWrap
|
||||
? {
|
||||
wordBreak: 'break-word',
|
||||
whiteSpace: 'pre-wrap',
|
||||
}
|
||||
: {}),
|
||||
span: {
|
||||
flexGrow: '1',
|
||||
textAlign: 'left',
|
||||
...textEllipsis(1),
|
||||
},
|
||||
'> svg': {
|
||||
fontSize: '20px',
|
||||
marginRight: '8px',
|
||||
flexShrink: '0',
|
||||
color: active
|
||||
? 'var(--affine-primary-color)'
|
||||
: 'var(--affine-icon-color)',
|
||||
},
|
||||
|
||||
':hover': disable
|
||||
? {}
|
||||
: {
|
||||
backgroundColor: 'var(--affine-hover-color)',
|
||||
'.operation-button': {
|
||||
visibility: 'visible',
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledRouteNavigationWrapper = styled('div')({
|
||||
height: '32px',
|
||||
width: '80px',
|
||||
marginRight: '16px',
|
||||
...displayFlex('space-between', 'center'),
|
||||
});
|
||||
@@ -1,85 +0,0 @@
|
||||
import { displayFlex, styled, textEllipsis } from '@affine/component';
|
||||
import { Link } from '@mui/material';
|
||||
import { baseTheme } from '@toeverything/theme';
|
||||
|
||||
export const StyledSliderBarInnerWrapper = styled('div')(() => {
|
||||
return {
|
||||
flexGrow: 1,
|
||||
margin: '0 2px',
|
||||
position: 'relative',
|
||||
height: 'calc(100% - 52px * 2)',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledLink = styled(Link)(() => {
|
||||
return {
|
||||
flexGrow: 1,
|
||||
textAlign: 'left',
|
||||
color: 'inherit',
|
||||
...displayFlex('flex-start', 'center'),
|
||||
':visited': {
|
||||
color: 'inherit',
|
||||
},
|
||||
overflow: 'hidden',
|
||||
div: {
|
||||
wordBreak: 'break-all',
|
||||
wordWrap: 'break-word',
|
||||
whiteSpace: 'nowrap',
|
||||
...textEllipsis(1),
|
||||
},
|
||||
userDrag: 'none',
|
||||
userSelect: 'none',
|
||||
appRegion: 'no-drag',
|
||||
WebkitUserSelect: 'none',
|
||||
WebkitUserDrag: 'none',
|
||||
WebkitAppRegion: 'no-drag',
|
||||
};
|
||||
});
|
||||
export const StyledNewPageButton = styled('button')(() => {
|
||||
return {
|
||||
width: '100%',
|
||||
height: '52px',
|
||||
...displayFlex('flex-start', 'center'),
|
||||
padding: '0 16px',
|
||||
svg: {
|
||||
fontSize: '20px',
|
||||
color: 'var(--affine-icon-color)',
|
||||
marginRight: '12px',
|
||||
},
|
||||
':hover': {
|
||||
color: 'var(--affine-primary-color)',
|
||||
svg: {
|
||||
color: 'var(--affine-primary-color)',
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
export const StyledSliderModalBackground = styled('div')<{ active: boolean }>(({
|
||||
active,
|
||||
}) => {
|
||||
return {
|
||||
transition: 'opacity .15s',
|
||||
pointerEvents: active ? 'auto' : 'none',
|
||||
opacity: active ? 1 : 0,
|
||||
position: 'fixed',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: active ? 0 : '100%',
|
||||
bottom: 0,
|
||||
zIndex: parseInt(baseTheme.zIndexModal) - 1,
|
||||
background: 'var(--affine-background-modal-color)',
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledScrollWrapper = styled('div')<{
|
||||
showTopBorder: boolean;
|
||||
}>(({ showTopBorder }) => {
|
||||
return {
|
||||
maxHeight: '50%',
|
||||
overflowY: 'auto',
|
||||
borderTop: '1px solid',
|
||||
borderColor: showTopBorder ? 'var(--affine-border-color)' : 'transparent',
|
||||
};
|
||||
});
|
||||
@@ -1,397 +0,0 @@
|
||||
import { WorkspaceList } from '@affine/component/workspace-list';
|
||||
import type {
|
||||
AffineCloudWorkspace,
|
||||
LocalWorkspace,
|
||||
} from '@affine/env/workspace';
|
||||
import { WorkspaceFlavour, WorkspaceSubPath } from '@affine/env/workspace';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import type { RootWorkspaceMetadata } from '@affine/workspace/atom';
|
||||
import { rootWorkspacesMetadataAtom } from '@affine/workspace/atom';
|
||||
import {
|
||||
AccountIcon,
|
||||
ImportIcon,
|
||||
Logo1Icon,
|
||||
MoreHorizontalIcon,
|
||||
PlusIcon,
|
||||
SignOutIcon,
|
||||
} from '@blocksuite/icons';
|
||||
import type { DragEndEvent } from '@dnd-kit/core';
|
||||
import { arrayMove } from '@dnd-kit/sortable';
|
||||
import { IconButton } from '@toeverything/components/button';
|
||||
import { Divider } from '@toeverything/components/divider';
|
||||
import { Menu, MenuIcon, MenuItem } from '@toeverything/components/menu';
|
||||
import {
|
||||
currentPageIdAtom,
|
||||
currentWorkspaceIdAtom,
|
||||
} from '@toeverything/infra/atom';
|
||||
import { useAtom, useAtomValue, useSetAtom } from 'jotai';
|
||||
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
|
||||
import { useSession } from 'next-auth/react';
|
||||
import { startTransition, useCallback, useMemo, useTransition } from 'react';
|
||||
|
||||
import {
|
||||
authAtom,
|
||||
openCreateWorkspaceModalAtom,
|
||||
openDisableCloudAlertModalAtom,
|
||||
openSettingModalAtom,
|
||||
} from '../../../../atoms';
|
||||
import type { AllWorkspace } from '../../../../shared';
|
||||
import { signOutCloud } from '../../../../utils/cloud-utils';
|
||||
import { useNavigateHelper } from '../.././../../hooks/use-navigate-helper';
|
||||
import {
|
||||
StyledCreateWorkspaceCardPill,
|
||||
StyledCreateWorkspaceCardPillContent,
|
||||
StyledCreateWorkspaceCardPillIcon,
|
||||
StyledImportWorkspaceCardPill,
|
||||
StyledItem,
|
||||
StyledModalBody,
|
||||
StyledModalContent,
|
||||
StyledModalFooterContent,
|
||||
StyledModalHeader,
|
||||
StyledModalHeaderContent,
|
||||
StyledModalHeaderLeft,
|
||||
StyledModalTitle,
|
||||
StyledOperationWrapper,
|
||||
StyledSignInCardPill,
|
||||
StyledSignInCardPillTextCotainer,
|
||||
StyledSignInCardPillTextPrimary,
|
||||
StyledSignInCardPillTextSecondary,
|
||||
StyledWorkspaceFlavourTitle,
|
||||
} from './styles';
|
||||
|
||||
interface WorkspaceModalProps {
|
||||
disabled?: boolean;
|
||||
workspaces: RootWorkspaceMetadata[];
|
||||
currentWorkspaceId: AllWorkspace['id'] | null;
|
||||
onClickWorkspace: (workspace: RootWorkspaceMetadata['id']) => void;
|
||||
onClickWorkspaceSetting: (workspace: RootWorkspaceMetadata['id']) => void;
|
||||
onNewWorkspace: () => void;
|
||||
onAddWorkspace: () => void;
|
||||
onMoveWorkspace: (activeId: string, overId: string) => void;
|
||||
}
|
||||
|
||||
const AccountMenu = ({
|
||||
onOpenAccountSetting,
|
||||
onSignOut,
|
||||
}: {
|
||||
onOpenAccountSetting: () => void;
|
||||
onSignOut: () => void;
|
||||
}) => {
|
||||
const t = useAFFiNEI18N();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<MenuItem
|
||||
preFix={
|
||||
<MenuIcon>
|
||||
<AccountIcon />
|
||||
</MenuIcon>
|
||||
}
|
||||
data-testid="editor-option-menu-import"
|
||||
onClick={onOpenAccountSetting}
|
||||
>
|
||||
{t['com.affine.workspace.cloud.account.settings']()}
|
||||
</MenuItem>
|
||||
<Divider />
|
||||
<MenuItem
|
||||
preFix={
|
||||
<MenuIcon>
|
||||
<SignOutIcon />
|
||||
</MenuIcon>
|
||||
}
|
||||
data-testid="editor-option-menu-import"
|
||||
onClick={onSignOut}
|
||||
>
|
||||
{t['com.affine.workspace.cloud.account.logout']()}
|
||||
</MenuItem>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const CloudWorkSpaceList = ({
|
||||
disabled,
|
||||
workspaces,
|
||||
onClickWorkspace,
|
||||
onClickWorkspaceSetting,
|
||||
currentWorkspaceId,
|
||||
onMoveWorkspace,
|
||||
}: WorkspaceModalProps) => {
|
||||
const t = useAFFiNEI18N();
|
||||
|
||||
return (
|
||||
<>
|
||||
<StyledModalHeader>
|
||||
<StyledModalHeaderLeft>
|
||||
<StyledWorkspaceFlavourTitle>
|
||||
{t['com.affine.workspace.cloud']()}
|
||||
</StyledWorkspaceFlavourTitle>
|
||||
</StyledModalHeaderLeft>
|
||||
</StyledModalHeader>
|
||||
<StyledModalContent>
|
||||
<WorkspaceList
|
||||
disabled={disabled}
|
||||
items={
|
||||
workspaces.filter(
|
||||
({ flavour }) => flavour === WorkspaceFlavour.AFFINE_CLOUD
|
||||
) as (AffineCloudWorkspace | LocalWorkspace)[]
|
||||
}
|
||||
currentWorkspaceId={currentWorkspaceId}
|
||||
onClick={onClickWorkspace}
|
||||
onSettingClick={onClickWorkspaceSetting}
|
||||
onDragEnd={useCallback(
|
||||
(event: DragEndEvent) => {
|
||||
const { active, over } = event;
|
||||
if (active.id !== over?.id) {
|
||||
onMoveWorkspace(active.id as string, over?.id as string);
|
||||
}
|
||||
},
|
||||
[onMoveWorkspace]
|
||||
)}
|
||||
/>
|
||||
</StyledModalContent>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const UserWithWorkspaceList = ({
|
||||
onEventEnd,
|
||||
}: {
|
||||
onEventEnd?: () => void;
|
||||
}) => {
|
||||
const setOpenCreateWorkspaceModal = useSetAtom(openCreateWorkspaceModalAtom);
|
||||
|
||||
const { jumpToSubPath, jumpToIndex } = useNavigateHelper();
|
||||
const workspaces = useAtomValue(rootWorkspacesMetadataAtom, {
|
||||
delay: 0,
|
||||
});
|
||||
const setWorkspaces = useSetAtom(rootWorkspacesMetadataAtom);
|
||||
const [currentWorkspaceId, setCurrentWorkspaceId] = useAtom(
|
||||
currentWorkspaceIdAtom
|
||||
);
|
||||
const setCurrentPageId = useSetAtom(currentPageIdAtom);
|
||||
const [, startCloseTransition] = useTransition();
|
||||
const setOpenSettingModalAtom = useSetAtom(openSettingModalAtom);
|
||||
const setSettingModalAtom = useSetAtom(openSettingModalAtom);
|
||||
|
||||
const t = useAFFiNEI18N();
|
||||
const setOpen = useSetAtom(authAtom);
|
||||
const setDisableCloudOpen = useSetAtom(openDisableCloudAlertModalAtom);
|
||||
// TODO: AFFiNE Cloud support
|
||||
const { data: session, status } = useSession();
|
||||
const isLoggedIn = useMemo(() => status === 'authenticated', [status]);
|
||||
const cloudWorkspaces = useMemo(
|
||||
() =>
|
||||
workspaces.filter(
|
||||
({ flavour }) => flavour === WorkspaceFlavour.AFFINE_CLOUD
|
||||
) as (AffineCloudWorkspace | LocalWorkspace)[],
|
||||
[workspaces]
|
||||
);
|
||||
const localWorkspaces = useMemo(
|
||||
() =>
|
||||
workspaces.filter(
|
||||
({ flavour }) => flavour === WorkspaceFlavour.LOCAL
|
||||
) as (AffineCloudWorkspace | LocalWorkspace)[],
|
||||
[workspaces]
|
||||
);
|
||||
|
||||
const onClickWorkspaceSetting = useCallback(
|
||||
(workspaceId: string) => {
|
||||
setOpenSettingModalAtom({
|
||||
open: true,
|
||||
activeTab: 'workspace',
|
||||
workspaceId,
|
||||
});
|
||||
onEventEnd?.();
|
||||
},
|
||||
[onEventEnd, setOpenSettingModalAtom]
|
||||
);
|
||||
|
||||
const onMoveWorkspace = useCallback(
|
||||
(activeId: string, overId: string) => {
|
||||
const oldIndex = workspaces.findIndex(w => w.id === activeId);
|
||||
const newIndex = workspaces.findIndex(w => w.id === overId);
|
||||
startTransition(() => {
|
||||
setWorkspaces(workspaces => arrayMove(workspaces, oldIndex, newIndex));
|
||||
});
|
||||
},
|
||||
[setWorkspaces, workspaces]
|
||||
);
|
||||
const onClickWorkspace = useCallback(
|
||||
(workspaceId: string) => {
|
||||
startCloseTransition(() => {
|
||||
setCurrentWorkspaceId(workspaceId);
|
||||
setCurrentPageId(null);
|
||||
jumpToSubPath(workspaceId, WorkspaceSubPath.ALL);
|
||||
});
|
||||
onEventEnd?.();
|
||||
},
|
||||
[jumpToSubPath, onEventEnd, setCurrentPageId, setCurrentWorkspaceId]
|
||||
);
|
||||
const onNewWorkspace = useCallback(() => {
|
||||
setOpenCreateWorkspaceModal('new');
|
||||
onEventEnd?.();
|
||||
}, [onEventEnd, setOpenCreateWorkspaceModal]);
|
||||
const onAddWorkspace = useCallback(async () => {
|
||||
setOpenCreateWorkspaceModal('add');
|
||||
onEventEnd?.();
|
||||
}, [onEventEnd, setOpenCreateWorkspaceModal]);
|
||||
|
||||
const onOpenAccountSetting = useCallback(() => {
|
||||
setSettingModalAtom(prev => ({
|
||||
...prev,
|
||||
open: true,
|
||||
activeTab: 'account',
|
||||
}));
|
||||
onEventEnd?.();
|
||||
}, [onEventEnd, setSettingModalAtom]);
|
||||
const onSignOut = useCallback(async () => {
|
||||
signOutCloud()
|
||||
.then(() => {
|
||||
jumpToIndex();
|
||||
})
|
||||
.catch(console.error);
|
||||
onEventEnd?.();
|
||||
}, [onEventEnd, jumpToIndex]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{!isLoggedIn ? (
|
||||
<StyledModalHeaderContent>
|
||||
<StyledSignInCardPill>
|
||||
<StyledItem
|
||||
onClick={async () => {
|
||||
if (!runtimeConfig.enableCloud) {
|
||||
setDisableCloudOpen(true);
|
||||
} else {
|
||||
setOpen(state => ({
|
||||
...state,
|
||||
openModal: true,
|
||||
}));
|
||||
}
|
||||
}}
|
||||
data-testid="cloud-signin-button"
|
||||
>
|
||||
<StyledCreateWorkspaceCardPillContent>
|
||||
<StyledCreateWorkspaceCardPillIcon>
|
||||
<Logo1Icon />
|
||||
</StyledCreateWorkspaceCardPillIcon>
|
||||
<StyledSignInCardPillTextCotainer>
|
||||
<StyledSignInCardPillTextPrimary>
|
||||
{t['com.affine.workspace.cloud.auth']()}
|
||||
</StyledSignInCardPillTextPrimary>
|
||||
<StyledSignInCardPillTextSecondary>
|
||||
{t['com.affine.workspace.cloud.description']()}
|
||||
</StyledSignInCardPillTextSecondary>
|
||||
</StyledSignInCardPillTextCotainer>
|
||||
</StyledCreateWorkspaceCardPillContent>
|
||||
</StyledItem>
|
||||
</StyledSignInCardPill>
|
||||
<Divider
|
||||
style={{
|
||||
margin: '12px 0px',
|
||||
}}
|
||||
/>
|
||||
</StyledModalHeaderContent>
|
||||
) : (
|
||||
<StyledModalHeaderContent>
|
||||
<StyledModalHeader>
|
||||
<StyledModalTitle>{session?.user.email}</StyledModalTitle>
|
||||
<StyledOperationWrapper>
|
||||
<Menu
|
||||
items={
|
||||
<AccountMenu
|
||||
onOpenAccountSetting={onOpenAccountSetting}
|
||||
onSignOut={onSignOut}
|
||||
/>
|
||||
}
|
||||
contentOptions={{
|
||||
side: 'right',
|
||||
sideOffset: 30,
|
||||
}}
|
||||
>
|
||||
<IconButton
|
||||
data-testid="more-button"
|
||||
icon={<MoreHorizontalIcon />}
|
||||
type="plain"
|
||||
/>
|
||||
</Menu>
|
||||
</StyledOperationWrapper>
|
||||
</StyledModalHeader>
|
||||
<Divider style={{ margin: '12px 0px' }} />
|
||||
</StyledModalHeaderContent>
|
||||
)}
|
||||
<StyledModalBody>
|
||||
{isLoggedIn && cloudWorkspaces.length !== 0 ? (
|
||||
<>
|
||||
<CloudWorkSpaceList
|
||||
workspaces={workspaces}
|
||||
onClickWorkspace={onClickWorkspace}
|
||||
onClickWorkspaceSetting={onClickWorkspaceSetting}
|
||||
onNewWorkspace={onNewWorkspace}
|
||||
onAddWorkspace={onAddWorkspace}
|
||||
currentWorkspaceId={currentWorkspaceId}
|
||||
onMoveWorkspace={onMoveWorkspace}
|
||||
/>
|
||||
<Divider
|
||||
style={{
|
||||
margin: '12px 0px',
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
) : null}
|
||||
<StyledModalHeader>
|
||||
<StyledWorkspaceFlavourTitle>
|
||||
{t['com.affine.workspace.local']()}
|
||||
</StyledWorkspaceFlavourTitle>
|
||||
</StyledModalHeader>
|
||||
<StyledModalContent>
|
||||
<WorkspaceList
|
||||
items={localWorkspaces}
|
||||
currentWorkspaceId={currentWorkspaceId}
|
||||
onClick={onClickWorkspace}
|
||||
onSettingClick={onClickWorkspaceSetting}
|
||||
onDragEnd={useCallback(
|
||||
(event: DragEndEvent) => {
|
||||
const { active, over } = event;
|
||||
if (active.id !== over?.id) {
|
||||
onMoveWorkspace(active.id as string, over?.id as string);
|
||||
}
|
||||
},
|
||||
[onMoveWorkspace]
|
||||
)}
|
||||
/>
|
||||
</StyledModalContent>
|
||||
{runtimeConfig.enableSQLiteProvider && environment.isDesktop ? (
|
||||
<StyledImportWorkspaceCardPill>
|
||||
<StyledItem onClick={onAddWorkspace} data-testid="add-workspace">
|
||||
<StyledCreateWorkspaceCardPillContent
|
||||
style={{ gap: '14px', paddingLeft: '2px' }}
|
||||
>
|
||||
<StyledCreateWorkspaceCardPillIcon style={{ fontSize: '24px' }}>
|
||||
<ImportIcon />
|
||||
</StyledCreateWorkspaceCardPillIcon>
|
||||
<div>
|
||||
<p>{t['com.affine.workspace.local.import']()}</p>
|
||||
</div>
|
||||
</StyledCreateWorkspaceCardPillContent>
|
||||
</StyledItem>
|
||||
</StyledImportWorkspaceCardPill>
|
||||
) : null}
|
||||
</StyledModalBody>
|
||||
<StyledModalFooterContent>
|
||||
<StyledCreateWorkspaceCardPill>
|
||||
<StyledItem onClick={onNewWorkspace} data-testid="new-workspace">
|
||||
<StyledCreateWorkspaceCardPillContent>
|
||||
<StyledCreateWorkspaceCardPillIcon>
|
||||
<PlusIcon />
|
||||
</StyledCreateWorkspaceCardPillIcon>
|
||||
<div>
|
||||
<p>{t['New Workspace']()}</p>
|
||||
</div>
|
||||
</StyledCreateWorkspaceCardPillContent>
|
||||
</StyledItem>
|
||||
</StyledCreateWorkspaceCardPill>
|
||||
</StyledModalFooterContent>
|
||||
</>
|
||||
);
|
||||
};
|
||||