mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-05 00:54:56 +00:00
Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cccea3ec3e | ||
|
|
39476d16ef | ||
|
|
49bead719d | ||
|
|
a9f46ed088 | ||
|
|
d3a544f4aa | ||
|
|
cd56d8a6e6 | ||
|
|
6ed5ec36bb | ||
|
|
0fa917aabb | ||
|
|
eb79e6bdc9 | ||
|
|
cf52a43773 | ||
|
|
9203980a8c | ||
|
|
d34eb2cbe5 | ||
|
|
7f3f993ce4 |
17
.github/actions/deploy/deploy.mjs
vendored
17
.github/actions/deploy/deploy.mjs
vendored
@@ -15,12 +15,13 @@ const {
|
||||
R2_SECRET_ACCESS_KEY,
|
||||
ENABLE_CAPTCHA,
|
||||
CAPTCHA_TURNSTILE_SECRET,
|
||||
OAUTH_EMAIL_SENDER,
|
||||
OAUTH_EMAIL_LOGIN,
|
||||
OAUTH_EMAIL_PASSWORD,
|
||||
MAILER_SENDER,
|
||||
MAILER_USER,
|
||||
MAILER_PASSWORD,
|
||||
AFFINE_GOOGLE_CLIENT_ID,
|
||||
AFFINE_GOOGLE_CLIENT_SECRET,
|
||||
CLOUD_SQL_IAM_ACCOUNT,
|
||||
CLOUD_LOGGER_IAM_ACCOUNT,
|
||||
GCLOUD_CONNECTION_NAME,
|
||||
GCLOUD_CLOUD_SQL_INTERNAL_ENDPOINT,
|
||||
REDIS_HOST,
|
||||
@@ -59,7 +60,9 @@ const createHelmCommand = ({ isDryRun }) => {
|
||||
? [
|
||||
`--set-json web.service.annotations=\"{ \\"cloud.google.com/neg\\": \\"{\\\\\\"ingress\\\\\\": true}\\" }\"`,
|
||||
`--set-json graphql.service.annotations=\"{ \\"cloud.google.com/neg\\": \\"{\\\\\\"ingress\\\\\\": true}\\" }\"`,
|
||||
`--set-json graphql.serviceAccount.annotations=\"{ \\"iam.gke.io/gcp-service-account\\": \\"${CLOUD_LOGGER_IAM_ACCOUNT}\\"}\"`,
|
||||
`--set-json sync.service.annotations=\"{ \\"cloud.google.com/neg\\": \\"{\\\\\\"ingress\\\\\\": true}\\" }\"`,
|
||||
`--set-json sync.serviceAccount.annotations=\"{ \\"iam.gke.io/gcp-service-account\\": \\"${CLOUD_LOGGER_IAM_ACCOUNT}\\"}\"`,
|
||||
`--set-json cloud-sql-proxy.serviceAccount.annotations=\"{ \\"iam.gke.io/gcp-service-account\\": \\"${CLOUD_SQL_IAM_ACCOUNT}\\" }\"`,
|
||||
`--set-json cloud-sql-proxy.nodeSelector=\"{ \\"iam.gke.io/gke-metadata-server-enabled\\": \\"true\\" }\"`,
|
||||
]
|
||||
@@ -103,15 +106,15 @@ const createHelmCommand = ({ isDryRun }) => {
|
||||
`--set-string graphql.app.objectStorage.r2.accountId="${R2_ACCOUNT_ID}"`,
|
||||
`--set-string graphql.app.objectStorage.r2.accessKeyId="${R2_ACCESS_KEY_ID}"`,
|
||||
`--set-string graphql.app.objectStorage.r2.secretAccessKey="${R2_SECRET_ACCESS_KEY}"`,
|
||||
`--set-string graphql.app.oauth.email.sender="${OAUTH_EMAIL_SENDER}"`,
|
||||
`--set-string graphql.app.oauth.email.login="${OAUTH_EMAIL_LOGIN}"`,
|
||||
`--set-string graphql.app.oauth.email.password="${OAUTH_EMAIL_PASSWORD}"`,
|
||||
`--set-string graphql.app.mailer.sender="${MAILER_SENDER}"`,
|
||||
`--set-string graphql.app.mailer.user="${MAILER_USER}"`,
|
||||
`--set-string graphql.app.mailer.password="${MAILER_PASSWORD}"`,
|
||||
`--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.experimental.enableJwstCodec=${isInternal}`,
|
||||
`--set graphql.app.features.earlyAccessPreview=false`,
|
||||
`--set sync.replicaCount=${syncReplicaCount}`,
|
||||
`--set-string sync.image.tag="${imageTag}"`,
|
||||
|
||||
2
.github/deployment/node/Dockerfile
vendored
2
.github/deployment/node/Dockerfile
vendored
@@ -8,4 +8,4 @@ RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends openssl && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
CMD ["node", "--es-module-specifier-resolution=node", "./dist/index.js"]
|
||||
CMD ["node", "--import", "./scripts/register.js", "./dist/index.js"]
|
||||
|
||||
6
.github/deployment/self-host/compose.yaml
vendored
6
.github/deployment/self-host/compose.yaml
vendored
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
affine:
|
||||
image: ghcr.io/toeverything/affine-graphql:beta
|
||||
image: ghcr.io/toeverything/affine-graphql:stable
|
||||
container_name: affine_selfhosted
|
||||
command:
|
||||
['sh', '-c', 'node ./scripts/self-host-predeploy && node ./dist/index.js']
|
||||
@@ -23,13 +23,11 @@ services:
|
||||
max-size: '1000m'
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- NODE_OPTIONS=--es-module-specifier-resolution node
|
||||
- NODE_OPTIONS="--import=./scripts/register.js"
|
||||
- AFFINE_CONFIG_PATH=/root/.affine/config
|
||||
- REDIS_SERVER_HOST=redis
|
||||
- DATABASE_URL=postgres://affine:affine@postgres:5432/affine
|
||||
- DISABLE_TELEMETRY=true
|
||||
- NODE_ENV=production
|
||||
- SERVER_FLAVOR=selfhosted
|
||||
- AFFINE_ADMIN_EMAIL=${AFFINE_ADMIN_EMAIL}
|
||||
- AFFINE_ADMIN_PASSWORD=${AFFINE_ADMIN_PASSWORD}
|
||||
redis:
|
||||
|
||||
@@ -39,6 +39,8 @@ spec:
|
||||
value: "--max-old-space-size=4096"
|
||||
- name: NO_COLOR
|
||||
value: "1"
|
||||
- name: DEPLOYMENT_TYPE
|
||||
value: "affine"
|
||||
- name: SERVER_FLAVOR
|
||||
value: "graphql"
|
||||
- name: AFFINE_ENV
|
||||
@@ -81,31 +83,33 @@ spec:
|
||||
value: "{{ .Values.app.captcha.enabled }}"
|
||||
- name: FEATURES_EARLY_ACCESS_PREVIEW
|
||||
value: "{{ .Values.app.features.earlyAccessPreview }}"
|
||||
- name: OAUTH_EMAIL_SENDER
|
||||
- name: FEATURES_SYNC_CLIENT_VERSION_CHECK
|
||||
value: "{{ .Values.app.features.syncClientVersionCheck }}"
|
||||
- name: MAILER_HOST
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: "{{ .Values.app.oauth.email.secretName }}"
|
||||
key: sender
|
||||
- name: OAUTH_EMAIL_LOGIN
|
||||
name: "{{ .Values.app.mailer.secretName }}"
|
||||
key: host
|
||||
- name: MAILER_PORT
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: "{{ .Values.app.oauth.email.secretName }}"
|
||||
key: login
|
||||
- name: OAUTH_EMAIL_SERVER
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: "{{ .Values.app.oauth.email.secretName }}"
|
||||
key: server
|
||||
- name: OAUTH_EMAIL_PORT
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: "{{ .Values.app.oauth.email.secretName }}"
|
||||
name: "{{ .Values.app.mailer.secretName }}"
|
||||
key: port
|
||||
- name: OAUTH_EMAIL_PASSWORD
|
||||
- name: MAILER_USER
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: "{{ .Values.app.oauth.email.secretName }}"
|
||||
name: "{{ .Values.app.mailer.secretName }}"
|
||||
key: user
|
||||
- name: MAILER_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: "{{ .Values.app.mailer.secretName }}"
|
||||
key: password
|
||||
- name: MAILER_SENDER
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: "{{ .Values.app.mailer.secretName }}"
|
||||
key: sender
|
||||
- name: STRIPE_API_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
|
||||
13
.github/helm/affine/charts/graphql/templates/mailer.yaml
vendored
Normal file
13
.github/helm/affine/charts/graphql/templates/mailer.yaml
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
{{- if .Values.app.mailer.secretName -}}
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: "{{ .Values.app.mailer.secretName }}"
|
||||
type: Opaque
|
||||
data:
|
||||
host: "{{ .Values.app.mailer.host | b64enc }}"
|
||||
port: "{{ .Values.app.mailer.port | b64enc }}"
|
||||
user: "{{ .Values.app.mailer.user | b64enc }}"
|
||||
password: "{{ .Values.app.mailer.password | b64enc }}"
|
||||
sender: "{{ .Values.app.mailer.sender | b64enc }}"
|
||||
{{- end }}
|
||||
@@ -1,15 +1,3 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: "{{ .Values.app.oauth.email.secretName }}"
|
||||
type: Opaque
|
||||
data:
|
||||
sender: "{{ .Values.app.oauth.email.sender | b64enc }}"
|
||||
login: "{{ .Values.app.oauth.email.login | b64enc }}"
|
||||
password: "{{ .Values.app.oauth.email.password | b64enc }}"
|
||||
server: "{{ .Values.app.oauth.email.server | b64enc }}"
|
||||
port: "{{ .Values.app.oauth.email.port | b64enc }}"
|
||||
---
|
||||
{{- if .Values.app.oauth.google.enabled -}}
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
|
||||
16
.github/helm/affine/charts/graphql/values.yaml
vendored
16
.github/helm/affine/charts/graphql/values.yaml
vendored
@@ -35,14 +35,7 @@ app:
|
||||
accountId: ''
|
||||
accessKeyId: ''
|
||||
secretAccessKey: ''
|
||||
oauth:
|
||||
email:
|
||||
secretName: 'oauth-email'
|
||||
sender: 'noreply@toeverything.info'
|
||||
login: ''
|
||||
password: ''
|
||||
server: 'smtp.gmail.com'
|
||||
port: '465'
|
||||
oauth:
|
||||
google:
|
||||
enabled: false
|
||||
secretName: oauth-google
|
||||
@@ -53,6 +46,13 @@ app:
|
||||
secretName: oauth-github
|
||||
clientId: ''
|
||||
clientSecret: ''
|
||||
mailer:
|
||||
secretName: 'mailer'
|
||||
host: 'smtp.gmail.com'
|
||||
port: '465'
|
||||
user: ''
|
||||
password: ''
|
||||
sender: 'noreply@toeverything.info'
|
||||
payment:
|
||||
stripe:
|
||||
secretName: 'stripe'
|
||||
|
||||
@@ -36,6 +36,8 @@ spec:
|
||||
value: "{{ .Values.env }}"
|
||||
- name: NO_COLOR
|
||||
value: "1"
|
||||
- name: DEPLOYMENT_TYPE
|
||||
value: "affine"
|
||||
- name: SERVER_FLAVOR
|
||||
value: "sync"
|
||||
- name: NEXTAUTH_URL
|
||||
|
||||
4
.github/workflows/build-test.yml
vendored
4
.github/workflows/build-test.yml
vendored
@@ -19,7 +19,7 @@ env:
|
||||
MACOSX_DEPLOYMENT_TARGET: '10.13'
|
||||
NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }}
|
||||
PLAYWRIGHT_BROWSERS_PATH: ${{ github.workspace }}/node_modules/.cache/ms-playwright
|
||||
DISABLE_TELEMETRY: true
|
||||
DEPLOYMENT_TYPE: affine
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
@@ -291,6 +291,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build-storage
|
||||
env:
|
||||
NODE_ENV: test
|
||||
DISTRIBUTION: browser
|
||||
services:
|
||||
postgres:
|
||||
@@ -447,7 +448,6 @@ jobs:
|
||||
${{ matrix.tests.script }}
|
||||
env:
|
||||
DEV_SERVER_URL: http://localhost:8080
|
||||
ENABLE_LOCAL_EMAIL: true
|
||||
|
||||
- name: Upload test results
|
||||
if: ${{ failure() }}
|
||||
|
||||
11
.github/workflows/deploy.yml
vendored
11
.github/workflows/deploy.yml
vendored
@@ -136,6 +136,10 @@ jobs:
|
||||
build-docker:
|
||||
name: Build Docker
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: 'write'
|
||||
id-token: 'write'
|
||||
packages: 'write'
|
||||
needs:
|
||||
- build-server
|
||||
- build-core
|
||||
@@ -275,9 +279,9 @@ jobs:
|
||||
R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
|
||||
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 }}
|
||||
MAILER_SENDER: ${{ secrets.OAUTH_EMAIL_SENDER }}
|
||||
MAILER_USER: ${{ secrets.OAUTH_EMAIL_LOGIN }}
|
||||
MAILER_PASSWORD: ${{ secrets.OAUTH_EMAIL_PASSWORD }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
AFFINE_GOOGLE_CLIENT_ID: ${{ secrets.AFFINE_GOOGLE_CLIENT_ID }}
|
||||
AFFINE_GOOGLE_CLIENT_SECRET: ${{ secrets.AFFINE_GOOGLE_CLIENT_SECRET }}
|
||||
@@ -290,6 +294,7 @@ jobs:
|
||||
REDIS_HOST: ${{ secrets.REDIS_HOST }}
|
||||
REDIS_PASSWORD: ${{ secrets.REDIS_PASSWORD }}
|
||||
CLOUD_SQL_IAM_ACCOUNT: ${{ secrets.CLOUD_SQL_IAM_ACCOUNT }}
|
||||
CLOUD_LOGGER_IAM_ACCOUNT: ${{ secrets.CLOUD_LOGGER_IAM_ACCOUNT }}
|
||||
STRIPE_API_KEY: ${{ secrets.STRIPE_API_KEY }}
|
||||
STRIPE_WEBHOOK_KEY: ${{ secrets.STRIPE_WEBHOOK_KEY }}
|
||||
STATIC_IP_NAME: ${{ secrets.STATIC_IP_NAME }}
|
||||
|
||||
@@ -59,9 +59,9 @@ You may need additional env for auth login. You may want to put your own one if
|
||||
For email login & password, please refer to https://nodemailer.com/usage/using-gmail/
|
||||
|
||||
```
|
||||
OAUTH_EMAIL_SENDER=
|
||||
OAUTH_EMAIL_LOGIN=
|
||||
OAUTH_EMAIL_PASSWORD=
|
||||
MAILER_SENDER=
|
||||
MAILER_USER=
|
||||
MAILER_PASSWORD=
|
||||
OAUTH_GOOGLE_ENABLED="true"
|
||||
OAUTH_GOOGLE_CLIENT_ID=
|
||||
OAUTH_GOOGLE_CLIENT_SECRET=
|
||||
|
||||
@@ -9,13 +9,13 @@
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"start": "node --loader ts-node/esm/transpile-only.mjs --es-module-specifier-resolution node ./src/index.ts",
|
||||
"start": "node --loader ts-node/esm/transpile-only.mjs ./src/index.ts",
|
||||
"dev": "nodemon ./src/index.ts",
|
||||
"test": "ava --concurrency 1 --serial",
|
||||
"test:coverage": "c8 ava --concurrency 1 --serial",
|
||||
"postinstall": "prisma generate",
|
||||
"data-migration": "node --loader ts-node/esm/transpile-only.mjs --es-module-specifier-resolution node ./src/data/index.ts",
|
||||
"predeploy": "yarn prisma migrate deploy && node --es-module-specifier-resolution node ./dist/data/index.js run"
|
||||
"data-migration": "node --loader ts-node/esm/transpile-only.mjs ./src/data/index.ts",
|
||||
"predeploy": "yarn prisma migrate deploy && node --import ./scripts/register.js ./dist/data/index.js run"
|
||||
},
|
||||
"dependencies": {
|
||||
"@apollo/server": "^4.9.5",
|
||||
@@ -86,6 +86,8 @@
|
||||
"semver": "^7.5.4",
|
||||
"socket.io": "^4.7.2",
|
||||
"stripe": "^14.5.0",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.3.3",
|
||||
"ws": "^8.14.2",
|
||||
"yjs": "^13.6.10",
|
||||
"zod": "^3.22.4"
|
||||
@@ -112,9 +114,7 @@
|
||||
"c8": "^9.0.0",
|
||||
"nodemon": "^3.0.1",
|
||||
"sinon": "^17.0.1",
|
||||
"supertest": "^6.3.3",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^5.3.2"
|
||||
"supertest": "^6.3.3"
|
||||
},
|
||||
"ava": {
|
||||
"timeout": "1m",
|
||||
@@ -139,10 +139,11 @@
|
||||
"environmentVariables": {
|
||||
"TS_NODE_PROJECT": "./tests/tsconfig.json",
|
||||
"NODE_ENV": "test",
|
||||
"ENABLE_LOCAL_EMAIL": "true",
|
||||
"OAUTH_EMAIL_LOGIN": "noreply@toeverything.info",
|
||||
"OAUTH_EMAIL_PASSWORD": "affine",
|
||||
"OAUTH_EMAIL_SENDER": "noreply@toeverything.info",
|
||||
"MAILER_HOST": "0.0.0.0",
|
||||
"MAILER_PORT": "1025",
|
||||
"MAILER_USER": "noreply@toeverything.info",
|
||||
"MAILER_PASSWORD": "affine",
|
||||
"MAILER_SENDER": "noreply@toeverything.info",
|
||||
"FEATURES_EARLY_ACCESS_PREVIEW": "false"
|
||||
}
|
||||
},
|
||||
@@ -162,7 +163,6 @@
|
||||
"env": {
|
||||
"TS_NODE_TRANSPILE_ONLY": true,
|
||||
"TS_NODE_PROJECT": "./tsconfig.json",
|
||||
"NODE_ENV": "development",
|
||||
"DEBUG": "affine:*",
|
||||
"FORCE_COLOR": true,
|
||||
"DEBUG_COLORS": true
|
||||
|
||||
11
packages/backend/server/scripts/loader.js
Normal file
11
packages/backend/server/scripts/loader.js
Normal file
@@ -0,0 +1,11 @@
|
||||
import { create, createEsmHooks } from 'ts-node';
|
||||
|
||||
const service = create({
|
||||
experimentalSpecifierResolution: 'node',
|
||||
transpileOnly: true,
|
||||
logError: true,
|
||||
skipProject: true,
|
||||
});
|
||||
const hooks = createEsmHooks(service);
|
||||
|
||||
export const resolve = hooks.resolve;
|
||||
4
packages/backend/server/scripts/register.js
Normal file
4
packages/backend/server/scripts/register.js
Normal file
@@ -0,0 +1,4 @@
|
||||
import { register } from 'node:module';
|
||||
import { pathToFileURL } from 'node:url';
|
||||
|
||||
register('./scripts/loader.js', pathToFileURL('./'));
|
||||
@@ -13,7 +13,10 @@ const configFiles = [
|
||||
];
|
||||
|
||||
function configCleaner(content) {
|
||||
return content.replace(/(\/\/#.*$)|(\/\/\s+TODO.*$)/gm, '');
|
||||
return content.replace(
|
||||
/(^\/\/#.*$)|(^\/\/\s+TODO.*$)|("use\sstrict";?)|(^.*eslint-disable.*$)/gm,
|
||||
''
|
||||
);
|
||||
}
|
||||
|
||||
function prepare() {
|
||||
@@ -39,11 +42,9 @@ function prepare() {
|
||||
function runPredeployScript() {
|
||||
console.log('running predeploy script.');
|
||||
execSync('yarn predeploy', {
|
||||
env: {
|
||||
...process.env,
|
||||
NODE_OPTIONS:
|
||||
(process.env.NODE_OPTIONS ?? '') + ' --import ./dist/prelude.js',
|
||||
},
|
||||
encoding: 'utf-8',
|
||||
env: process.env,
|
||||
stdio: 'inherit',
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ export class AppController {
|
||||
return {
|
||||
compatibility: this.config.version,
|
||||
message: `AFFiNE ${this.config.version} Server`,
|
||||
type: this.config.type,
|
||||
flavor: this.config.flavor,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ export class AppModuleBuilder {
|
||||
},
|
||||
],
|
||||
imports: this.modules,
|
||||
controllers: this.config.flavor.selfhosted ? [] : [AppController],
|
||||
controllers: this.config.isSelfhosted ? [] : [AppController],
|
||||
})
|
||||
class AppModule {}
|
||||
|
||||
@@ -132,9 +132,9 @@ function buildAppModule() {
|
||||
// sync server only
|
||||
.useIf(config => config.flavor.sync, SyncModule)
|
||||
|
||||
// main server only
|
||||
// graphql server only
|
||||
.useIf(
|
||||
config => config.flavor.main,
|
||||
config => config.flavor.graphql,
|
||||
ServerConfigModule,
|
||||
WebSocketModule,
|
||||
GqlModule,
|
||||
@@ -147,7 +147,7 @@ function buildAppModule() {
|
||||
|
||||
// self hosted server only
|
||||
.useIf(
|
||||
config => config.flavor.selfhosted,
|
||||
config => config.isSelfhosted,
|
||||
ServeStaticModule.forRoot({
|
||||
rootPath: join('/app', 'static'),
|
||||
})
|
||||
|
||||
@@ -3,8 +3,7 @@ AFFiNE.ENV_MAP = {
|
||||
AFFINE_SERVER_PORT: ['port', 'int'],
|
||||
AFFINE_SERVER_HOST: 'host',
|
||||
AFFINE_SERVER_SUB_PATH: 'path',
|
||||
AFFIHE_SERVER_HTTPS: ['https', 'boolean'],
|
||||
AFFINE_ENV: 'affineEnv',
|
||||
AFFINE_SERVER_HTTPS: ['https', 'boolean'],
|
||||
DATABASE_URL: 'db.url',
|
||||
ENABLE_CAPTCHA: ['auth.captcha.enable', 'boolean'],
|
||||
CAPTCHA_TURNSTILE_SECRET: ['auth.captcha.turnstile.secret', 'string'],
|
||||
@@ -14,11 +13,12 @@ AFFiNE.ENV_MAP = {
|
||||
OAUTH_GITHUB_ENABLED: ['auth.oauthProviders.github.enabled', 'boolean'],
|
||||
OAUTH_GITHUB_CLIENT_ID: 'auth.oauthProviders.github.clientId',
|
||||
OAUTH_GITHUB_CLIENT_SECRET: 'auth.oauthProviders.github.clientSecret',
|
||||
OAUTH_EMAIL_LOGIN: 'auth.email.login',
|
||||
OAUTH_EMAIL_SENDER: 'auth.email.sender',
|
||||
OAUTH_EMAIL_SERVER: 'auth.email.server',
|
||||
OAUTH_EMAIL_PORT: ['auth.email.port', 'int'],
|
||||
OAUTH_EMAIL_PASSWORD: 'auth.email.password',
|
||||
MAILER_HOST: 'mailer.host',
|
||||
MAILER_PORT: ['mailer.port', 'int'],
|
||||
MAILER_USER: 'mailer.auth.user',
|
||||
MAILER_PASSWORD: 'mailer.auth.pass',
|
||||
MAILER_SENDER: 'mailer.from.address',
|
||||
MAILER_SECURE: ['mailer.secure', 'boolean'],
|
||||
THROTTLE_TTL: ['rateLimiter.ttl', 'int'],
|
||||
THROTTLE_LIMIT: ['rateLimiter.limit', 'int'],
|
||||
REDIS_SERVER_HOST: 'plugins.redis.host',
|
||||
@@ -28,13 +28,10 @@ AFFiNE.ENV_MAP = {
|
||||
REDIS_SERVER_DATABASE: ['plugins.redis.db', 'int'],
|
||||
DOC_MERGE_INTERVAL: ['doc.manager.updatePollInterval', 'int'],
|
||||
DOC_MERGE_USE_JWST_CODEC: [
|
||||
'doc.manager.experimentalMergeWithJwstCodec',
|
||||
'doc.manager.experimentalMergeWithYOcto',
|
||||
'boolean',
|
||||
],
|
||||
ENABLE_LOCAL_EMAIL: ['auth.localEmail', 'boolean'],
|
||||
STRIPE_API_KEY: 'plugins.payment.stripe.keys.APIKey',
|
||||
STRIPE_WEBHOOK_KEY: 'plugins.payment.stripe.keys.webhookKey',
|
||||
FEATURES_EARLY_ACCESS_PREVIEW: ['featureFlags.earlyAccessPreview', 'boolean'],
|
||||
};
|
||||
|
||||
export default AFFiNE;
|
||||
|
||||
54
packages/backend/server/src/config/affine.self.ts
Normal file
54
packages/backend/server/src/config/affine.self.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
// Custom configurations for AFFiNE Cloud
|
||||
// ====================================================================================
|
||||
// Q: WHY THIS FILE EXISTS?
|
||||
// A: AFFiNE deployment environment may have a lot of custom environment variables,
|
||||
// which are not suitable to be put in the `affine.ts` file.
|
||||
// For example, AFFiNE Cloud Clusters are deployed on Google Cloud Platform.
|
||||
// We need to enable the `gcloud` plugin to make sure the nodes working well,
|
||||
// but the default selfhost version may not require it.
|
||||
// So it's not a good idea to put such logic in the common `affine.ts` file.
|
||||
//
|
||||
// ```
|
||||
// if (AFFiNE.deploy) {
|
||||
// AFFiNE.plugins.use('gcloud');
|
||||
// }
|
||||
// ```
|
||||
// ====================================================================================
|
||||
const env = process.env;
|
||||
|
||||
AFFiNE.metrics.enabled = !AFFiNE.node.test;
|
||||
|
||||
if (env.R2_OBJECT_STORAGE_ACCOUNT_ID) {
|
||||
AFFiNE.storage.providers.r2 = {
|
||||
accountId: env.R2_OBJECT_STORAGE_ACCOUNT_ID,
|
||||
credentials: {
|
||||
accessKeyId: env.R2_OBJECT_STORAGE_ACCESS_KEY_ID!,
|
||||
secretAccessKey: env.R2_OBJECT_STORAGE_SECRET_ACCESS_KEY!,
|
||||
},
|
||||
};
|
||||
AFFiNE.storage.storages.avatar.provider = 'r2';
|
||||
AFFiNE.storage.storages.avatar.bucket = 'account-avatar';
|
||||
AFFiNE.storage.storages.avatar.publicLinkFactory = key =>
|
||||
`https://avatar.affineassets.com/${key}`;
|
||||
|
||||
AFFiNE.storage.storages.blob.provider = 'r2';
|
||||
AFFiNE.storage.storages.blob.bucket = `workspace-blobs-${
|
||||
AFFiNE.affine.canary ? 'canary' : 'prod'
|
||||
}`;
|
||||
}
|
||||
|
||||
AFFiNE.plugins.use('redis');
|
||||
AFFiNE.plugins.use('payment');
|
||||
|
||||
if (AFFiNE.deploy) {
|
||||
AFFiNE.mailer = {
|
||||
service: 'gmail',
|
||||
auth: {
|
||||
user: env.MAILER_USER,
|
||||
pass: env.MAILER_PASSWORD,
|
||||
},
|
||||
};
|
||||
|
||||
AFFiNE.plugins.use('gcloud');
|
||||
}
|
||||
@@ -1,39 +1,94 @@
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
// Custom configurations
|
||||
const env = process.env;
|
||||
|
||||
// TODO(@forehalo): detail explained
|
||||
// Storage
|
||||
if (env.R2_OBJECT_STORAGE_ACCOUNT_ID) {
|
||||
AFFiNE.storage.providers.r2 = {
|
||||
accountId: env.R2_OBJECT_STORAGE_ACCOUNT_ID,
|
||||
credentials: {
|
||||
accessKeyId: env.R2_OBJECT_STORAGE_ACCESS_KEY_ID!,
|
||||
secretAccessKey: env.R2_OBJECT_STORAGE_SECRET_ACCESS_KEY!,
|
||||
},
|
||||
};
|
||||
AFFiNE.storage.storages.avatar.provider = 'r2';
|
||||
AFFiNE.storage.storages.avatar.bucket = 'account-avatar';
|
||||
AFFiNE.storage.storages.avatar.publicLinkFactory = key =>
|
||||
`https://avatar.affineassets.com/${key}`;
|
||||
|
||||
AFFiNE.storage.storages.blob.provider = 'r2';
|
||||
AFFiNE.storage.storages.blob.bucket = `workspace-blobs-${
|
||||
AFFiNE.affine.canary ? 'canary' : 'prod'
|
||||
}`;
|
||||
}
|
||||
|
||||
// Metrics
|
||||
AFFiNE.metrics.enabled = true;
|
||||
|
||||
// Plugins Section Start
|
||||
AFFiNE.plugins.use('payment', {
|
||||
stripe: {
|
||||
keys: {},
|
||||
apiVersion: '2023-10-16',
|
||||
},
|
||||
//
|
||||
// ###############################################################
|
||||
// ## AFFiNE Configuration System ##
|
||||
// ###############################################################
|
||||
// Here is the file of all AFFiNE configurations that will affect runtime behavior.
|
||||
// Override any configuration here and it will be merged when starting the server.
|
||||
// Any changes in this file won't take effect before server restarted.
|
||||
//
|
||||
//
|
||||
// > Configurations merge order
|
||||
// 1. load environment variables (`.env` if provided, and from system)
|
||||
// 2. load `src/fundamentals/config/default.ts` for all default settings
|
||||
// 3. apply `./affine.ts` patches (this file)
|
||||
// 4. apply `./affine.env.ts` patches
|
||||
//
|
||||
//
|
||||
// ###############################################################
|
||||
// ## General settings ##
|
||||
// ###############################################################
|
||||
//
|
||||
// /* The unique identity of the server */
|
||||
// AFFiNE.serverId = 'some-randome-uuid';
|
||||
//
|
||||
// /* The name of AFFiNE Server, may show on the UI */
|
||||
// AFFiNE.serverName = 'Your Cool AFFiNE Selfhosted Cloud';
|
||||
//
|
||||
// /* Whether the server is deployed behind a HTTPS proxied environment */
|
||||
AFFiNE.https = false;
|
||||
// /* Domain of your server that your server will be available at */
|
||||
AFFiNE.host = 'localhost';
|
||||
// /* The local port of your server that will listen on */
|
||||
AFFiNE.port = 3010;
|
||||
// /* The sub path of your server */
|
||||
// /* For example, if you set `AFFiNE.path = '/affine'`, then the server will be available at `${domain}/affine` */
|
||||
// AFFiNE.path = '/affine';
|
||||
//
|
||||
//
|
||||
// ###############################################################
|
||||
// ## Database settings ##
|
||||
// ###############################################################
|
||||
//
|
||||
// /* The URL of the database where most of AFFiNE server data will be stored in */
|
||||
// AFFiNE.db.url = 'postgres://user:passsword@localhost:5432/affine';
|
||||
//
|
||||
//
|
||||
// ###############################################################
|
||||
// ## Server Function settings ##
|
||||
// ###############################################################
|
||||
//
|
||||
// /* Whether enable metrics and tracing while running the server */
|
||||
// /* The metrics will be available at `http://localhost:9464/metrics` with [Prometheus] format exported */
|
||||
// AFFiNE.metrics.enabled = true;
|
||||
//
|
||||
// /* GraphQL configurations that control the behavior of the Apollo Server behind */
|
||||
// /* @see https://www.apollographql.com/docs/apollo-server/api/apollo-server */
|
||||
// AFFiNE.graphql = {
|
||||
// /* Path to mount GraphQL API */
|
||||
// path: '/graphql',
|
||||
// buildSchemaOptions: {
|
||||
// numberScalarMode: 'integer',
|
||||
// },
|
||||
// /* Whether allow client to query the schema introspection */
|
||||
// introspection: true,
|
||||
// /* Whether enable GraphQL Playground UI */
|
||||
// playground: true,
|
||||
// }
|
||||
//
|
||||
// /* Doc Store & Collaberation */
|
||||
// /* How long the buffer time of creating a new history snapshot when doc get updated */
|
||||
// AFFiNE.doc.history.interval = 1000 * 60 * 10; // 10 minutes
|
||||
//
|
||||
// /* Use `y-octo` to merge updates at the same time when merging using Yjs */
|
||||
// AFFiNE.doc.manager.experimentalMergeWithYOcto = true;
|
||||
//
|
||||
// /* How often the manager will start a new turn of merging pending updates into doc snapshot */
|
||||
// AFFiNE.doc.manager.updatePollInterval = 1000 * 3;
|
||||
//
|
||||
//
|
||||
// ###############################################################
|
||||
// ## Plugins settings ##
|
||||
// ###############################################################
|
||||
//
|
||||
// /* Redis Plugin */
|
||||
// /* Provide caching and session storing backed by Redis. */
|
||||
// /* Useful when you deploy AFFiNE server in a cluster. */
|
||||
AFFiNE.plugins.use('redis', {
|
||||
/* override options */
|
||||
});
|
||||
AFFiNE.plugins.use('redis');
|
||||
// Plugins Section end
|
||||
|
||||
export default AFFiNE;
|
||||
// /* Payment Plugin */
|
||||
AFFiNE.plugins.use('payment', {
|
||||
stripe: { keys: {}, apiVersion: '2023-10-16' },
|
||||
});
|
||||
//
|
||||
|
||||
@@ -115,27 +115,9 @@ export const NextAuthOptionsProvider: FactoryProvider<NextAuthOptions> = {
|
||||
};
|
||||
|
||||
const nextAuthOptions: NextAuthOptions = {
|
||||
providers: [
|
||||
// @ts-expect-error esm interop issue
|
||||
Email.default({
|
||||
server: {
|
||||
host: config.auth.email.server,
|
||||
port: config.auth.email.port,
|
||||
auth: {
|
||||
user: config.auth.email.login,
|
||||
pass: config.auth.email.password,
|
||||
},
|
||||
},
|
||||
from: config.auth.email.sender,
|
||||
sendVerificationRequest: (params: SendVerificationRequestParams) =>
|
||||
sendVerificationRequest(config, logger, mailer, session, params),
|
||||
}),
|
||||
],
|
||||
providers: [],
|
||||
adapter: prismaAdapter,
|
||||
debug: !config.node.prod,
|
||||
session: {
|
||||
strategy: 'database',
|
||||
},
|
||||
logger: {
|
||||
debug(code, metadata) {
|
||||
logger.debug(`${code}: ${JSON.stringify(metadata)}`);
|
||||
@@ -188,6 +170,16 @@ export const NextAuthOptionsProvider: FactoryProvider<NextAuthOptions> = {
|
||||
})
|
||||
);
|
||||
|
||||
if (config.mailer && mailer) {
|
||||
nextAuthOptions.providers.push(
|
||||
// @ts-expect-error esm interop issue
|
||||
Email.default({
|
||||
sendVerificationRequest: (params: SendVerificationRequestParams) =>
|
||||
sendVerificationRequest(config, logger, mailer, session, params),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if (config.auth.oauthProviders.github) {
|
||||
nextAuthOptions.providers.push(
|
||||
// @ts-expect-error esm interop issue
|
||||
@@ -214,6 +206,11 @@ export const NextAuthOptionsProvider: FactoryProvider<NextAuthOptions> = {
|
||||
);
|
||||
}
|
||||
|
||||
if (nextAuthOptions.providers.length > 1) {
|
||||
// not only credentials provider
|
||||
nextAuthOptions.session = { strategy: 'database' };
|
||||
}
|
||||
|
||||
nextAuthOptions.jwt = {
|
||||
encode: async ({ token, maxAge }) =>
|
||||
encode(config, prisma, token, maxAge),
|
||||
|
||||
@@ -136,7 +136,7 @@ export class AuthService {
|
||||
return (
|
||||
!!outcome.success &&
|
||||
// skip hostname check in dev mode
|
||||
(this.config.affineEnv === 'dev' || outcome.hostname === this.config.host)
|
||||
(this.config.node.dev || outcome.hostname === this.config.host)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ export async function sendVerificationRequest(
|
||||
session: SessionService,
|
||||
params: SendVerificationRequestParams
|
||||
) {
|
||||
const { identifier, url, provider } = params;
|
||||
const { identifier, url } = params;
|
||||
const urlWithToken = new URL(url);
|
||||
const callbackUrl = urlWithToken.searchParams.get('callbackUrl') || '';
|
||||
if (!callbackUrl) {
|
||||
@@ -28,7 +28,6 @@ export async function sendVerificationRequest(
|
||||
|
||||
const result = await mailer.sendSignInEmail(urlWithToken.toString(), {
|
||||
to: identifier,
|
||||
from: provider.from,
|
||||
});
|
||||
logger.log(`send verification email success: ${result.accepted.join(', ')}`);
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { Field, ObjectType, Query, registerEnumType } from '@nestjs/graphql';
|
||||
|
||||
import { DeploymentType } from '../fundamentals';
|
||||
|
||||
export enum ServerFeature {
|
||||
Payment = 'payment',
|
||||
}
|
||||
@@ -9,6 +11,10 @@ registerEnumType(ServerFeature, {
|
||||
name: 'ServerFeature',
|
||||
});
|
||||
|
||||
registerEnumType(DeploymentType, {
|
||||
name: 'ServerDeploymentType',
|
||||
});
|
||||
|
||||
const ENABLED_FEATURES: ServerFeature[] = [];
|
||||
export function ADD_ENABLED_FEATURES(feature: ServerFeature) {
|
||||
ENABLED_FEATURES.push(feature);
|
||||
@@ -28,6 +34,9 @@ export class ServerConfigType {
|
||||
@Field({ description: 'server base url' })
|
||||
baseUrl!: string;
|
||||
|
||||
@Field(() => DeploymentType, { description: 'server type' })
|
||||
type!: DeploymentType;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
@@ -46,7 +55,11 @@ export class ServerConfigResolver {
|
||||
name: AFFiNE.serverName,
|
||||
version: AFFiNE.version,
|
||||
baseUrl: AFFiNE.baseUrl,
|
||||
flavor: AFFiNE.flavor.type,
|
||||
type: AFFiNE.type,
|
||||
// BACKWARD COMPATIBILITY
|
||||
// the old flavors contains `selfhosted` but it actually not flavor but deployment type
|
||||
// this field should be removed after frontend feature flags implemented
|
||||
flavor: AFFiNE.type,
|
||||
features: ENABLED_FEATURES,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -125,11 +125,7 @@ export class DocManager implements OnModuleInit, OnModuleDestroy {
|
||||
const doc = await this.recoverDoc(...updates);
|
||||
|
||||
// test jwst codec
|
||||
if (
|
||||
this.config.affine.canary &&
|
||||
this.config.doc.manager.experimentalMergeWithJwstCodec &&
|
||||
updates.length < 100 /* avoid overloading */
|
||||
) {
|
||||
if (this.config.doc.manager.experimentalMergeWithYOcto) {
|
||||
metrics.jwst.counter('codec_merge_counter').add(1);
|
||||
const yjsResult = Buffer.from(encodeStateAsUpdate(doc));
|
||||
let log = false;
|
||||
@@ -180,7 +176,7 @@ export class DocManager implements OnModuleInit, OnModuleDestroy {
|
||||
}, this.config.doc.manager.updatePollInterval);
|
||||
|
||||
this.logger.log('Automation started');
|
||||
if (this.config.doc.manager.experimentalMergeWithJwstCodec) {
|
||||
if (this.config.doc.manager.experimentalMergeWithYOcto) {
|
||||
this.logger.warn(
|
||||
'Experimental feature enabled: merge updates with jwst codec is enabled'
|
||||
);
|
||||
@@ -386,7 +382,7 @@ export class DocManager implements OnModuleInit, OnModuleDestroy {
|
||||
// take it ease, we don't want to overload db and or cpu
|
||||
// if we limit the taken number here,
|
||||
// user will never see the latest doc if there are too many updates pending to be merged.
|
||||
take: 100,
|
||||
take: this.config.doc.manager.maxUpdatesPullCount,
|
||||
});
|
||||
|
||||
// perf(memory): avoid sorting in db
|
||||
|
||||
@@ -56,7 +56,7 @@ export class WorkspacesController {
|
||||
this.logger.warn(`Blob ${workspaceId}/${name} has no metadata`);
|
||||
}
|
||||
|
||||
res.setHeader('cache-control', 'public, max-age=31536000, immutable');
|
||||
res.setHeader('cache-control', 'public, max-age=2592000, immutable');
|
||||
body.pipe(res);
|
||||
}
|
||||
|
||||
@@ -106,6 +106,7 @@ export class WorkspacesController {
|
||||
}
|
||||
|
||||
res.setHeader('content-type', 'application/octet-stream');
|
||||
res.setHeader('cache-control', 'no-cache');
|
||||
res.send(update);
|
||||
}
|
||||
|
||||
@@ -142,6 +143,7 @@ export class WorkspacesController {
|
||||
|
||||
if (history) {
|
||||
res.setHeader('content-type', 'application/octet-stream');
|
||||
res.setHeader('cache-control', 'public, max-age=2592000, immutable');
|
||||
res.send(history.blob);
|
||||
} else {
|
||||
throw new NotFoundException('Doc history not found');
|
||||
|
||||
@@ -8,7 +8,7 @@ export class SelfHostAdmin1605053000403 {
|
||||
// do the migration
|
||||
static async up(db: PrismaClient, ref: ModuleRef) {
|
||||
const config = ref.get(Config, { strict: false });
|
||||
if (config.flavor.selfhosted) {
|
||||
if (config.isSelfhosted) {
|
||||
if (
|
||||
!process.env.AFFINE_ADMIN_EMAIL ||
|
||||
!process.env.AFFINE_ADMIN_PASSWORD
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { ApolloDriverConfig } from '@nestjs/apollo';
|
||||
import SMTPTransport from 'nodemailer/lib/smtp-transport';
|
||||
|
||||
import type { LeafPaths } from '../utils/types';
|
||||
import { EnvConfigType } from './env';
|
||||
@@ -18,18 +19,22 @@ export enum ExternalAccount {
|
||||
firebase = 'firebase',
|
||||
}
|
||||
|
||||
export type ServerFlavor =
|
||||
| 'allinone'
|
||||
| 'main'
|
||||
// @deprecated
|
||||
| 'graphql'
|
||||
| 'sync'
|
||||
| 'selfhosted';
|
||||
export type ServerFlavor = 'allinone' | 'graphql' | 'sync';
|
||||
export type AFFINE_ENV = 'dev' | 'beta' | 'production';
|
||||
export type NODE_ENV = 'development' | 'test' | 'production';
|
||||
|
||||
export enum DeploymentType {
|
||||
Affine = 'affine',
|
||||
Selfhosted = 'selfhosted',
|
||||
}
|
||||
|
||||
export type ConfigPaths = LeafPaths<
|
||||
Omit<
|
||||
AFFiNEConfig,
|
||||
| 'ENV_MAP'
|
||||
| 'version'
|
||||
| 'type'
|
||||
| 'isSelfhosted'
|
||||
| 'flavor'
|
||||
| 'env'
|
||||
| 'affine'
|
||||
@@ -63,27 +68,36 @@ export interface AFFiNEConfig {
|
||||
*/
|
||||
readonly version: string;
|
||||
|
||||
/**
|
||||
* Deployment type, AFFiNE Cloud, or Selfhosted
|
||||
*/
|
||||
get type(): DeploymentType;
|
||||
|
||||
/**
|
||||
* Fast detect whether currently deployed in a selfhosted environment
|
||||
*/
|
||||
get isSelfhosted(): boolean;
|
||||
|
||||
/**
|
||||
* Server flavor
|
||||
*/
|
||||
get flavor(): {
|
||||
type: string;
|
||||
main: boolean;
|
||||
graphql: boolean;
|
||||
sync: boolean;
|
||||
selfhosted: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* Deployment environment
|
||||
*/
|
||||
readonly affineEnv: 'dev' | 'beta' | 'production';
|
||||
readonly AFFINE_ENV: AFFINE_ENV;
|
||||
/**
|
||||
* alias to `process.env.NODE_ENV`
|
||||
*
|
||||
* @default 'production'
|
||||
* @default 'development'
|
||||
* @env NODE_ENV
|
||||
*/
|
||||
readonly env: string;
|
||||
readonly NODE_ENV: NODE_ENV;
|
||||
|
||||
/**
|
||||
* fast AFFiNE environment judge
|
||||
@@ -101,6 +115,7 @@ export interface AFFiNEConfig {
|
||||
dev: boolean;
|
||||
test: boolean;
|
||||
};
|
||||
|
||||
get deploy(): boolean;
|
||||
|
||||
/**
|
||||
@@ -249,18 +264,6 @@ export interface AFFiNEConfig {
|
||||
}
|
||||
>
|
||||
>;
|
||||
/**
|
||||
* whether to use local email service to send email
|
||||
* local debug only
|
||||
*/
|
||||
localEmail: boolean;
|
||||
email: {
|
||||
server: string;
|
||||
port: number;
|
||||
login: string;
|
||||
sender: string;
|
||||
password: string;
|
||||
};
|
||||
captcha: {
|
||||
/**
|
||||
* whether to enable captcha
|
||||
@@ -284,6 +287,13 @@ export interface AFFiNEConfig {
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Configurations for mail service used to post auth or bussiness mails.
|
||||
*
|
||||
* @see https://nodemailer.com/smtp/
|
||||
*/
|
||||
mailer?: SMTPTransport.Options;
|
||||
|
||||
doc: {
|
||||
manager: {
|
||||
/**
|
||||
@@ -302,11 +312,17 @@ export interface AFFiNEConfig {
|
||||
updatePollInterval: number;
|
||||
|
||||
/**
|
||||
* Use JwstCodec to merge updates at the same time when merging using Yjs.
|
||||
* The maximum number of updates that will be pulled from the server at once.
|
||||
* Existing for avoiding the server to be overloaded when there are too many updates for one doc.
|
||||
*/
|
||||
maxUpdatesPullCount: number;
|
||||
|
||||
/**
|
||||
* Use `y-octo` to merge updates at the same time when merging using Yjs.
|
||||
*
|
||||
* This is an experimental feature, and aimed to check the correctness of JwstCodec.
|
||||
*/
|
||||
experimentalMergeWithJwstCodec: boolean;
|
||||
experimentalMergeWithYOcto: boolean;
|
||||
};
|
||||
history: {
|
||||
/**
|
||||
|
||||
@@ -6,7 +6,14 @@ import { merge } from 'lodash-es';
|
||||
import parse from 'parse-duration';
|
||||
|
||||
import pkg from '../../../package.json' assert { type: 'json' };
|
||||
import type { AFFiNEConfig, ServerFlavor } from './def';
|
||||
import {
|
||||
type AFFINE_ENV,
|
||||
AFFiNEConfig,
|
||||
DeploymentType,
|
||||
type NODE_ENV,
|
||||
type ServerFlavor,
|
||||
} from './def';
|
||||
import { readEnv } from './env';
|
||||
import { getDefaultAFFiNEStorageConfig } from './storage';
|
||||
|
||||
// Don't use this in production
|
||||
@@ -46,40 +53,62 @@ const jwtKeyPair = (function () {
|
||||
})();
|
||||
|
||||
export const getDefaultAFFiNEConfig: () => AFFiNEConfig = () => {
|
||||
let isHttps: boolean | null = null;
|
||||
let flavor = (process.env.SERVER_FLAVOR ?? 'allinone') as ServerFlavor;
|
||||
const NODE_ENV = readEnv<NODE_ENV>('NODE_ENV', 'development', [
|
||||
'development',
|
||||
'test',
|
||||
'production',
|
||||
]);
|
||||
const AFFINE_ENV = readEnv<AFFINE_ENV>('AFFINE_ENV', 'dev', [
|
||||
'dev',
|
||||
'beta',
|
||||
'production',
|
||||
]);
|
||||
const flavor = readEnv<ServerFlavor>('SERVER_FLAVOR', 'allinone', [
|
||||
'allinone',
|
||||
'graphql',
|
||||
'sync',
|
||||
]);
|
||||
const deploymentType = readEnv<DeploymentType>(
|
||||
'DEPLOYMENT_TYPE',
|
||||
NODE_ENV === 'development'
|
||||
? DeploymentType.Affine
|
||||
: DeploymentType.Selfhosted,
|
||||
Object.values(DeploymentType)
|
||||
);
|
||||
const isSelfhosted = deploymentType === DeploymentType.Selfhosted;
|
||||
|
||||
const defaultConfig = {
|
||||
serverId: 'affine-nestjs-server',
|
||||
serverName: flavor === 'selfhosted' ? 'Self-Host Cloud' : 'AFFiNE Cloud',
|
||||
serverName: isSelfhosted ? 'Self-Host Cloud' : 'AFFiNE Cloud',
|
||||
version: pkg.version,
|
||||
get type() {
|
||||
return deploymentType;
|
||||
},
|
||||
get isSelfhosted() {
|
||||
return isSelfhosted;
|
||||
},
|
||||
get flavor() {
|
||||
if (flavor === 'graphql') {
|
||||
flavor = 'main';
|
||||
}
|
||||
return {
|
||||
type: flavor,
|
||||
main: flavor === 'main' || flavor === 'allinone',
|
||||
graphql: flavor === 'graphql' || flavor === 'allinone',
|
||||
sync: flavor === 'sync' || flavor === 'allinone',
|
||||
selfhosted: flavor === 'selfhosted',
|
||||
};
|
||||
},
|
||||
ENV_MAP: {},
|
||||
affineEnv: 'dev',
|
||||
AFFINE_ENV,
|
||||
get affine() {
|
||||
const env = this.affineEnv;
|
||||
return {
|
||||
canary: env === 'dev',
|
||||
beta: env === 'beta',
|
||||
stable: env === 'production',
|
||||
canary: AFFINE_ENV === 'dev',
|
||||
beta: AFFINE_ENV === 'beta',
|
||||
stable: AFFINE_ENV === 'production',
|
||||
};
|
||||
},
|
||||
env: process.env.NODE_ENV ?? 'development',
|
||||
NODE_ENV,
|
||||
get node() {
|
||||
const env = this.env;
|
||||
return {
|
||||
prod: env === 'production',
|
||||
dev: env === 'development',
|
||||
test: env === 'test',
|
||||
prod: NODE_ENV === 'production',
|
||||
dev: NODE_ENV === 'development',
|
||||
test: NODE_ENV === 'test',
|
||||
};
|
||||
},
|
||||
get deploy() {
|
||||
@@ -88,12 +117,7 @@ export const getDefaultAFFiNEConfig: () => AFFiNEConfig = () => {
|
||||
featureFlags: {
|
||||
earlyAccessPreview: false,
|
||||
},
|
||||
get https() {
|
||||
return isHttps ?? !this.node.dev;
|
||||
},
|
||||
set https(value: boolean) {
|
||||
isHttps = value;
|
||||
},
|
||||
https: false,
|
||||
host: 'localhost',
|
||||
port: 3010,
|
||||
path: '',
|
||||
@@ -142,14 +166,6 @@ export const getDefaultAFFiNEConfig: () => AFFiNEConfig = () => {
|
||||
return this.privateKey;
|
||||
},
|
||||
oauthProviders: {},
|
||||
localEmail: false,
|
||||
email: {
|
||||
server: 'smtp.gmail.com',
|
||||
port: 465,
|
||||
login: '',
|
||||
sender: '',
|
||||
password: '',
|
||||
},
|
||||
},
|
||||
storage: getDefaultAFFiNEStorageConfig(),
|
||||
rateLimiter: {
|
||||
@@ -160,7 +176,8 @@ export const getDefaultAFFiNEConfig: () => AFFiNEConfig = () => {
|
||||
manager: {
|
||||
enableUpdateAutoMerging: flavor !== 'sync',
|
||||
updatePollInterval: 3000,
|
||||
experimentalMergeWithJwstCodec: false,
|
||||
maxUpdatesPullCount: 500,
|
||||
experimentalMergeWithYOcto: false,
|
||||
},
|
||||
history: {
|
||||
interval: 1000 * 60 * 10 /* 10 mins */,
|
||||
|
||||
@@ -48,3 +48,24 @@ export function applyEnvToConfig(rawConfig: AFFiNEConfig) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function readEnv<T>(
|
||||
env: string,
|
||||
defaultValue: T,
|
||||
availableValues?: T[]
|
||||
) {
|
||||
const value = process.env[env];
|
||||
if (value === undefined) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
if (availableValues && !availableValues.includes(value as any)) {
|
||||
throw new Error(
|
||||
`Invalid value '${value}' for environment variable ${env}, expected one of [${availableValues.join(
|
||||
', '
|
||||
)}]`
|
||||
);
|
||||
}
|
||||
|
||||
return value as T;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ export {
|
||||
applyEnvToConfig,
|
||||
Config,
|
||||
type ConfigPaths,
|
||||
DeploymentType,
|
||||
getDefaultAFFiNEStorageConfig,
|
||||
} from './config';
|
||||
export { EventEmitter, type EventPayload, OnEvent } from './event';
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
import { Global, Module } from '@nestjs/common';
|
||||
|
||||
import { OptionalModule } from '../nestjs';
|
||||
import { MailService } from './mail.service';
|
||||
import { MAILER } from './mailer';
|
||||
|
||||
@Global()
|
||||
@OptionalModule({
|
||||
providers: [MAILER],
|
||||
exports: [MAILER],
|
||||
requires: ['mailer.auth.user', 'mailer.auth.pass'],
|
||||
})
|
||||
class MailerModule {}
|
||||
|
||||
@Global()
|
||||
@Module({
|
||||
providers: [MAILER, MailService],
|
||||
imports: [MailerModule],
|
||||
providers: [MailService],
|
||||
exports: [MailService],
|
||||
})
|
||||
export class MailModule {}
|
||||
|
||||
@@ -1,30 +1,28 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Inject, Injectable, Optional } from '@nestjs/common';
|
||||
|
||||
import { Config } from '../config';
|
||||
import {
|
||||
MAILER_SERVICE,
|
||||
type MailerService,
|
||||
type Options,
|
||||
type Response,
|
||||
} from './mailer';
|
||||
import { MAILER_SERVICE, type MailerService, type Options } from './mailer';
|
||||
import { emailTemplate } from './template';
|
||||
@Injectable()
|
||||
export class MailService {
|
||||
constructor(
|
||||
@Inject(MAILER_SERVICE) private readonly mailer: MailerService,
|
||||
private readonly config: Config
|
||||
private readonly config: Config,
|
||||
@Optional() @Inject(MAILER_SERVICE) private readonly mailer?: MailerService
|
||||
) {}
|
||||
|
||||
async sendMail(options: Options): Promise<Response> {
|
||||
return this.mailer.sendMail(options);
|
||||
async sendMail(options: Options) {
|
||||
if (!this.mailer) {
|
||||
throw new Error('Mailer service is not configured.');
|
||||
}
|
||||
|
||||
return this.mailer.sendMail({
|
||||
from: this.config.mailer?.from,
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
hasConfigured() {
|
||||
return (
|
||||
!!this.config.auth.email.login &&
|
||||
!!this.config.auth.email.password &&
|
||||
!!this.config.auth.email.sender
|
||||
);
|
||||
return !!this.mailer;
|
||||
}
|
||||
|
||||
async sendInviteEmail(
|
||||
@@ -80,7 +78,6 @@ export class MailService {
|
||||
});
|
||||
|
||||
return this.sendMail({
|
||||
from: this.config.auth.email.sender,
|
||||
to,
|
||||
subject: `${invitationInfo.user.name} invited you to join ${invitationInfo.workspace.name}`,
|
||||
html,
|
||||
@@ -119,7 +116,6 @@ export class MailService {
|
||||
buttonUrl: url,
|
||||
});
|
||||
return this.sendMail({
|
||||
from: this.config.auth.email.sender,
|
||||
to,
|
||||
subject: `Modify your AFFiNE password`,
|
||||
html,
|
||||
@@ -135,7 +131,6 @@ export class MailService {
|
||||
buttonUrl: url,
|
||||
});
|
||||
return this.sendMail({
|
||||
from: this.config.auth.email.sender,
|
||||
to,
|
||||
subject: `Set your AFFiNE password`,
|
||||
html,
|
||||
@@ -150,7 +145,6 @@ export class MailService {
|
||||
buttonUrl: url,
|
||||
});
|
||||
return this.sendMail({
|
||||
from: this.config.auth.email.sender,
|
||||
to,
|
||||
subject: `Verify your current email for AFFiNE`,
|
||||
html,
|
||||
@@ -165,7 +159,6 @@ export class MailService {
|
||||
buttonUrl: url,
|
||||
});
|
||||
return this.sendMail({
|
||||
from: this.config.auth.email.sender,
|
||||
to,
|
||||
subject: `Verify your new email for AFFiNE`,
|
||||
html,
|
||||
@@ -177,7 +170,6 @@ export class MailService {
|
||||
content: `As per your request, we have changed your email. Please make sure you're using ${to} when you log in the next time. `,
|
||||
});
|
||||
return this.sendMail({
|
||||
from: this.config.auth.email.sender,
|
||||
to,
|
||||
subject: `Your email has been changed`,
|
||||
html,
|
||||
@@ -200,7 +192,6 @@ export class MailService {
|
||||
content: `${inviteeName} has joined ${workspaceName}`,
|
||||
});
|
||||
return this.sendMail({
|
||||
from: this.config.auth.email.sender,
|
||||
to,
|
||||
subject: title,
|
||||
html,
|
||||
@@ -223,7 +214,6 @@ export class MailService {
|
||||
content: `${inviteeName} has left your workspace`,
|
||||
});
|
||||
return this.sendMail({
|
||||
from: this.config.auth.email.sender,
|
||||
to,
|
||||
subject: title,
|
||||
html,
|
||||
|
||||
@@ -11,28 +11,11 @@ export type Response = SMTPTransport.SentMessageInfo;
|
||||
export type Options = SMTPTransport.Options;
|
||||
|
||||
export const MAILER: FactoryProvider<
|
||||
Transporter<SMTPTransport.SentMessageInfo>
|
||||
Transporter<SMTPTransport.SentMessageInfo> | undefined
|
||||
> = {
|
||||
provide: MAILER_SERVICE,
|
||||
useFactory: (config: Config) => {
|
||||
if (config.auth.localEmail) {
|
||||
return createTransport({
|
||||
host: '0.0.0.0',
|
||||
port: 1025,
|
||||
secure: false,
|
||||
auth: {
|
||||
user: config.auth.email.login,
|
||||
pass: config.auth.email.password,
|
||||
},
|
||||
});
|
||||
}
|
||||
return createTransport({
|
||||
service: 'gmail',
|
||||
auth: {
|
||||
user: config.auth.email.login,
|
||||
pass: config.auth.email.password,
|
||||
},
|
||||
});
|
||||
return config.mailer ? createTransport(config.mailer) : undefined;
|
||||
},
|
||||
inject: [Config],
|
||||
};
|
||||
|
||||
@@ -1,28 +1,48 @@
|
||||
import { Global, Module, OnModuleDestroy, OnModuleInit } from '@nestjs/common';
|
||||
import {
|
||||
Global,
|
||||
Module,
|
||||
OnModuleDestroy,
|
||||
OnModuleInit,
|
||||
Provider,
|
||||
} from '@nestjs/common';
|
||||
import { ModuleRef } from '@nestjs/core';
|
||||
import { NodeSDK } from '@opentelemetry/sdk-node';
|
||||
|
||||
import { Config, parseEnvValue } from '../config';
|
||||
import { createSDK, registerCustomMetrics } from './opentelemetry';
|
||||
import { Config } from '../config';
|
||||
import {
|
||||
LocalOpentelemetryFactory,
|
||||
OpentelemetryFactory,
|
||||
registerCustomMetrics,
|
||||
} from './opentelemetry';
|
||||
|
||||
const factorProvider: Provider = {
|
||||
provide: OpentelemetryFactory,
|
||||
useFactory: (config: Config) => {
|
||||
return config.metrics.enabled ? new LocalOpentelemetryFactory() : null;
|
||||
},
|
||||
inject: [Config],
|
||||
};
|
||||
|
||||
@Global()
|
||||
@Module({})
|
||||
@Module({
|
||||
providers: [factorProvider],
|
||||
exports: [factorProvider],
|
||||
})
|
||||
export class MetricsModule implements OnModuleInit, OnModuleDestroy {
|
||||
private sdk: NodeSDK | null = null;
|
||||
constructor(private readonly config: Config) {}
|
||||
constructor(private readonly ref: ModuleRef) {}
|
||||
|
||||
onModuleInit() {
|
||||
if (
|
||||
this.config.metrics.enabled &&
|
||||
!parseEnvValue(process.env.DISABLE_TELEMETRY, 'boolean')
|
||||
) {
|
||||
this.sdk = createSDK();
|
||||
const factor = this.ref.get(OpentelemetryFactory, { strict: false });
|
||||
if (factor) {
|
||||
this.sdk = factor.create();
|
||||
this.sdk.start();
|
||||
registerCustomMetrics();
|
||||
}
|
||||
}
|
||||
|
||||
async onModuleDestroy() {
|
||||
if (this.config.metrics.enabled && this.sdk) {
|
||||
if (this.sdk) {
|
||||
await this.sdk.shutdown();
|
||||
}
|
||||
}
|
||||
@@ -30,3 +50,4 @@ export class MetricsModule implements OnModuleInit, OnModuleDestroy {
|
||||
|
||||
export * from './metrics';
|
||||
export * from './utils';
|
||||
export { OpentelemetryFactory };
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import { MetricExporter } from '@google-cloud/opentelemetry-cloud-monitoring-exporter';
|
||||
import { TraceExporter } from '@google-cloud/opentelemetry-cloud-trace-exporter';
|
||||
import { GcpDetectorSync } from '@google-cloud/opentelemetry-resource-util';
|
||||
import { OnModuleDestroy } from '@nestjs/common';
|
||||
import { metrics } from '@opentelemetry/api';
|
||||
import {
|
||||
CompositePropagator,
|
||||
@@ -18,16 +16,13 @@ import { NestInstrumentation } from '@opentelemetry/instrumentation-nestjs-core'
|
||||
import { SocketIoInstrumentation } from '@opentelemetry/instrumentation-socket.io';
|
||||
import { Resource } from '@opentelemetry/resources';
|
||||
import {
|
||||
ConsoleMetricExporter,
|
||||
type MeterProvider,
|
||||
MetricProducer,
|
||||
MetricReader,
|
||||
PeriodicExportingMetricReader,
|
||||
} from '@opentelemetry/sdk-metrics';
|
||||
import { NodeSDK } from '@opentelemetry/sdk-node';
|
||||
import {
|
||||
BatchSpanProcessor,
|
||||
ConsoleSpanExporter,
|
||||
SpanExporter,
|
||||
TraceIdRatioBasedSampler,
|
||||
} from '@opentelemetry/sdk-trace-node';
|
||||
@@ -38,7 +33,7 @@ import { PrismaMetricProducer } from './prisma';
|
||||
|
||||
const { PrismaInstrumentation } = prismaInstrument;
|
||||
|
||||
abstract class OpentelemetryFactor {
|
||||
export abstract class OpentelemetryFactory {
|
||||
abstract getMetricReader(): MetricReader;
|
||||
abstract getSpanExporter(): SpanExporter;
|
||||
|
||||
@@ -59,7 +54,7 @@ abstract class OpentelemetryFactor {
|
||||
|
||||
getResource() {
|
||||
return new Resource({
|
||||
[SemanticResourceAttributes.K8S_NAMESPACE_NAME]: AFFiNE.affineEnv,
|
||||
[SemanticResourceAttributes.K8S_NAMESPACE_NAME]: AFFiNE.AFFINE_ENV,
|
||||
[SemanticResourceAttributes.SERVICE_NAME]: AFFiNE.flavor.type,
|
||||
[SemanticResourceAttributes.SERVICE_VERSION]: AFFiNE.version,
|
||||
});
|
||||
@@ -85,32 +80,20 @@ abstract class OpentelemetryFactor {
|
||||
}
|
||||
}
|
||||
|
||||
class GCloudOpentelemetryFactor extends OpentelemetryFactor {
|
||||
override getResource(): Resource {
|
||||
return super.getResource().merge(new GcpDetectorSync().detect());
|
||||
export class LocalOpentelemetryFactory
|
||||
extends OpentelemetryFactory
|
||||
implements OnModuleDestroy
|
||||
{
|
||||
private readonly metricsExporter = new PrometheusExporter({
|
||||
metricProducers: this.getMetricsProducers(),
|
||||
});
|
||||
|
||||
async onModuleDestroy() {
|
||||
await this.metricsExporter.shutdown();
|
||||
}
|
||||
|
||||
override getMetricReader(): MetricReader {
|
||||
return new PeriodicExportingMetricReader({
|
||||
exportIntervalMillis: 30000,
|
||||
exportTimeoutMillis: 10000,
|
||||
exporter: new MetricExporter({
|
||||
prefix: 'custom.googleapis.com',
|
||||
}),
|
||||
metricProducers: this.getMetricsProducers(),
|
||||
});
|
||||
}
|
||||
|
||||
override getSpanExporter(): SpanExporter {
|
||||
return new TraceExporter();
|
||||
}
|
||||
}
|
||||
|
||||
class LocalOpentelemetryFactor extends OpentelemetryFactor {
|
||||
override getMetricReader(): MetricReader {
|
||||
return new PrometheusExporter({
|
||||
metricProducers: this.getMetricsProducers(),
|
||||
});
|
||||
return this.metricsExporter;
|
||||
}
|
||||
|
||||
override getSpanExporter(): SpanExporter {
|
||||
@@ -118,33 +101,6 @@ class LocalOpentelemetryFactor extends OpentelemetryFactor {
|
||||
}
|
||||
}
|
||||
|
||||
class DebugOpentelemetryFactor extends OpentelemetryFactor {
|
||||
override getMetricReader(): MetricReader {
|
||||
return new PeriodicExportingMetricReader({
|
||||
exporter: new ConsoleMetricExporter(),
|
||||
metricProducers: this.getMetricsProducers(),
|
||||
});
|
||||
}
|
||||
|
||||
override getSpanExporter(): SpanExporter {
|
||||
return new ConsoleSpanExporter();
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(@forehalo): make it configurable
|
||||
export function createSDK() {
|
||||
let factor: OpentelemetryFactor | null = null;
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
factor = new GCloudOpentelemetryFactor();
|
||||
} else if (process.env.DEBUG_METRICS) {
|
||||
factor = new DebugOpentelemetryFactor();
|
||||
} else {
|
||||
factor = new LocalOpentelemetryFactor();
|
||||
}
|
||||
|
||||
return factor?.create();
|
||||
}
|
||||
|
||||
function getMeterProvider() {
|
||||
return metrics.getMeterProvider();
|
||||
}
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
/// <reference types="./global.d.ts" />
|
||||
// keep the config import at the top
|
||||
// eslint-disable-next-line simple-import-sort/imports
|
||||
import './prelude';
|
||||
|
||||
import { Logger } from '@nestjs/common';
|
||||
|
||||
import { createApp } from './app';
|
||||
|
||||
const app = await createApp();
|
||||
const listeningHost = AFFiNE.deploy ? '0.0.0.0' : 'localhost';
|
||||
await app.listen(AFFiNE.port, listeningHost);
|
||||
|
||||
console.log(
|
||||
`AFFiNE Server has been started on http://${listeningHost}:${AFFiNE.port}.`
|
||||
);
|
||||
console.log(`And the public server should be recognized as ${AFFiNE.baseUrl}`);
|
||||
const logger = new Logger('App');
|
||||
|
||||
logger.log(`AFFiNE Server is running in [${AFFiNE.type}] mode`);
|
||||
logger.log(`Listening on http://${listeningHost}:${AFFiNE.port}`);
|
||||
logger.log(`And the public server should be recognized as ${AFFiNE.baseUrl}`);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { GCloudConfig } from './gcloud/config';
|
||||
import { PaymentConfig } from './payment';
|
||||
import { RedisOptions } from './redis';
|
||||
|
||||
@@ -5,6 +6,7 @@ declare module '../fundamentals/config' {
|
||||
interface PluginsConfig {
|
||||
readonly payment: PaymentConfig;
|
||||
readonly redis: RedisOptions;
|
||||
readonly gcloud: GCloudConfig;
|
||||
}
|
||||
|
||||
export type AvailablePlugins = keyof PluginsConfig;
|
||||
|
||||
1
packages/backend/server/src/plugins/gcloud/config.ts
Normal file
1
packages/backend/server/src/plugins/gcloud/config.ts
Normal file
@@ -0,0 +1 @@
|
||||
export interface GCloudConfig {}
|
||||
10
packages/backend/server/src/plugins/gcloud/index.ts
Normal file
10
packages/backend/server/src/plugins/gcloud/index.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { Global } from '@nestjs/common';
|
||||
|
||||
import { OptionalModule } from '../../fundamentals';
|
||||
import { GCloudMetrics } from './metrics';
|
||||
|
||||
@Global()
|
||||
@OptionalModule({
|
||||
imports: [GCloudMetrics],
|
||||
})
|
||||
export class GCloudModule {}
|
||||
46
packages/backend/server/src/plugins/gcloud/metrics.ts
Normal file
46
packages/backend/server/src/plugins/gcloud/metrics.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { MetricExporter } from '@google-cloud/opentelemetry-cloud-monitoring-exporter';
|
||||
import { TraceExporter } from '@google-cloud/opentelemetry-cloud-trace-exporter';
|
||||
import { GcpDetectorSync } from '@google-cloud/opentelemetry-resource-util';
|
||||
import { Global, Provider } from '@nestjs/common';
|
||||
import { Resource } from '@opentelemetry/resources';
|
||||
import {
|
||||
MetricReader,
|
||||
PeriodicExportingMetricReader,
|
||||
} from '@opentelemetry/sdk-metrics';
|
||||
import { SpanExporter } from '@opentelemetry/sdk-trace-node';
|
||||
|
||||
import { OptionalModule } from '../../fundamentals';
|
||||
import { OpentelemetryFactory } from '../../fundamentals/metrics';
|
||||
|
||||
export class GCloudOpentelemetryFactory extends OpentelemetryFactory {
|
||||
override getResource(): Resource {
|
||||
return super.getResource().merge(new GcpDetectorSync().detect());
|
||||
}
|
||||
|
||||
override getMetricReader(): MetricReader {
|
||||
return new PeriodicExportingMetricReader({
|
||||
exportIntervalMillis: 30000,
|
||||
exportTimeoutMillis: 10000,
|
||||
exporter: new MetricExporter({
|
||||
prefix: 'custom.googleapis.com',
|
||||
}),
|
||||
metricProducers: this.getMetricsProducers(),
|
||||
});
|
||||
}
|
||||
|
||||
override getSpanExporter(): SpanExporter {
|
||||
return new TraceExporter();
|
||||
}
|
||||
}
|
||||
|
||||
const factorProvider: Provider = {
|
||||
provide: OpentelemetryFactory,
|
||||
useFactory: () => new GCloudOpentelemetryFactory(),
|
||||
};
|
||||
|
||||
@Global()
|
||||
@OptionalModule({
|
||||
if: config => config.metrics.enabled,
|
||||
overrides: [factorProvider],
|
||||
})
|
||||
export class GCloudMetrics {}
|
||||
@@ -1,8 +1,10 @@
|
||||
import type { AvailablePlugins } from '../fundamentals/config';
|
||||
import { GCloudModule } from './gcloud';
|
||||
import { PaymentModule } from './payment';
|
||||
import { RedisModule } from './redis';
|
||||
|
||||
export const pluginsMap = new Map<AvailablePlugins, AFFiNEModule>([
|
||||
['payment', PaymentModule],
|
||||
['redis', RedisModule],
|
||||
['gcloud', GCloudModule],
|
||||
]);
|
||||
|
||||
@@ -23,6 +23,7 @@ import { StripeWebhook } from './webhook';
|
||||
// 'plugins.payment.stripe.keys.webhookKey',
|
||||
// ],
|
||||
contributesTo: ServerFeature.Payment,
|
||||
if: config => config.flavor.graphql,
|
||||
})
|
||||
export class PaymentModule {}
|
||||
|
||||
|
||||
@@ -337,7 +337,7 @@ export class UserSubscriptionResolver {
|
||||
|
||||
// @FIXME(@forehalo): should not mock any api for selfhosted server
|
||||
// the frontend should avoid calling such api if feature is not enabled
|
||||
if (this.config.flavor.selfhosted) {
|
||||
if (this.config.isSelfhosted) {
|
||||
const start = new Date();
|
||||
const end = new Date();
|
||||
end.setFullYear(start.getFullYear() + 1);
|
||||
|
||||
@@ -5,6 +5,7 @@ import { join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
import { config } from 'dotenv';
|
||||
import { omit } from 'lodash-es';
|
||||
|
||||
import {
|
||||
applyEnvToConfig,
|
||||
@@ -43,14 +44,23 @@ async function load() {
|
||||
// 3. load env => config map to `globalThis.AFFiNE.ENV_MAP
|
||||
await loadRemote(AFFiNE_CONFIG_PATH, 'affine.env.js');
|
||||
|
||||
// 4. apply `process.env` map overriding to `globalThis.AFFiNE`
|
||||
applyEnvToConfig(globalThis.AFFiNE);
|
||||
|
||||
// 5. load `config/affine` to patch custom configs
|
||||
// 4. load `config/affine` to patch custom configs
|
||||
await loadRemote(AFFiNE_CONFIG_PATH, 'affine.js');
|
||||
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.log('AFFiNE Config:', JSON.stringify(globalThis.AFFiNE, null, 2));
|
||||
// 5. load `config/affine.self` to patch custom configs
|
||||
// This is the file only take effect in [AFFiNE Cloud]
|
||||
if (!AFFiNE.isSelfhosted) {
|
||||
await loadRemote(AFFiNE_CONFIG_PATH, 'affine.self.js');
|
||||
}
|
||||
|
||||
// 6. apply `process.env` map overriding to `globalThis.AFFiNE`
|
||||
applyEnvToConfig(globalThis.AFFiNE);
|
||||
|
||||
if (AFFiNE.node.dev) {
|
||||
console.log(
|
||||
'AFFiNE Config:',
|
||||
JSON.stringify(omit(globalThis.AFFiNE, 'ENV_MAP'), null, 2)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -240,10 +240,18 @@ type ServerConfigType {
|
||||
"""server identical name could be shown as badge on user interface"""
|
||||
name: String!
|
||||
|
||||
"""server type"""
|
||||
type: ServerDeploymentType!
|
||||
|
||||
"""server version"""
|
||||
version: String!
|
||||
}
|
||||
|
||||
enum ServerDeploymentType {
|
||||
Affine
|
||||
Selfhosted
|
||||
}
|
||||
|
||||
enum ServerFeature {
|
||||
Payment
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ test.afterEach.always(async () => {
|
||||
|
||||
test('should be able to get config', t => {
|
||||
t.true(typeof config.host === 'string');
|
||||
t.is(config.env, 'test');
|
||||
t.is(config.NODE_ENV, 'test');
|
||||
});
|
||||
|
||||
test('should be able to override config', async t => {
|
||||
|
||||
@@ -7,7 +7,7 @@ import type {
|
||||
WorkspaceInfoSnapshot,
|
||||
} from '@blocksuite/store';
|
||||
import { Job } from '@blocksuite/store';
|
||||
import type { createStore, WritableAtom } from 'jotai/vanilla';
|
||||
import type { createStore, WritableAtom } from 'jotai';
|
||||
import { Map as YMap } from 'yjs';
|
||||
|
||||
import { getLatestVersions } from '../migration/blocksuite';
|
||||
@@ -29,6 +29,7 @@ export function initEmptyPage(page: Page, title?: string) {
|
||||
*/
|
||||
export async function buildShowcaseWorkspace(
|
||||
workspace: Workspace,
|
||||
blobStorage: { set: (key: string, blob: Blob) => Promise<string> },
|
||||
{
|
||||
store,
|
||||
atoms,
|
||||
@@ -46,6 +47,7 @@ export async function buildShowcaseWorkspace(
|
||||
const { onboarding } = await import('@affine/templates');
|
||||
|
||||
const info = onboarding['info.json'] as WorkspaceInfoSnapshot;
|
||||
const blob = onboarding['blob.json'] as { [key: string]: string };
|
||||
|
||||
const migrationMiddleware: JobMiddleware = ({ slots, workspace }) => {
|
||||
slots.afterImport.on(payload => {
|
||||
@@ -88,27 +90,29 @@ export async function buildShowcaseWorkspace(
|
||||
.getMap('meta')
|
||||
.set('blockVersions', new YMap(Object.entries(newVersions)));
|
||||
|
||||
for (const [key, base64] of Object.entries(blob)) {
|
||||
await blobStorage.set(key, new Blob([base64ToUint8Array(base64)]));
|
||||
}
|
||||
|
||||
// todo: find better way to do the following
|
||||
// perhaps put them into middleware?
|
||||
{
|
||||
// the "AFFiNE - not just a note-taking app" page should be set to edgeless mode
|
||||
// the "Write, Draw, Plan all at Once." page should be set to edgeless mode
|
||||
const edgelessPage1 = (workspace.meta.pages as PageMeta[])?.find(
|
||||
p => p.title === 'AFFiNE - not just a note-taking app'
|
||||
p => p.title === 'Write, Draw, Plan all at Once.'
|
||||
)?.id;
|
||||
|
||||
if (edgelessPage1) {
|
||||
workspace.setPageMeta(edgelessPage1, { jumpOnce: true });
|
||||
store.set(atoms.pageMode, edgelessPage1, 'edgeless');
|
||||
}
|
||||
|
||||
// should jump to "Getting Started" by default
|
||||
const gettingStartedPage = (workspace.meta.pages as PageMeta[])?.find(p =>
|
||||
p.title.startsWith('Getting Started')
|
||||
)?.id;
|
||||
|
||||
if (gettingStartedPage) {
|
||||
workspace.setPageMeta(gettingStartedPage, {
|
||||
jumpOnce: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function base64ToUint8Array(base64: string) {
|
||||
const binaryString = atob(base64);
|
||||
const binaryArray = binaryString.split('').map(function (char) {
|
||||
return char.charCodeAt(0);
|
||||
});
|
||||
return new Uint8Array(binaryArray);
|
||||
}
|
||||
|
||||
@@ -13,6 +13,9 @@ export function fixWorkspaceVersion(rootDoc: YDoc) {
|
||||
* Blocksuite just set the value, do nothing else.
|
||||
*/
|
||||
function doFix() {
|
||||
if (meta.size === 0) {
|
||||
return;
|
||||
}
|
||||
const workspaceVersion = meta.get('workspaceVersion');
|
||||
if (typeof workspaceVersion !== 'number' || workspaceVersion < 2) {
|
||||
transact(
|
||||
|
||||
@@ -19,6 +19,7 @@ export interface SyncPeerStatus {
|
||||
loadedDocs: number;
|
||||
pendingPullUpdates: number;
|
||||
pendingPushUpdates: number;
|
||||
rootDocLoaded: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -54,6 +55,7 @@ export class SyncPeer {
|
||||
loadedDocs: 0,
|
||||
pendingPullUpdates: 0,
|
||||
pendingPushUpdates: 0,
|
||||
rootDocLoaded: false,
|
||||
};
|
||||
onStatusChange = new Slot<SyncPeerStatus>();
|
||||
readonly abort = new AbortController();
|
||||
@@ -119,6 +121,7 @@ export class SyncPeer {
|
||||
loadedDocs: 0,
|
||||
pendingPullUpdates: 0,
|
||||
pendingPushUpdates: 0,
|
||||
rootDocLoaded: this.status.rootDocLoaded,
|
||||
};
|
||||
await Promise.race([
|
||||
new Promise<void>(resolve => {
|
||||
@@ -291,6 +294,13 @@ export class SyncPeer {
|
||||
(await this.storage.pull(doc.guid, encodeStateVector(doc))) ?? {};
|
||||
throwIfAborted(abort);
|
||||
|
||||
if (docData !== undefined && doc.guid === this.rootDoc.guid) {
|
||||
this.status = {
|
||||
...this.status,
|
||||
rootDocLoaded: true,
|
||||
};
|
||||
}
|
||||
|
||||
if (docData) {
|
||||
applyUpdate(doc, docData, 'load');
|
||||
}
|
||||
@@ -391,6 +401,7 @@ export class SyncPeer {
|
||||
this.state.pullUpdatesQueue.length + (this.state.subdocLoading ? 1 : 0),
|
||||
pendingPushUpdates:
|
||||
this.state.pushUpdatesQueue.length + (this.state.pushingUpdate ? 1 : 0),
|
||||
rootDocLoaded: this.status.rootDocLoaded,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -19,10 +19,10 @@ export async function createFirstAppData() {
|
||||
localStorage.setItem('is-first-open', 'false');
|
||||
const workspaceId = await workspaceManager.createWorkspace(
|
||||
WorkspaceFlavour.LOCAL,
|
||||
async workspace => {
|
||||
async (workspace, blob) => {
|
||||
workspace.meta.setName(DEFAULT_WORKSPACE_NAME);
|
||||
if (runtimeConfig.enablePreloading) {
|
||||
await buildShowcaseWorkspace(workspace, {
|
||||
await buildShowcaseWorkspace(workspace, blob, {
|
||||
store: getCurrentStore(),
|
||||
atoms: {
|
||||
pageMode: setPageModeAtom,
|
||||
|
||||
@@ -150,10 +150,10 @@ export const CreateWorkspaceModal = ({
|
||||
// fix me later
|
||||
const id = await workspaceManager.createWorkspace(
|
||||
WorkspaceFlavour.LOCAL,
|
||||
async workspace => {
|
||||
async (workspace, blob) => {
|
||||
workspace.meta.setName(name);
|
||||
if (runtimeConfig.enablePreloading) {
|
||||
await buildShowcaseWorkspace(workspace, {
|
||||
await buildShowcaseWorkspace(workspace, blob, {
|
||||
store: getCurrentStore(),
|
||||
atoms: {
|
||||
pageMode: setPageModeAtom,
|
||||
|
||||
@@ -21,12 +21,12 @@ class CustomAttachmentService extends AttachmentService {
|
||||
}
|
||||
|
||||
function customLoadFonts(service: PageService): void {
|
||||
const officialDomains = new Set(['affine.pro', 'affine.fail']);
|
||||
const officialDomains = new Set(['app.affine.pro', 'affine.fail']);
|
||||
if (!officialDomains.has(window.location.host)) {
|
||||
const fonts = CanvasTextFonts.map(font => ({
|
||||
...font,
|
||||
// self-hosted fonts are served from /assets
|
||||
url: '/assets' + new URL(font.url).pathname.split('/').pop(),
|
||||
url: '/assets/' + new URL(font.url).pathname.split('/').pop(),
|
||||
}));
|
||||
service.fontLoader.load(fonts);
|
||||
} else {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { serverConfigQuery } from '@affine/graphql';
|
||||
import { serverConfigQuery, ServerDeploymentType } from '@affine/graphql';
|
||||
import type { BareFetcher, Middleware } from 'swr';
|
||||
|
||||
import { useQueryImmutable } from '../use-query';
|
||||
@@ -25,20 +25,20 @@ const useServerConfig = () => {
|
||||
return config.serverConfig;
|
||||
};
|
||||
|
||||
export const useServerFlavor = () => {
|
||||
export const useServerType = () => {
|
||||
const config = useServerConfig();
|
||||
|
||||
if (!config) {
|
||||
return 'local';
|
||||
}
|
||||
|
||||
return config.flavor;
|
||||
return config.type;
|
||||
};
|
||||
|
||||
export const useSelfHosted = () => {
|
||||
const serverFlavor = useServerFlavor();
|
||||
const serverType = useServerType();
|
||||
|
||||
return ['local', 'selfhosted'].includes(serverFlavor);
|
||||
return ['local', ServerDeploymentType.Selfhosted].includes(serverType);
|
||||
};
|
||||
|
||||
export const useServerBaseUrl = () => {
|
||||
|
||||
@@ -6,7 +6,8 @@ import {
|
||||
workspaceListLoadingStatusAtom,
|
||||
workspaceManagerAtom,
|
||||
} from '@affine/core/modules/workspace';
|
||||
import { type Workspace } from '@affine/workspace';
|
||||
import type { Workspace } from '@affine/workspace';
|
||||
import { type SyncEngineStatus } from '@affine/workspace';
|
||||
import { useAtom, useAtomValue } from 'jotai';
|
||||
import {
|
||||
type ReactElement,
|
||||
@@ -75,25 +76,32 @@ export const Component = (): ReactElement => {
|
||||
|
||||
const [workspaceIsLoading, setWorkspaceIsLoading] = useState(true);
|
||||
|
||||
const [syncEngineStatus, setSyncEngineStatus] = useState<SyncEngineStatus>();
|
||||
|
||||
useEffect(() => {
|
||||
if (!workspace) {
|
||||
setSyncEngineStatus(undefined);
|
||||
}
|
||||
return workspace?.engine.sync.onStatusChange.on(setSyncEngineStatus)
|
||||
.dispose;
|
||||
}, [workspace]);
|
||||
|
||||
// hotfix: avoid doing operation, before workspace is loaded
|
||||
useEffect(() => {
|
||||
if (!workspace) {
|
||||
setWorkspaceIsLoading(true);
|
||||
return;
|
||||
}
|
||||
const metaYMap = workspace.blockSuiteWorkspace.doc.getMap('meta');
|
||||
|
||||
const handleYMapChanged = () => {
|
||||
setWorkspaceIsLoading(metaYMap.size === 0);
|
||||
};
|
||||
|
||||
handleYMapChanged();
|
||||
|
||||
metaYMap.observe(handleYMapChanged);
|
||||
return () => {
|
||||
metaYMap.unobserve(handleYMapChanged);
|
||||
};
|
||||
}, [workspace]);
|
||||
if (
|
||||
[syncEngineStatus?.local, ...(syncEngineStatus?.remotes ?? [])].some(
|
||||
p => p?.rootDocLoaded === true
|
||||
)
|
||||
) {
|
||||
setWorkspaceIsLoading(false);
|
||||
} else {
|
||||
setWorkspaceIsLoading(true);
|
||||
}
|
||||
}, [syncEngineStatus, workspace]);
|
||||
|
||||
// if listLoading is false, we can show 404 page, otherwise we should show loading page.
|
||||
if (listLoading === false && meta === undefined) {
|
||||
|
||||
@@ -677,7 +677,7 @@ query serverConfig {
|
||||
baseUrl
|
||||
name
|
||||
features
|
||||
flavor
|
||||
type
|
||||
}
|
||||
}`,
|
||||
};
|
||||
|
||||
@@ -4,6 +4,6 @@ query serverConfig {
|
||||
baseUrl
|
||||
name
|
||||
features
|
||||
flavor
|
||||
type
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,6 +71,11 @@ export enum PublicPageMode {
|
||||
Page = 'Page',
|
||||
}
|
||||
|
||||
export enum ServerDeploymentType {
|
||||
Affine = 'Affine',
|
||||
Selfhosted = 'Selfhosted',
|
||||
}
|
||||
|
||||
export enum ServerFeature {
|
||||
Payment = 'Payment',
|
||||
}
|
||||
@@ -673,7 +678,7 @@ export type ServerConfigQuery = {
|
||||
baseUrl: string;
|
||||
name: string;
|
||||
features: Array<ServerFeature>;
|
||||
flavor: string;
|
||||
type: ServerDeploymentType;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -1,211 +0,0 @@
|
||||
{
|
||||
"type": "page",
|
||||
"meta": {
|
||||
"id": "-P-O4GSfVGTkI16zJRVa4",
|
||||
"title": "Templates Galleries ",
|
||||
"createDate": 1691551731225,
|
||||
"tags": []
|
||||
},
|
||||
"blocks": {
|
||||
"type": "block",
|
||||
"id": "Tz41kDyemg",
|
||||
"flavour": "affine:page",
|
||||
"props": {
|
||||
"title": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "Templates Galleries "
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "block",
|
||||
"id": "PCxQvHuwt1",
|
||||
"flavour": "affine:surface",
|
||||
"props": {
|
||||
"elements": {}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "PMY4JPuq4o",
|
||||
"flavour": "affine:note",
|
||||
"props": {
|
||||
"xywh": "[0,0,800,529]",
|
||||
"background": "--affine-background-secondary-color",
|
||||
"index": "a0",
|
||||
"hidden": false,
|
||||
"edgeless": {
|
||||
"style": {
|
||||
"borderRadius": 8,
|
||||
"borderSize": 4,
|
||||
"borderStyle": "solid",
|
||||
"shadowType": "--affine-note-shadow-box"
|
||||
}
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "block",
|
||||
"id": "1KJadeDAj-",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "text",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": []
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "_a8e4OM_PP",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "text",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "No matter if you're "
|
||||
},
|
||||
{
|
||||
"insert": "organizing your personal life",
|
||||
"attributes": {
|
||||
"bold": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"insert": " or "
|
||||
},
|
||||
{
|
||||
"insert": "getting things done at work",
|
||||
"attributes": {
|
||||
"bold": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"insert": ", our templates galleries have got you covered! "
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "ndmUz6zEr1",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "text",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "Here We offer a wide range of resources to meet your unique needs and help you achieve your goals, whether in your personal or professional life."
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "yIcapYtWKm",
|
||||
"flavour": "affine:divider",
|
||||
"props": {},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "gPqHv8Whaq",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "quote",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "Tired of managing your notes or coming up with a effient work plan? Whether you're a student, parent, or have diverse interests, here we provide a few templates to kick off your journey and unlock your true potential with AFFiNE and reap incredible benefits."
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "pp12c2slOV",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "text",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": " ",
|
||||
"attributes": {
|
||||
"reference": {
|
||||
"type": "LinkedPage",
|
||||
"pageId": "0nee_XzkrN23Xy7HMoXL-"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"insert": " "
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "VyoH5kUerc",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "quote",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "Staying organized and efficient in a dynamic work environment is essential for today's working people. AFFiNE elevates productivity for project managers, software engineers, and professionals alike. We're excited to provide insights into how AFFiNE transforms work life. "
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "XvCeZ-f-ib",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "text",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": " ",
|
||||
"attributes": {
|
||||
"reference": {
|
||||
"type": "LinkedPage",
|
||||
"pageId": "nPPyOV0JBNKu5hvW9hE00"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,178 +0,0 @@
|
||||
{
|
||||
"type": "page",
|
||||
"meta": {
|
||||
"id": "0nee_XzkrN23Xy7HMoXL-",
|
||||
"title": "Personal Home",
|
||||
"createDate": 1691552082822,
|
||||
"tags": []
|
||||
},
|
||||
"blocks": {
|
||||
"type": "block",
|
||||
"id": "QmQb34xduM",
|
||||
"flavour": "affine:page",
|
||||
"props": {
|
||||
"title": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "Personal Home"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "block",
|
||||
"id": "f4WAKZ67ki",
|
||||
"flavour": "affine:surface",
|
||||
"props": {
|
||||
"elements": {}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "xj7QSmgQgT",
|
||||
"flavour": "affine:note",
|
||||
"props": {
|
||||
"xywh": "[0,0,800,547]",
|
||||
"background": "--affine-background-secondary-color",
|
||||
"index": "a0",
|
||||
"hidden": false,
|
||||
"edgeless": {
|
||||
"style": {
|
||||
"borderRadius": 8,
|
||||
"borderSize": 4,
|
||||
"borderStyle": "solid",
|
||||
"shadowType": "--affine-note-shadow-box"
|
||||
}
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "block",
|
||||
"id": "qlnEliaAwr",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "text",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": []
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "U6w_UTi2p5",
|
||||
"flavour": "affine:list",
|
||||
"props": {
|
||||
"type": "bulleted",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": " ",
|
||||
"attributes": {
|
||||
"reference": {
|
||||
"type": "LinkedPage",
|
||||
"pageId": "e5cYLNpIRUb-nwpSZGdix"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"insert": " "
|
||||
}
|
||||
]
|
||||
},
|
||||
"checked": false,
|
||||
"collapsed": false
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "YN7S4Pe19c",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "quote",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "Personal knowledge management (PKM for short) is "
|
||||
},
|
||||
{
|
||||
"insert": "the process of collecting, organizing, and storing information",
|
||||
"attributes": {
|
||||
"bold": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"insert": ", so it's easier to search for, retrieve, share, expand upon, and use later on."
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "V9nK8F7B0R",
|
||||
"flavour": "affine:divider",
|
||||
"props": {},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "jj-qOseCj-",
|
||||
"flavour": "affine:list",
|
||||
"props": {
|
||||
"type": "bulleted",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": " ",
|
||||
"attributes": {
|
||||
"reference": {
|
||||
"type": "LinkedPage",
|
||||
"pageId": "Scod6coKmJJ-waH1-jkiW"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"checked": false,
|
||||
"collapsed": false
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "hgSgQdYwWJ",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "quote",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "An event planning template ",
|
||||
"attributes": {
|
||||
"bold": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"insert": "is an efficient framework that details the steps you and your team need to consider taking to plan and execute a successful event."
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,310 +0,0 @@
|
||||
{
|
||||
"type": "page",
|
||||
"meta": {
|
||||
"id": "GWsRbUtMF4Ee6foVg-H9R",
|
||||
"title": "Meeting Summary ",
|
||||
"createDate": 1691635388447,
|
||||
"tags": [
|
||||
"Oe5dSe1DDJ"
|
||||
]
|
||||
},
|
||||
"blocks": {
|
||||
"type": "block",
|
||||
"id": "EYboj1it1i",
|
||||
"flavour": "affine:page",
|
||||
"props": {
|
||||
"title": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "Meeting Summary "
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "block",
|
||||
"id": "86K_nlhKXG",
|
||||
"flavour": "affine:surface",
|
||||
"props": {
|
||||
"elements": {}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "q7fYMX90uJ",
|
||||
"flavour": "affine:note",
|
||||
"props": {
|
||||
"xywh": "[0,0,800,1237]",
|
||||
"background": "--affine-background-secondary-color",
|
||||
"index": "a0",
|
||||
"hidden": false,
|
||||
"edgeless": {
|
||||
"style": {
|
||||
"borderRadius": 8,
|
||||
"borderSize": 4,
|
||||
"borderStyle": "solid",
|
||||
"shadowType": "--affine-note-shadow-box"
|
||||
}
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "block",
|
||||
"id": "IuRC73HZLH",
|
||||
"flavour": "affine:image",
|
||||
"props": {
|
||||
"caption": "",
|
||||
"sourceId": "/static/f9yKnlNMgKhF-CxOgHBsXkxfViCCkC6KwTv6Uj2Fcjw=.png",
|
||||
"width": 752,
|
||||
"height": 501.6328125,
|
||||
"index": "a0",
|
||||
"xywh": "[0,0,0,0]",
|
||||
"rotate": 0,
|
||||
"size": -1
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "YuOqeDHzoe",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "quote",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "Project:"
|
||||
},
|
||||
{
|
||||
"insert": " [Link the project plan]\n",
|
||||
"attributes": {
|
||||
"background": "var(--affine-text-highlight-grey)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"insert": "]Attendees: "
|
||||
},
|
||||
{
|
||||
"insert": "[@mention teammates]",
|
||||
"attributes": {
|
||||
"background": "var(--affine-text-highlight-grey)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"insert": "\nDate & Time:"
|
||||
},
|
||||
{
|
||||
"insert": " [type /today]",
|
||||
"attributes": {
|
||||
"background": "var(--affine-text-highlight-grey)"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "jPGxHHA_QX",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "h1",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "Agenda"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "sCk70nT5eX",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "text",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "Write down the main topic to discuss."
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "iKwcxVFpCH",
|
||||
"flavour": "affine:list",
|
||||
"props": {
|
||||
"type": "bulleted",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "E.g. content roadmap"
|
||||
}
|
||||
]
|
||||
},
|
||||
"checked": false,
|
||||
"collapsed": false
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "RPED0t8fSj",
|
||||
"flavour": "affine:list",
|
||||
"props": {
|
||||
"type": "bulleted",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "Get aligned on the main upcoming topics"
|
||||
}
|
||||
]
|
||||
},
|
||||
"checked": false,
|
||||
"collapsed": false
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "D3Y93H8WvU",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "h1",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "Notes"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "BfO7S4Gkfm",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "quote",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "Cover any key information discussed in the meeting."
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "Stcz30R0Ad",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "h1",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "Outcomes"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "YKIkBUmv01",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "quote",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "Summarize key decisions in a tidy paragraph and communicate them to the team."
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "JzAgpzljrp",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "h1",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "Action items"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "FSkrwB-T33",
|
||||
"flavour": "affine:list",
|
||||
"props": {
|
||||
"type": "todo",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "E.g. @jess review the content roadmap"
|
||||
}
|
||||
]
|
||||
},
|
||||
"checked": false,
|
||||
"collapsed": false
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "abGz-82vvA",
|
||||
"flavour": "affine:list",
|
||||
"props": {
|
||||
"type": "todo",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "E.g. @mike contact copywriter freelancers"
|
||||
}
|
||||
]
|
||||
},
|
||||
"checked": false,
|
||||
"collapsed": false
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,457 +0,0 @@
|
||||
{
|
||||
"type": "page",
|
||||
"meta": {
|
||||
"id": "RCpxnWMtBWmUZy5awgJBh",
|
||||
"title": "OKR Template",
|
||||
"createDate": 1691636192263,
|
||||
"tags": [
|
||||
"q3mceOl_zi",
|
||||
"g1L5dXKctL"
|
||||
]
|
||||
},
|
||||
"blocks": {
|
||||
"type": "block",
|
||||
"id": "0tJt1nfXpr",
|
||||
"flavour": "affine:page",
|
||||
"props": {
|
||||
"title": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "OKR Template"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "block",
|
||||
"id": "-SeDPuI6pE",
|
||||
"flavour": "affine:surface",
|
||||
"props": {
|
||||
"elements": {}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "gjPKqwOdlZ",
|
||||
"flavour": "affine:note",
|
||||
"props": {
|
||||
"xywh": "[0,0,800,1418]",
|
||||
"background": "--affine-background-secondary-color",
|
||||
"index": "a0",
|
||||
"hidden": false,
|
||||
"edgeless": {
|
||||
"style": {
|
||||
"borderRadius": 8,
|
||||
"borderSize": 4,
|
||||
"borderStyle": "solid",
|
||||
"shadowType": "--affine-note-shadow-box"
|
||||
}
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "block",
|
||||
"id": "syUYSO6Hlw",
|
||||
"flavour": "affine:image",
|
||||
"props": {
|
||||
"caption": "",
|
||||
"sourceId": "/static/VuXYyM9JUv1Fv_qjg1v5Go4Zksz0r4NXFeh3Na7JkIc=.png",
|
||||
"width": 752,
|
||||
"height": 501.2734375,
|
||||
"index": "a0",
|
||||
"xywh": "[0,0,0,0]",
|
||||
"rotate": 0,
|
||||
"size": -1
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "B12ua5IVdH",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "quote",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "Using "
|
||||
},
|
||||
{
|
||||
"insert": "OKR (Objectives and Key Results)",
|
||||
"attributes": {
|
||||
"link": "https://en.wikipedia.org/wiki/Objectives_and_key_results"
|
||||
}
|
||||
},
|
||||
{
|
||||
"insert": " helps teams work together to create and break down their goals. They also figure out how to reach those goals and use their own ideas and creativity. Every week, the team checks in to make sure everyone understands the goals, shares their thoughts, and makes changes as needed to get the job done well and quickly."
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "UWKiht4_yF",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "text",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": []
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "tsqRR7Aqqe",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "h2",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "I. Synchronization of team OKR progress "
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "TzALQTyjsF",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "text",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": []
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "-RNdJMcB-M",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "text",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "All team members collectively update the latest progress and the next step plan for the person responsible for the OKR. These updates are discussed during the weekly Team OKR meeting."
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "_E43A1XjkR",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "text",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": []
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "lbLc-rsTQ_",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "h1",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "O1: Fourth quarter revenue 1 million "
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "IbLsSTqZWu",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "text",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": []
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "LfH4-lvRy1",
|
||||
"flavour": "affine:database",
|
||||
"props": {
|
||||
"views": [
|
||||
{
|
||||
"id": "BjTEwK-3iR",
|
||||
"name": "table",
|
||||
"mode": "table",
|
||||
"columns": [
|
||||
{
|
||||
"id": "f15PcPjtU8",
|
||||
"width": 246
|
||||
},
|
||||
{
|
||||
"id": "LfH4-lvRy1",
|
||||
"hide": false,
|
||||
"width": 260
|
||||
},
|
||||
{
|
||||
"id": "ftL1qPRgYl",
|
||||
"hide": false,
|
||||
"width": 200
|
||||
},
|
||||
{
|
||||
"id": "3s4GtwNZTH",
|
||||
"hide": false,
|
||||
"width": 108
|
||||
},
|
||||
{
|
||||
"id": "IwZCoBvV9W",
|
||||
"hide": false,
|
||||
"width": 142
|
||||
}
|
||||
],
|
||||
"filter": {
|
||||
"type": "group",
|
||||
"op": "and",
|
||||
"conditions": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"title": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "KR setting & reviewing "
|
||||
}
|
||||
]
|
||||
},
|
||||
"cells": {
|
||||
"C75yOBGfeP": {
|
||||
"ftL1qPRgYl": {
|
||||
"columnId": "ftL1qPRgYl",
|
||||
"value": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "the planning case has been reviewed, see doc for details "
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"IwZCoBvV9W": {
|
||||
"columnId": "IwZCoBvV9W",
|
||||
"value": 85
|
||||
}
|
||||
},
|
||||
"iouBY8Zk33": {
|
||||
"ftL1qPRgYl": {
|
||||
"columnId": "ftL1qPRgYl",
|
||||
"value": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "worning on it "
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"IwZCoBvV9W": {
|
||||
"columnId": "IwZCoBvV9W",
|
||||
"value": 46
|
||||
},
|
||||
"3s4GtwNZTH": {
|
||||
"columnId": "3s4GtwNZTH",
|
||||
"value": "Qp1kGLAbfE"
|
||||
}
|
||||
},
|
||||
"jRFdpU21Jf": {
|
||||
"ftL1qPRgYl": {
|
||||
"columnId": "ftL1qPRgYl",
|
||||
"value": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "waiting "
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"IwZCoBvV9W": {
|
||||
"columnId": "IwZCoBvV9W",
|
||||
"value": 3
|
||||
},
|
||||
"3s4GtwNZTH": {
|
||||
"columnId": "3s4GtwNZTH",
|
||||
"value": "yCF1NgLWhh"
|
||||
}
|
||||
},
|
||||
"CLLfj990JA": {
|
||||
"ftL1qPRgYl": {
|
||||
"columnId": "ftL1qPRgYl",
|
||||
"value": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "The planning case has been reviewed, see doc for details "
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"IwZCoBvV9W": {
|
||||
"columnId": "IwZCoBvV9W",
|
||||
"value": 70
|
||||
},
|
||||
"3s4GtwNZTH": {
|
||||
"columnId": "3s4GtwNZTH",
|
||||
"value": "KHKc4e79W8"
|
||||
}
|
||||
}
|
||||
},
|
||||
"columns": [
|
||||
{
|
||||
"id": "f15PcPjtU8",
|
||||
"type": "title",
|
||||
"name": "Key Resluts ",
|
||||
"data": {}
|
||||
},
|
||||
{
|
||||
"type": "title",
|
||||
"id": "LfH4-lvRy1",
|
||||
"name": "Title",
|
||||
"data": {}
|
||||
},
|
||||
{
|
||||
"type": "progress",
|
||||
"name": "progress bar ",
|
||||
"data": {},
|
||||
"id": "IwZCoBvV9W"
|
||||
},
|
||||
{
|
||||
"type": "rich-text",
|
||||
"name": "Notes ",
|
||||
"data": {
|
||||
"options": []
|
||||
},
|
||||
"id": "ftL1qPRgYl"
|
||||
},
|
||||
{
|
||||
"type": "select",
|
||||
"name": "priority ",
|
||||
"data": {
|
||||
"options": [
|
||||
{
|
||||
"id": "yCF1NgLWhh",
|
||||
"value": "not urgent",
|
||||
"color": "var(--affine-tag-blue)"
|
||||
},
|
||||
{
|
||||
"id": "Qp1kGLAbfE",
|
||||
"value": "urgent",
|
||||
"color": "var(--affine-tag-red)"
|
||||
},
|
||||
{
|
||||
"id": "KHKc4e79W8",
|
||||
"value": "important",
|
||||
"color": "var(--affine-tag-yellow)"
|
||||
}
|
||||
]
|
||||
},
|
||||
"id": "3s4GtwNZTH"
|
||||
}
|
||||
]
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "block",
|
||||
"id": "CLLfj990JA",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "text",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "The campus activity was successfully released, and the exposure was not less than 1 million people"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "iouBY8Zk33",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "text",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "describe Key results "
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "jRFdpU21Jf",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "text",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "KR1: Describe Key results "
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "PmstSRjisx",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "text",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": []
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,330 +0,0 @@
|
||||
{
|
||||
"type": "page",
|
||||
"meta": {
|
||||
"id": "Scod6coKmJJ-waH1-jkiW",
|
||||
"title": "Brief Event planning ",
|
||||
"createDate": 1691634722239,
|
||||
"tags": [
|
||||
"ze07JVwBu4"
|
||||
]
|
||||
},
|
||||
"blocks": {
|
||||
"type": "block",
|
||||
"id": "SMCf2aOH8T",
|
||||
"flavour": "affine:page",
|
||||
"props": {
|
||||
"title": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "Brief Event planning "
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "block",
|
||||
"id": "_07_dOaECY",
|
||||
"flavour": "affine:surface",
|
||||
"props": {
|
||||
"elements": {}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "0n7YghSHPc",
|
||||
"flavour": "affine:note",
|
||||
"props": {
|
||||
"xywh": "[0,0,800,527]",
|
||||
"background": "--affine-background-secondary-color",
|
||||
"index": "a0",
|
||||
"hidden": false,
|
||||
"edgeless": {
|
||||
"style": {
|
||||
"borderRadius": 8,
|
||||
"borderSize": 4,
|
||||
"borderStyle": "solid",
|
||||
"shadowType": "--affine-note-shadow-box"
|
||||
}
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "block",
|
||||
"id": "d6Alacwr32",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "text",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": []
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "9slwdYgqgq",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "text",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "An event brief outline the goals and key elements needed to plan a successful event. This AFFiNE Event Planning template will help you gain alignment and kick off your event planning. "
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "6dH3Amz0rn",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "text",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": []
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "ObsV2gBxUe",
|
||||
"flavour": "affine:database",
|
||||
"props": {
|
||||
"views": [
|
||||
{
|
||||
"id": "sfIqqrnI2q",
|
||||
"name": "table",
|
||||
"mode": "table",
|
||||
"columns": [
|
||||
{
|
||||
"id": "WtNvmw4ls-",
|
||||
"width": 186
|
||||
},
|
||||
{
|
||||
"id": "ObsV2gBxUe",
|
||||
"hide": false,
|
||||
"width": 246
|
||||
},
|
||||
{
|
||||
"id": "FCYCytB4wx",
|
||||
"hide": false,
|
||||
"width": 162
|
||||
},
|
||||
{
|
||||
"id": "mGltZNTJmw",
|
||||
"hide": false,
|
||||
"width": 176
|
||||
},
|
||||
{
|
||||
"id": "ozFBusFd0s",
|
||||
"hide": false,
|
||||
"width": 200
|
||||
}
|
||||
],
|
||||
"filter": {
|
||||
"type": "group",
|
||||
"op": "and",
|
||||
"conditions": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"title": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "Event Planning"
|
||||
}
|
||||
]
|
||||
},
|
||||
"cells": {
|
||||
"pFlIzSXLOj": {
|
||||
"mGltZNTJmw": {
|
||||
"columnId": "mGltZNTJmw",
|
||||
"value": [
|
||||
"bsTnd_L1aE",
|
||||
"oU49ElCHCG"
|
||||
]
|
||||
},
|
||||
"FCYCytB4wx": {
|
||||
"columnId": "FCYCytB4wx",
|
||||
"value": 100
|
||||
},
|
||||
"ozFBusFd0s": {
|
||||
"columnId": "ozFBusFd0s",
|
||||
"value": "https://affine.pro/blog/free-event-planning-templates-for-professsional-2023"
|
||||
}
|
||||
},
|
||||
"4XPamQdWpq": {
|
||||
"mGltZNTJmw": {
|
||||
"columnId": "mGltZNTJmw",
|
||||
"value": [
|
||||
"oU49ElCHCG",
|
||||
"d6riek6K4d"
|
||||
]
|
||||
},
|
||||
"FCYCytB4wx": {
|
||||
"columnId": "FCYCytB4wx",
|
||||
"value": 50
|
||||
}
|
||||
},
|
||||
"3uJL-VNu7K": {
|
||||
"mGltZNTJmw": {
|
||||
"columnId": "mGltZNTJmw",
|
||||
"value": [
|
||||
"bsTnd_L1aE",
|
||||
"z3vOXFIT-T"
|
||||
]
|
||||
},
|
||||
"FCYCytB4wx": {
|
||||
"columnId": "FCYCytB4wx",
|
||||
"value": 8
|
||||
}
|
||||
}
|
||||
},
|
||||
"columns": [
|
||||
{
|
||||
"id": "WtNvmw4ls-",
|
||||
"type": "title",
|
||||
"name": "Title",
|
||||
"data": {}
|
||||
},
|
||||
{
|
||||
"type": "title",
|
||||
"id": "ObsV2gBxUe",
|
||||
"name": "Title",
|
||||
"data": {}
|
||||
},
|
||||
{
|
||||
"type": "multi-select",
|
||||
"name": "Tag",
|
||||
"data": {
|
||||
"options": [
|
||||
{
|
||||
"id": "z3vOXFIT-T",
|
||||
"value": "not important",
|
||||
"color": "var(--affine-tag-teal)"
|
||||
},
|
||||
{
|
||||
"id": "ZbJHxXJK3L",
|
||||
"value": "nit important",
|
||||
"color": "var(--affine-tag-pink)"
|
||||
},
|
||||
{
|
||||
"id": "d6riek6K4d",
|
||||
"value": "important",
|
||||
"color": "var(--affine-tag-yellow)"
|
||||
},
|
||||
{
|
||||
"id": "oU49ElCHCG",
|
||||
"value": "urgent",
|
||||
"color": "var(--affine-tag-purple)"
|
||||
},
|
||||
{
|
||||
"id": "bsTnd_L1aE",
|
||||
"value": "must",
|
||||
"color": "var(--affine-tag-green)"
|
||||
},
|
||||
{
|
||||
"id": "hHjqAauZu6",
|
||||
"value": "urgent",
|
||||
"color": "var(--affine-tag-gray)"
|
||||
}
|
||||
]
|
||||
},
|
||||
"id": "mGltZNTJmw"
|
||||
},
|
||||
{
|
||||
"type": "progress",
|
||||
"name": "progress",
|
||||
"data": {},
|
||||
"id": "FCYCytB4wx"
|
||||
},
|
||||
{
|
||||
"type": "link",
|
||||
"name": "additional link ",
|
||||
"data": {},
|
||||
"id": "ozFBusFd0s"
|
||||
}
|
||||
]
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "block",
|
||||
"id": "pFlIzSXLOj",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "text",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "research"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "4XPamQdWpq",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "text",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "book accommondation"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "3uJL-VNu7K",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "text",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "arrange transportation "
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "xf1m62_pbc",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "text",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": []
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,750 +0,0 @@
|
||||
{
|
||||
"type": "page",
|
||||
"meta": {
|
||||
"id": "b1F2xKjgZ4ISHhXNe1kck",
|
||||
"title": "Annual Performance Review",
|
||||
"createDate": 1691575011078,
|
||||
"tags": [
|
||||
"8qcYPCTK0h"
|
||||
]
|
||||
},
|
||||
"blocks": {
|
||||
"type": "block",
|
||||
"id": "mMBWzZtpvM",
|
||||
"flavour": "affine:page",
|
||||
"props": {
|
||||
"title": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "Annual Performance Review"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "block",
|
||||
"id": "XSMCxudQf4",
|
||||
"flavour": "affine:surface",
|
||||
"props": {
|
||||
"elements": {}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "2xxenQgE1-",
|
||||
"flavour": "affine:note",
|
||||
"props": {
|
||||
"xywh": "[0,0,800,91]",
|
||||
"background": "--affine-background-secondary-color",
|
||||
"index": "a0",
|
||||
"hidden": false,
|
||||
"edgeless": {
|
||||
"style": {
|
||||
"borderRadius": 8,
|
||||
"borderSize": 4,
|
||||
"borderStyle": "solid",
|
||||
"shadowType": "--affine-note-shadow-box"
|
||||
}
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "block",
|
||||
"id": "G7wJk4W9lz",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "text",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": []
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "ca2-VFQV45",
|
||||
"flavour": "affine:note",
|
||||
"props": {
|
||||
"xywh": "[0,0,800,1719]",
|
||||
"background": "--affine-background-secondary-color",
|
||||
"index": "a0",
|
||||
"hidden": false,
|
||||
"edgeless": {
|
||||
"style": {
|
||||
"borderRadius": 8,
|
||||
"borderSize": 4,
|
||||
"borderStyle": "solid",
|
||||
"shadowType": "--affine-note-shadow-box"
|
||||
}
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "block",
|
||||
"id": "viFZDjrBqJ",
|
||||
"flavour": "affine:image",
|
||||
"props": {
|
||||
"caption": "",
|
||||
"sourceId": "/static/gZLmSgmwumNdgf0eIfOSW44emctrLyFUaZapbk8eZ6s=.png",
|
||||
"width": 752,
|
||||
"height": 422.9140625,
|
||||
"index": "a0",
|
||||
"xywh": "[0,0,0,0]",
|
||||
"rotate": 0,
|
||||
"size": -1
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "XPiPjHYnvE",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "text",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "Employee Information:",
|
||||
"attributes": {
|
||||
"underline": true,
|
||||
"bold": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "qpyYi7g_c6",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "text",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "Name:",
|
||||
"attributes": {
|
||||
"bold": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"insert": " "
|
||||
},
|
||||
{
|
||||
"insert": "[Employee Name]",
|
||||
"attributes": {
|
||||
"background": "var(--affine-text-highlight-grey)"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "NpbigpLTNh",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "text",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "Department:",
|
||||
"attributes": {
|
||||
"bold": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"insert": " "
|
||||
},
|
||||
{
|
||||
"insert": "[Department Name]",
|
||||
"attributes": {
|
||||
"background": "var(--affine-text-highlight-grey)"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "e1ax4lVFjW",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "text",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "Job Title:",
|
||||
"attributes": {
|
||||
"bold": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"insert": " "
|
||||
},
|
||||
{
|
||||
"insert": "[Job Title]",
|
||||
"attributes": {
|
||||
"background": "var(--affine-text-highlight-grey)"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "rEKXjYtGDR",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "text",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "Date of Joining:",
|
||||
"attributes": {
|
||||
"bold": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"insert": " "
|
||||
},
|
||||
{
|
||||
"insert": "[Date of Joining]",
|
||||
"attributes": {
|
||||
"background": "var(--affine-text-highlight-grey)"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "gYcGYxQ88e",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "text",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "Review Date: ",
|
||||
"attributes": {
|
||||
"bold": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"insert": " "
|
||||
},
|
||||
{
|
||||
"insert": "[Review Date]",
|
||||
"attributes": {
|
||||
"background": "var(--affine-text-highlight-grey)"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "OyFSyuWbzw",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "text",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": []
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "VcUt-bpjIK",
|
||||
"flavour": "affine:database",
|
||||
"props": {
|
||||
"views": [
|
||||
{
|
||||
"id": "bIFdicaEON",
|
||||
"name": "table",
|
||||
"mode": "table",
|
||||
"columns": [
|
||||
{
|
||||
"id": "jPry_IOhQt",
|
||||
"width": 343
|
||||
},
|
||||
{
|
||||
"id": "VcUt-bpjIK",
|
||||
"hide": false,
|
||||
"width": 260
|
||||
},
|
||||
{
|
||||
"id": "teyC7JgUni",
|
||||
"hide": false,
|
||||
"width": 200
|
||||
}
|
||||
],
|
||||
"filter": {
|
||||
"type": "group",
|
||||
"op": "and",
|
||||
"conditions": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"title": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "Employee's performance "
|
||||
}
|
||||
]
|
||||
},
|
||||
"cells": {
|
||||
"VwZ3681tdd": {
|
||||
"teyC7JgUni": {
|
||||
"columnId": "teyC7JgUni",
|
||||
"value": [
|
||||
"rwjdYVF3jd"
|
||||
]
|
||||
}
|
||||
},
|
||||
"jT3Oo2XSeJ": {
|
||||
"teyC7JgUni": {
|
||||
"columnId": "teyC7JgUni",
|
||||
"value": [
|
||||
"4dHxaZ8cKk"
|
||||
]
|
||||
}
|
||||
},
|
||||
"RviIfLJUPX": {
|
||||
"teyC7JgUni": {
|
||||
"columnId": "teyC7JgUni",
|
||||
"value": [
|
||||
"wgvB2fzMu6"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"columns": [
|
||||
{
|
||||
"id": "jPry_IOhQt",
|
||||
"type": "title",
|
||||
"name": "Title",
|
||||
"data": {}
|
||||
},
|
||||
{
|
||||
"type": "title",
|
||||
"id": "VcUt-bpjIK",
|
||||
"name": "Title",
|
||||
"data": {}
|
||||
},
|
||||
{
|
||||
"type": "multi-select",
|
||||
"name": "score ",
|
||||
"data": {
|
||||
"options": [
|
||||
{
|
||||
"id": "rwjdYVF3jd",
|
||||
"value": "awsome",
|
||||
"color": "var(--affine-tag-yellow)"
|
||||
},
|
||||
{
|
||||
"id": "4dHxaZ8cKk",
|
||||
"value": "well",
|
||||
"color": "var(--affine-tag-purple)"
|
||||
},
|
||||
{
|
||||
"id": "wgvB2fzMu6",
|
||||
"value": "good",
|
||||
"color": "var(--affine-tag-green)"
|
||||
},
|
||||
{
|
||||
"id": "YGPLhMJkni",
|
||||
"value": "done",
|
||||
"color": "var(--affine-tag-pink)"
|
||||
},
|
||||
{
|
||||
"id": "Q1-TuffR-9",
|
||||
"value": "in progress",
|
||||
"color": "var(--affine-tag-white)"
|
||||
}
|
||||
]
|
||||
},
|
||||
"id": "teyC7JgUni"
|
||||
}
|
||||
]
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "block",
|
||||
"id": "VwZ3681tdd",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "text",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "[List Employee's Performance Goal 1]"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "jT3Oo2XSeJ",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "text",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "[List Employee's Performance Goal 2]"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "RviIfLJUPX",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "text",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "[List Employee's Performance Goal 3]"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "a0xWifxvFi",
|
||||
"flavour": "affine:database",
|
||||
"props": {
|
||||
"views": [
|
||||
{
|
||||
"id": "MvodpC_iAj",
|
||||
"name": "table",
|
||||
"mode": "table",
|
||||
"columns": [
|
||||
{
|
||||
"id": "lvyHgZEelb",
|
||||
"width": 354
|
||||
},
|
||||
{
|
||||
"id": "a0xWifxvFi",
|
||||
"hide": false,
|
||||
"width": 260
|
||||
},
|
||||
{
|
||||
"id": "ZdTyvkjQwz",
|
||||
"hide": false,
|
||||
"width": 200
|
||||
}
|
||||
],
|
||||
"filter": {
|
||||
"type": "group",
|
||||
"op": "and",
|
||||
"conditions": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"title": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "Job responsibility "
|
||||
}
|
||||
]
|
||||
},
|
||||
"cells": {},
|
||||
"columns": [
|
||||
{
|
||||
"id": "lvyHgZEelb",
|
||||
"type": "title",
|
||||
"name": "Title",
|
||||
"data": {}
|
||||
},
|
||||
{
|
||||
"type": "title",
|
||||
"id": "a0xWifxvFi",
|
||||
"name": "Title",
|
||||
"data": {}
|
||||
},
|
||||
{
|
||||
"type": "multi-select",
|
||||
"name": "Tag",
|
||||
"data": {
|
||||
"options": []
|
||||
},
|
||||
"id": "ZdTyvkjQwz"
|
||||
}
|
||||
]
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "block",
|
||||
"id": "ZEi_-9SS-Y",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "text",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "[List Job Responsibility 1]"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "8LPVaagwtP",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "text",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "[List Job Responsibility 2]"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "lYnANRxW0A",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "text",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "[List Job Responsibility 3]"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "YiQINiDM6W",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "text",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "Employee's Signature: "
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "Zp-YtzbUUK",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "text",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "Date: "
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "ZMfsmg_5oZ",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "text",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": []
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "XH7xQMlX_0",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "text",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "Manager's Signature: "
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "mBO-_pWGoi",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "text",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "Date: "
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "X_SNf6rQf4",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "text",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": []
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "0LF9mjFcX9",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "text",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "[Company Name]",
|
||||
"attributes": {
|
||||
"background": "var(--affine-text-highlight-grey)"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "d2IrxHbD5D",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "text",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "[Address]",
|
||||
"attributes": {
|
||||
"background": "var(--affine-text-highlight-grey)"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "IdLESwrZ-U",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "text",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "[Phone Number]",
|
||||
"attributes": {
|
||||
"background": "var(--affine-text-highlight-grey)"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "6YwnA2HHQM",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "text",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "[Email]",
|
||||
"attributes": {
|
||||
"background": "var(--affine-text-highlight-grey)"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
5
packages/frontend/templates/onboarding/blob.json
Normal file
5
packages/frontend/templates/onboarding/blob.json
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@@ -1,175 +1,21 @@
|
||||
{
|
||||
"type": "info",
|
||||
"id": "XdwdkEYm8fFZcYAdDa0qT",
|
||||
"blockVersions": {
|
||||
"affine:code": 1,
|
||||
"affine:paragraph": 1,
|
||||
"affine:page": 2,
|
||||
"affine:list": 1,
|
||||
"affine:note": 1,
|
||||
"affine:divider": 1,
|
||||
"affine:image": 1,
|
||||
"affine:surface": 5,
|
||||
"affine:bookmark": 1,
|
||||
"affine:frame": 1,
|
||||
"affine:database": 3,
|
||||
"affine:surface-ref": 1,
|
||||
"affine:data-view": 1,
|
||||
"affine:attachment": 1,
|
||||
"affine:embed-github": 1,
|
||||
"affine:embed-html": 1
|
||||
},
|
||||
"id": "dc61f2e3-a973-432f-a463-164a15cfc778",
|
||||
"pageVersion": 2,
|
||||
"workspaceVersion": 2,
|
||||
"properties": {
|
||||
"tags": {
|
||||
"options": [
|
||||
{
|
||||
"id": "icg1n5UdkP",
|
||||
"value": "Travel",
|
||||
"color": "var(--affine-tag-gray)"
|
||||
},
|
||||
{
|
||||
"id": "Oe5dSe1DDJ",
|
||||
"value": "Quick summary",
|
||||
"color": "var(--affine-tag-green)"
|
||||
},
|
||||
{
|
||||
"id": "g1L5dXKctL",
|
||||
"value": "OKR",
|
||||
"color": "var(--affine-tag-purple)"
|
||||
},
|
||||
{
|
||||
"id": "q3mceOl_zi",
|
||||
"value": "Streamline your workflow",
|
||||
"color": "var(--affine-tag-teal)"
|
||||
},
|
||||
{
|
||||
"id": "ze07JVwBu4",
|
||||
"value": "Plan",
|
||||
"color": "var(--affine-tag-teal)"
|
||||
},
|
||||
{
|
||||
"id": "8qcYPCTK0h",
|
||||
"value": "Review",
|
||||
"color": "var(--affine-tag-orange)"
|
||||
},
|
||||
{
|
||||
"id": "wg-fBtd2eI",
|
||||
"value": "Engage",
|
||||
"color": "var(--affine-tag-pink)"
|
||||
},
|
||||
{
|
||||
"id": "QYFD_HeQc-",
|
||||
"value": "Create",
|
||||
"color": "var(--affine-tag-blue)"
|
||||
},
|
||||
{
|
||||
"id": "ZHBa2NtdSo",
|
||||
"value": "Learn",
|
||||
"color": "var(--affine-tag-yellow)"
|
||||
}
|
||||
]
|
||||
"options": []
|
||||
}
|
||||
},
|
||||
"pages": [
|
||||
{
|
||||
"id": "rzyBHDgN5KIlEYzB9oBaD",
|
||||
"title": "Getting Started👇",
|
||||
"createDate": 1691548231530,
|
||||
"tags": [
|
||||
"ZHBa2NtdSo",
|
||||
"QYFD_HeQc-",
|
||||
"wg-fBtd2eI"
|
||||
],
|
||||
"updatedDate": 1691676331623,
|
||||
"favorite": true,
|
||||
"jumpOnce": false
|
||||
},
|
||||
{
|
||||
"id": "9iIScyvuIB_kUKbs1AYOQ",
|
||||
"title": "AFFiNE - not just a note-taking app",
|
||||
"createDate": 1691548220794,
|
||||
"id": "W-d9_llZ6rE-qoTiHKTk4",
|
||||
"createDate": 1706862386590,
|
||||
"tags": [],
|
||||
"updatedDate": 1691676775642,
|
||||
"favorite": false
|
||||
},
|
||||
{
|
||||
"id": "-P-O4GSfVGTkI16zJRVa4",
|
||||
"title": "Templates Galleries ",
|
||||
"createDate": 1691551731225,
|
||||
"tags": [],
|
||||
"updatedDate": 1691654611175,
|
||||
"favorite": false
|
||||
},
|
||||
{
|
||||
"id": "0nee_XzkrN23Xy7HMoXL-",
|
||||
"title": "Personal Home",
|
||||
"createDate": 1691552082822,
|
||||
"tags": [],
|
||||
"updatedDate": 1691654606912,
|
||||
"favorite": false
|
||||
},
|
||||
{
|
||||
"id": "nPPyOV0JBNKu5hvW9hE00",
|
||||
"title": "Working Home",
|
||||
"createDate": 1691552090989,
|
||||
"tags": [],
|
||||
"updatedDate": 1691646748171,
|
||||
"favorite": false
|
||||
},
|
||||
{
|
||||
"id": "wbXL4bZcblxLKC6ETqcQ1",
|
||||
"title": "AFFiNE's Personal project management",
|
||||
"createDate": 1691564303138,
|
||||
"tags": [],
|
||||
"updatedDate": 1691646845195
|
||||
},
|
||||
{
|
||||
"id": "e5cYLNpIRUb-nwpSZGdix",
|
||||
"title": "Personal-Knowledge Management ",
|
||||
"createDate": 1691574859042,
|
||||
"tags": [],
|
||||
"updatedDate": 1691648159371
|
||||
},
|
||||
{
|
||||
"id": "b1F2xKjgZ4ISHhXNe1kck",
|
||||
"title": "Annual Performance Review",
|
||||
"createDate": 1691575011078,
|
||||
"tags": [
|
||||
"8qcYPCTK0h"
|
||||
],
|
||||
"updatedDate": 1691645074511,
|
||||
"favorite": false
|
||||
},
|
||||
{
|
||||
"id": "Scod6coKmJJ-waH1-jkiW",
|
||||
"title": "Brief Event planning ",
|
||||
"createDate": 1691634722239,
|
||||
"tags": [
|
||||
"ze07JVwBu4"
|
||||
],
|
||||
"updatedDate": 1691647069662,
|
||||
"favorite": false
|
||||
},
|
||||
{
|
||||
"id": "GWsRbUtMF4Ee6foVg-H9R",
|
||||
"title": "Meeting Summary ",
|
||||
"createDate": 1691635388447,
|
||||
"tags": [
|
||||
"Oe5dSe1DDJ"
|
||||
],
|
||||
"updatedDate": 1691645873930
|
||||
},
|
||||
{
|
||||
"id": "RCpxnWMtBWmUZy5awgJBh",
|
||||
"title": "OKR Template",
|
||||
"createDate": 1691636192263,
|
||||
"tags": [
|
||||
"q3mceOl_zi",
|
||||
"g1L5dXKctL"
|
||||
],
|
||||
"updatedDate": 1691645102104
|
||||
"favorite": false,
|
||||
"title": "Write, Draw, Plan all at Once.",
|
||||
"updatedDate": 1709110332309
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,355 +0,0 @@
|
||||
{
|
||||
"type": "page",
|
||||
"meta": {
|
||||
"id": "nPPyOV0JBNKu5hvW9hE00",
|
||||
"title": "Working Home",
|
||||
"createDate": 1691552090989,
|
||||
"tags": []
|
||||
},
|
||||
"blocks": {
|
||||
"type": "block",
|
||||
"id": "qsMt8nCetT",
|
||||
"flavour": "affine:page",
|
||||
"props": {
|
||||
"title": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "Working Home"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "block",
|
||||
"id": "yC-F6Rj9bA",
|
||||
"flavour": "affine:surface",
|
||||
"props": {
|
||||
"elements": {}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "pk_Cjkpyd4",
|
||||
"flavour": "affine:note",
|
||||
"props": {
|
||||
"xywh": "[0,0,800,939]",
|
||||
"background": "--affine-background-secondary-color",
|
||||
"index": "a0",
|
||||
"hidden": false,
|
||||
"edgeless": {
|
||||
"style": {
|
||||
"borderRadius": 8,
|
||||
"borderSize": 4,
|
||||
"borderStyle": "solid",
|
||||
"shadowType": "--affine-note-shadow-box"
|
||||
}
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "block",
|
||||
"id": "IAOXx-DBBz",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "text",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": []
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "5hR-BR03ms",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "text",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": " ",
|
||||
"attributes": {
|
||||
"reference": {
|
||||
"type": "LinkedPage",
|
||||
"pageId": "b1F2xKjgZ4ISHhXNe1kck"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "zVgCQKWH-c",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "quote",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "This "
|
||||
},
|
||||
{
|
||||
"insert": "Annual Performance Review template",
|
||||
"attributes": {
|
||||
"bold": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"insert": " is ideal for businesses seeking a structured and comprehensive approach to evaluating employee performance. By utilizing this template, you will gain clarity on individual strengths and areas for improvement, fostering effective communication and driving professional growth within your organization."
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "ym2g48zSU_",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "text",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": []
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "o1UyrWV30t",
|
||||
"flavour": "affine:divider",
|
||||
"props": {},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "1EUiH1t60b",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "text",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": " ",
|
||||
"attributes": {
|
||||
"reference": {
|
||||
"type": "LinkedPage",
|
||||
"pageId": "GWsRbUtMF4Ee6foVg-H9R"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "HmzMXKFaci",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "quote",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "Meeting minutes are an official record of a meeting",
|
||||
"attributes": {
|
||||
"bold": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"insert": " for its participants. They're also sources of information for teammates who were unable to attend."
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "v5CGQQPDrt",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "text",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": []
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "lcf4te4rxB",
|
||||
"flavour": "affine:divider",
|
||||
"props": {},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "AbN8iFn0RW",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "text",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": []
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "uXdWxSSWgl",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "text",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": " ",
|
||||
"attributes": {
|
||||
"reference": {
|
||||
"type": "LinkedPage",
|
||||
"pageId": "RCpxnWMtBWmUZy5awgJBh"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "TBjwPij8cJ",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "quote",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "Maximize your team's performance and drive goal-oriented success with our extensive collection of "
|
||||
},
|
||||
{
|
||||
"insert": "OKR templates",
|
||||
"attributes": {
|
||||
"bold": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"insert": ", specifically crafted to facilitate effective objective setting, key result tracking, and overall performance improvement."
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "mjgDUguqmV",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "text",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": []
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "c_gX1ik0kK",
|
||||
"flavour": "affine:divider",
|
||||
"props": {},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "IU3gGmIffA",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "text",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": []
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "LnERZXy0xj",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "text",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "🔔Reminder: Click the "
|
||||
},
|
||||
{
|
||||
"insert": "+New page ",
|
||||
"attributes": {
|
||||
"bold": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"insert": "to start your journey in AFFiNE "
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "oi0BxPtiit",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "text",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "✏Unleash your creativity and elevate your work efficiency to new heights by customizing templates that perfectly align with your unique needs and objectives."
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "DuRxwNJe2w",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "text",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": []
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,419 +0,0 @@
|
||||
{
|
||||
"type": "page",
|
||||
"meta": {
|
||||
"id": "rzyBHDgN5KIlEYzB9oBaD",
|
||||
"title": "Getting Started👇",
|
||||
"createDate": 1691548231530,
|
||||
"tags": [
|
||||
"ZHBa2NtdSo",
|
||||
"QYFD_HeQc-",
|
||||
"wg-fBtd2eI"
|
||||
]
|
||||
},
|
||||
"blocks": {
|
||||
"type": "block",
|
||||
"id": "1FO1aYcosq",
|
||||
"flavour": "affine:page",
|
||||
"props": {
|
||||
"title": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "Getting Started👇"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "block",
|
||||
"id": "f7o2Osxfa_",
|
||||
"flavour": "affine:surface",
|
||||
"props": {
|
||||
"elements": {}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "NDAzEfCZnv",
|
||||
"flavour": "affine:note",
|
||||
"props": {
|
||||
"xywh": "[-12.5684605441112,-52.45959387999099,800,582]",
|
||||
"background": "--affine-background-secondary-color",
|
||||
"index": "a0",
|
||||
"hidden": false,
|
||||
"edgeless": {
|
||||
"style": {
|
||||
"borderRadius": 8,
|
||||
"borderSize": 4,
|
||||
"borderStyle": "solid",
|
||||
"shadowType": "--affine-note-shadow-box"
|
||||
}
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "block",
|
||||
"id": "jArWNRgiLB",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "text",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "Basic things you should know: "
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "TUx2T-R2zT",
|
||||
"flavour": "affine:list",
|
||||
"props": {
|
||||
"type": "todo",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "Drag "
|
||||
},
|
||||
{
|
||||
"insert": "blocks ",
|
||||
"attributes": {
|
||||
"bold": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"insert": "(put a symbol) at the bottom right of the page to insert a new block, list, database, etc."
|
||||
}
|
||||
]
|
||||
},
|
||||
"checked": false,
|
||||
"collapsed": false
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "block",
|
||||
"id": "kyqLH3oNHK",
|
||||
"flavour": "affine:list",
|
||||
"props": {
|
||||
"type": "todo",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "Hit \""
|
||||
},
|
||||
{
|
||||
"insert": "/",
|
||||
"attributes": {
|
||||
"bold": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"insert": " \"to see all the types of content you can add - headings, videos, link pages, etc."
|
||||
}
|
||||
]
|
||||
},
|
||||
"checked": false,
|
||||
"collapsed": false
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "kyQSXZ39ce",
|
||||
"flavour": "affine:list",
|
||||
"props": {
|
||||
"type": "todo",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "Edit any text, and "
|
||||
},
|
||||
{
|
||||
"insert": "stylish",
|
||||
"attributes": {
|
||||
"bold": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"insert": " "
|
||||
},
|
||||
{
|
||||
"insert": "your",
|
||||
"attributes": {
|
||||
"italic": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"insert": " "
|
||||
},
|
||||
{
|
||||
"insert": "writing",
|
||||
"attributes": {
|
||||
"underline": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"insert": " freely."
|
||||
}
|
||||
]
|
||||
},
|
||||
"checked": false,
|
||||
"collapsed": false
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "tKK8W8BwHQ",
|
||||
"flavour": "affine:list",
|
||||
"props": {
|
||||
"type": "todo",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "Follow "
|
||||
},
|
||||
{
|
||||
"insert": " ",
|
||||
"attributes": {
|
||||
"reference": {
|
||||
"type": "LinkedPage",
|
||||
"pageId": "9iIScyvuIB_kUKbs1AYOQ"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"insert": "knowing how to "
|
||||
},
|
||||
{
|
||||
"insert": "write, draw, and plan all at once",
|
||||
"attributes": {
|
||||
"bold": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"insert": "."
|
||||
}
|
||||
]
|
||||
},
|
||||
"checked": false,
|
||||
"collapsed": false
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "block",
|
||||
"id": "APwWQJEqlF",
|
||||
"flavour": "affine:list",
|
||||
"props": {
|
||||
"type": "todo",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "Not sure where to start with? Refer to"
|
||||
},
|
||||
{
|
||||
"insert": " ",
|
||||
"attributes": {
|
||||
"reference": {
|
||||
"type": "LinkedPage",
|
||||
"pageId": "-P-O4GSfVGTkI16zJRVa4"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"insert": "in your sidebar to get started with pre-built pages."
|
||||
}
|
||||
]
|
||||
},
|
||||
"checked": false,
|
||||
"collapsed": false
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "q4UGzfn08o",
|
||||
"flavour": "affine:list",
|
||||
"props": {
|
||||
"type": "todo",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "Click the drop-down menu "
|
||||
},
|
||||
{
|
||||
"insert": "\"v\" next to the title for more advanced options",
|
||||
"attributes": {
|
||||
"bold": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"insert": ", such as the ability to one-click export your work into PDF/HTML/Markdown formats."
|
||||
}
|
||||
]
|
||||
},
|
||||
"checked": false,
|
||||
"collapsed": false
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "yDnnnpnCFh",
|
||||
"flavour": "affine:list",
|
||||
"props": {
|
||||
"type": "todo",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "👉 Encounter"
|
||||
},
|
||||
{
|
||||
"insert": " ",
|
||||
"attributes": {
|
||||
"bold": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"insert": "a question? Click the \""
|
||||
},
|
||||
{
|
||||
"insert": "?\" Button ",
|
||||
"attributes": {
|
||||
"bold": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"insert": "at the bottom right for more guides, or directly send us a message."
|
||||
}
|
||||
]
|
||||
},
|
||||
"checked": false,
|
||||
"collapsed": false
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "Uq0yIH932N",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "h6",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "🎉 Congratulations! You already go through all of this list!"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "Ag86EJnVv0",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "text",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "Click the"
|
||||
},
|
||||
{
|
||||
"insert": " ",
|
||||
"attributes": {
|
||||
"background": "var(--affine-text-highlight-grey)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"insert": "+ New Page",
|
||||
"attributes": {
|
||||
"background": "var(--affine-text-highlight-grey)",
|
||||
"bold": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"insert": " ",
|
||||
"attributes": {
|
||||
"background": "var(--affine-text-highlight-grey)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"insert": "button at the bottom of your sidebar to start your journey in AFFiNE"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "iQUJlC0oQT",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "text",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": []
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "wtxc_a6qxN",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "text",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "⏰ Kindly Reminder: This page is automatically set up in the"
|
||||
},
|
||||
{
|
||||
"insert": " ",
|
||||
"attributes": {
|
||||
"background": "var(--affine-text-highlight-grey)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"insert": "+New Workspace",
|
||||
"attributes": {
|
||||
"background": "var(--affine-text-highlight-grey)",
|
||||
"bold": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"insert": ". If you ever find yourself unsure about how to use AFFiNE, simply click on the workspace avatar and select \"Add a New Workspace\" to revisit this page and get the guidance you need."
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,191 +0,0 @@
|
||||
{
|
||||
"type": "page",
|
||||
"meta": {
|
||||
"id": "wbXL4bZcblxLKC6ETqcQ1",
|
||||
"title": "AFFiNE's Personal project management",
|
||||
"createDate": 1691564303138,
|
||||
"tags": []
|
||||
},
|
||||
"blocks": {
|
||||
"type": "block",
|
||||
"id": "LvRIWQ1EAT",
|
||||
"flavour": "affine:page",
|
||||
"props": {
|
||||
"title": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "AFFiNE's Personal project management"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "block",
|
||||
"id": "9eBKQxt8ax",
|
||||
"flavour": "affine:surface",
|
||||
"props": {
|
||||
"elements": {}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "2nHeyqzDMf",
|
||||
"flavour": "affine:note",
|
||||
"props": {
|
||||
"xywh": "[0,0,800,361]",
|
||||
"background": "--affine-background-secondary-color",
|
||||
"index": "a0",
|
||||
"hidden": false,
|
||||
"edgeless": {
|
||||
"style": {
|
||||
"borderRadius": 8,
|
||||
"borderSize": 4,
|
||||
"borderStyle": "solid",
|
||||
"shadowType": "--affine-note-shadow-box"
|
||||
}
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "block",
|
||||
"id": "g0M-Toj8iK",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "text",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "To-do-list "
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "JpcfJa6HqU",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "text",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "/"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "w9Oyl2J6bd",
|
||||
"flavour": "affine:database",
|
||||
"props": {
|
||||
"views": [
|
||||
{
|
||||
"id": "zp1yuVbPHH",
|
||||
"name": "table",
|
||||
"mode": "table",
|
||||
"columns": [
|
||||
{
|
||||
"id": "46SguLuWz3",
|
||||
"width": 185
|
||||
},
|
||||
{
|
||||
"id": "uSHvJhR2FN",
|
||||
"width": 200
|
||||
}
|
||||
],
|
||||
"filter": {
|
||||
"type": "group",
|
||||
"op": "and",
|
||||
"conditions": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"title": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": [
|
||||
{
|
||||
"insert": "Database"
|
||||
}
|
||||
]
|
||||
},
|
||||
"cells": {},
|
||||
"columns": [
|
||||
{
|
||||
"id": "46SguLuWz3",
|
||||
"type": "title",
|
||||
"name": "Title",
|
||||
"data": {}
|
||||
},
|
||||
{
|
||||
"type": "title",
|
||||
"id": "w9Oyl2J6bd",
|
||||
"name": "Title",
|
||||
"data": {}
|
||||
},
|
||||
{
|
||||
"type": "multi-select",
|
||||
"name": "Tag",
|
||||
"data": {
|
||||
"options": []
|
||||
},
|
||||
"id": "uSHvJhR2FN"
|
||||
}
|
||||
]
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "block",
|
||||
"id": "yP88q8kMa_",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "text",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": []
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "xo7ok9TmO7",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "text",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": []
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"id": "dOQULUJfK6",
|
||||
"flavour": "affine:paragraph",
|
||||
"props": {
|
||||
"type": "text",
|
||||
"text": {
|
||||
"$blocksuite:internal:text$": true,
|
||||
"delta": []
|
||||
}
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,29 +1,11 @@
|
||||
/* eslint-disable simple-import-sort/imports */
|
||||
// Auto generated, do not edit manually
|
||||
import json_0 from './onboarding/wbXL4bZcblxLKC6ETqcQ1.snapshot.json';
|
||||
import json_1 from './onboarding/rzyBHDgN5KIlEYzB9oBaD.snapshot.json';
|
||||
import json_2 from './onboarding/nPPyOV0JBNKu5hvW9hE00.snapshot.json';
|
||||
import json_3 from './onboarding/info.json';
|
||||
import json_4 from './onboarding/e5cYLNpIRUb-nwpSZGdix.snapshot.json';
|
||||
import json_5 from './onboarding/b1F2xKjgZ4ISHhXNe1kck.snapshot.json';
|
||||
import json_6 from './onboarding/Scod6coKmJJ-waH1-jkiW.snapshot.json';
|
||||
import json_7 from './onboarding/RCpxnWMtBWmUZy5awgJBh.snapshot.json';
|
||||
import json_8 from './onboarding/GWsRbUtMF4Ee6foVg-H9R.snapshot.json';
|
||||
import json_9 from './onboarding/9iIScyvuIB_kUKbs1AYOQ.snapshot.json';
|
||||
import json_10 from './onboarding/0nee_XzkrN23Xy7HMoXL-.snapshot.json';
|
||||
import json_11 from './onboarding/-P-O4GSfVGTkI16zJRVa4.snapshot.json';
|
||||
import json_0 from './onboarding/info.json';
|
||||
import json_1 from './onboarding/blob.json';
|
||||
import json_2 from './onboarding/W-d9_llZ6rE-qoTiHKTk4.snapshot.json';
|
||||
|
||||
export const onboarding = {
|
||||
'wbXL4bZcblxLKC6ETqcQ1.snapshot.json': json_0,
|
||||
'rzyBHDgN5KIlEYzB9oBaD.snapshot.json': json_1,
|
||||
'nPPyOV0JBNKu5hvW9hE00.snapshot.json': json_2,
|
||||
'info.json': json_3,
|
||||
'e5cYLNpIRUb-nwpSZGdix.snapshot.json': json_4,
|
||||
'b1F2xKjgZ4ISHhXNe1kck.snapshot.json': json_5,
|
||||
'Scod6coKmJJ-waH1-jkiW.snapshot.json': json_6,
|
||||
'RCpxnWMtBWmUZy5awgJBh.snapshot.json': json_7,
|
||||
'GWsRbUtMF4Ee6foVg-H9R.snapshot.json': json_8,
|
||||
'9iIScyvuIB_kUKbs1AYOQ.snapshot.json': json_9,
|
||||
'0nee_XzkrN23Xy7HMoXL-.snapshot.json': json_10,
|
||||
'-P-O4GSfVGTkI16zJRVa4.snapshot.json': json_11
|
||||
'info.json': json_0,
|
||||
'blob.json': json_1,
|
||||
'W-d9_llZ6rE-qoTiHKTk4.snapshot.json': json_2
|
||||
}
|
||||
@@ -19,6 +19,13 @@ export function createSQLiteStorage(workspaceId: string): SyncStorage {
|
||||
);
|
||||
|
||||
if (update) {
|
||||
if (
|
||||
update.byteLength === 0 ||
|
||||
(update.byteLength === 2 && update[0] === 0 && update[1] === 0)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
data: update,
|
||||
state: encodeStateVectorFromUpdate(update),
|
||||
|
||||
@@ -50,13 +50,12 @@ const config: PlaywrightTestConfig = {
|
||||
DEBUG: 'affine:*',
|
||||
FORCE_COLOR: 'true',
|
||||
DEBUG_COLORS: 'true',
|
||||
ENABLE_LOCAL_EMAIL: process.env.ENABLE_LOCAL_EMAIL ?? 'true',
|
||||
NEXTAUTH_URL: 'http://localhost:8080',
|
||||
OAUTH_EMAIL_SENDER: 'noreply@toeverything.info',
|
||||
OAUTH_EMAIL_LOGIN: 'noreply@toeverything.info',
|
||||
OAUTH_EMAIL_PASSWORD: 'affine',
|
||||
STRIPE_API_KEY: '1',
|
||||
STRIPE_WEBHOOK_KEY: '1',
|
||||
MAILER_HOST: '0.0.0.0',
|
||||
MAILER_PORT: '1025',
|
||||
MAILER_SENDER: 'noreply@toeverything.info',
|
||||
MAILER_USER: 'noreply@toeverything.info',
|
||||
MAILER_PASSWORD: 'affine',
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
@@ -45,9 +45,8 @@ const config: PlaywrightTestConfig = {
|
||||
DEBUG: 'affine:*',
|
||||
FORCE_COLOR: 'true',
|
||||
DEBUG_COLORS: 'true',
|
||||
ENABLE_LOCAL_EMAIL: process.env.ENABLE_LOCAL_EMAIL ?? 'true',
|
||||
NEXTAUTH_URL: 'http://localhost:8080',
|
||||
OAUTH_EMAIL_SENDER: 'noreply@toeverything.info',
|
||||
MAILER_SENDER: 'noreply@toeverything.info',
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
48
yarn.lock
48
yarn.lock
@@ -709,8 +709,8 @@ __metadata:
|
||||
socket.io: "npm:^4.7.2"
|
||||
stripe: "npm:^14.5.0"
|
||||
supertest: "npm:^6.3.3"
|
||||
ts-node: "npm:^10.9.1"
|
||||
typescript: "npm:^5.3.2"
|
||||
ts-node: "npm:^10.9.2"
|
||||
typescript: "npm:^5.3.3"
|
||||
ws: "npm:^8.14.2"
|
||||
yjs: "npm:^13.6.10"
|
||||
zod: "npm:^3.22.4"
|
||||
@@ -34173,7 +34173,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ts-node@npm:10.9.1, ts-node@npm:^10.9.1":
|
||||
"ts-node@npm:10.9.1":
|
||||
version: 10.9.1
|
||||
resolution: "ts-node@npm:10.9.1"
|
||||
dependencies:
|
||||
@@ -34211,6 +34211,44 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ts-node@npm:^10.9.1, ts-node@npm:^10.9.2":
|
||||
version: 10.9.2
|
||||
resolution: "ts-node@npm:10.9.2"
|
||||
dependencies:
|
||||
"@cspotcode/source-map-support": "npm:^0.8.0"
|
||||
"@tsconfig/node10": "npm:^1.0.7"
|
||||
"@tsconfig/node12": "npm:^1.0.7"
|
||||
"@tsconfig/node14": "npm:^1.0.0"
|
||||
"@tsconfig/node16": "npm:^1.0.2"
|
||||
acorn: "npm:^8.4.1"
|
||||
acorn-walk: "npm:^8.1.1"
|
||||
arg: "npm:^4.1.0"
|
||||
create-require: "npm:^1.1.0"
|
||||
diff: "npm:^4.0.1"
|
||||
make-error: "npm:^1.1.1"
|
||||
v8-compile-cache-lib: "npm:^3.0.1"
|
||||
yn: "npm:3.1.1"
|
||||
peerDependencies:
|
||||
"@swc/core": ">=1.2.50"
|
||||
"@swc/wasm": ">=1.2.50"
|
||||
"@types/node": "*"
|
||||
typescript: ">=2.7"
|
||||
peerDependenciesMeta:
|
||||
"@swc/core":
|
||||
optional: true
|
||||
"@swc/wasm":
|
||||
optional: true
|
||||
bin:
|
||||
ts-node: dist/bin.js
|
||||
ts-node-cwd: dist/bin-cwd.js
|
||||
ts-node-esm: dist/bin-esm.js
|
||||
ts-node-script: dist/bin-script.js
|
||||
ts-node-transpile-only: dist/bin-transpile.js
|
||||
ts-script: dist/bin-script-deprecated.js
|
||||
checksum: a91a15b3c9f76ac462f006fa88b6bfa528130dcfb849dd7ef7f9d640832ab681e235b8a2bc58ecde42f72851cc1d5d4e22c901b0c11aa51001ea1d395074b794
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tsconfck@npm:^2.1.0":
|
||||
version: 2.1.2
|
||||
resolution: "tsconfck@npm:2.1.2"
|
||||
@@ -34392,7 +34430,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"typescript@npm:5.3.3, typescript@npm:^5.2.2, typescript@npm:^5.3.2":
|
||||
"typescript@npm:5.3.3, typescript@npm:^5.2.2, typescript@npm:^5.3.2, typescript@npm:^5.3.3":
|
||||
version: 5.3.3
|
||||
resolution: "typescript@npm:5.3.3"
|
||||
bin:
|
||||
@@ -34402,7 +34440,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"typescript@patch:typescript@npm%3A5.3.3#optional!builtin<compat/typescript>, typescript@patch:typescript@npm%3A^5.2.2#optional!builtin<compat/typescript>, typescript@patch:typescript@npm%3A^5.3.2#optional!builtin<compat/typescript>":
|
||||
"typescript@patch:typescript@npm%3A5.3.3#optional!builtin<compat/typescript>, typescript@patch:typescript@npm%3A^5.2.2#optional!builtin<compat/typescript>, typescript@patch:typescript@npm%3A^5.3.2#optional!builtin<compat/typescript>, typescript@patch:typescript@npm%3A^5.3.3#optional!builtin<compat/typescript>":
|
||||
version: 5.3.3
|
||||
resolution: "typescript@patch:typescript@npm%3A5.3.3#optional!builtin<compat/typescript>::version=5.3.3&hash=e012d7"
|
||||
bin:
|
||||
|
||||
Reference in New Issue
Block a user