mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-04 16:44:56 +00:00
Compare commits
2 Commits
v2026.2.2-
...
06-18-feat
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2dc69a3bef | ||
|
|
e8d774a2ad |
@@ -5,14 +5,7 @@ rustflags = ["-C", "target-feature=+crt-static"]
|
||||
[target.'cfg(target_os = "linux")']
|
||||
rustflags = ["-C", "link-args=-Wl,--warn-unresolved-symbols"]
|
||||
[target.'cfg(target_os = "macos")']
|
||||
rustflags = [
|
||||
"-C",
|
||||
"link-args=-Wl,-undefined,dynamic_lookup,-no_fixup_chains",
|
||||
"-C",
|
||||
"link-args=-all_load",
|
||||
"-C",
|
||||
"link-args=-weak_framework ScreenCaptureKit",
|
||||
]
|
||||
rustflags = ["-C", "link-args=-Wl,-undefined,dynamic_lookup,-no_fixup_chains", "-C", "link-args=-all_load", "-C", "link-args=-weak_framework ScreenCaptureKit"]
|
||||
# https://sourceware.org/bugzilla/show_bug.cgi?id=21032
|
||||
# https://sourceware.org/bugzilla/show_bug.cgi?id=21031
|
||||
# https://github.com/rust-lang/rust/issues/134820
|
||||
|
||||
@@ -6,6 +6,7 @@ yarn install
|
||||
|
||||
# Build Server Dependencies
|
||||
yarn affine @affine/server-native build
|
||||
yarn affine @affine/reader build
|
||||
|
||||
# Create database
|
||||
yarn affine @affine/server prisma migrate reset -f
|
||||
|
||||
@@ -2,8 +2,6 @@ version: '3.8'
|
||||
|
||||
services:
|
||||
app:
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
image: mcr.microsoft.com/devcontainers/base:bookworm
|
||||
volumes:
|
||||
- ../..:/workspaces:cached
|
||||
@@ -27,7 +25,7 @@ services:
|
||||
image: redis
|
||||
|
||||
indexer:
|
||||
image: manticoresearch/manticore:${MANTICORE_VERSION:-10.1.0}
|
||||
image: manticoresearch/manticore:${MANTICORE_VERSION:-9.3.2}
|
||||
ulimits:
|
||||
nproc: 65535
|
||||
nofile:
|
||||
|
||||
@@ -12,4 +12,4 @@ DB_DATABASE_NAME=affine
|
||||
# ELASTIC_PLATFORM=linux/arm64
|
||||
|
||||
# manticoresearch
|
||||
MANTICORE_VERSION=10.1.0
|
||||
MANTICORE_VERSION=9.3.2
|
||||
|
||||
@@ -15,7 +15,13 @@ yarn affine cert --install
|
||||
|
||||
```bash
|
||||
# certificates will be located at `./.docker/dev/certs/${domain}`
|
||||
yarn affine cert --domain affine.localhost
|
||||
yarn affine cert --domain dev.affine.fail
|
||||
```
|
||||
|
||||
### 3. Enable nginx service in compose.yml
|
||||
### 3. Enable dns and nginx service in compose.yml
|
||||
|
||||
### 4. Add custom dns server
|
||||
|
||||
```bash
|
||||
echo "nameserver 127.0.0.1" | sudo tee /etc/resolver/dev.affine.fail
|
||||
```
|
||||
|
||||
@@ -18,23 +18,15 @@ services:
|
||||
ports:
|
||||
- 6379:6379
|
||||
|
||||
# https://mailpit.axllent.org/docs/install/docker/
|
||||
mailpit:
|
||||
image: axllent/mailpit:latest
|
||||
mailhog:
|
||||
image: mailhog/mailhog:latest
|
||||
ports:
|
||||
- 1025:1025
|
||||
- 8025:8025
|
||||
environment:
|
||||
MP_MAX_MESSAGES: 5000
|
||||
MP_DATABASE: /data/mailpit.db
|
||||
MP_SMTP_AUTH_ACCEPT_ANY: 1
|
||||
MP_SMTP_AUTH_ALLOW_INSECURE: 1
|
||||
volumes:
|
||||
- mailpit_data:/data
|
||||
|
||||
# https://manual.manticoresearch.com/Starting_the_server/Docker
|
||||
manticoresearch:
|
||||
image: manticoresearch/manticore:${MANTICORE_VERSION:-10.1.0}
|
||||
image: manticoresearch/manticore:${MANTICORE_VERSION:-9.3.2}
|
||||
ports:
|
||||
- 9308:9308
|
||||
ulimits:
|
||||
@@ -81,6 +73,17 @@ services:
|
||||
# timeout: 10s
|
||||
# retries: 120
|
||||
|
||||
# dns:
|
||||
# image: strm/dnsmasq
|
||||
# volumes:
|
||||
# - ./dnsmasq.conf:/etc/dnsmasq.d/local.conf
|
||||
# ports:
|
||||
# - "53:53/udp"
|
||||
# cap_add:
|
||||
# - NET_ADMIN
|
||||
# depends_on:
|
||||
# - nginx
|
||||
|
||||
# nginx:
|
||||
# image: nginx:alpine
|
||||
# volumes:
|
||||
@@ -95,5 +98,4 @@ networks:
|
||||
volumes:
|
||||
postgres_data:
|
||||
manticoresearch_data:
|
||||
mailpit_data:
|
||||
elasticsearch_data:
|
||||
|
||||
2
.docker/dev/dnsmasq.conf
Normal file
2
.docker/dev/dnsmasq.conf
Normal file
@@ -0,0 +1,2 @@
|
||||
log-queries
|
||||
address=/dev.affine.fail/127.0.0.1
|
||||
@@ -1,7 +1,7 @@
|
||||
name: affine
|
||||
services:
|
||||
affine:
|
||||
image: ghcr.io/toeverything/affine:${AFFINE_REVISION:-stable}
|
||||
image: ghcr.io/toeverything/affine-graphql:${AFFINE_REVISION:-stable}
|
||||
container_name: affine_server
|
||||
ports:
|
||||
- '${PORT:-3010}:3010'
|
||||
@@ -25,7 +25,7 @@ services:
|
||||
restart: unless-stopped
|
||||
|
||||
affine_migration:
|
||||
image: ghcr.io/toeverything/affine:${AFFINE_REVISION:-stable}
|
||||
image: ghcr.io/toeverything/affine-graphql:${AFFINE_REVISION:-stable}
|
||||
container_name: affine_migration_job
|
||||
volumes:
|
||||
# custom configurations
|
||||
|
||||
@@ -148,11 +148,6 @@
|
||||
"description": "Whether allow new registrations.\n@default true",
|
||||
"default": true
|
||||
},
|
||||
"allowSignupForOauth": {
|
||||
"type": "boolean",
|
||||
"description": "Whether allow new registrations via configured oauth.\n@default true",
|
||||
"default": true
|
||||
},
|
||||
"requireEmailDomainVerification": {
|
||||
"type": "boolean",
|
||||
"description": "Whether require email domain record verification before accessing restricted resources.\n@default false",
|
||||
@@ -195,11 +190,6 @@
|
||||
"type": "object",
|
||||
"description": "Configuration for mailer module",
|
||||
"properties": {
|
||||
"SMTP.name": {
|
||||
"type": "string",
|
||||
"description": "Name of the email server (e.g. your domain name)\n@default \"AFFiNE Server\"\n@environment `MAILER_SERVERNAME`",
|
||||
"default": "AFFiNE Server"
|
||||
},
|
||||
"SMTP.host": {
|
||||
"type": "string",
|
||||
"description": "Host of the email server (e.g. smtp.gmail.com)\n@default \"\"\n@environment `MAILER_HOST`",
|
||||
@@ -222,52 +212,12 @@
|
||||
},
|
||||
"SMTP.sender": {
|
||||
"type": "string",
|
||||
"description": "Sender of all the emails (e.g. \"AFFiNE Self Hosted <noreply@example.com>\")\n@default \"AFFiNE Self Hosted <noreply@example.com>\"\n@environment `MAILER_SENDER`",
|
||||
"default": "AFFiNE Self Hosted <noreply@example.com>"
|
||||
"description": "Sender of all the emails (e.g. \"AFFiNE Team <noreply@affine.pro>\")\n@default \"\"\n@environment `MAILER_SENDER`",
|
||||
"default": ""
|
||||
},
|
||||
"SMTP.ignoreTLS": {
|
||||
"type": "boolean",
|
||||
"description": "Whether ignore email server's TLS certificate verification. Enable it for self-signed certificates.\n@default false\n@environment `MAILER_IGNORE_TLS`",
|
||||
"default": false
|
||||
},
|
||||
"fallbackDomains": {
|
||||
"type": "array",
|
||||
"description": "The emails from these domains are always sent using the fallback SMTP server.\n@default []",
|
||||
"default": []
|
||||
},
|
||||
"fallbackSMTP.name": {
|
||||
"type": "string",
|
||||
"description": "Name of the fallback email server (e.g. your domain name)\n@default \"AFFiNE Server\"",
|
||||
"default": "AFFiNE Server"
|
||||
},
|
||||
"fallbackSMTP.host": {
|
||||
"type": "string",
|
||||
"description": "Host of the email server (e.g. smtp.gmail.com)\n@default \"\"",
|
||||
"default": ""
|
||||
},
|
||||
"fallbackSMTP.port": {
|
||||
"type": "number",
|
||||
"description": "Port of the email server (they commonly are 25, 465 or 587)\n@default 465",
|
||||
"default": 465
|
||||
},
|
||||
"fallbackSMTP.username": {
|
||||
"type": "string",
|
||||
"description": "Username used to authenticate the email server\n@default \"\"",
|
||||
"default": ""
|
||||
},
|
||||
"fallbackSMTP.password": {
|
||||
"type": "string",
|
||||
"description": "Password used to authenticate the email server\n@default \"\"",
|
||||
"default": ""
|
||||
},
|
||||
"fallbackSMTP.sender": {
|
||||
"type": "string",
|
||||
"description": "Sender of all the emails (e.g. \"AFFiNE Self Hosted <noreply@example.com>\")\n@default \"\"",
|
||||
"default": ""
|
||||
},
|
||||
"fallbackSMTP.ignoreTLS": {
|
||||
"type": "boolean",
|
||||
"description": "Whether ignore email server's TLS certificate verification. Enable it for self-signed certificates.\n@default false",
|
||||
"description": "Whether ignore email server's TSL certification verification. Enable it for self-signed certificates.\n@default false\n@environment `MAILER_IGNORE_TLS`",
|
||||
"default": false
|
||||
}
|
||||
}
|
||||
@@ -337,42 +287,8 @@
|
||||
},
|
||||
"config": {
|
||||
"type": "object",
|
||||
"description": "The config for the S3 compatible storage provider.",
|
||||
"description": "The config for the s3 compatible storage provider. directly passed to aws-sdk client.\n@link https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html",
|
||||
"properties": {
|
||||
"endpoint": {
|
||||
"type": "string",
|
||||
"description": "The S3 compatible endpoint. Example: \"https://s3.us-east-1.amazonaws.com\" or \"https://<account>.r2.cloudflarestorage.com\"."
|
||||
},
|
||||
"region": {
|
||||
"type": "string",
|
||||
"description": "The region for the storage provider. Example: \"us-east-1\" or \"auto\" for R2."
|
||||
},
|
||||
"forcePathStyle": {
|
||||
"type": "boolean",
|
||||
"description": "Whether to use path-style bucket addressing."
|
||||
},
|
||||
"requestTimeoutMs": {
|
||||
"type": "number",
|
||||
"description": "Request timeout in milliseconds."
|
||||
},
|
||||
"minPartSize": {
|
||||
"type": "number",
|
||||
"description": "Minimum multipart part size in bytes."
|
||||
},
|
||||
"presign": {
|
||||
"type": "object",
|
||||
"description": "Presigned URL behavior configuration.",
|
||||
"properties": {
|
||||
"expiresInSeconds": {
|
||||
"type": "number",
|
||||
"description": "Expiration time in seconds for presigned URLs."
|
||||
},
|
||||
"signContentTypeForPut": {
|
||||
"type": "boolean",
|
||||
"description": "Whether to sign Content-Type for presigned PUT."
|
||||
}
|
||||
}
|
||||
},
|
||||
"credentials": {
|
||||
"type": "object",
|
||||
"description": "The credentials for the s3 compatible storage provider.",
|
||||
@@ -382,9 +298,6 @@
|
||||
},
|
||||
"secretAccessKey": {
|
||||
"type": "string"
|
||||
},
|
||||
"sessionToken": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -406,42 +319,8 @@
|
||||
},
|
||||
"config": {
|
||||
"type": "object",
|
||||
"description": "The config for the S3 compatible storage provider.",
|
||||
"description": "The config for the s3 compatible storage provider. directly passed to aws-sdk client.\n@link https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html",
|
||||
"properties": {
|
||||
"endpoint": {
|
||||
"type": "string",
|
||||
"description": "The S3 compatible endpoint. Example: \"https://s3.us-east-1.amazonaws.com\" or \"https://<account>.r2.cloudflarestorage.com\"."
|
||||
},
|
||||
"region": {
|
||||
"type": "string",
|
||||
"description": "The region for the storage provider. Example: \"us-east-1\" or \"auto\" for R2."
|
||||
},
|
||||
"forcePathStyle": {
|
||||
"type": "boolean",
|
||||
"description": "Whether to use path-style bucket addressing."
|
||||
},
|
||||
"requestTimeoutMs": {
|
||||
"type": "number",
|
||||
"description": "Request timeout in milliseconds."
|
||||
},
|
||||
"minPartSize": {
|
||||
"type": "number",
|
||||
"description": "Minimum multipart part size in bytes."
|
||||
},
|
||||
"presign": {
|
||||
"type": "object",
|
||||
"description": "Presigned URL behavior configuration.",
|
||||
"properties": {
|
||||
"expiresInSeconds": {
|
||||
"type": "number",
|
||||
"description": "Expiration time in seconds for presigned URLs."
|
||||
},
|
||||
"signContentTypeForPut": {
|
||||
"type": "boolean",
|
||||
"description": "Whether to sign Content-Type for presigned PUT."
|
||||
}
|
||||
}
|
||||
},
|
||||
"credentials": {
|
||||
"type": "object",
|
||||
"description": "The credentials for the s3 compatible storage provider.",
|
||||
@@ -451,9 +330,6 @@
|
||||
},
|
||||
"secretAccessKey": {
|
||||
"type": "string"
|
||||
},
|
||||
"sessionToken": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -471,7 +347,7 @@
|
||||
},
|
||||
"urlPrefix": {
|
||||
"type": "string",
|
||||
"description": "The custom domain URL prefix for the cloudflare r2 storage provider.\nWhen `enabled=true` and `urlPrefix` + `signKey` are provided, the server will:\n- Redirect GET requests to this custom domain with an HMAC token.\n- Return upload URLs under `/api/storage/*` for uploads.\nPresigned/upload proxy TTL is 1 hour.\nsee https://developers.cloudflare.com/waf/custom-rules/use-cases/configure-token-authentication/ to configure it.\nExample value: \"https://storage.example.com\"\nExample rule: is_timed_hmac_valid_v0(\"your_secret\", http.request.uri, 10800, http.request.timestamp.sec, 6)"
|
||||
"description": "The presigned url prefix for the cloudflare r2 storage provider.\nsee https://developers.cloudflare.com/waf/custom-rules/use-cases/configure-token-authentication/ to configure it.\nExample value: \"https://storage.example.com\"\nExample rule: is_timed_hmac_valid_v0(\"your_secret\", http.request.uri, 10800, http.request.timestamp.sec, 6)"
|
||||
},
|
||||
"signKey": {
|
||||
"type": "string",
|
||||
@@ -532,42 +408,8 @@
|
||||
},
|
||||
"config": {
|
||||
"type": "object",
|
||||
"description": "The config for the S3 compatible storage provider.",
|
||||
"description": "The config for the s3 compatible storage provider. directly passed to aws-sdk client.\n@link https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html",
|
||||
"properties": {
|
||||
"endpoint": {
|
||||
"type": "string",
|
||||
"description": "The S3 compatible endpoint. Example: \"https://s3.us-east-1.amazonaws.com\" or \"https://<account>.r2.cloudflarestorage.com\"."
|
||||
},
|
||||
"region": {
|
||||
"type": "string",
|
||||
"description": "The region for the storage provider. Example: \"us-east-1\" or \"auto\" for R2."
|
||||
},
|
||||
"forcePathStyle": {
|
||||
"type": "boolean",
|
||||
"description": "Whether to use path-style bucket addressing."
|
||||
},
|
||||
"requestTimeoutMs": {
|
||||
"type": "number",
|
||||
"description": "Request timeout in milliseconds."
|
||||
},
|
||||
"minPartSize": {
|
||||
"type": "number",
|
||||
"description": "Minimum multipart part size in bytes."
|
||||
},
|
||||
"presign": {
|
||||
"type": "object",
|
||||
"description": "Presigned URL behavior configuration.",
|
||||
"properties": {
|
||||
"expiresInSeconds": {
|
||||
"type": "number",
|
||||
"description": "Expiration time in seconds for presigned URLs."
|
||||
},
|
||||
"signContentTypeForPut": {
|
||||
"type": "boolean",
|
||||
"description": "Whether to sign Content-Type for presigned PUT."
|
||||
}
|
||||
}
|
||||
},
|
||||
"credentials": {
|
||||
"type": "object",
|
||||
"description": "The credentials for the s3 compatible storage provider.",
|
||||
@@ -577,9 +419,6 @@
|
||||
},
|
||||
"secretAccessKey": {
|
||||
"type": "string"
|
||||
},
|
||||
"sessionToken": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -601,42 +440,8 @@
|
||||
},
|
||||
"config": {
|
||||
"type": "object",
|
||||
"description": "The config for the S3 compatible storage provider.",
|
||||
"description": "The config for the s3 compatible storage provider. directly passed to aws-sdk client.\n@link https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html",
|
||||
"properties": {
|
||||
"endpoint": {
|
||||
"type": "string",
|
||||
"description": "The S3 compatible endpoint. Example: \"https://s3.us-east-1.amazonaws.com\" or \"https://<account>.r2.cloudflarestorage.com\"."
|
||||
},
|
||||
"region": {
|
||||
"type": "string",
|
||||
"description": "The region for the storage provider. Example: \"us-east-1\" or \"auto\" for R2."
|
||||
},
|
||||
"forcePathStyle": {
|
||||
"type": "boolean",
|
||||
"description": "Whether to use path-style bucket addressing."
|
||||
},
|
||||
"requestTimeoutMs": {
|
||||
"type": "number",
|
||||
"description": "Request timeout in milliseconds."
|
||||
},
|
||||
"minPartSize": {
|
||||
"type": "number",
|
||||
"description": "Minimum multipart part size in bytes."
|
||||
},
|
||||
"presign": {
|
||||
"type": "object",
|
||||
"description": "Presigned URL behavior configuration.",
|
||||
"properties": {
|
||||
"expiresInSeconds": {
|
||||
"type": "number",
|
||||
"description": "Expiration time in seconds for presigned URLs."
|
||||
},
|
||||
"signContentTypeForPut": {
|
||||
"type": "boolean",
|
||||
"description": "Whether to sign Content-Type for presigned PUT."
|
||||
}
|
||||
}
|
||||
},
|
||||
"credentials": {
|
||||
"type": "object",
|
||||
"description": "The credentials for the s3 compatible storage provider.",
|
||||
@@ -646,9 +451,6 @@
|
||||
},
|
||||
"secretAccessKey": {
|
||||
"type": "string"
|
||||
},
|
||||
"sessionToken": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -666,7 +468,7 @@
|
||||
},
|
||||
"urlPrefix": {
|
||||
"type": "string",
|
||||
"description": "The custom domain URL prefix for the cloudflare r2 storage provider.\nWhen `enabled=true` and `urlPrefix` + `signKey` are provided, the server will:\n- Redirect GET requests to this custom domain with an HMAC token.\n- Return upload URLs under `/api/storage/*` for uploads.\nPresigned/upload proxy TTL is 1 hour.\nsee https://developers.cloudflare.com/waf/custom-rules/use-cases/configure-token-authentication/ to configure it.\nExample value: \"https://storage.example.com\"\nExample rule: is_timed_hmac_valid_v0(\"your_secret\", http.request.uri, 10800, http.request.timestamp.sec, 6)"
|
||||
"description": "The presigned url prefix for the cloudflare r2 storage provider.\nsee https://developers.cloudflare.com/waf/custom-rules/use-cases/configure-token-authentication/ to configure it.\nExample value: \"https://storage.example.com\"\nExample rule: is_timed_hmac_valid_v0(\"your_secret\", http.request.uri, 10800, http.request.timestamp.sec, 6)"
|
||||
},
|
||||
"signKey": {
|
||||
"type": "string",
|
||||
@@ -738,16 +540,6 @@
|
||||
"description": "Where the server get deployed(FQDN).\n@default \"localhost\"\n@environment `AFFINE_SERVER_HOST`",
|
||||
"default": "localhost"
|
||||
},
|
||||
"hosts": {
|
||||
"type": "array",
|
||||
"description": "Multiple hosts the server will accept requests from.\n@default []",
|
||||
"default": []
|
||||
},
|
||||
"listenAddr": {
|
||||
"type": "string",
|
||||
"description": "The address to listen on (e.g., 0.0.0.0 for IPv4, :: for IPv6).\n@default \"0.0.0.0\"\n@environment `LISTEN_ADDR`",
|
||||
"default": "0.0.0.0"
|
||||
},
|
||||
"port": {
|
||||
"type": "number",
|
||||
"description": "Which port the server will listen on.\n@default 3010\n@environment `AFFINE_SERVER_PORT`",
|
||||
@@ -764,10 +556,10 @@
|
||||
"type": "object",
|
||||
"description": "Configuration for flags module",
|
||||
"properties": {
|
||||
"allowGuestDemoWorkspace": {
|
||||
"earlyAccessControl": {
|
||||
"type": "boolean",
|
||||
"description": "Whether allow guest users to create demo workspaces.\n@default true",
|
||||
"default": true
|
||||
"description": "Only allow users with early access features to access the app\n@default false",
|
||||
"default": false
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -782,45 +574,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"telemetry": {
|
||||
"type": "object",
|
||||
"description": "Configuration for telemetry module",
|
||||
"properties": {
|
||||
"allowedOrigin": {
|
||||
"type": "array",
|
||||
"description": "Allowed origins for telemetry collection.\n@default [\"localhost\",\"127.0.0.1\"]",
|
||||
"default": [
|
||||
"localhost",
|
||||
"127.0.0.1"
|
||||
]
|
||||
},
|
||||
"ga4.measurementId": {
|
||||
"type": "string",
|
||||
"description": "GA4 Measurement ID for Measurement Protocol.\n@default \"\"\n@environment `GA4_MEASUREMENT_ID`",
|
||||
"default": ""
|
||||
},
|
||||
"ga4.apiSecret": {
|
||||
"type": "string",
|
||||
"description": "GA4 API secret for Measurement Protocol.\n@default \"\"\n@environment `GA4_API_SECRET`",
|
||||
"default": ""
|
||||
},
|
||||
"dedupe.ttlHours": {
|
||||
"type": "number",
|
||||
"description": "Telemetry dedupe TTL in hours.\n@default 24",
|
||||
"default": 24
|
||||
},
|
||||
"dedupe.maxEntries": {
|
||||
"type": "number",
|
||||
"description": "Telemetry dedupe max entries.\n@default 100000",
|
||||
"default": 100000
|
||||
},
|
||||
"batch.maxEvents": {
|
||||
"type": "number",
|
||||
"description": "Max events per telemetry batch.\n@default 25",
|
||||
"default": 25
|
||||
}
|
||||
}
|
||||
},
|
||||
"client": {
|
||||
"type": "object",
|
||||
"description": "Configuration for client module",
|
||||
@@ -837,40 +590,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"calendar": {
|
||||
"type": "object",
|
||||
"description": "Configuration for calendar module",
|
||||
"properties": {
|
||||
"google": {
|
||||
"type": "object",
|
||||
"description": "Google Calendar integration config\n@default {\"enabled\":false,\"clientId\":\"\",\"clientSecret\":\"\",\"externalWebhookUrl\":\"\",\"webhookVerificationToken\":\"\"}\n@link https://developers.google.com/calendar/api/guides/push",
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"clientId": {
|
||||
"type": "string"
|
||||
},
|
||||
"clientSecret": {
|
||||
"type": "string"
|
||||
},
|
||||
"externalWebhookUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"webhookVerificationToken": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"enabled": false,
|
||||
"clientId": "",
|
||||
"clientSecret": "",
|
||||
"externalWebhookUrl": "",
|
||||
"webhookVerificationToken": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"captcha": {
|
||||
"type": "object",
|
||||
"description": "Configuration for captcha module",
|
||||
@@ -900,34 +619,14 @@
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean",
|
||||
"description": "Whether to enable the copilot plugin. <br> Document: <a href=\"https://docs.affine.pro/self-host-affine/administer/ai\" target=\"_blank\">https://docs.affine.pro/self-host-affine/administer/ai</a>\n@default false",
|
||||
"description": "Whether to enable the copilot plugin.\n@default false",
|
||||
"default": false
|
||||
},
|
||||
"scenarios": {
|
||||
"type": "object",
|
||||
"description": "Use custom models in scenarios and override default settings.\n@default {\"override_enabled\":false,\"scenarios\":{\"audio_transcribing\":\"gemini-2.5-flash\",\"chat\":\"gemini-2.5-flash\",\"embedding\":\"gemini-embedding-001\",\"image\":\"gpt-image-1\",\"rerank\":\"gpt-4.1\",\"coding\":\"claude-sonnet-4-5@20250929\",\"complex_text_generation\":\"gpt-4o-2024-08-06\",\"quick_decision_making\":\"gpt-5-mini\",\"quick_text_generation\":\"gemini-2.5-flash\",\"polish_and_summarize\":\"gemini-2.5-flash\"}}",
|
||||
"default": {
|
||||
"override_enabled": false,
|
||||
"scenarios": {
|
||||
"audio_transcribing": "gemini-2.5-flash",
|
||||
"chat": "gemini-2.5-flash",
|
||||
"embedding": "gemini-embedding-001",
|
||||
"image": "gpt-image-1",
|
||||
"rerank": "gpt-4.1",
|
||||
"coding": "claude-sonnet-4-5@20250929",
|
||||
"complex_text_generation": "gpt-4o-2024-08-06",
|
||||
"quick_decision_making": "gpt-5-mini",
|
||||
"quick_text_generation": "gemini-2.5-flash",
|
||||
"polish_and_summarize": "gemini-2.5-flash"
|
||||
}
|
||||
}
|
||||
},
|
||||
"providers.openai": {
|
||||
"type": "object",
|
||||
"description": "The config for the openai provider.\n@default {\"apiKey\":\"\",\"baseURL\":\"https://api.openai.com/v1\"}\n@link https://github.com/openai/openai-node",
|
||||
"description": "The config for the openai provider.\n@default {\"apiKey\":\"\"}\n@link https://github.com/openai/openai-node",
|
||||
"default": {
|
||||
"apiKey": "",
|
||||
"baseURL": "https://api.openai.com/v1"
|
||||
"apiKey": ""
|
||||
}
|
||||
},
|
||||
"providers.fal": {
|
||||
@@ -939,10 +638,9 @@
|
||||
},
|
||||
"providers.gemini": {
|
||||
"type": "object",
|
||||
"description": "The config for the gemini provider.\n@default {\"apiKey\":\"\",\"baseURL\":\"https://generativelanguage.googleapis.com/v1beta\"}",
|
||||
"description": "The config for the gemini provider.\n@default {\"apiKey\":\"\"}",
|
||||
"default": {
|
||||
"apiKey": "",
|
||||
"baseURL": "https://generativelanguage.googleapis.com/v1beta"
|
||||
"apiKey": ""
|
||||
}
|
||||
},
|
||||
"providers.geminiVertex": {
|
||||
@@ -989,10 +687,9 @@
|
||||
},
|
||||
"providers.anthropic": {
|
||||
"type": "object",
|
||||
"description": "The config for the anthropic provider.\n@default {\"apiKey\":\"\",\"baseURL\":\"https://api.anthropic.com/v1\"}",
|
||||
"description": "The config for the anthropic provider.\n@default {\"apiKey\":\"\"}",
|
||||
"default": {
|
||||
"apiKey": "",
|
||||
"baseURL": "https://api.anthropic.com/v1"
|
||||
"apiKey": ""
|
||||
}
|
||||
},
|
||||
"providers.anthropicVertex": {
|
||||
@@ -1030,11 +727,6 @@
|
||||
},
|
||||
"default": {}
|
||||
},
|
||||
"providers.morph": {
|
||||
"type": "object",
|
||||
"description": "The config for the morph provider.\n@default {}",
|
||||
"default": {}
|
||||
},
|
||||
"unsplash": {
|
||||
"type": "object",
|
||||
"description": "The config for the unsplash key.\n@default {\"key\":\"\"}",
|
||||
@@ -1089,42 +781,8 @@
|
||||
},
|
||||
"config": {
|
||||
"type": "object",
|
||||
"description": "The config for the S3 compatible storage provider.",
|
||||
"description": "The config for the s3 compatible storage provider. directly passed to aws-sdk client.\n@link https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html",
|
||||
"properties": {
|
||||
"endpoint": {
|
||||
"type": "string",
|
||||
"description": "The S3 compatible endpoint. Example: \"https://s3.us-east-1.amazonaws.com\" or \"https://<account>.r2.cloudflarestorage.com\"."
|
||||
},
|
||||
"region": {
|
||||
"type": "string",
|
||||
"description": "The region for the storage provider. Example: \"us-east-1\" or \"auto\" for R2."
|
||||
},
|
||||
"forcePathStyle": {
|
||||
"type": "boolean",
|
||||
"description": "Whether to use path-style bucket addressing."
|
||||
},
|
||||
"requestTimeoutMs": {
|
||||
"type": "number",
|
||||
"description": "Request timeout in milliseconds."
|
||||
},
|
||||
"minPartSize": {
|
||||
"type": "number",
|
||||
"description": "Minimum multipart part size in bytes."
|
||||
},
|
||||
"presign": {
|
||||
"type": "object",
|
||||
"description": "Presigned URL behavior configuration.",
|
||||
"properties": {
|
||||
"expiresInSeconds": {
|
||||
"type": "number",
|
||||
"description": "Expiration time in seconds for presigned URLs."
|
||||
},
|
||||
"signContentTypeForPut": {
|
||||
"type": "boolean",
|
||||
"description": "Whether to sign Content-Type for presigned PUT."
|
||||
}
|
||||
}
|
||||
},
|
||||
"credentials": {
|
||||
"type": "object",
|
||||
"description": "The credentials for the s3 compatible storage provider.",
|
||||
@@ -1134,9 +792,6 @@
|
||||
},
|
||||
"secretAccessKey": {
|
||||
"type": "string"
|
||||
},
|
||||
"sessionToken": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1158,42 +813,8 @@
|
||||
},
|
||||
"config": {
|
||||
"type": "object",
|
||||
"description": "The config for the S3 compatible storage provider.",
|
||||
"description": "The config for the s3 compatible storage provider. directly passed to aws-sdk client.\n@link https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html",
|
||||
"properties": {
|
||||
"endpoint": {
|
||||
"type": "string",
|
||||
"description": "The S3 compatible endpoint. Example: \"https://s3.us-east-1.amazonaws.com\" or \"https://<account>.r2.cloudflarestorage.com\"."
|
||||
},
|
||||
"region": {
|
||||
"type": "string",
|
||||
"description": "The region for the storage provider. Example: \"us-east-1\" or \"auto\" for R2."
|
||||
},
|
||||
"forcePathStyle": {
|
||||
"type": "boolean",
|
||||
"description": "Whether to use path-style bucket addressing."
|
||||
},
|
||||
"requestTimeoutMs": {
|
||||
"type": "number",
|
||||
"description": "Request timeout in milliseconds."
|
||||
},
|
||||
"minPartSize": {
|
||||
"type": "number",
|
||||
"description": "Minimum multipart part size in bytes."
|
||||
},
|
||||
"presign": {
|
||||
"type": "object",
|
||||
"description": "Presigned URL behavior configuration.",
|
||||
"properties": {
|
||||
"expiresInSeconds": {
|
||||
"type": "number",
|
||||
"description": "Expiration time in seconds for presigned URLs."
|
||||
},
|
||||
"signContentTypeForPut": {
|
||||
"type": "boolean",
|
||||
"description": "Whether to sign Content-Type for presigned PUT."
|
||||
}
|
||||
}
|
||||
},
|
||||
"credentials": {
|
||||
"type": "object",
|
||||
"description": "The credentials for the s3 compatible storage provider.",
|
||||
@@ -1203,9 +824,6 @@
|
||||
},
|
||||
"secretAccessKey": {
|
||||
"type": "string"
|
||||
},
|
||||
"sessionToken": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1223,7 +841,7 @@
|
||||
},
|
||||
"urlPrefix": {
|
||||
"type": "string",
|
||||
"description": "The custom domain URL prefix for the cloudflare r2 storage provider.\nWhen `enabled=true` and `urlPrefix` + `signKey` are provided, the server will:\n- Redirect GET requests to this custom domain with an HMAC token.\n- Return upload URLs under `/api/storage/*` for uploads.\nPresigned/upload proxy TTL is 1 hour.\nsee https://developers.cloudflare.com/waf/custom-rules/use-cases/configure-token-authentication/ to configure it.\nExample value: \"https://storage.example.com\"\nExample rule: is_timed_hmac_valid_v0(\"your_secret\", http.request.uri, 10800, http.request.timestamp.sec, 6)"
|
||||
"description": "The presigned url prefix for the cloudflare r2 storage provider.\nsee https://developers.cloudflare.com/waf/custom-rules/use-cases/configure-token-authentication/ to configure it.\nExample value: \"https://storage.example.com\"\nExample rule: is_timed_hmac_valid_v0(\"your_secret\", http.request.uri, 10800, http.request.timestamp.sec, 6)"
|
||||
},
|
||||
"signKey": {
|
||||
"type": "string",
|
||||
@@ -1246,6 +864,22 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"customerIo": {
|
||||
"type": "object",
|
||||
"description": "Configuration for customerIo module",
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean",
|
||||
"description": "Enable customer.io integration\n@default false",
|
||||
"default": false
|
||||
},
|
||||
"token": {
|
||||
"type": "string",
|
||||
"description": "Customer.io token\n@default \"\"",
|
||||
"default": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"indexer": {
|
||||
"type": "object",
|
||||
"description": "Configuration for indexer module",
|
||||
@@ -1287,22 +921,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"customerIo": {
|
||||
"type": "object",
|
||||
"description": "Configuration for customerIo module",
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean",
|
||||
"description": "Enable customer.io integration\n@default false",
|
||||
"default": false
|
||||
},
|
||||
"token": {
|
||||
"type": "string",
|
||||
"description": "Customer.io token\n@default \"\"",
|
||||
"default": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"oauth": {
|
||||
"type": "object",
|
||||
"description": "Configuration for oauth module",
|
||||
@@ -1403,33 +1021,18 @@
|
||||
},
|
||||
"apiKey": {
|
||||
"type": "string",
|
||||
"description": "[Deprecated] Stripe API key. Use payment.stripe.apiKey instead.\n@default \"\"\n@environment `STRIPE_API_KEY`",
|
||||
"description": "Stripe API key to enable payment service.\n@default \"\"\n@environment `STRIPE_API_KEY`",
|
||||
"default": ""
|
||||
},
|
||||
"webhookKey": {
|
||||
"type": "string",
|
||||
"description": "[Deprecated] Stripe webhook key. Use payment.stripe.webhookKey instead.\n@default \"\"\n@environment `STRIPE_WEBHOOK_KEY`",
|
||||
"description": "Stripe webhook key to enable payment service.\n@default \"\"\n@environment `STRIPE_WEBHOOK_KEY`",
|
||||
"default": ""
|
||||
},
|
||||
"stripe": {
|
||||
"type": "object",
|
||||
"description": "Stripe sdk options and credentials\n@default {\"apiKey\":\"\",\"webhookKey\":\"\"}\n@link https://docs.stripe.com/api",
|
||||
"default": {
|
||||
"apiKey": "",
|
||||
"webhookKey": ""
|
||||
}
|
||||
},
|
||||
"revenuecat": {
|
||||
"type": "object",
|
||||
"description": "RevenueCat integration configs\n@default {\"enabled\":false,\"apiKey\":\"\",\"projectId\":\"\",\"webhookAuth\":\"\",\"environment\":\"production\",\"productMap\":{}}\n@link https://www.revenuecat.com/docs/",
|
||||
"default": {
|
||||
"enabled": false,
|
||||
"apiKey": "",
|
||||
"projectId": "",
|
||||
"webhookAuth": "",
|
||||
"environment": "production",
|
||||
"productMap": {}
|
||||
}
|
||||
"description": "Stripe sdk options\n@default {}\n@link https://docs.stripe.com/api",
|
||||
"default": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
# Editor configuration, see http://editorconfig.org
|
||||
root = true
|
||||
|
||||
[*.rs]
|
||||
max_line_length = 120
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
|
||||
8
.github/ISSUE_TEMPLATE/BUG-REPORT.yml
vendored
8
.github/ISSUE_TEMPLATE/BUG-REPORT.yml
vendored
@@ -74,11 +74,3 @@ body:
|
||||
description: |
|
||||
Links? References? Anything that will give us more context about the issue you are encountering!
|
||||
Tip: You can attach images here
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Is your content generated by AI?
|
||||
description: >
|
||||
(Required) Please confirm that the content you submit was not generated by AI or only minimally edited by AI.
|
||||
If an administrator believes the post contains a large amount of AI-generated content, they may directly close the question.
|
||||
options:
|
||||
- label: I confirm that the content I submitted was **not** generated by AI / **merely contained minimal** AI edits.
|
||||
|
||||
8
.github/ISSUE_TEMPLATE/FEATURE-REQUEST.yml
vendored
8
.github/ISSUE_TEMPLATE/FEATURE-REQUEST.yml
vendored
@@ -35,11 +35,3 @@ body:
|
||||
See the AFFiNE [Contributing Guide](https://github.com/toeverything/affine/blob/canary/CONTRIBUTING.md) to get started.
|
||||
options:
|
||||
- label: Yes I'd like to help by submitting a PR!
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Is your content generated by AI?
|
||||
description: >
|
||||
(Required) Please confirm that the content you submit was not generated by AI or only minimally edited by AI.
|
||||
If an administrator believes the post contains a large amount of AI-generated content, they may directly close the question.
|
||||
options:
|
||||
- label: I confirm that the content I submitted was **not** generated by AI / **merely contained minimal** AI edits.
|
||||
|
||||
6
.github/actions/build-rust/action.yml
vendored
6
.github/actions/build-rust/action.yml
vendored
@@ -75,11 +75,7 @@ runs:
|
||||
shell: bash
|
||||
if: ${{ runner.os != 'Windows' && inputs.no-build != 'true' }}
|
||||
run: |
|
||||
if [[ "${{ inputs.target }}" == "x86_64-unknown-linux-gnu" ]]; then
|
||||
yarn workspace ${{ inputs.package }} build --target ${{ inputs.target }}
|
||||
else
|
||||
yarn workspace ${{ inputs.package }} build --target ${{ inputs.target }} --use-napi-cross
|
||||
fi
|
||||
yarn workspace ${{ inputs.package }} build --target ${{ inputs.target }} --use-napi-cross
|
||||
env:
|
||||
DEBUG: 'napi:*'
|
||||
|
||||
|
||||
5
.github/actions/deploy/action.yml
vendored
5
.github/actions/deploy/action.yml
vendored
@@ -1,6 +1,9 @@
|
||||
name: 'Deploy to Cluster'
|
||||
description: 'Deploy AFFiNE Cloud to cluster'
|
||||
inputs:
|
||||
build-type:
|
||||
description: 'Align with App build type, canary|beta|stable|internal'
|
||||
default: 'canary'
|
||||
gcp-project-number:
|
||||
description: 'GCP project number'
|
||||
required: true
|
||||
@@ -33,3 +36,5 @@ runs:
|
||||
- name: Deploy
|
||||
shell: bash
|
||||
run: node ./.github/actions/deploy/deploy.mjs
|
||||
env:
|
||||
BUILD_TYPE: '${{ inputs.build-type }}'
|
||||
|
||||
45
.github/actions/deploy/deploy.mjs
vendored
45
.github/actions/deploy/deploy.mjs
vendored
@@ -29,25 +29,25 @@ const isInternal = buildType === 'internal';
|
||||
|
||||
const replicaConfig = {
|
||||
stable: {
|
||||
web: 2,
|
||||
graphql: Number(process.env.PRODUCTION_GRAPHQL_REPLICA) || 2,
|
||||
sync: Number(process.env.PRODUCTION_SYNC_REPLICA) || 2,
|
||||
renderer: Number(process.env.PRODUCTION_RENDERER_REPLICA) || 2,
|
||||
doc: Number(process.env.PRODUCTION_DOC_REPLICA) || 2,
|
||||
web: 3,
|
||||
graphql: Number(process.env.PRODUCTION_GRAPHQL_REPLICA) || 3,
|
||||
sync: Number(process.env.PRODUCTION_SYNC_REPLICA) || 3,
|
||||
renderer: Number(process.env.PRODUCTION_RENDERER_REPLICA) || 3,
|
||||
doc: Number(process.env.PRODUCTION_DOC_REPLICA) || 3,
|
||||
},
|
||||
beta: {
|
||||
web: 1,
|
||||
graphql: Number(process.env.BETA_GRAPHQL_REPLICA) || 1,
|
||||
sync: Number(process.env.BETA_SYNC_REPLICA) || 1,
|
||||
renderer: Number(process.env.BETA_RENDERER_REPLICA) || 1,
|
||||
doc: Number(process.env.BETA_DOC_REPLICA) || 1,
|
||||
web: 2,
|
||||
graphql: Number(process.env.BETA_GRAPHQL_REPLICA) || 2,
|
||||
sync: Number(process.env.BETA_SYNC_REPLICA) || 2,
|
||||
renderer: Number(process.env.BETA_RENDERER_REPLICA) || 2,
|
||||
doc: Number(process.env.BETA_DOC_REPLICA) || 2,
|
||||
},
|
||||
canary: {
|
||||
web: 1,
|
||||
graphql: 1,
|
||||
sync: 1,
|
||||
renderer: 1,
|
||||
doc: 1,
|
||||
web: 2,
|
||||
graphql: 2,
|
||||
sync: 2,
|
||||
renderer: 2,
|
||||
doc: 2,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -126,10 +126,7 @@ const createHelmCommand = ({ isDryRun }) => {
|
||||
? 'internal'
|
||||
: 'dev';
|
||||
|
||||
const hosts = (DEPLOY_HOST || CANARY_DEPLOY_HOST)
|
||||
.split(',')
|
||||
.map(host => host.trim())
|
||||
.filter(host => host);
|
||||
const host = DEPLOY_HOST || CANARY_DEPLOY_HOST;
|
||||
const deployCommand = [
|
||||
`helm upgrade --install affine .github/helm/affine`,
|
||||
`--namespace ${namespace}`,
|
||||
@@ -138,9 +135,7 @@ const createHelmCommand = ({ isDryRun }) => {
|
||||
`--set-string global.app.buildType="${buildType}"`,
|
||||
`--set global.ingress.enabled=true`,
|
||||
`--set-json global.ingress.annotations="{ \\"kubernetes.io/ingress.class\\": \\"gce\\", \\"kubernetes.io/ingress.allow-http\\": \\"true\\", \\"kubernetes.io/ingress.global-static-ip-name\\": \\"${STATIC_IP_NAME}\\" }"`,
|
||||
...hosts.map(
|
||||
(host, index) => `--set global.ingress.hosts[${index}]=${host}`
|
||||
),
|
||||
`--set-string global.ingress.host="${host}"`,
|
||||
`--set-string global.version="${APP_VERSION}"`,
|
||||
...redisAndPostgres,
|
||||
...indexerOptions,
|
||||
@@ -148,14 +143,14 @@ const createHelmCommand = ({ isDryRun }) => {
|
||||
`--set-string web.image.tag="${imageTag}"`,
|
||||
`--set graphql.replicaCount=${replica.graphql}`,
|
||||
`--set-string graphql.image.tag="${imageTag}"`,
|
||||
`--set graphql.app.host=${hosts[0]}`,
|
||||
`--set graphql.app.host=${host}`,
|
||||
`--set sync.replicaCount=${replica.sync}`,
|
||||
`--set-string sync.image.tag="${imageTag}"`,
|
||||
`--set-string renderer.image.tag="${imageTag}"`,
|
||||
`--set renderer.app.host=${hosts[0]}`,
|
||||
`--set renderer.app.host=${host}`,
|
||||
`--set renderer.replicaCount=${replica.renderer}`,
|
||||
`--set-string doc.image.tag="${imageTag}"`,
|
||||
`--set doc.app.host=${hosts[0]}`,
|
||||
`--set doc.app.host=${host}`,
|
||||
`--set doc.replicaCount=${replica.doc}`,
|
||||
...serviceAnnotations,
|
||||
...resources,
|
||||
|
||||
41
.github/actions/prepare-release/action.yml
vendored
41
.github/actions/prepare-release/action.yml
vendored
@@ -1,41 +0,0 @@
|
||||
name: Prepare Release
|
||||
description: 'Prepare Release'
|
||||
outputs:
|
||||
APP_VERSION:
|
||||
description: 'App Version'
|
||||
value: ${{ steps.get-version.outputs.APP_VERSION }}
|
||||
GIT_SHORT_HASH:
|
||||
description: 'Git Short Hash'
|
||||
value: ${{ steps.get-version.outputs.GIT_SHORT_HASH }}
|
||||
BUILD_TYPE:
|
||||
description: 'Build Type'
|
||||
value: ${{ steps.get-version.outputs.BUILD_TYPE }}
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: Get Version
|
||||
id: get-version
|
||||
shell: bash
|
||||
run: |
|
||||
GIT_SHORT_HASH=$(git rev-parse --short HEAD)
|
||||
if [ "${{ github.ref_type }}" == "tag" ]; then
|
||||
APP_VERSION=$(echo "${{ github.ref_name }}" | sed 's/^v//')
|
||||
else
|
||||
APP_VERSION=$(date '+%Y.%-m.%-d-canary.%-H%M')
|
||||
fi
|
||||
if [[ "$APP_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
BUILD_TYPE=stable
|
||||
elif [[ "$APP_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+-beta\.[0-9]+$ ]]; then
|
||||
BUILD_TYPE=beta
|
||||
elif [[ "$APP_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+-canary\.[0-9a-f]+$ ]]; then
|
||||
BUILD_TYPE=canary
|
||||
else
|
||||
echo "Error: unsupported version string: $APP_VERSION" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo $APP_VERSION
|
||||
echo $GIT_SHORT_HASH
|
||||
echo $BUILD_TYPE
|
||||
echo "APP_VERSION=$APP_VERSION" >> "$GITHUB_OUTPUT"
|
||||
echo "GIT_SHORT_HASH=$GIT_SHORT_HASH" >> "$GITHUB_OUTPUT"
|
||||
echo "BUILD_TYPE=$BUILD_TYPE" >> "$GITHUB_OUTPUT"
|
||||
5
.github/actions/server-test-env/action.yml
vendored
5
.github/actions/server-test-env/action.yml
vendored
@@ -4,6 +4,11 @@ description: 'Prepare Server Test Environment'
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: Bundle @affine/reader
|
||||
shell: bash
|
||||
run: |
|
||||
yarn affine @affine/reader build
|
||||
|
||||
- name: Initialize database
|
||||
shell: bash
|
||||
run: |
|
||||
|
||||
26
.github/actions/setup-version/action.yml
vendored
26
.github/actions/setup-version/action.yml
vendored
@@ -1,18 +1,24 @@
|
||||
name: Setup Version
|
||||
description: 'Setup Version'
|
||||
inputs:
|
||||
app-version:
|
||||
outputs:
|
||||
APP_VERSION:
|
||||
description: 'App Version'
|
||||
required: true
|
||||
ios-app-version:
|
||||
description: 'iOS App Store Version (Optional, use App version if empty)'
|
||||
required: false
|
||||
type: string
|
||||
value: ${{ steps.version.outputs.APP_VERSION }}
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: 'Write Version'
|
||||
id: version
|
||||
shell: bash
|
||||
env:
|
||||
IOS_APP_VERSION: ${{ inputs.ios-app-version }}
|
||||
run: ./scripts/set-version.sh ${{ inputs.app-version }}
|
||||
run: |
|
||||
if [ "${{ github.ref_type }}" == "tag" ]; then
|
||||
APP_VERSION=$(echo "${{ github.ref_name }}" | sed 's/^v//')
|
||||
else
|
||||
PACKAGE_VERSION=$(node -p "require('./package.json').version")
|
||||
TIME_VERSION=$(date +%Y%m%d%H%M)
|
||||
GIT_SHORT_HASH=$(git rev-parse --short HEAD)
|
||||
APP_VERSION=$PACKAGE_VERSION-nightly-$GIT_SHORT_HASH
|
||||
fi
|
||||
echo $APP_VERSION
|
||||
echo "APP_VERSION=$APP_VERSION" >> "$GITHUB_OUTPUT"
|
||||
./scripts/set-version.sh $APP_VERSION
|
||||
|
||||
7
.github/deployment/node/Dockerfile
vendored
7
.github/deployment/node/Dockerfile
vendored
@@ -7,12 +7,7 @@ COPY ./packages/frontend/apps/mobile/dist /app/static/mobile
|
||||
WORKDIR /app
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends openssl libjemalloc2 && \
|
||||
apt-get install -y --no-install-recommends openssl && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Enable jemalloc by preloading the library
|
||||
ENV LD_PRELOAD=libjemalloc.so.2
|
||||
|
||||
EXPOSE 3010
|
||||
|
||||
CMD ["node", "./dist/main.js"]
|
||||
|
||||
2
.github/helm/affine/Chart.yaml
vendored
2
.github/helm/affine/Chart.yaml
vendored
@@ -3,4 +3,4 @@ name: affine
|
||||
description: AFFiNE cloud chart
|
||||
type: application
|
||||
version: 0.0.0
|
||||
appVersion: "0.26.0"
|
||||
appVersion: "0.21.0"
|
||||
|
||||
2
.github/helm/affine/charts/doc/Chart.yaml
vendored
2
.github/helm/affine/charts/doc/Chart.yaml
vendored
@@ -3,7 +3,7 @@ name: doc
|
||||
description: AFFiNE doc server
|
||||
type: application
|
||||
version: 0.0.0
|
||||
appVersion: "0.26.0"
|
||||
appVersion: "0.20.0"
|
||||
dependencies:
|
||||
- name: gcloud-sql-proxy
|
||||
version: 0.0.0
|
||||
|
||||
2
.github/helm/affine/charts/doc/values.yaml
vendored
2
.github/helm/affine/charts/doc/values.yaml
vendored
@@ -1,6 +1,6 @@
|
||||
replicaCount: 1
|
||||
image:
|
||||
repository: ghcr.io/toeverything/affine
|
||||
repository: ghcr.io/toeverything/affine-graphql
|
||||
pullPolicy: IfNotPresent
|
||||
tag: ''
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
replicaCount: 2
|
||||
replicaCount: 3
|
||||
enabled: false
|
||||
database:
|
||||
connectionName: ""
|
||||
@@ -33,11 +33,8 @@ service:
|
||||
|
||||
resources:
|
||||
limits:
|
||||
memory: "1Gi"
|
||||
cpu: "1"
|
||||
requests:
|
||||
memory: "512Mi"
|
||||
cpu: "100m"
|
||||
memory: "4Gi"
|
||||
cpu: "2"
|
||||
|
||||
volumes: []
|
||||
volumeMounts: []
|
||||
|
||||
@@ -3,7 +3,7 @@ name: graphql
|
||||
description: AFFiNE GraphQL server
|
||||
type: application
|
||||
version: 0.0.0
|
||||
appVersion: "0.26.0"
|
||||
appVersion: "0.21.0"
|
||||
dependencies:
|
||||
- name: gcloud-sql-proxy
|
||||
version: 0.0.0
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
replicaCount: 1
|
||||
image:
|
||||
repository: ghcr.io/toeverything/affine
|
||||
repository: ghcr.io/toeverything/affine-graphql
|
||||
pullPolicy: IfNotPresent
|
||||
tag: ''
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ name: renderer
|
||||
description: AFFiNE renderer server
|
||||
type: application
|
||||
version: 0.0.0
|
||||
appVersion: "0.26.0"
|
||||
appVersion: "0.16.0"
|
||||
dependencies:
|
||||
- name: gcloud-sql-proxy
|
||||
version: 0.0.0
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
replicaCount: 1
|
||||
image:
|
||||
repository: ghcr.io/toeverything/affine
|
||||
repository: ghcr.io/toeverything/affine-graphql
|
||||
pullPolicy: IfNotPresent
|
||||
tag: ''
|
||||
|
||||
|
||||
2
.github/helm/affine/charts/sync/Chart.yaml
vendored
2
.github/helm/affine/charts/sync/Chart.yaml
vendored
@@ -3,7 +3,7 @@ name: sync
|
||||
description: AFFiNE Sync Server
|
||||
type: application
|
||||
version: 0.0.0
|
||||
appVersion: "0.26.0"
|
||||
appVersion: "0.21.0"
|
||||
dependencies:
|
||||
- name: gcloud-sql-proxy
|
||||
version: 0.0.0
|
||||
|
||||
2
.github/helm/affine/charts/sync/values.yaml
vendored
2
.github/helm/affine/charts/sync/values.yaml
vendored
@@ -1,6 +1,6 @@
|
||||
replicaCount: 1
|
||||
image:
|
||||
repository: ghcr.io/toeverything/affine
|
||||
repository: ghcr.io/toeverything/affine-graphql
|
||||
pullPolicy: IfNotPresent
|
||||
tag: ''
|
||||
|
||||
|
||||
14
.github/helm/affine/templates/ingress.yaml
vendored
14
.github/helm/affine/templates/ingress.yaml
vendored
@@ -36,8 +36,7 @@ spec:
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
rules:
|
||||
{{- range .Values.global.ingress.hosts }}
|
||||
- host: {{ . | quote }}
|
||||
- host: "{{ .Values.global.ingress.host }}"
|
||||
http:
|
||||
paths:
|
||||
- path: /socket.io
|
||||
@@ -46,34 +45,33 @@ spec:
|
||||
service:
|
||||
name: affine-sync
|
||||
port:
|
||||
number: {{ $.Values.sync.service.port }}
|
||||
number: {{ .Values.sync.service.port }}
|
||||
- path: /graphql
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: affine-graphql
|
||||
port:
|
||||
number: {{ $.Values.graphql.service.port }}
|
||||
number: {{ .Values.graphql.service.port }}
|
||||
- path: /api
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: affine-graphql
|
||||
port:
|
||||
number: {{ $.Values.graphql.service.port }}
|
||||
number: {{ .Values.graphql.service.port }}
|
||||
- path: /workspace
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: affine-renderer
|
||||
port:
|
||||
number: {{ $.Values.renderer.service.port }}
|
||||
number: {{ .Values.renderer.service.port }}
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: affine-web
|
||||
port:
|
||||
number: {{ $.Values.web.service.port }}
|
||||
{{- end }}
|
||||
number: {{ .Values.web.service.port }}
|
||||
{{- end }}
|
||||
|
||||
8
.github/helm/affine/values.yaml
vendored
8
.github/helm/affine/values.yaml
vendored
@@ -4,13 +4,7 @@ global:
|
||||
ingress:
|
||||
enabled: false
|
||||
className: ''
|
||||
# hosts for ingress rules
|
||||
# e.g.
|
||||
# hosts:
|
||||
# - affine.pro
|
||||
# - www.affine.pro
|
||||
hosts:
|
||||
- affine.pro
|
||||
host: affine.pro
|
||||
tls: []
|
||||
secret:
|
||||
secretName: 'server-private-key'
|
||||
|
||||
61
.github/workflows/build-images.yml
vendored
61
.github/workflows/build-images.yml
vendored
@@ -3,13 +3,7 @@ name: Build Images
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
build-type:
|
||||
type: string
|
||||
required: true
|
||||
app-version:
|
||||
type: string
|
||||
required: true
|
||||
git-short-hash:
|
||||
flavor:
|
||||
type: string
|
||||
required: true
|
||||
|
||||
@@ -22,13 +16,12 @@ jobs:
|
||||
build-web:
|
||||
name: Build @affine/web
|
||||
runs-on: ubuntu-latest
|
||||
environment: ${{ inputs.build-type }}
|
||||
environment: ${{ github.event.inputs.flavor }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Version
|
||||
id: version
|
||||
uses: ./.github/actions/setup-version
|
||||
with:
|
||||
app-version: ${{ inputs.app-version }}
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
- name: Build Core
|
||||
@@ -37,16 +30,15 @@ jobs:
|
||||
R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }}
|
||||
R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
|
||||
R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
|
||||
BUILD_TYPE: ${{ inputs.build-type }}
|
||||
BUILD_TYPE: ${{ github.event.inputs.flavor }}
|
||||
CAPTCHA_SITE_KEY: ${{ secrets.CAPTCHA_SITE_KEY }}
|
||||
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
|
||||
SENTRY_PROJECT: 'affine-web'
|
||||
SENTRY_RELEASE: ${{ inputs.app-version }}
|
||||
SENTRY_RELEASE: ${{ steps.version.outputs.APP_VERSION }}
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
||||
PERFSEE_TOKEN: ${{ secrets.PERFSEE_TOKEN }}
|
||||
MIXPANEL_TOKEN: ${{ secrets.MIXPANEL_TOKEN }}
|
||||
GA4_MEASUREMENT_ID: ${{ secrets.GA4_MEASUREMENT_ID }}
|
||||
- name: Upload web artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
@@ -57,13 +49,12 @@ jobs:
|
||||
build-admin:
|
||||
name: Build @affine/admin
|
||||
runs-on: ubuntu-latest
|
||||
environment: ${{ inputs.build-type }}
|
||||
environment: ${{ github.event.inputs.flavor }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Version
|
||||
id: version
|
||||
uses: ./.github/actions/setup-version
|
||||
with:
|
||||
app-version: ${{ inputs.app-version }}
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
- name: Build Admin
|
||||
@@ -72,7 +63,7 @@ jobs:
|
||||
R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }}
|
||||
R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
|
||||
R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
|
||||
BUILD_TYPE: ${{ inputs.build-type }}
|
||||
BUILD_TYPE: ${{ github.event.inputs.flavor }}
|
||||
CAPTCHA_SITE_KEY: ${{ secrets.CAPTCHA_SITE_KEY }}
|
||||
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
|
||||
SENTRY_PROJECT: 'affine-admin'
|
||||
@@ -80,7 +71,6 @@ jobs:
|
||||
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
||||
PERFSEE_TOKEN: ${{ secrets.PERFSEE_TOKEN }}
|
||||
MIXPANEL_TOKEN: ${{ secrets.MIXPANEL_TOKEN }}
|
||||
GA4_MEASUREMENT_ID: ${{ secrets.GA4_MEASUREMENT_ID }}
|
||||
- name: Upload admin artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
@@ -91,13 +81,12 @@ jobs:
|
||||
build-mobile:
|
||||
name: Build @affine/mobile
|
||||
runs-on: ubuntu-latest
|
||||
environment: ${{ inputs.build-type }}
|
||||
environment: ${{ github.event.inputs.flavor }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Version
|
||||
id: version
|
||||
uses: ./.github/actions/setup-version
|
||||
with:
|
||||
app-version: ${{ inputs.app-version }}
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
- name: Build Mobile
|
||||
@@ -106,7 +95,7 @@ jobs:
|
||||
R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }}
|
||||
R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
|
||||
R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
|
||||
BUILD_TYPE: ${{ inputs.build-type }}
|
||||
BUILD_TYPE: ${{ github.event.inputs.flavor }}
|
||||
CAPTCHA_SITE_KEY: ${{ secrets.CAPTCHA_SITE_KEY }}
|
||||
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
|
||||
SENTRY_PROJECT: 'affine-mobile'
|
||||
@@ -114,7 +103,6 @@ jobs:
|
||||
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
||||
PERFSEE_TOKEN: ${{ secrets.PERFSEE_TOKEN }}
|
||||
MIXPANEL_TOKEN: ${{ secrets.MIXPANEL_TOKEN }}
|
||||
GA4_MEASUREMENT_ID: ${{ secrets.GA4_MEASUREMENT_ID }}
|
||||
- name: Upload mobile artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
@@ -125,7 +113,7 @@ jobs:
|
||||
build-server-native:
|
||||
name: Build Server native - ${{ matrix.targets.name }}
|
||||
runs-on: ubuntu-latest
|
||||
environment: ${{ inputs.build-type }}
|
||||
environment: ${{ github.event.inputs.flavor }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -140,9 +128,8 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Version
|
||||
id: version
|
||||
uses: ./.github/actions/setup-version
|
||||
with:
|
||||
app-version: ${{ inputs.app-version }}
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
@@ -174,9 +161,8 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Version
|
||||
id: version
|
||||
uses: ./.github/actions/setup-version
|
||||
with:
|
||||
app-version: ${{ inputs.app-version }}
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
@@ -190,6 +176,8 @@ jobs:
|
||||
path: ./packages/backend/native
|
||||
- name: List server-native files
|
||||
run: ls -alh ./packages/backend/native
|
||||
- name: Build @affine/reader
|
||||
run: yarn workspace @affine/reader build
|
||||
- name: Build Server
|
||||
run: yarn workspace @affine/server build
|
||||
- name: Upload server dist
|
||||
@@ -214,6 +202,16 @@ jobs:
|
||||
with:
|
||||
name: server-dist
|
||||
path: ./packages/backend/server/dist
|
||||
- name: Setup env
|
||||
run: |
|
||||
echo "GIT_SHORT_HASH=$(git rev-parse --short HEAD)" >> "$GITHUB_ENV"
|
||||
if [ -z "${{ inputs.flavor }}" ]
|
||||
then
|
||||
echo "RELEASE_FLAVOR=canary" >> "$GITHUB_ENV"
|
||||
else
|
||||
echo "RELEASE_FLAVOR=${{ inputs.flavor }}" >> "$GITHUB_ENV"
|
||||
fi
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
@@ -265,9 +263,8 @@ jobs:
|
||||
run: mv ./node_modules ./packages/backend/server
|
||||
|
||||
- name: Setup Version
|
||||
id: version
|
||||
uses: ./.github/actions/setup-version
|
||||
with:
|
||||
app-version: ${{ inputs.app-version }}
|
||||
|
||||
- name: Build front Dockerfile
|
||||
uses: docker/build-push-action@v6
|
||||
@@ -278,7 +275,7 @@ jobs:
|
||||
platforms: linux/amd64,linux/arm64
|
||||
provenance: true
|
||||
file: .github/deployment/front/Dockerfile
|
||||
tags: ghcr.io/toeverything/affine-front:${{inputs.build-type}}-${{ inputs.git-short-hash }}
|
||||
tags: ghcr.io/toeverything/affine-front:${{env.RELEASE_FLAVOR}}-${{ env.GIT_SHORT_HASH }},ghcr.io/toeverything/affine-front:${{env.RELEASE_FLAVOR}}
|
||||
|
||||
- name: Build graphql Dockerfile
|
||||
uses: docker/build-push-action@v6
|
||||
@@ -289,4 +286,4 @@ jobs:
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||
provenance: true
|
||||
file: .github/deployment/node/Dockerfile
|
||||
tags: ghcr.io/toeverything/affine:${{inputs.build-type}}-${{ inputs.git-short-hash }}
|
||||
tags: ghcr.io/toeverything/affine-graphql:${{env.RELEASE_FLAVOR}}-${{ env.GIT_SHORT_HASH }},ghcr.io/toeverything/affine-graphql:${{env.RELEASE_FLAVOR}}
|
||||
|
||||
25
.github/workflows/build-selfhost-image.yml
vendored
Normal file
25
.github/workflows/build-selfhost-image.yml
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
name: Build Selfhost Image
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
flavor:
|
||||
description: 'Select distribution to build'
|
||||
type: choice
|
||||
default: canary
|
||||
options:
|
||||
- canary
|
||||
- beta
|
||||
- stable
|
||||
|
||||
permissions:
|
||||
contents: 'write'
|
||||
id-token: 'write'
|
||||
packages: 'write'
|
||||
|
||||
jobs:
|
||||
build-image:
|
||||
name: Build Image
|
||||
uses: ./.github/workflows/build-images.yml
|
||||
with:
|
||||
flavor: ${{ github.event.inputs.flavor }}
|
||||
203
.github/workflows/build-test.yml
vendored
203
.github/workflows/build-test.yml
vendored
@@ -11,7 +11,6 @@ on:
|
||||
paths-ignore:
|
||||
- README.md
|
||||
pull_request:
|
||||
merge_group:
|
||||
|
||||
env:
|
||||
DEBUG: napi:*
|
||||
@@ -19,7 +18,7 @@ env:
|
||||
APP_NAME: affine
|
||||
AFFINE_ENV: dev
|
||||
COVERAGE: true
|
||||
MACOSX_DEPLOYMENT_TARGET: '11.6'
|
||||
MACOSX_DEPLOYMENT_TARGET: '10.13'
|
||||
DEPLOYMENT_TYPE: affine
|
||||
AFFINE_INDEXER_ENABLED: true
|
||||
|
||||
@@ -28,11 +27,26 @@ concurrency:
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
optimize_ci:
|
||||
name: Optimize CI
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
skip: ${{ steps.check_skip.outputs.skip }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Graphite CI Optimizer
|
||||
uses: withgraphite/graphite-ci-action@main
|
||||
id: check_skip
|
||||
with:
|
||||
graphite_token: ${{ secrets.GRAPHITE_CI_OPTIMIZER_TOKEN }}
|
||||
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
NODE_OPTIONS: --max-old-space-size=14384
|
||||
needs: optimize_ci
|
||||
if: needs.optimize_ci.outputs.skip == 'false'
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
@@ -66,6 +80,9 @@ jobs:
|
||||
lint:
|
||||
name: Lint
|
||||
runs-on: ubuntu-24.04-arm
|
||||
needs: optimize_ci
|
||||
if: needs.optimize_ci.outputs.skip == 'false'
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Run oxlint
|
||||
@@ -91,6 +108,8 @@ jobs:
|
||||
typecheck:
|
||||
name: Typecheck
|
||||
runs-on: ubuntu-24.04-arm
|
||||
needs: optimize_ci
|
||||
if: needs.optimize_ci.outputs.skip == 'false'
|
||||
env:
|
||||
NODE_OPTIONS: --max-old-space-size=14384
|
||||
steps:
|
||||
@@ -118,6 +137,8 @@ jobs:
|
||||
lint-rust:
|
||||
name: Lint Rust
|
||||
runs-on: ubuntu-latest
|
||||
needs: optimize_ci
|
||||
if: needs.optimize_ci.outputs.skip == 'false'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/build-rust
|
||||
@@ -138,7 +159,9 @@ jobs:
|
||||
name: Check Git Status
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- optimize_ci
|
||||
- build-server-native
|
||||
if: needs.optimize_ci.outputs.skip == 'false'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Node.js
|
||||
@@ -152,6 +175,11 @@ jobs:
|
||||
name: server-native.node
|
||||
path: ./packages/backend/native
|
||||
|
||||
- name: Bundle @affine/reader
|
||||
shell: bash
|
||||
run: |
|
||||
yarn workspace @affine/reader build
|
||||
|
||||
- name: Run Check
|
||||
run: |
|
||||
yarn affine init
|
||||
@@ -169,6 +197,8 @@ jobs:
|
||||
check-yarn-binary:
|
||||
name: Check yarn binary
|
||||
runs-on: ubuntu-latest
|
||||
needs: optimize_ci
|
||||
if: needs.optimize_ci.outputs.skip == 'false'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Run check
|
||||
@@ -179,10 +209,12 @@ jobs:
|
||||
e2e-blocksuite-test:
|
||||
name: E2E BlockSuite Test
|
||||
runs-on: ubuntu-latest
|
||||
needs: optimize_ci
|
||||
if: needs.optimize_ci.outputs.skip == 'false'
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
shard: [1, 2]
|
||||
shard: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Node.js
|
||||
@@ -210,10 +242,12 @@ jobs:
|
||||
e2e-blocksuite-cross-browser-test:
|
||||
name: E2E BlockSuite Cross Browser Test
|
||||
runs-on: ubuntu-latest
|
||||
needs: optimize_ci
|
||||
if: needs.optimize_ci.outputs.skip == 'false'
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
shard: [1]
|
||||
shard: [1, 2]
|
||||
browser: ['chromium', 'firefox', 'webkit']
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -244,6 +278,8 @@ jobs:
|
||||
e2e-test:
|
||||
name: E2E Test
|
||||
runs-on: ubuntu-24.04-arm
|
||||
needs: optimize_ci
|
||||
if: needs.optimize_ci.outputs.skip == 'false'
|
||||
env:
|
||||
DISTRIBUTION: web
|
||||
IN_CI_TEST: true
|
||||
@@ -251,7 +287,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
shard: [1, 2, 3, 4, 5]
|
||||
shard: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Node.js
|
||||
@@ -276,13 +312,15 @@ jobs:
|
||||
e2e-mobile-test:
|
||||
name: E2E Mobile Test
|
||||
runs-on: ubuntu-latest
|
||||
needs: optimize_ci
|
||||
if: needs.optimize_ci.outputs.skip == 'false'
|
||||
env:
|
||||
DISTRIBUTION: mobile
|
||||
IN_CI_TEST: true
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
shard: [1, 2]
|
||||
shard: [1, 2, 3, 4, 5]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Node.js
|
||||
@@ -307,13 +345,15 @@ jobs:
|
||||
name: Unit Test
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- optimize_ci
|
||||
- build-native
|
||||
if: needs.optimize_ci.outputs.skip == 'false'
|
||||
env:
|
||||
DISTRIBUTION: web
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
shard: [1, 2, 3]
|
||||
shard: [1, 2, 3, 4, 5]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Node.js
|
||||
@@ -344,6 +384,8 @@ jobs:
|
||||
build-native:
|
||||
name: Build AFFiNE native (${{ matrix.spec.target }})
|
||||
runs-on: ${{ matrix.spec.os }}
|
||||
needs: optimize_ci
|
||||
if: needs.optimize_ci.outputs.skip == 'false'
|
||||
env:
|
||||
CARGO_PROFILE_RELEASE_DEBUG: '1'
|
||||
strategy:
|
||||
@@ -386,6 +428,8 @@ jobs:
|
||||
build-windows-native:
|
||||
name: Build AFFiNE native (${{ matrix.spec.target }})
|
||||
runs-on: ${{ matrix.spec.os }}
|
||||
needs: optimize_ci
|
||||
if: needs.optimize_ci.outputs.skip == 'false'
|
||||
env:
|
||||
CARGO_PROFILE_RELEASE_DEBUG: '1'
|
||||
strategy:
|
||||
@@ -433,6 +477,8 @@ jobs:
|
||||
build-server-native:
|
||||
name: Build Server native
|
||||
runs-on: ubuntu-latest
|
||||
needs: optimize_ci
|
||||
if: needs.optimize_ci.outputs.skip == 'false'
|
||||
env:
|
||||
CARGO_PROFILE_RELEASE_DEBUG: '1'
|
||||
steps:
|
||||
@@ -458,6 +504,8 @@ jobs:
|
||||
build-electron-renderer:
|
||||
name: Build @affine/electron renderer
|
||||
runs-on: ubuntu-latest
|
||||
needs: optimize_ci
|
||||
if: needs.optimize_ci.outputs.skip == 'false'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Node.js
|
||||
@@ -483,7 +531,9 @@ jobs:
|
||||
name: Native Unit Test
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- optimize_ci
|
||||
- build-native
|
||||
if: needs.optimize_ci.outputs.skip == 'false'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Node.js
|
||||
@@ -503,12 +553,14 @@ jobs:
|
||||
name: Server Test
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- optimize_ci
|
||||
- build-server-native
|
||||
if: needs.optimize_ci.outputs.skip == 'false'
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
node_index: [0, 1, 2, 3]
|
||||
total_nodes: [4]
|
||||
node_index: [0, 1, 2, 3, 4, 5, 6, 7]
|
||||
total_nodes: [8]
|
||||
env:
|
||||
NODE_ENV: test
|
||||
DATABASE_URL: postgresql://affine:affine@localhost:5432/affine
|
||||
@@ -535,7 +587,7 @@ jobs:
|
||||
- 1025:1025
|
||||
- 8025:8025
|
||||
indexer:
|
||||
image: manticoresearch/manticore:10.1.0
|
||||
image: manticoresearch/manticore:9.3.2
|
||||
ports:
|
||||
- 9308:9308
|
||||
steps:
|
||||
@@ -576,7 +628,9 @@ jobs:
|
||||
name: Server Test with Elasticsearch
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- optimize_ci
|
||||
- build-server-native
|
||||
if: needs.optimize_ci.outputs.skip == 'false'
|
||||
strategy:
|
||||
fail-fast: false
|
||||
env:
|
||||
@@ -659,7 +713,9 @@ jobs:
|
||||
name: Server E2E Test
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- optimize_ci
|
||||
- build-server-native
|
||||
if: needs.optimize_ci.outputs.skip == 'false'
|
||||
env:
|
||||
NODE_ENV: test
|
||||
DATABASE_URL: postgresql://affine:affine@localhost:5432/affine
|
||||
@@ -681,7 +737,7 @@ jobs:
|
||||
ports:
|
||||
- 6379:6379
|
||||
indexer:
|
||||
image: manticoresearch/manticore:10.1.0
|
||||
image: manticoresearch/manticore:9.3.2
|
||||
ports:
|
||||
- 9308:9308
|
||||
steps:
|
||||
@@ -717,6 +773,9 @@ jobs:
|
||||
miri:
|
||||
name: miri code check
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- optimize_ci
|
||||
if: needs.optimize_ci.outputs.skip == 'false'
|
||||
env:
|
||||
RUST_BACKTRACE: full
|
||||
CARGO_TERM_COLOR: always
|
||||
@@ -730,9 +789,7 @@ jobs:
|
||||
toolchain: nightly
|
||||
components: miri
|
||||
- name: Install latest nextest release
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: nextest@0.9.98
|
||||
uses: taiki-e/install-action@nextest
|
||||
|
||||
- name: Miri Code Check
|
||||
continue-on-error: true
|
||||
@@ -742,6 +799,9 @@ jobs:
|
||||
loom:
|
||||
name: loom thread test
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- optimize_ci
|
||||
if: needs.optimize_ci.outputs.skip == 'false'
|
||||
env:
|
||||
RUSTFLAGS: --cfg loom
|
||||
RUST_BACKTRACE: full
|
||||
@@ -754,9 +814,7 @@ jobs:
|
||||
with:
|
||||
toolchain: stable
|
||||
- name: Install latest nextest release
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: nextest@0.9.98
|
||||
uses: taiki-e/install-action@nextest
|
||||
|
||||
- name: Loom Thread Test
|
||||
run: |
|
||||
@@ -765,6 +823,9 @@ jobs:
|
||||
fuzzing:
|
||||
name: fuzzing
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- optimize_ci
|
||||
if: needs.optimize_ci.outputs.skip == 'false'
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
steps:
|
||||
@@ -798,9 +859,57 @@ jobs:
|
||||
name: fuzz-artifact
|
||||
path: packages/common/y-octo/utils/fuzz/artifacts/**/*
|
||||
|
||||
y-octo-binding-test:
|
||||
name: y-octo binding test on ${{ matrix.settings.target }}
|
||||
runs-on: ${{ matrix.settings.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
settings:
|
||||
- { target: 'x86_64-unknown-linux-gnu', os: 'ubuntu-latest' }
|
||||
- { target: 'aarch64-unknown-linux-gnu', os: 'ubuntu-24.04-arm' }
|
||||
- { target: 'x86_64-apple-darwin', os: 'macos-13' }
|
||||
- { target: 'aarch64-apple-darwin', os: 'macos-latest' }
|
||||
- { target: 'x86_64-pc-windows-msvc', os: 'windows-latest' }
|
||||
- { target: 'aarch64-pc-windows-msvc', os: 'windows-11-arm' }
|
||||
needs:
|
||||
- optimize_ci
|
||||
if: needs.optimize_ci.outputs.skip == 'false'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
extra-flags: workspaces focus @affine-tools/cli @affine/monorepo @y-octo/node
|
||||
electron-install: false
|
||||
- name: Install rustup (Windows 11 ARM)
|
||||
if: matrix.settings.os == 'windows-11-arm'
|
||||
shell: pwsh
|
||||
run: |
|
||||
Invoke-WebRequest -Uri "https://static.rust-lang.org/rustup/dist/aarch64-pc-windows-msvc/rustup-init.exe" -OutFile rustup-init.exe
|
||||
.\rustup-init.exe --default-toolchain none -y
|
||||
"$env:USERPROFILE\.cargo\bin" | Out-File -Append -Encoding ascii $env:GITHUB_PATH
|
||||
"CARGO_HOME=$env:USERPROFILE\.cargo" | Out-File -Append -Encoding ascii $env:GITHUB_ENV
|
||||
- name: Install Rust (Windows 11 ARM)
|
||||
if: matrix.settings.os == 'windows-11-arm'
|
||||
shell: pwsh
|
||||
run: |
|
||||
rustup install stable
|
||||
rustup target add ${{ matrix.settings.target }}
|
||||
cargo --version
|
||||
- name: Build Rust
|
||||
uses: ./.github/actions/build-rust
|
||||
with:
|
||||
target: ${{ matrix.settings.target }}
|
||||
package: '@y-octo/node'
|
||||
- name: Run tests
|
||||
run: yarn affine @y-octo/node test
|
||||
|
||||
rust-test:
|
||||
name: Run native tests
|
||||
runs-on: ubuntu-latest
|
||||
needs: optimize_ci
|
||||
if: needs.optimize_ci.outputs.skip == 'false'
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
steps:
|
||||
@@ -812,9 +921,7 @@ jobs:
|
||||
no-build: 'true'
|
||||
|
||||
- name: Install latest nextest release
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: nextest@0.9.98
|
||||
uses: taiki-e/install-action@nextest
|
||||
|
||||
- name: Run tests
|
||||
run: cargo nextest run --workspace --exclude affine_server_native --features use-as-lib --release --no-fail-fast
|
||||
@@ -823,7 +930,9 @@ jobs:
|
||||
name: Server Copilot Api Test
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- optimize_ci
|
||||
- build-server-native
|
||||
if: needs.optimize_ci.outputs.skip == 'false'
|
||||
env:
|
||||
NODE_ENV: test
|
||||
DISTRIBUTION: web
|
||||
@@ -851,7 +960,7 @@ jobs:
|
||||
- 1025:1025
|
||||
- 8025:8025
|
||||
indexer:
|
||||
image: manticoresearch/manticore:10.1.0
|
||||
image: manticoresearch/manticore:9.3.2
|
||||
ports:
|
||||
- 9308:9308
|
||||
steps:
|
||||
@@ -924,8 +1033,8 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
shardIndex: [1, 2, 3, 4, 5]
|
||||
shardTotal: [5]
|
||||
shardIndex: [1, 2, 3, 4, 5, 6, 7, 8]
|
||||
shardTotal: [8]
|
||||
needs:
|
||||
- build-server-native
|
||||
services:
|
||||
@@ -945,7 +1054,7 @@ jobs:
|
||||
ports:
|
||||
- 6379:6379
|
||||
indexer:
|
||||
image: manticoresearch/manticore:10.1.0
|
||||
image: manticoresearch/manticore:9.3.2
|
||||
ports:
|
||||
- 9308:9308
|
||||
steps:
|
||||
@@ -1005,8 +1114,10 @@ jobs:
|
||||
name: ${{ matrix.tests.name }}
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- optimize_ci
|
||||
- build-server-native
|
||||
- build-native
|
||||
if: needs.optimize_ci.outputs.skip == 'false'
|
||||
env:
|
||||
DISTRIBUTION: web
|
||||
DATABASE_URL: postgresql://affine:affine@localhost:5432/affine
|
||||
@@ -1016,21 +1127,24 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
tests:
|
||||
- name: 'Cloud E2E Test 1/10'
|
||||
- name: 'Cloud E2E Test 1/6'
|
||||
shard: 1
|
||||
script: yarn affine @affine-test/affine-cloud e2e --forbid-only --shard=1/10
|
||||
- name: 'Cloud E2E Test 2/10'
|
||||
script: yarn affine @affine-test/affine-cloud e2e --forbid-only --shard=1/6
|
||||
- name: 'Cloud E2E Test 2/6'
|
||||
shard: 2
|
||||
script: yarn affine @affine-test/affine-cloud e2e --forbid-only --shard=2/10
|
||||
- name: 'Cloud E2E Test 3/10'
|
||||
script: yarn affine @affine-test/affine-cloud e2e --forbid-only --shard=2/6
|
||||
- name: 'Cloud E2E Test 3/6'
|
||||
shard: 3
|
||||
script: yarn affine @affine-test/affine-cloud e2e --forbid-only --shard=3/10
|
||||
- name: 'Cloud E2E Test 4/10'
|
||||
script: yarn affine @affine-test/affine-cloud e2e --forbid-only --shard=3/6
|
||||
- name: 'Cloud E2E Test 4/6'
|
||||
shard: 4
|
||||
script: yarn affine @affine-test/affine-cloud e2e --forbid-only --shard=4/10
|
||||
- name: 'Cloud E2E Test 5/10'
|
||||
script: yarn affine @affine-test/affine-cloud e2e --forbid-only --shard=4/6
|
||||
- name: 'Cloud E2E Test 5/6'
|
||||
shard: 5
|
||||
script: yarn affine @affine-test/affine-cloud e2e --forbid-only --shard=5/10
|
||||
script: yarn affine @affine-test/affine-cloud e2e --forbid-only --shard=5/6
|
||||
- name: 'Cloud E2E Test 6/6'
|
||||
shard: 6
|
||||
script: yarn affine @affine-test/affine-cloud e2e --forbid-only --shard=6/6
|
||||
- name: 'Cloud Desktop E2E Test'
|
||||
shard: desktop
|
||||
script: |
|
||||
@@ -1062,7 +1176,7 @@ jobs:
|
||||
- 1025:1025
|
||||
- 8025:8025
|
||||
indexer:
|
||||
image: manticoresearch/manticore:10.1.0
|
||||
image: manticoresearch/manticore:9.3.2
|
||||
ports:
|
||||
- 9308:9308
|
||||
steps:
|
||||
@@ -1107,8 +1221,10 @@ jobs:
|
||||
name: Desktop Test (${{ matrix.spec.os }}, ${{ matrix.spec.platform }}, ${{ matrix.spec.arch }}, ${{ matrix.spec.target }}, ${{ matrix.spec.test }})
|
||||
runs-on: ${{ matrix.spec.os }}
|
||||
needs:
|
||||
- optimize_ci
|
||||
- build-electron-renderer
|
||||
- build-native
|
||||
if: needs.optimize_ci.outputs.skip == 'false'
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -1203,8 +1319,10 @@ jobs:
|
||||
name: Desktop bundle check (${{ matrix.spec.os }}, ${{ matrix.spec.platform }}, ${{ matrix.spec.arch }}, ${{ matrix.spec.target }}, ${{ matrix.spec.test }})
|
||||
runs-on: ${{ matrix.spec.os }}
|
||||
needs:
|
||||
- optimize_ci
|
||||
- build-electron-renderer
|
||||
- build-native
|
||||
if: needs.optimize_ci.outputs.skip == 'false'
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -1293,7 +1411,7 @@ jobs:
|
||||
run: |
|
||||
sudo add-apt-repository universe
|
||||
sudo apt install -y libfuse2 elfutils flatpak flatpak-builder
|
||||
flatpak remote-add --user --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo
|
||||
flatpak remote-add --user --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
|
||||
flatpak update
|
||||
# some flatpak deps need git protocol.file.allow
|
||||
git config --global protocol.file.allow always
|
||||
@@ -1308,6 +1426,17 @@ jobs:
|
||||
run: |
|
||||
yarn affine @affine/electron node ./scripts/macos-arm64-output-check.ts
|
||||
|
||||
test-build-mobile-app:
|
||||
uses: ./.github/workflows/release-mobile.yml
|
||||
needs: optimize_ci
|
||||
if: needs.optimize_ci.outputs.skip == 'false'
|
||||
with:
|
||||
build-type: canary
|
||||
build-target: development
|
||||
secrets: inherit
|
||||
permissions:
|
||||
id-token: 'write'
|
||||
|
||||
test-done:
|
||||
needs:
|
||||
- analyze
|
||||
@@ -1329,6 +1458,7 @@ jobs:
|
||||
- miri
|
||||
- loom
|
||||
- fuzzing
|
||||
- y-octo-binding-test
|
||||
- server-test
|
||||
- server-e2e-test
|
||||
- rust-test
|
||||
@@ -1337,6 +1467,7 @@ jobs:
|
||||
- desktop-test
|
||||
- desktop-bundle-check
|
||||
- cloud-e2e-test
|
||||
- test-build-mobile-app
|
||||
if: always()
|
||||
runs-on: ubuntu-latest
|
||||
name: 3, 2, 1 Launch
|
||||
|
||||
8
.github/workflows/copilot-test.yml
vendored
8
.github/workflows/copilot-test.yml
vendored
@@ -60,7 +60,7 @@ jobs:
|
||||
- 1025:1025
|
||||
- 8025:8025
|
||||
indexer:
|
||||
image: manticoresearch/manticore:10.1.0
|
||||
image: manticoresearch/manticore:9.3.2
|
||||
ports:
|
||||
- 9308:9308
|
||||
steps:
|
||||
@@ -109,8 +109,8 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
shardIndex: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
||||
shardTotal: [10]
|
||||
shardIndex: [1, 2, 3, 4, 5, 6, 7, 8]
|
||||
shardTotal: [8]
|
||||
needs:
|
||||
- build-server-native
|
||||
services:
|
||||
@@ -130,7 +130,7 @@ jobs:
|
||||
ports:
|
||||
- 6379:6379
|
||||
indexer:
|
||||
image: manticoresearch/manticore:10.1.0
|
||||
image: manticoresearch/manticore:9.3.2
|
||||
ports:
|
||||
- 9308:9308
|
||||
steps:
|
||||
|
||||
32
.github/workflows/deploy-automatically.yml
vendored
Normal file
32
.github/workflows/deploy-automatically.yml
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
name: Deploy Automatically
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v[0-9]+.[0-9]+.[0-9]+-canary.[0-9]+'
|
||||
schedule:
|
||||
- cron: '0 9 * * *'
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
actions: write
|
||||
|
||||
jobs:
|
||||
dispatch-deploy:
|
||||
runs-on: ubuntu-latest
|
||||
name: Setup Deploy
|
||||
steps:
|
||||
- name: dispatch deploy by tag
|
||||
if: ${{ github.event_name == 'push' }}
|
||||
uses: benc-uk/workflow-dispatch@v1
|
||||
with:
|
||||
workflow: deploy.yml
|
||||
inputs: '{ "flavor": "canary" }'
|
||||
- name: dispatch deploy by schedule
|
||||
if: ${{ github.event_name == 'schedule' }}
|
||||
uses: benc-uk/workflow-dispatch@v1
|
||||
with:
|
||||
workflow: deploy.yml
|
||||
inputs: '{ "flavor": "canary" }'
|
||||
ref: canary
|
||||
189
.github/workflows/deploy.yml
vendored
Normal file
189
.github/workflows/deploy.yml
vendored
Normal file
@@ -0,0 +1,189 @@
|
||||
name: Deploy
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
flavor:
|
||||
description: 'Select what enverionment to deploy to'
|
||||
type: choice
|
||||
default: canary
|
||||
options:
|
||||
- canary
|
||||
- beta
|
||||
- stable
|
||||
- internal
|
||||
|
||||
permissions:
|
||||
contents: 'write'
|
||||
id-token: 'write'
|
||||
packages: 'write'
|
||||
|
||||
jobs:
|
||||
output-prev-version:
|
||||
name: Output previous version
|
||||
runs-on: ubuntu-latest
|
||||
environment: ${{ github.event.inputs.flavor }}
|
||||
outputs:
|
||||
prev: ${{ steps.print.outputs.version }}
|
||||
namespace: ${{ steps.print.outputs.namespace }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Auth to Cluster
|
||||
uses: './.github/actions/cluster-auth'
|
||||
with:
|
||||
gcp-project-number: ${{ secrets.GCP_PROJECT_NUMBER }}
|
||||
gcp-project-id: ${{ secrets.GCP_PROJECT_ID }}
|
||||
service-account: ${{ secrets.GCP_HELM_DEPLOY_SERVICE_ACCOUNT }}
|
||||
cluster-name: ${{ secrets.GCP_CLUSTER_NAME }}
|
||||
cluster-location: ${{ secrets.GCP_CLUSTER_LOCATION }}
|
||||
- name: Output previous version
|
||||
id: print
|
||||
run: |
|
||||
namespace=""
|
||||
if [ "${{ github.event.inputs.flavor }}" = "canary" ]; then
|
||||
namespace="dev"
|
||||
elif [ "${{ github.event.inputs.flavor }}" = "beta" ]; then
|
||||
namespace="beta"
|
||||
elif [ "${{ github.event.inputs.flavor }}" = "stable" ]; then
|
||||
namespace="production"
|
||||
else
|
||||
echo "Invalid flavor: ${{ github.event.inputs.flavor }}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Namespace set to: $namespace"
|
||||
|
||||
# Get the previous version from the deployment
|
||||
prev_version=$(kubectl get deployment -n $namespace affine-graphql -o=jsonpath='{.spec.template.spec.containers[0].image}' | awk -F '-' '{print $3}')
|
||||
|
||||
echo "Previous version: $prev_version"
|
||||
echo "version=$prev_version" >> $GITHUB_OUTPUT
|
||||
echo "namesapce=$namespace" >> $GITHUB_OUTPUT
|
||||
|
||||
build-images:
|
||||
name: Build Images
|
||||
uses: ./.github/workflows/build-images.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
flavor: ${{ github.event.inputs.flavor }}
|
||||
|
||||
deploy:
|
||||
name: Deploy to cluster
|
||||
if: ${{ github.event_name == 'workflow_dispatch' }}
|
||||
environment: ${{ github.event.inputs.flavor }}
|
||||
needs:
|
||||
- build-images
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Version
|
||||
id: version
|
||||
uses: ./.github/actions/setup-version
|
||||
- name: Deploy to ${{ github.event.inputs.flavor }}
|
||||
uses: ./.github/actions/deploy
|
||||
with:
|
||||
build-type: ${{ github.event.inputs.flavor }}
|
||||
gcp-project-number: ${{ secrets.GCP_PROJECT_NUMBER }}
|
||||
gcp-project-id: ${{ secrets.GCP_PROJECT_ID }}
|
||||
service-account: ${{ secrets.GCP_HELM_DEPLOY_SERVICE_ACCOUNT }}
|
||||
cluster-name: ${{ secrets.GCP_CLUSTER_NAME }}
|
||||
cluster-location: ${{ secrets.GCP_CLUSTER_LOCATION }}
|
||||
env:
|
||||
APP_VERSION: ${{ steps.version.outputs.APP_VERSION }}
|
||||
DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
|
||||
CANARY_DEPLOY_HOST: ${{ secrets.CANARY_DEPLOY_HOST }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
DATABASE_URL: ${{ secrets.DATABASE_URL }}
|
||||
DATABASE_USERNAME: ${{ secrets.DATABASE_USERNAME }}
|
||||
DATABASE_PASSWORD: ${{ secrets.DATABASE_PASSWORD }}
|
||||
DATABASE_NAME: ${{ secrets.DATABASE_NAME }}
|
||||
GCLOUD_CONNECTION_NAME: ${{ secrets.GCLOUD_CONNECTION_NAME }}
|
||||
REDIS_SERVER_HOST: ${{ secrets.REDIS_SERVER_HOST }}
|
||||
REDIS_SERVER_PASSWORD: ${{ secrets.REDIS_SERVER_PASSWORD }}
|
||||
CLOUD_SQL_IAM_ACCOUNT: ${{ secrets.CLOUD_SQL_IAM_ACCOUNT }}
|
||||
APP_IAM_ACCOUNT: ${{ secrets.APP_IAM_ACCOUNT }}
|
||||
STATIC_IP_NAME: ${{ secrets.STATIC_IP_NAME }}
|
||||
AFFINE_INDEXER_SEARCH_PROVIDER: ${{ secrets.AFFINE_INDEXER_SEARCH_PROVIDER }}
|
||||
AFFINE_INDEXER_SEARCH_ENDPOINT: ${{ secrets.AFFINE_INDEXER_SEARCH_ENDPOINT }}
|
||||
AFFINE_INDEXER_SEARCH_API_KEY: ${{ secrets.AFFINE_INDEXER_SEARCH_API_KEY }}
|
||||
|
||||
deploy-done:
|
||||
needs:
|
||||
- output-prev-version
|
||||
- build-images
|
||||
- deploy
|
||||
if: always()
|
||||
runs-on: ubuntu-latest
|
||||
name: Post deploy message
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
repository: toeverything/blocksuite
|
||||
path: blocksuite
|
||||
fetch-depth: 0
|
||||
fetch-tags: true
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
extra-flags: 'workspaces focus @affine/changelog'
|
||||
electron-install: false
|
||||
- name: Output deployed info
|
||||
if: ${{ always() && !contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled') }}
|
||||
id: set_info
|
||||
run: |
|
||||
if [ "${{ github.event.inputs.flavor }}" = "canary" ]; then
|
||||
echo "deployed_url=https://affine.fail" >> $GITHUB_OUTPUT
|
||||
elif [ "${{ github.event.inputs.flavor }}" = "beta" ]; then
|
||||
echo "deployed_url=https://insider.affine.pro" >> $GITHUB_OUTPUT
|
||||
elif [ "${{ github.event.inputs.flavor }}" = "stable" ]; then
|
||||
echo "deployed_url=https://app.affine.pro" >> $GITHUB_OUTPUT
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
env:
|
||||
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
|
||||
- name: Post Success event to a Slack channel
|
||||
if: ${{ always() && !contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled') }}
|
||||
run: node ./tools/changelog/index.js
|
||||
env:
|
||||
CHANNEL_ID: ${{ secrets.RELEASE_SLACK_CHNNEL_ID }}
|
||||
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
|
||||
DEPLOYED_URL: ${{ steps.set_info.outputs.deployed_url }}
|
||||
PREV_VERSION: ${{ needs.output-prev-version.outputs.prev }}
|
||||
NAMESPACE: ${{ needs.output-prev-version.outputs.namespace }}
|
||||
DEPLOYMENT: 'SERVER'
|
||||
FLAVOR: ${{ github.event.inputs.flavor }}
|
||||
BLOCKSUITE_REPO_PATH: ${{ github.workspace }}/blocksuite
|
||||
- name: Post Failed event to a Slack channel
|
||||
id: failed-slack
|
||||
uses: slackapi/slack-github-action@v2.1.0
|
||||
if: ${{ always() && contains(needs.*.result, 'failure') }}
|
||||
with:
|
||||
method: chat.postMessage
|
||||
token: ${{ secrets.SLACK_BOT_TOKEN }}
|
||||
payload: |
|
||||
channel: ${{ secrets.RELEASE_SLACK_CHNNEL_ID }}
|
||||
text: "<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|Backend deploy failed `${{ github.event.inputs.flavor }}`>"
|
||||
blocks:
|
||||
- type: section
|
||||
text:
|
||||
type: mrkdwn
|
||||
text: "<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|Backend deploy failed `${{ github.event.inputs.flavor }}`>"
|
||||
- name: Post Cancel event to a Slack channel
|
||||
id: cancel-slack
|
||||
uses: slackapi/slack-github-action@v2.1.0
|
||||
if: ${{ always() && contains(needs.*.result, 'cancelled') && !contains(needs.*.result, 'failure') }}
|
||||
with:
|
||||
token: ${{ secrets.SLACK_BOT_TOKEN }}
|
||||
method: chat.postMessage
|
||||
payload: |
|
||||
channel: ${{ secrets.RELEASE_SLACK_CHNNEL_ID }}
|
||||
text: "<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|Backend deploy cancelled `${{ github.event.inputs.flavor }}`>"
|
||||
blocks:
|
||||
- type: section
|
||||
text:
|
||||
type: mrkdwn
|
||||
text: "<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|Backend deploy cancelled `${{ github.event.inputs.flavor }}`>"
|
||||
66
.github/workflows/helm-releaser.yml
vendored
Normal file
66
.github/workflows/helm-releaser.yml
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
name: Release Charts
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [canary]
|
||||
paths:
|
||||
- '.github/helm/**/Chart.yml'
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Checkout Helm chart repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: toeverything/helm-charts
|
||||
path: .helm-chart-repo
|
||||
ref: gh-pages
|
||||
token: ${{ secrets.HELM_RELEASER_TOKEN }}
|
||||
|
||||
- name: Install Helm
|
||||
uses: azure/setup-helm@v4
|
||||
|
||||
- name: Install chart releaser
|
||||
run: |
|
||||
set -e
|
||||
arch="$(dpkg --print-architecture)"
|
||||
curl -s https://api.github.com/repos/helm/chart-releaser/releases/latest \
|
||||
| yq --indent 0 --no-colors --input-format json --unwrapScalar \
|
||||
".assets[] | select(.name | test("\""^chart-releaser_.+_linux_${arch}\.tar\.gz$"\"")) | .browser_download_url" \
|
||||
| xargs curl -SsL \
|
||||
| tar zxf - -C /usr/local/bin
|
||||
|
||||
- name: Package charts
|
||||
working-directory: .helm-chart-repo
|
||||
run: |
|
||||
mkdir -p .cr-index
|
||||
helm repo add bitnami https://charts.bitnami.com/bitnami
|
||||
helm repo update
|
||||
|
||||
helm dependencies build ../.github/helm/affine
|
||||
helm dependencies build ../.github/helm/affine-cloud
|
||||
cr package ../.github/helm/affine
|
||||
cr package ../.github/helm/affine-cloud
|
||||
|
||||
- name: Publish charts
|
||||
working-directory: .helm-chart-repo
|
||||
run: |
|
||||
set -ex
|
||||
git config --local user.name "$GITHUB_ACTOR"
|
||||
git config --local user.email "$GITHUB_ACTOR@users.noreply.github.com"
|
||||
owner=$(cut -d '/' -f 1 <<< '${{ github.repository }}')
|
||||
repo=helm-charts
|
||||
git_hash=$(git rev-parse HEAD)
|
||||
cr upload --commit "$git_hash" \
|
||||
--git-repo "$repo" --owner "$owner" \
|
||||
--token '${{ secrets.HELM_RELEASER_TOKEN }}' \
|
||||
--skip-existing
|
||||
cr index --git-repo "$repo" --owner "$owner" \
|
||||
--token '${{ secrets.HELM_RELEASER_TOKEN }}' \
|
||||
--index-path .cr-index --push
|
||||
19
.github/workflows/label-checker.yml
vendored
Normal file
19
.github/workflows/label-checker.yml
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
name: Label Checker
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
- opened
|
||||
- labeled
|
||||
- unlabeled
|
||||
branches:
|
||||
- canary
|
||||
|
||||
jobs:
|
||||
check_labels:
|
||||
name: PR should not have a blocked label
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: docker://agilepathway/pull-request-label-checker:latest
|
||||
with:
|
||||
none_of: blocked
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
12
.github/workflows/pr-auto-assign.yml
vendored
Normal file
12
.github/workflows/pr-auto-assign.yml
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
name: Pull request auto assign
|
||||
|
||||
# on: pull_request
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, ready_for_review]
|
||||
|
||||
jobs:
|
||||
add-reviews:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: kentaro-m/auto-assign-action@v2.0.0
|
||||
38
.github/workflows/release-automatically.yml
vendored
Normal file
38
.github/workflows/release-automatically.yml
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
name: Release Desktop/Mobile Automatically
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v[0-9]+.[0-9]+.[0-9]+-canary.[0-9]+'
|
||||
schedule:
|
||||
- cron: '0 9 * * *'
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
actions: write
|
||||
|
||||
jobs:
|
||||
dispatch-release-desktop:
|
||||
runs-on: ubuntu-latest
|
||||
name: Setup Release Desktop
|
||||
steps:
|
||||
- name: dispatch desktop release by tag
|
||||
if: ${{ github.event_name == 'push' }}
|
||||
uses: benc-uk/workflow-dispatch@v1
|
||||
with:
|
||||
workflow: release-desktop.yml
|
||||
inputs: '{ "build-type": "canary", "is-draft": false, "is-pre-release": true }'
|
||||
|
||||
- name: dispatch desktop release by schedule
|
||||
if: ${{ github.event_name == 'schedule' }}
|
||||
uses: benc-uk/workflow-dispatch@v1
|
||||
with:
|
||||
workflow: release-desktop.yml
|
||||
inputs: '{ "build-type": "canary", "is-draft": false, "is-pre-release": true }'
|
||||
ref: canary
|
||||
- name: dispatch desktop release by tag
|
||||
uses: benc-uk/workflow-dispatch@v1
|
||||
with:
|
||||
workflow: release-mobile.yml
|
||||
inputs: '{ "build-type": "canary", "build-target": "distribution" }'
|
||||
66
.github/workflows/release-cloud.yml
vendored
66
.github/workflows/release-cloud.yml
vendored
@@ -1,66 +0,0 @@
|
||||
name: Release Cloud
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
build-type:
|
||||
required: true
|
||||
type: string
|
||||
app-version:
|
||||
required: true
|
||||
type: string
|
||||
git-short-hash:
|
||||
required: true
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: 'write'
|
||||
id-token: 'write'
|
||||
packages: 'write'
|
||||
|
||||
jobs:
|
||||
build-images:
|
||||
name: Build Images
|
||||
uses: ./.github/workflows/build-images.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
build-type: ${{ inputs.build-type }}
|
||||
app-version: ${{ inputs.app-version }}
|
||||
git-short-hash: ${{ inputs.git-short-hash }}
|
||||
|
||||
deploy:
|
||||
name: Deploy to cluster
|
||||
environment: ${{ inputs.build-type }}
|
||||
needs:
|
||||
- build-images
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Deploy to ${{ inputs.build-type }}
|
||||
uses: ./.github/actions/deploy
|
||||
with:
|
||||
gcp-project-number: ${{ secrets.GCP_PROJECT_NUMBER }}
|
||||
gcp-project-id: ${{ secrets.GCP_PROJECT_ID }}
|
||||
service-account: ${{ secrets.GCP_HELM_DEPLOY_SERVICE_ACCOUNT }}
|
||||
cluster-name: ${{ secrets.GCP_CLUSTER_NAME }}
|
||||
cluster-location: ${{ secrets.GCP_CLUSTER_LOCATION }}
|
||||
env:
|
||||
BUILD_TYPE: ${{ inputs.build-type }}
|
||||
APP_VERSION: ${{ inputs.app-version }}
|
||||
GIT_SHORT_HASH: ${{ inputs.git-short-hash }}
|
||||
DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
|
||||
CANARY_DEPLOY_HOST: ${{ secrets.CANARY_DEPLOY_HOST }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
DATABASE_URL: ${{ secrets.DATABASE_URL }}
|
||||
DATABASE_USERNAME: ${{ secrets.DATABASE_USERNAME }}
|
||||
DATABASE_PASSWORD: ${{ secrets.DATABASE_PASSWORD }}
|
||||
DATABASE_NAME: ${{ secrets.DATABASE_NAME }}
|
||||
GCLOUD_CONNECTION_NAME: ${{ secrets.GCLOUD_CONNECTION_NAME }}
|
||||
REDIS_SERVER_HOST: ${{ secrets.REDIS_SERVER_HOST }}
|
||||
REDIS_SERVER_PASSWORD: ${{ secrets.REDIS_SERVER_PASSWORD }}
|
||||
CLOUD_SQL_IAM_ACCOUNT: ${{ secrets.CLOUD_SQL_IAM_ACCOUNT }}
|
||||
APP_IAM_ACCOUNT: ${{ secrets.APP_IAM_ACCOUNT }}
|
||||
STATIC_IP_NAME: ${{ secrets.STATIC_IP_NAME }}
|
||||
AFFINE_INDEXER_SEARCH_PROVIDER: ${{ secrets.AFFINE_INDEXER_SEARCH_PROVIDER }}
|
||||
AFFINE_INDEXER_SEARCH_ENDPOINT: ${{ secrets.AFFINE_INDEXER_SEARCH_ENDPOINT }}
|
||||
AFFINE_INDEXER_SEARCH_API_KEY: ${{ secrets.AFFINE_INDEXER_SEARCH_API_KEY }}
|
||||
227
.github/workflows/release-desktop-platform.yml
vendored
227
.github/workflows/release-desktop-platform.yml
vendored
@@ -1,227 +0,0 @@
|
||||
name: Release Desktop Platform
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
build_type:
|
||||
required: true
|
||||
type: string
|
||||
app_version:
|
||||
required: true
|
||||
type: string
|
||||
git_short_hash:
|
||||
required: true
|
||||
type: string
|
||||
runner:
|
||||
required: true
|
||||
type: string
|
||||
platform:
|
||||
required: true
|
||||
type: string
|
||||
arch:
|
||||
required: true
|
||||
type: string
|
||||
target:
|
||||
required: true
|
||||
type: string
|
||||
apple_codesign:
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
install_linux_deps:
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
enable_scripts:
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
outputs:
|
||||
files_to_be_signed:
|
||||
description: Files to be signed (Windows only)
|
||||
value: ${{ jobs.build.outputs.files_to_be_signed }}
|
||||
|
||||
permissions:
|
||||
actions: write
|
||||
contents: write
|
||||
security-events: write
|
||||
id-token: write
|
||||
attestations: write
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ${{ inputs.runner }}
|
||||
outputs:
|
||||
files_to_be_signed: ${{ steps.get_files_to_be_signed.outputs.FILES_TO_BE_SIGNED }}
|
||||
env:
|
||||
BUILD_TYPE: ${{ inputs.build_type }}
|
||||
RELEASE_VERSION: ${{ inputs.app_version }}
|
||||
DEBUG: 'affine:*,napi:*'
|
||||
APP_NAME: affine
|
||||
MACOSX_DEPLOYMENT_TARGET: '12.0'
|
||||
SKIP_GENERATE_ASSETS: 1
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
|
||||
SENTRY_PROJECT: 'affine'
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
||||
SENTRY_RELEASE: ${{ inputs.app_version }}
|
||||
MIXPANEL_TOKEN: ${{ secrets.MIXPANEL_TOKEN }}
|
||||
GA4_MEASUREMENT_ID: ${{ secrets.GA4_MEASUREMENT_ID }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Version
|
||||
uses: ./.github/actions/setup-version
|
||||
with:
|
||||
app-version: ${{ inputs.app_version }}
|
||||
|
||||
- name: Setup Node.js
|
||||
timeout-minutes: 10
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
extra-flags: workspaces focus @affine/electron @affine/monorepo @affine/nbstore @toeverything/infra
|
||||
hard-link-nm: false
|
||||
nmHoistingLimits: workspaces
|
||||
enableScripts: ${{ inputs.enable_scripts }}
|
||||
|
||||
- name: Build AFFiNE native
|
||||
uses: ./.github/actions/build-rust
|
||||
with:
|
||||
target: ${{ inputs.target }}
|
||||
package: '@affine/native'
|
||||
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: desktop-web
|
||||
path: packages/frontend/apps/electron/resources/web-static
|
||||
|
||||
- name: Build Desktop Layers
|
||||
run: yarn affine @affine/electron build
|
||||
|
||||
- name: Signing By Apple Developer ID
|
||||
if: ${{ inputs.platform == 'darwin' && inputs.apple_codesign }}
|
||||
uses: apple-actions/import-codesign-certs@v5
|
||||
with:
|
||||
p12-file-base64: ${{ secrets.CERTIFICATES_P12 }}
|
||||
p12-password: ${{ secrets.CERTIFICATES_P12_PASSWORD }}
|
||||
|
||||
- name: Install additional dependencies on Linux
|
||||
if: ${{ inputs.platform == 'linux' && inputs.install_linux_deps }}
|
||||
run: |
|
||||
df -h
|
||||
sudo add-apt-repository universe
|
||||
sudo apt install -y libfuse2 elfutils flatpak flatpak-builder
|
||||
flatpak remote-add --user --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo
|
||||
flatpak update
|
||||
# some flatpak deps need git protocol.file.allow
|
||||
git config --global protocol.file.allow always
|
||||
# clean up apt cache to save disk space
|
||||
sudo -E apt-get -y purge azure-cli* zulu* hhvm* llvm* firefox* google* dotnet* aspnetcore* powershell* adoptopenjdk* mysql* php* mongodb* moby* snap* || true
|
||||
sudo -E apt-get -qq autoremove --purge
|
||||
sudo rm -rf /usr/share/dotnet /opt/ghc /opt/hostedtoolcache/CodeQL /usr/local/lib/android
|
||||
sudo apt-get clean
|
||||
rm -rf ~/.cache/yarn ~/.npm
|
||||
df -h
|
||||
|
||||
- name: Remove nbstore node_modules (darwin/linux)
|
||||
if: ${{ inputs.platform != 'win32' }}
|
||||
shell: bash
|
||||
# node_modules of nbstore is not needed for building, and it will make the build process out of memory
|
||||
run: |
|
||||
cargo clean
|
||||
rm -rf packages/frontend/apps/electron/node_modules/@affine/nbstore/node_modules/@blocksuite
|
||||
rm -rf packages/frontend/apps/electron/node_modules/@affine/native/node_modules
|
||||
|
||||
- name: Remove nbstore node_modules (windows)
|
||||
if: ${{ inputs.platform == 'win32' }}
|
||||
shell: bash
|
||||
run: |
|
||||
rm -rf packages/frontend/apps/electron/node_modules/@affine/nbstore/node_modules/@blocksuite/affine/node_modules
|
||||
rm -rf packages/frontend/apps/electron/node_modules/@affine/native/node_modules
|
||||
|
||||
- name: make
|
||||
if: ${{ inputs.platform != 'win32' }}
|
||||
run: yarn affine @affine/electron make --platform=${{ inputs.platform }} --arch=${{ inputs.arch }}
|
||||
env:
|
||||
SKIP_WEB_BUILD: 1
|
||||
HOIST_NODE_MODULES: 1
|
||||
NODE_OPTIONS: --max-old-space-size=14384
|
||||
|
||||
- name: package
|
||||
if: ${{ inputs.platform == 'win32' }}
|
||||
run: |
|
||||
yarn affine @affine/electron package --platform=${{ inputs.platform }} --arch=${{ inputs.arch }}
|
||||
env:
|
||||
SKIP_WEB_BUILD: 1
|
||||
HOIST_NODE_MODULES: 1
|
||||
NODE_OPTIONS: --max-old-space-size=14384
|
||||
|
||||
- name: signing DMG
|
||||
if: ${{ inputs.platform == 'darwin' && inputs.apple_codesign }}
|
||||
run: |
|
||||
codesign --force --sign "Developer ID Application: TOEVERYTHING PTE. LTD." packages/frontend/apps/electron/out/${{ env.BUILD_TYPE }}/make/AFFiNE.dmg
|
||||
|
||||
- name: Save artifacts (mac)
|
||||
if: ${{ inputs.platform == 'darwin' }}
|
||||
run: |
|
||||
mkdir -p builds
|
||||
mv packages/frontend/apps/electron/out/*/make/*.dmg ./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-macos-${{ inputs.arch }}.dmg
|
||||
mv packages/frontend/apps/electron/out/*/make/zip/darwin/${{ inputs.arch }}/*.zip ./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-macos-${{ inputs.arch }}.zip
|
||||
|
||||
- name: Save artifacts (linux)
|
||||
if: ${{ inputs.platform == 'linux' }}
|
||||
run: |
|
||||
mkdir -p builds
|
||||
mv packages/frontend/apps/electron/out/*/make/zip/linux/${{ inputs.arch }}/*.zip ./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-linux-${{ inputs.arch }}.zip
|
||||
mv packages/frontend/apps/electron/out/*/make/*.AppImage ./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-linux-${{ inputs.arch }}.appimage
|
||||
mv packages/frontend/apps/electron/out/*/make/deb/${{ inputs.arch }}/*.deb ./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-linux-${{ inputs.arch }}.deb
|
||||
mv packages/frontend/apps/electron/out/*/make/flatpak/*/*.flatpak ./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-linux-${{ inputs.arch }}.flatpak
|
||||
|
||||
- uses: actions/attest-build-provenance@v2
|
||||
if: ${{ inputs.platform == 'darwin' }}
|
||||
with:
|
||||
subject-path: |
|
||||
./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-macos-${{ inputs.arch }}.zip
|
||||
./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-macos-${{ inputs.arch }}.dmg
|
||||
|
||||
- uses: actions/attest-build-provenance@v2
|
||||
if: ${{ inputs.platform == 'linux' }}
|
||||
with:
|
||||
subject-path: |
|
||||
./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-linux-${{ inputs.arch }}.zip
|
||||
./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-linux-${{ inputs.arch }}.appimage
|
||||
./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-linux-${{ inputs.arch }}.deb
|
||||
./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-linux-${{ inputs.arch }}.flatpak
|
||||
|
||||
- name: Upload Artifact
|
||||
if: ${{ inputs.platform == 'darwin' || inputs.platform == 'linux' }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: affine-${{ inputs.platform }}-${{ inputs.arch }}-builds
|
||||
path: builds
|
||||
|
||||
- name: get all files to be signed
|
||||
id: get_files_to_be_signed
|
||||
if: ${{ inputs.platform == 'win32' }}
|
||||
shell: pwsh
|
||||
run: |
|
||||
Set-Variable -Name FILES_TO_BE_SIGNED -Value ((Get-ChildItem -Path packages/frontend/apps/electron/out -Recurse -File | Where-Object { $_.Extension -in @(".exe", ".node", ".dll", ".msi") } | ForEach-Object { '"' + $_.FullName.Replace((Get-Location).Path + '\packages\frontend\apps\electron\out\', '') + '"' }) -join ' ')
|
||||
"FILES_TO_BE_SIGNED=$FILES_TO_BE_SIGNED" >> $env:GITHUB_OUTPUT
|
||||
echo $FILES_TO_BE_SIGNED
|
||||
|
||||
- name: Zip artifacts for faster upload
|
||||
if: ${{ inputs.platform == 'win32' }}
|
||||
shell: pwsh
|
||||
run: Compress-Archive -CompressionLevel Fastest -Path packages/frontend/apps/electron/out/* -DestinationPath archive.zip
|
||||
|
||||
- name: Save packaged artifacts for signing
|
||||
if: ${{ inputs.platform == 'win32' }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: packaged-${{ inputs.platform }}-${{ inputs.arch }}
|
||||
path: |
|
||||
archive.zip
|
||||
!**/*.map
|
||||
386
.github/workflows/release-desktop.yml
vendored
386
.github/workflows/release-desktop.yml
vendored
@@ -1,32 +1,27 @@
|
||||
name: Release Desktop
|
||||
name: Release Desktop App
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
build-type:
|
||||
description: 'Build Type'
|
||||
type: choice
|
||||
required: true
|
||||
type: string
|
||||
app-version:
|
||||
default: canary
|
||||
options:
|
||||
- canary
|
||||
- beta
|
||||
- stable
|
||||
is-draft:
|
||||
description: 'Draft Release?'
|
||||
type: boolean
|
||||
required: true
|
||||
type: string
|
||||
git-short-hash:
|
||||
default: true
|
||||
is-pre-release:
|
||||
description: 'Pre Release? (labeled as "PreRelease")'
|
||||
type: boolean
|
||||
required: true
|
||||
type: string
|
||||
desktop_macos:
|
||||
description: 'Desktop - macOS'
|
||||
required: false
|
||||
default: true
|
||||
type: boolean
|
||||
desktop_windows:
|
||||
description: 'Desktop - Windows'
|
||||
required: false
|
||||
default: true
|
||||
type: boolean
|
||||
desktop_linux:
|
||||
description: 'Desktop - Linux'
|
||||
required: false
|
||||
default: true
|
||||
type: boolean
|
||||
|
||||
permissions:
|
||||
actions: write
|
||||
@@ -36,23 +31,22 @@ permissions:
|
||||
attestations: write
|
||||
|
||||
env:
|
||||
BUILD_TYPE: ${{ inputs.build-type }}
|
||||
RELEASE_VERSION: ${{ inputs.app-version }}
|
||||
BUILD_TYPE: ${{ github.event.inputs.build-type }}
|
||||
DEBUG: 'affine:*,napi:*'
|
||||
APP_NAME: affine
|
||||
MACOSX_DEPLOYMENT_TARGET: '11.6'
|
||||
MACOSX_DEPLOYMENT_TARGET: '10.13'
|
||||
|
||||
jobs:
|
||||
before-make:
|
||||
if: ${{ inputs.desktop_macos || inputs.desktop_windows || inputs.desktop_linux }}
|
||||
runs-on: ubuntu-latest
|
||||
environment: ${{ inputs.build-type }}
|
||||
environment: ${{ github.event.inputs.build-type }}
|
||||
outputs:
|
||||
RELEASE_VERSION: ${{ steps.version.outputs.APP_VERSION }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Version
|
||||
id: version
|
||||
uses: ./.github/actions/setup-version
|
||||
with:
|
||||
app-version: ${{ inputs.app-version }}
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
- name: Setup @sentry/cli
|
||||
@@ -64,19 +58,17 @@ jobs:
|
||||
SENTRY_PROJECT: 'affine'
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
||||
SENTRY_RELEASE: ${{ inputs.app-version }}
|
||||
RELEASE_VERSION: ${{ inputs.app-version }}
|
||||
SENTRY_RELEASE: ${{ steps.version.outputs.APP_VERSION }}
|
||||
RELEASE_VERSION: ${{ steps.version.outputs.APP_VERSION }}
|
||||
MIXPANEL_TOKEN: ${{ secrets.MIXPANEL_TOKEN }}
|
||||
GA4_MEASUREMENT_ID: ${{ secrets.GA4_MEASUREMENT_ID }}
|
||||
|
||||
- name: Upload web artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: desktop-web
|
||||
name: web
|
||||
path: packages/frontend/apps/electron/resources/web-static
|
||||
|
||||
make-distribution-macos:
|
||||
if: ${{ inputs.desktop_macos }}
|
||||
make-distribution:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -89,90 +81,221 @@ jobs:
|
||||
platform: darwin
|
||||
arch: arm64
|
||||
target: aarch64-apple-darwin
|
||||
needs: before-make
|
||||
uses: ./.github/workflows/release-desktop-platform.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
build_type: ${{ inputs.build-type }}
|
||||
app_version: ${{ inputs.app-version }}
|
||||
git_short_hash: ${{ inputs.git-short-hash }}
|
||||
runner: ${{ matrix.spec.runner }}
|
||||
platform: ${{ matrix.spec.platform }}
|
||||
arch: ${{ matrix.spec.arch }}
|
||||
target: ${{ matrix.spec.target }}
|
||||
apple_codesign: true
|
||||
|
||||
make-distribution-linux:
|
||||
if: ${{ inputs.desktop_linux }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
spec:
|
||||
- runner: ubuntu-latest
|
||||
platform: linux
|
||||
arch: x64
|
||||
target: x86_64-unknown-linux-gnu
|
||||
runs-on: ${{ matrix.spec.runner }}
|
||||
needs: before-make
|
||||
uses: ./.github/workflows/release-desktop-platform.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
build_type: ${{ inputs.build-type }}
|
||||
app_version: ${{ inputs.app-version }}
|
||||
git_short_hash: ${{ inputs.git-short-hash }}
|
||||
runner: ${{ matrix.spec.runner }}
|
||||
platform: ${{ matrix.spec.platform }}
|
||||
arch: ${{ matrix.spec.arch }}
|
||||
target: ${{ matrix.spec.target }}
|
||||
install_linux_deps: true
|
||||
environment: ${{ github.event.inputs.build-type }}
|
||||
env:
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
SKIP_GENERATE_ASSETS: 1
|
||||
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
|
||||
SENTRY_PROJECT: 'affine'
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
||||
SENTRY_RELEASE: ${{ needs.before-make.outputs.RELEASE_VERSION }}
|
||||
MIXPANEL_TOKEN: ${{ secrets.MIXPANEL_TOKEN }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Version
|
||||
id: version
|
||||
uses: ./.github/actions/setup-version
|
||||
- name: Setup Node.js
|
||||
timeout-minutes: 10
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
extra-flags: workspaces focus @affine/electron @affine/monorepo @affine/nbstore @toeverything/infra
|
||||
hard-link-nm: false
|
||||
nmHoistingLimits: workspaces
|
||||
enableScripts: false
|
||||
- name: Build AFFiNE native
|
||||
uses: ./.github/actions/build-rust
|
||||
with:
|
||||
target: ${{ matrix.spec.target }}
|
||||
package: '@affine/native'
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: web
|
||||
path: packages/frontend/apps/electron/resources/web-static
|
||||
|
||||
package-distribution-windows-x64:
|
||||
if: ${{ inputs.desktop_windows }}
|
||||
needs: before-make
|
||||
uses: ./.github/workflows/release-desktop-platform.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
build_type: ${{ inputs.build-type }}
|
||||
app_version: ${{ inputs.app-version }}
|
||||
git_short_hash: ${{ inputs.git-short-hash }}
|
||||
runner: windows-latest
|
||||
platform: win32
|
||||
arch: x64
|
||||
target: x86_64-pc-windows-msvc
|
||||
enable_scripts: true
|
||||
- name: Build Desktop Layers
|
||||
run: yarn affine @affine/electron build
|
||||
|
||||
package-distribution-windows-arm64:
|
||||
if: ${{ inputs.desktop_windows }}
|
||||
- name: Signing By Apple Developer ID
|
||||
if: ${{ matrix.spec.platform == 'darwin' }}
|
||||
uses: apple-actions/import-codesign-certs@v5
|
||||
with:
|
||||
p12-file-base64: ${{ secrets.CERTIFICATES_P12 }}
|
||||
p12-password: ${{ secrets.CERTIFICATES_P12_PASSWORD }}
|
||||
|
||||
- name: Install additional dependencies on Linux
|
||||
if: ${{ matrix.spec.platform == 'linux' }}
|
||||
run: |
|
||||
sudo add-apt-repository universe
|
||||
sudo apt install -y libfuse2 elfutils flatpak flatpak-builder
|
||||
flatpak remote-add --user --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
|
||||
flatpak update
|
||||
# some flatpak deps need git protocol.file.allow
|
||||
git config --global protocol.file.allow always
|
||||
|
||||
- name: Remove nbstore node_modules
|
||||
shell: bash
|
||||
# node_modules of nbstore is not needed for building, and it will make the build process out of memory
|
||||
run: |
|
||||
rm -rf packages/frontend/apps/electron/node_modules/@affine/nbstore/node_modules/@blocksuite
|
||||
rm -rf packages/frontend/apps/electron/node_modules/@affine/native/node_modules
|
||||
|
||||
- name: make
|
||||
run: yarn affine @affine/electron make --platform=${{ matrix.spec.platform }} --arch=${{ matrix.spec.arch }}
|
||||
env:
|
||||
SKIP_WEB_BUILD: 1
|
||||
HOIST_NODE_MODULES: 1
|
||||
NODE_OPTIONS: --max-old-space-size=14384
|
||||
|
||||
- name: signing DMG
|
||||
if: ${{ matrix.spec.platform == 'darwin' }}
|
||||
run: |
|
||||
codesign --force --sign "Developer ID Application: TOEVERYTHING PTE. LTD." packages/frontend/apps/electron/out/${{ env.BUILD_TYPE }}/make/AFFiNE.dmg
|
||||
|
||||
- name: Save artifacts (mac)
|
||||
if: ${{ matrix.spec.platform == 'darwin' }}
|
||||
run: |
|
||||
mkdir -p builds
|
||||
mv packages/frontend/apps/electron/out/*/make/*.dmg ./builds/affine-${{ needs.before-make.outputs.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-macos-${{ matrix.spec.arch }}.dmg
|
||||
mv packages/frontend/apps/electron/out/*/make/zip/darwin/${{ matrix.spec.arch }}/*.zip ./builds/affine-${{ needs.before-make.outputs.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-macos-${{ matrix.spec.arch }}.zip
|
||||
- name: Save artifacts (linux)
|
||||
if: ${{ matrix.spec.platform == 'linux' }}
|
||||
run: |
|
||||
mkdir -p builds
|
||||
mv packages/frontend/apps/electron/out/*/make/zip/linux/${{ matrix.spec.arch }}/*.zip ./builds/affine-${{ needs.before-make.outputs.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-linux-${{ matrix.spec.arch }}.zip
|
||||
mv packages/frontend/apps/electron/out/*/make/*.AppImage ./builds/affine-${{ needs.before-make.outputs.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-linux-${{ matrix.spec.arch }}.appimage
|
||||
mv packages/frontend/apps/electron/out/*/make/deb/${{ matrix.spec.arch }}/*.deb ./builds/affine-${{ needs.before-make.outputs.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-linux-${{ matrix.spec.arch }}.deb
|
||||
mv packages/frontend/apps/electron/out/*/make/flatpak/*/*.flatpak ./builds/affine-${{ needs.before-make.outputs.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-linux-${{ matrix.spec.arch }}.flatpak
|
||||
|
||||
- uses: actions/attest-build-provenance@v2
|
||||
if: ${{ matrix.spec.platform == 'darwin' }}
|
||||
with:
|
||||
subject-path: |
|
||||
./builds/affine-${{ needs.before-make.outputs.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-macos-${{ matrix.spec.arch }}.zip
|
||||
./builds/affine-${{ needs.before-make.outputs.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-macos-${{ matrix.spec.arch }}.dmg
|
||||
|
||||
- uses: actions/attest-build-provenance@v2
|
||||
if: ${{ matrix.spec.platform == 'linux' }}
|
||||
with:
|
||||
subject-path: |
|
||||
./builds/affine-${{ needs.before-make.outputs.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-linux-x64.zip
|
||||
./builds/affine-${{ needs.before-make.outputs.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-linux-x64.appimage
|
||||
./builds/affine-${{ needs.before-make.outputs.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-linux-x64.deb
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: affine-${{ matrix.spec.platform }}-${{ matrix.spec.arch }}-builds
|
||||
path: builds
|
||||
|
||||
package-distribution-windows:
|
||||
environment: ${{ github.event.inputs.build-type }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
spec:
|
||||
- runner: windows-latest
|
||||
platform: win32
|
||||
arch: x64
|
||||
target: x86_64-pc-windows-msvc
|
||||
- runner: windows-latest
|
||||
platform: win32
|
||||
arch: arm64
|
||||
target: aarch64-pc-windows-msvc
|
||||
runs-on: ${{ matrix.spec.runner }}
|
||||
needs: before-make
|
||||
uses: ./.github/workflows/release-desktop-platform.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
build_type: ${{ inputs.build-type }}
|
||||
app_version: ${{ inputs.app-version }}
|
||||
git_short_hash: ${{ inputs.git-short-hash }}
|
||||
runner: windows-latest
|
||||
platform: win32
|
||||
arch: arm64
|
||||
target: aarch64-pc-windows-msvc
|
||||
enable_scripts: true
|
||||
outputs:
|
||||
FILES_TO_BE_SIGNED_x64: ${{ steps.get_files_to_be_signed.outputs.FILES_TO_BE_SIGNED_x64 }}
|
||||
FILES_TO_BE_SIGNED_arm64: ${{ steps.get_files_to_be_signed.outputs.FILES_TO_BE_SIGNED_arm64 }}
|
||||
env:
|
||||
SKIP_GENERATE_ASSETS: 1
|
||||
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
|
||||
SENTRY_PROJECT: 'affine'
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
||||
SENTRY_RELEASE: ${{ needs.before-make.outputs.RELEASE_VERSION }}
|
||||
MIXPANEL_TOKEN: ${{ secrets.MIXPANEL_TOKEN }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Version
|
||||
id: version
|
||||
uses: ./.github/actions/setup-version
|
||||
- name: Setup Node.js
|
||||
timeout-minutes: 10
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
extra-flags: workspaces focus @affine/electron @affine/monorepo @affine/nbstore @toeverything/infra
|
||||
hard-link-nm: false
|
||||
nmHoistingLimits: workspaces
|
||||
- name: Build AFFiNE native
|
||||
uses: ./.github/actions/build-rust
|
||||
with:
|
||||
target: ${{ matrix.spec.target }}
|
||||
package: '@affine/native'
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: web
|
||||
path: packages/frontend/apps/electron/resources/web-static
|
||||
|
||||
- name: Build Desktop Layers
|
||||
run: yarn affine @affine/electron build
|
||||
|
||||
- name: Remove nbstore node_modules
|
||||
shell: bash
|
||||
# node_modules of nbstore is not needed for building, and it will make the build process out of memory
|
||||
run: |
|
||||
rm -rf packages/frontend/apps/electron/node_modules/@affine/nbstore/node_modules/@blocksuite/affine/node_modules
|
||||
rm -rf packages/frontend/apps/electron/node_modules/@affine/native/node_modules
|
||||
|
||||
- name: package
|
||||
run: |
|
||||
yarn affine @affine/electron package --platform=${{ matrix.spec.platform }} --arch=${{ matrix.spec.arch }}
|
||||
env:
|
||||
SKIP_WEB_BUILD: 1
|
||||
HOIST_NODE_MODULES: 1
|
||||
NODE_OPTIONS: --max-old-space-size=14384
|
||||
|
||||
- name: get all files to be signed
|
||||
id: get_files_to_be_signed
|
||||
run: |
|
||||
Set-Variable -Name FILES_TO_BE_SIGNED -Value ((Get-ChildItem -Path packages/frontend/apps/electron/out -Recurse -File | Where-Object { $_.Extension -in @(".exe", ".node", ".dll", ".msi") } | ForEach-Object { '"' + $_.FullName.Replace((Get-Location).Path + '\packages\frontend\apps\electron\out\', '') + '"' }) -join ' ')
|
||||
"FILES_TO_BE_SIGNED_${{ matrix.spec.arch }}=$FILES_TO_BE_SIGNED" >> $env:GITHUB_OUTPUT
|
||||
echo $FILES_TO_BE_SIGNED
|
||||
|
||||
- name: Zip artifacts for faster upload
|
||||
run: Compress-Archive -CompressionLevel Fastest -Path packages/frontend/apps/electron/out/* -DestinationPath archive.zip
|
||||
|
||||
- name: Save packaged artifacts for signing
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: packaged-${{ matrix.spec.platform }}-${{ matrix.spec.arch }}
|
||||
path: |
|
||||
archive.zip
|
||||
!**/*.map
|
||||
|
||||
sign-packaged-artifacts-windows_x64:
|
||||
if: ${{ inputs.desktop_windows }}
|
||||
needs: package-distribution-windows-x64
|
||||
needs: package-distribution-windows
|
||||
uses: ./.github/workflows/windows-signer.yml
|
||||
with:
|
||||
files: ${{ needs.package-distribution-windows-x64.outputs.files_to_be_signed }}
|
||||
files: ${{ needs.package-distribution-windows.outputs.FILES_TO_BE_SIGNED_x64 }}
|
||||
artifact-name: packaged-win32-x64
|
||||
|
||||
sign-packaged-artifacts-windows_arm64:
|
||||
if: ${{ inputs.desktop_windows }}
|
||||
needs: package-distribution-windows-arm64
|
||||
needs: package-distribution-windows
|
||||
uses: ./.github/workflows/windows-signer.yml
|
||||
with:
|
||||
files: ${{ needs.package-distribution-windows-arm64.outputs.files_to_be_signed }}
|
||||
files: ${{ needs.package-distribution-windows.outputs.FILES_TO_BE_SIGNED_arm64 }}
|
||||
artifact-name: packaged-win32-arm64
|
||||
|
||||
make-windows-installer:
|
||||
if: ${{ inputs.desktop_windows }}
|
||||
needs:
|
||||
- sign-packaged-artifacts-windows_x64
|
||||
- sign-packaged-artifacts-windows_arm64
|
||||
@@ -191,9 +314,8 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Version
|
||||
id: version
|
||||
uses: ./.github/actions/setup-version
|
||||
with:
|
||||
app-version: ${{ inputs.app-version }}
|
||||
- name: Setup Node.js
|
||||
timeout-minutes: 10
|
||||
uses: ./.github/actions/setup-node
|
||||
@@ -234,7 +356,6 @@ jobs:
|
||||
path: archive.zip
|
||||
|
||||
sign-installer-artifacts-windows-x64:
|
||||
if: ${{ inputs.desktop_windows }}
|
||||
needs: make-windows-installer
|
||||
uses: ./.github/workflows/windows-signer.yml
|
||||
with:
|
||||
@@ -242,7 +363,6 @@ jobs:
|
||||
artifact-name: installer-win32-x64
|
||||
|
||||
sign-installer-artifacts-windows-arm64:
|
||||
if: ${{ inputs.desktop_windows }}
|
||||
needs: make-windows-installer
|
||||
uses: ./.github/workflows/windows-signer.yml
|
||||
with:
|
||||
@@ -250,7 +370,6 @@ jobs:
|
||||
artifact-name: installer-win32-arm64
|
||||
|
||||
finalize-installer-windows:
|
||||
if: ${{ inputs.desktop_windows }}
|
||||
needs:
|
||||
[
|
||||
sign-installer-artifacts-windows-x64,
|
||||
@@ -280,16 +399,16 @@ jobs:
|
||||
- name: Save artifacts
|
||||
run: |
|
||||
mkdir -p builds
|
||||
mv packages/frontend/apps/electron/out/*/make/zip/win32/${{ matrix.spec.arch }}/AFFiNE*-win32-${{ matrix.spec.arch }}-*.zip ./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-windows-${{ matrix.spec.arch }}.zip
|
||||
mv packages/frontend/apps/electron/out/*/make/squirrel.windows/${{ matrix.spec.arch }}/*.exe ./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-windows-${{ matrix.spec.arch }}.exe
|
||||
mv packages/frontend/apps/electron/out/*/make/nsis.windows/${{ matrix.spec.arch }}/*.exe ./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-windows-${{ matrix.spec.arch }}.nsis.exe
|
||||
mv packages/frontend/apps/electron/out/*/make/zip/win32/${{ matrix.spec.arch }}/AFFiNE*-win32-${{ matrix.spec.arch }}-*.zip ./builds/affine-${{ needs.before-make.outputs.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-windows-${{ matrix.spec.arch }}.zip
|
||||
mv packages/frontend/apps/electron/out/*/make/squirrel.windows/${{ matrix.spec.arch }}/*.exe ./builds/affine-${{ needs.before-make.outputs.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-windows-${{ matrix.spec.arch }}.exe
|
||||
mv packages/frontend/apps/electron/out/*/make/nsis.windows/${{ matrix.spec.arch }}/*.exe ./builds/affine-${{ needs.before-make.outputs.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-windows-${{ matrix.spec.arch }}.nsis.exe
|
||||
|
||||
- uses: actions/attest-build-provenance@v2
|
||||
with:
|
||||
subject-path: |
|
||||
./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-windows-${{ matrix.spec.arch }}.zip
|
||||
./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-windows-${{ matrix.spec.arch }}.exe
|
||||
./builds/affine-${{ env.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-windows-${{ matrix.spec.arch }}.nsis.exe
|
||||
./builds/affine-${{ needs.before-make.outputs.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-windows-${{ matrix.spec.arch }}.zip
|
||||
./builds/affine-${{ needs.before-make.outputs.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-windows-${{ matrix.spec.arch }}.exe
|
||||
./builds/affine-${{ needs.before-make.outputs.RELEASE_VERSION }}-${{ env.BUILD_TYPE }}-windows-${{ matrix.spec.arch }}.nsis.exe
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
@@ -298,18 +417,17 @@ jobs:
|
||||
path: builds
|
||||
|
||||
release:
|
||||
if: ${{ inputs.desktop_macos && inputs.desktop_linux && inputs.desktop_windows }}
|
||||
needs:
|
||||
[
|
||||
before-make,
|
||||
make-distribution-macos,
|
||||
make-distribution-linux,
|
||||
finalize-installer-windows,
|
||||
]
|
||||
needs: [before-make, make-distribution, finalize-installer-windows]
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: web
|
||||
path: web-static
|
||||
- name: Zip web-static
|
||||
run: zip -r web-static.zip web-static
|
||||
- name: Download Artifacts (macos-x64)
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
@@ -347,12 +465,32 @@ jobs:
|
||||
run: |
|
||||
node ./scripts/generate-release-yml.mjs
|
||||
env:
|
||||
RELEASE_VERSION: ${{ env.RELEASE_VERSION }}
|
||||
- name: Create GitHub Release
|
||||
RELEASE_VERSION: ${{ needs.before-make.outputs.RELEASE_VERSION }}
|
||||
- name: Create Release Draft
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
name: ${{ env.RELEASE_VERSION }}
|
||||
draft: ${{ inputs.build-type == 'stable' }}
|
||||
prerelease: ${{ inputs.build-type != 'stable' }}
|
||||
tag_name: v${{ env.RELEASE_VERSION}}
|
||||
files: ./release/*
|
||||
name: ${{ needs.before-make.outputs.RELEASE_VERSION }}
|
||||
body: ''
|
||||
draft: ${{ github.event.inputs.is-draft }}
|
||||
prerelease: ${{ github.event.inputs.is-pre-release }}
|
||||
files: |
|
||||
./release/*
|
||||
./release/.env.example
|
||||
- name: Create Nightly Release Draft
|
||||
if: ${{ github.ref_type == 'branch' }}
|
||||
uses: softprops/action-gh-release@v2
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||
with:
|
||||
# Temporarily, treat release from branch as nightly release, artifact saved to AFFiNE-Releases.
|
||||
# Need to improve internal build and nightly release logic.
|
||||
repository: 'toeverything/AFFiNE-Releases'
|
||||
name: ${{ needs.before-make.outputs.RELEASE_VERSION }}
|
||||
tag_name: ${{ needs.before-make.outputs.RELEASE_VERSION }}
|
||||
body: ''
|
||||
draft: false
|
||||
prerelease: true
|
||||
files: |
|
||||
./release/*
|
||||
./release/.env.example
|
||||
|
||||
103
.github/workflows/release-mobile.yml
vendored
103
.github/workflows/release-mobile.yml
vendored
@@ -1,36 +1,68 @@
|
||||
name: Release Mobile
|
||||
name: Release Mobile App
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
app-version:
|
||||
type: string
|
||||
required: true
|
||||
git-short-hash:
|
||||
build-target:
|
||||
description: 'Build Target'
|
||||
type: string
|
||||
required: true
|
||||
build-type:
|
||||
description: 'Build Type'
|
||||
type: string
|
||||
required: true
|
||||
ios-app-version:
|
||||
type: string
|
||||
required: false
|
||||
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
build-target:
|
||||
description: 'Build Target'
|
||||
type: choice
|
||||
required: true
|
||||
default: distribution
|
||||
options:
|
||||
- development
|
||||
- distribution
|
||||
build-type:
|
||||
description: 'Build Type'
|
||||
type: choice
|
||||
required: true
|
||||
default: canary
|
||||
options:
|
||||
- canary
|
||||
- beta
|
||||
- stable
|
||||
env:
|
||||
BUILD_TYPE: ${{ inputs.build-type }}
|
||||
BUILD_TYPE: ${{ inputs.build-type || github.event.inputs.build-type }}
|
||||
BUILD_TARGET: ${{ inputs.build-target || github.event.inputs.build-target }}
|
||||
DEBUG: napi:*
|
||||
KEYCHAIN_NAME: ${{ github.workspace }}/signing_temp
|
||||
|
||||
jobs:
|
||||
build-ios-web:
|
||||
output-env:
|
||||
runs-on: ubuntu-latest
|
||||
environment: ${{ inputs.build-type }}
|
||||
outputs:
|
||||
ENVIRONMENT: ${{ steps.env.outputs.ENVIRONMENT }}
|
||||
steps:
|
||||
- name: Output Environment
|
||||
id: env
|
||||
run: |
|
||||
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
|
||||
echo "ENVIRONMENT=${{ github.event.inputs.build-type }}" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "ENVIRONMENT=" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
build-ios-web:
|
||||
needs:
|
||||
- output-env
|
||||
runs-on: ubuntu-24.04-arm
|
||||
environment: ${{ needs.output-env.outputs.ENVIRONMENT }}
|
||||
outputs:
|
||||
RELEASE_VERSION: ${{ steps.version.outputs.APP_VERSION }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Version
|
||||
id: version
|
||||
uses: ./.github/actions/setup-version
|
||||
with:
|
||||
app-version: ${{ inputs.app-version }}
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
- name: Setup @sentry/cli
|
||||
@@ -40,13 +72,12 @@ jobs:
|
||||
env:
|
||||
PUBLIC_PATH: '/'
|
||||
MIXPANEL_TOKEN: ${{ secrets.MIXPANEL_TOKEN }}
|
||||
GA4_MEASUREMENT_ID: ${{ secrets.GA4_MEASUREMENT_ID }}
|
||||
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
|
||||
SENTRY_PROJECT: 'affine'
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
||||
SENTRY_RELEASE: ${{ inputs.app-version }}
|
||||
RELEASE_VERSION: ${{ inputs.app-version }}
|
||||
SENTRY_RELEASE: ${{ steps.version.outputs.APP_VERSION }}
|
||||
RELEASE_VERSION: ${{ steps.version.outputs.APP_VERSION }}
|
||||
- name: Upload ios artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
@@ -54,13 +85,17 @@ jobs:
|
||||
path: packages/frontend/apps/ios/dist
|
||||
|
||||
build-android-web:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04-arm
|
||||
needs:
|
||||
- output-env
|
||||
environment: ${{ needs.output-env.outputs.ENVIRONMENT }}
|
||||
outputs:
|
||||
RELEASE_VERSION: ${{ steps.version.outputs.APP_VERSION }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Version
|
||||
id: version
|
||||
uses: ./.github/actions/setup-version
|
||||
with:
|
||||
app-version: ${{ inputs.app-version }}
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
- name: Setup @sentry/cli
|
||||
@@ -70,12 +105,12 @@ jobs:
|
||||
env:
|
||||
PUBLIC_PATH: '/'
|
||||
MIXPANEL_TOKEN: ${{ secrets.MIXPANEL_TOKEN }}
|
||||
GA4_MEASUREMENT_ID: ${{ secrets.GA4_MEASUREMENT_ID }}
|
||||
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
|
||||
SENTRY_PROJECT: 'affine'
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
||||
SENTRY_RELEASE: ${{ inputs.app-version }}
|
||||
SENTRY_RELEASE: ${{ steps.version.outputs.APP_VERSION }}
|
||||
RELEASE_VERSION: ${{ steps.version.outputs.APP_VERSION }}
|
||||
- name: Upload android artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
@@ -83,19 +118,11 @@ jobs:
|
||||
path: packages/frontend/apps/android/dist
|
||||
|
||||
ios:
|
||||
runs-on: 'macos-15'
|
||||
runs-on: ${{ github.ref_name == 'canary' && 'macos-latest' || 'blaze/macos-14' }}
|
||||
needs:
|
||||
- build-ios-web
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Version
|
||||
uses: ./.github/actions/setup-version
|
||||
with:
|
||||
app-version: ${{ inputs.app-version }}
|
||||
ios-app-version: ${{ inputs.ios-app-version }}
|
||||
- name: 'Update Code Sign Identity'
|
||||
shell: bash
|
||||
run: ./packages/frontend/apps/ios/update_code_sign_identity.sh
|
||||
- name: Download mobile artifact
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
@@ -112,7 +139,7 @@ jobs:
|
||||
enableScripts: false
|
||||
- uses: maxim-lobanov/setup-xcode@v1
|
||||
with:
|
||||
xcode-version: 26.2
|
||||
xcode-version: 16.2
|
||||
- name: Install Swiftformat
|
||||
run: brew install swiftformat
|
||||
- name: Cap sync
|
||||
@@ -130,6 +157,7 @@ jobs:
|
||||
package: 'affine_mobile_native'
|
||||
no-build: 'true'
|
||||
- name: Testflight
|
||||
if: ${{ env.BUILD_TYPE != 'stable' }}
|
||||
working-directory: packages/frontend/apps/ios/App
|
||||
run: |
|
||||
echo -n "${{ env.BUILD_PROVISION_PROFILE }}" | base64 --decode -o $PP_PATH
|
||||
@@ -137,7 +165,6 @@ jobs:
|
||||
cp $PP_PATH ~/Library/MobileDevice/Provisioning\ Profiles
|
||||
fastlane beta
|
||||
env:
|
||||
BUILD_TARGET: distribution
|
||||
BUILD_PROVISION_PROFILE: ${{ secrets.BUILD_PROVISION_PROFILE }}
|
||||
PP_PATH: ${{ runner.temp }}/build_pp.mobileprovision
|
||||
APPLE_STORE_CONNECT_API_KEY_ID: ${{ secrets.APPLE_STORE_CONNECT_API_KEY_ID }}
|
||||
@@ -153,9 +180,8 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Version
|
||||
id: version
|
||||
uses: ./.github/actions/setup-version
|
||||
with:
|
||||
app-version: ${{ inputs.app-version }}
|
||||
- name: Download mobile artifact
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
@@ -188,6 +214,7 @@ jobs:
|
||||
- name: Auth gcloud
|
||||
id: auth
|
||||
uses: google-github-actions/auth@v2
|
||||
if: ${{ env.BUILD_TARGET == 'distribution' }}
|
||||
with:
|
||||
workload_identity_provider: 'projects/${{ secrets.GCP_PROJECT_NUMBER }}/locations/global/workloadIdentityPools/github-actions/providers/github-actions-helm-deploy'
|
||||
service_account: '${{ secrets.GCP_HELM_DEPLOY_SERVICE_ACCOUNT }}'
|
||||
@@ -201,6 +228,7 @@ jobs:
|
||||
cache: 'gradle'
|
||||
- name: Auto increment version code
|
||||
id: bump
|
||||
if: ${{ env.BUILD_TARGET == 'distribution' }}
|
||||
run: yarn affine @affine/playstore-auto-bump bump
|
||||
env:
|
||||
GOOGLE_APPLICATION_CREDENTIALS: ${{ steps.auth.outputs.credentials_file_path }}
|
||||
@@ -212,13 +240,14 @@ jobs:
|
||||
AFFINE_ANDROID_KEYSTORE_PASSWORD: ${{ secrets.AFFINE_ANDROID_KEYSTORE_PASSWORD }}
|
||||
AFFINE_ANDROID_KEYSTORE_ALIAS_PASSWORD: ${{ secrets.AFFINE_ANDROID_KEYSTORE_ALIAS_PASSWORD }}
|
||||
AFFINE_ANDROID_SIGN_KEYSTORE: ${{ secrets.AFFINE_ANDROID_SIGN_KEYSTORE }}
|
||||
VERSION_NAME: ${{ inputs.app-version }}
|
||||
VERSION_NAME: ${{ steps.version.outputs.APP_VERSION }}
|
||||
- name: Upload to Google Play
|
||||
uses: r0adkll/upload-google-play@v1
|
||||
if: ${{ env.BUILD_TARGET == 'distribution' }}
|
||||
with:
|
||||
serviceAccountJson: ${{ steps.auth.outputs.credentials_file_path }}
|
||||
packageName: app.affine.pro
|
||||
releaseName: ${{ inputs.app-version }}
|
||||
releaseName: ${{ steps.version.outputs.APP_VERSION }}
|
||||
releaseFiles: packages/frontend/apps/android/App/app/build/outputs/bundle/${{ env.BUILD_TYPE }}Release/app-${{ env.BUILD_TYPE }}-release-signed.aab
|
||||
track: internal
|
||||
status: draft
|
||||
|
||||
210
.github/workflows/release.yml
vendored
210
.github/workflows/release.yml
vendored
@@ -1,210 +0,0 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 9 * * *'
|
||||
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
web:
|
||||
description: 'Release Web?'
|
||||
required: true
|
||||
type: boolean
|
||||
default: false
|
||||
desktop_macos:
|
||||
description: 'Desktop - macOS'
|
||||
required: true
|
||||
type: boolean
|
||||
default: false
|
||||
desktop_windows:
|
||||
description: 'Desktop - Windows'
|
||||
required: true
|
||||
type: boolean
|
||||
default: false
|
||||
desktop_linux:
|
||||
description: 'Desktop - Linux'
|
||||
required: true
|
||||
type: boolean
|
||||
default: false
|
||||
mobile:
|
||||
description: 'Release Mobile?'
|
||||
required: true
|
||||
type: boolean
|
||||
default: false
|
||||
ios-app-version:
|
||||
description: 'iOS App Store Version (Optional, use tag version if empty)'
|
||||
required: false
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
actions: write
|
||||
id-token: write
|
||||
packages: write
|
||||
security-events: write
|
||||
attestations: write
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
prepare:
|
||||
name: Prepare
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
APP_VERSION: ${{ steps.prepare.outputs.APP_VERSION }}
|
||||
GIT_SHORT_HASH: ${{ steps.prepare.outputs.GIT_SHORT_HASH }}
|
||||
BUILD_TYPE: ${{ steps.prepare.outputs.BUILD_TYPE }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Prepare Release
|
||||
id: prepare
|
||||
uses: ./.github/actions/prepare-release
|
||||
|
||||
canary-gate:
|
||||
name: Canary Gate
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- prepare
|
||||
outputs:
|
||||
SHOULD_RELEASE: ${{ steps.decide.outputs.SHOULD_RELEASE }}
|
||||
LAST_CANARY_TAG: ${{ steps.decide.outputs.LAST_CANARY_TAG }}
|
||||
LAST_CANARY_SHA: ${{ steps.decide.outputs.LAST_CANARY_SHA }}
|
||||
steps:
|
||||
- name: Decide whether to release
|
||||
id: decide
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const buildType = '${{ needs.prepare.outputs.BUILD_TYPE }}'
|
||||
if (buildType !== 'canary') {
|
||||
core.setOutput('SHOULD_RELEASE', 'true')
|
||||
return
|
||||
}
|
||||
|
||||
const owner = context.repo.owner
|
||||
const repo = context.repo.repo
|
||||
const currentSha = context.sha
|
||||
const canaryTagRe = /^v\d+\.\d+\.\d+-canary\.[0-9a-f]+$/i
|
||||
|
||||
let page = 1
|
||||
const perPage = 100
|
||||
let lastCanary = null
|
||||
|
||||
while (!lastCanary && page <= 10) {
|
||||
const { data } = await github.rest.repos.listTags({
|
||||
owner,
|
||||
repo,
|
||||
per_page: perPage,
|
||||
page,
|
||||
})
|
||||
|
||||
for (const tag of data) {
|
||||
if (canaryTagRe.test(tag.name)) {
|
||||
lastCanary = tag
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (data.length < perPage) break
|
||||
page++
|
||||
}
|
||||
|
||||
if (!lastCanary) {
|
||||
core.warning('No canary tags found; proceeding with canary release.')
|
||||
core.setOutput('SHOULD_RELEASE', 'true')
|
||||
return
|
||||
}
|
||||
|
||||
core.setOutput('LAST_CANARY_TAG', lastCanary.name)
|
||||
core.setOutput('LAST_CANARY_SHA', lastCanary.commit.sha)
|
||||
|
||||
const shouldRelease = lastCanary.commit.sha !== currentSha
|
||||
core.info(`Latest canary tag ${lastCanary.name} -> ${lastCanary.commit.sha}; current ${currentSha}; should_release=${shouldRelease}`)
|
||||
core.setOutput('SHOULD_RELEASE', shouldRelease ? 'true' : 'false')
|
||||
|
||||
cloud:
|
||||
name: Release Cloud
|
||||
if: ${{ inputs.web || github.event_name != 'workflow_dispatch' }}
|
||||
needs:
|
||||
- prepare
|
||||
uses: ./.github/workflows/release-cloud.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
build-type: ${{ needs.prepare.outputs.BUILD_TYPE }}
|
||||
app-version: ${{ needs.prepare.outputs.APP_VERSION }}
|
||||
git-short-hash: ${{ needs.prepare.outputs.GIT_SHORT_HASH }}
|
||||
|
||||
image:
|
||||
name: Release Docker Image
|
||||
if: ${{ needs.canary-gate.outputs.SHOULD_RELEASE == 'true' }}
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- prepare
|
||||
- canary-gate
|
||||
- cloud
|
||||
steps:
|
||||
- uses: trstringer/manual-approval@v1
|
||||
if: ${{ needs.prepare.outputs.BUILD_TYPE == 'stable' }}
|
||||
name: Wait for approval
|
||||
with:
|
||||
secret: ${{ secrets.GITHUB_TOKEN }}
|
||||
approvers: darkskygit,pengx17,L-Sun,EYHN
|
||||
minimum-approvals: 1
|
||||
fail-on-denial: true
|
||||
issue-title: Please confirm to release docker image
|
||||
issue-body: |
|
||||
Env: ${{ needs.prepare.outputs.BUILD_TYPE }}
|
||||
Candidate: ghcr.io/toeverything/affine:${{ needs.prepare.outputs.BUILD_TYPE }}-${{ needs.prepare.outputs.GIT_SHORT_HASH }}
|
||||
Tag: ghcr.io/toeverything/affine:${{ needs.prepare.outputs.BUILD_TYPE }}
|
||||
|
||||
> comment with "approve", "approved", "lgtm", "yes" to approve
|
||||
> comment with "deny", "denied", "no" to deny
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
logout: false
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Tag Image
|
||||
run: |
|
||||
docker buildx imagetools create --tag ghcr.io/toeverything/affine:${{needs.prepare.outputs.BUILD_TYPE}} ghcr.io/toeverything/affine:${{needs.prepare.outputs.BUILD_TYPE}}-${{needs.prepare.outputs.GIT_SHORT_HASH}}
|
||||
docker buildx imagetools create --tag ghcr.io/toeverything/affine:${{needs.prepare.outputs.APP_VERSION}} ghcr.io/toeverything/affine:${{needs.prepare.outputs.BUILD_TYPE}}-${{needs.prepare.outputs.GIT_SHORT_HASH}}
|
||||
|
||||
desktop:
|
||||
name: Release Desktop
|
||||
if: >-
|
||||
${{
|
||||
(github.event_name != 'workflow_dispatch' && needs.canary-gate.outputs.SHOULD_RELEASE == 'true') ||
|
||||
inputs.desktop_macos ||
|
||||
inputs.desktop_windows ||
|
||||
inputs.desktop_linux
|
||||
}}
|
||||
needs:
|
||||
- prepare
|
||||
- canary-gate
|
||||
uses: ./.github/workflows/release-desktop.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
build-type: ${{ needs.prepare.outputs.BUILD_TYPE }}
|
||||
app-version: ${{ needs.prepare.outputs.APP_VERSION }}
|
||||
git-short-hash: ${{ needs.prepare.outputs.GIT_SHORT_HASH }}
|
||||
desktop_macos: ${{ github.event_name != 'workflow_dispatch' || inputs.desktop_macos }}
|
||||
desktop_windows: ${{ github.event_name != 'workflow_dispatch' || inputs.desktop_windows }}
|
||||
desktop_linux: ${{ github.event_name != 'workflow_dispatch' || inputs.desktop_linux }}
|
||||
|
||||
mobile:
|
||||
name: Release Mobile
|
||||
if: ${{ inputs.mobile }}
|
||||
needs:
|
||||
- prepare
|
||||
uses: ./.github/workflows/release-mobile.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
build-type: ${{ needs.prepare.outputs.BUILD_TYPE }}
|
||||
app-version: ${{ needs.prepare.outputs.APP_VERSION }}
|
||||
git-short-hash: ${{ needs.prepare.outputs.GIT_SHORT_HASH }}
|
||||
ios-app-version: ${{ inputs.ios-app-version }}
|
||||
2
.github/workflows/windows-signer.yml
vendored
2
.github/workflows/windows-signer.yml
vendored
@@ -29,7 +29,7 @@ jobs:
|
||||
shell: cmd
|
||||
run: |
|
||||
cd ${{ env.ARCHIVE_DIR }}/out
|
||||
signtool sign /tr http://timestamp.globalsign.com/tsa/r6advanced1 /td sha256 /fd sha256 /a ${{ inputs.files }}
|
||||
signtool sign /tr http://timestamp.sectigo.com /td sha256 /fd sha256 /a ${{ inputs.files }}
|
||||
- name: zip file
|
||||
shell: cmd
|
||||
run: |
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -33,9 +33,6 @@ node_modules
|
||||
!.vscode/launch.template.json
|
||||
!.vscode/extensions.json
|
||||
|
||||
# Kiro
|
||||
.kiro
|
||||
|
||||
# misc
|
||||
/.sass-cache
|
||||
/connect.lock
|
||||
@@ -47,7 +44,6 @@ testem.log
|
||||
.pnpm-debug.log
|
||||
/typings
|
||||
tsconfig.tsbuildinfo
|
||||
.context
|
||||
|
||||
# System Files
|
||||
.DS_Store
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
**/node_modules
|
||||
.yarn
|
||||
.github/helm
|
||||
.git
|
||||
.vscode
|
||||
.yarnrc.yml
|
||||
.docker
|
||||
|
||||
14
.taplo.toml
14
.taplo.toml
@@ -1,11 +1,7 @@
|
||||
exclude = [
|
||||
"node_modules/**/*.toml",
|
||||
"target/**/*.toml",
|
||||
"packages/frontend/apps/ios/App/Packages/AffineGraphQL/**/*.toml",
|
||||
]
|
||||
include = ["./*.toml", "./packages/**/*.toml"]
|
||||
|
||||
# https://taplo.tamasfe.dev/configuration/formatter-options.html
|
||||
[formatting]
|
||||
align_entries = true
|
||||
indent_tables = true
|
||||
reorder_keys = true
|
||||
align_entries = true
|
||||
column_width = 180
|
||||
reorder_arrays = true
|
||||
reorder_keys = true
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -12,4 +12,4 @@ npmPublishAccess: public
|
||||
|
||||
npmRegistryServer: "https://registry.npmjs.org"
|
||||
|
||||
yarnPath: .yarn/releases/yarn-4.12.0.cjs
|
||||
yarnPath: .yarn/releases/yarn-4.9.1.cjs
|
||||
|
||||
2422
Cargo.lock
generated
2422
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
210
Cargo.toml
210
Cargo.toml
@@ -3,6 +3,7 @@ members = [
|
||||
"./packages/backend/native",
|
||||
"./packages/common/native",
|
||||
"./packages/common/y-octo/core",
|
||||
"./packages/common/y-octo/node",
|
||||
"./packages/common/y-octo/utils",
|
||||
"./packages/frontend/mobile-native",
|
||||
"./packages/frontend/native",
|
||||
@@ -12,124 +13,93 @@ members = [
|
||||
]
|
||||
resolver = "3"
|
||||
|
||||
[workspace.package]
|
||||
edition = "2024"
|
||||
[workspace.package]
|
||||
edition = "2024"
|
||||
|
||||
[workspace.dependencies]
|
||||
affine_common = { path = "./packages/common/native" }
|
||||
affine_nbstore = { path = "./packages/frontend/native/nbstore" }
|
||||
ahash = "0.8"
|
||||
anyhow = "1"
|
||||
arbitrary = { version = "1.3", features = ["derive"] }
|
||||
assert-json-diff = "2.0"
|
||||
async-lock = { version = "3.4.0", features = ["loom"] }
|
||||
base64-simd = "0.8"
|
||||
bitvec = "1.0"
|
||||
block2 = "0.6"
|
||||
byteorder = "1.5"
|
||||
chrono = "0.4"
|
||||
clap = { version = "4.4", features = ["derive"] }
|
||||
core-foundation = "0.10"
|
||||
coreaudio-rs = "0.12"
|
||||
cpal = "0.15"
|
||||
criterion = { version = "0.5", features = ["html_reports"] }
|
||||
criterion2 = { version = "3", default-features = false }
|
||||
crossbeam-channel = "0.5"
|
||||
dispatch2 = "0.3"
|
||||
docx-parser = { git = "https://github.com/toeverything/docx-parser" }
|
||||
dotenvy = "0.15"
|
||||
file-format = { version = "0.28", features = ["reader"] }
|
||||
homedir = "0.3"
|
||||
infer = { version = "0.19.0" }
|
||||
lasso = { version = "0.7", features = ["multi-threaded"] }
|
||||
lib0 = { version = "0.16", features = ["lib0-serde"] }
|
||||
libc = "0.2"
|
||||
log = "0.4"
|
||||
loom = { version = "0.7", features = ["checkpoint"] }
|
||||
memory-indexer = "0.3.0"
|
||||
mimalloc = "0.1"
|
||||
mp4parse = "0.17"
|
||||
nanoid = "0.4"
|
||||
napi = { version = "3.7.0", features = [
|
||||
"async",
|
||||
"chrono_date",
|
||||
"error_anyhow",
|
||||
"napi9",
|
||||
"serde",
|
||||
] }
|
||||
napi-build = { version = "2" }
|
||||
napi-derive = { version = "3.4" }
|
||||
nom = "8"
|
||||
notify = { version = "8", features = ["serde"] }
|
||||
objc2 = "0.6"
|
||||
objc2-foundation = "0.3"
|
||||
once_cell = "1"
|
||||
ordered-float = "5"
|
||||
parking_lot = "0.12"
|
||||
path-ext = "0.1.2"
|
||||
pdf-extract = { git = "https://github.com/toeverything/pdf-extract", branch = "darksky/improve-font-decoding" }
|
||||
phf = { version = "0.11", features = ["macros"] }
|
||||
proptest = "1.3"
|
||||
proptest-derive = "0.5"
|
||||
pulldown-cmark = "0.13"
|
||||
rand = "0.9"
|
||||
rand_chacha = "0.9"
|
||||
rand_distr = "0.5"
|
||||
rayon = "1.10"
|
||||
readability = { version = "0.3.0", default-features = false }
|
||||
regex = "1.10"
|
||||
rubato = "0.16"
|
||||
screencapturekit = "0.3"
|
||||
serde = "1"
|
||||
serde_json = "1"
|
||||
sha3 = "0.10"
|
||||
smol_str = "0.3"
|
||||
sqlx = { version = "0.8", default-features = false, features = [
|
||||
"chrono",
|
||||
"macros",
|
||||
"migrate",
|
||||
"runtime-tokio",
|
||||
"sqlite",
|
||||
"tls-rustls",
|
||||
] }
|
||||
strum_macros = "0.27.0"
|
||||
symphonia = { version = "0.5", features = ["all", "opt-simd"] }
|
||||
text-splitter = "0.27"
|
||||
thiserror = "2"
|
||||
tiktoken-rs = "0.7"
|
||||
tokio = "1.45"
|
||||
tree-sitter = { version = "0.25" }
|
||||
tree-sitter-c = { version = "0.24" }
|
||||
tree-sitter-c-sharp = { version = "0.23" }
|
||||
tree-sitter-cpp = { version = "0.23" }
|
||||
tree-sitter-go = { version = "0.23" }
|
||||
tree-sitter-java = { version = "0.23" }
|
||||
tree-sitter-javascript = { version = "0.23" }
|
||||
tree-sitter-kotlin-ng = { version = "1.1" }
|
||||
tree-sitter-python = { version = "0.23" }
|
||||
tree-sitter-rust = { version = "0.24" }
|
||||
tree-sitter-scala = { version = "0.24" }
|
||||
tree-sitter-typescript = { version = "0.23" }
|
||||
uniffi = "0.29"
|
||||
url = { version = "2.5" }
|
||||
uuid = "1.8"
|
||||
v_htmlescape = "0.15"
|
||||
windows = { version = "0.61", features = [
|
||||
"Win32_Devices_FunctionDiscovery",
|
||||
"Win32_Foundation",
|
||||
"Win32_Media_Audio",
|
||||
"Win32_System_Com",
|
||||
"Win32_System_Com_StructuredStorage",
|
||||
"Win32_System_Diagnostics_ToolHelp",
|
||||
"Win32_System_ProcessStatus",
|
||||
"Win32_System_Threading",
|
||||
"Win32_System_Variant",
|
||||
"Win32_UI_Shell_PropertiesSystem",
|
||||
] }
|
||||
windows-core = { version = "0.61" }
|
||||
y-octo = { path = "./packages/common/y-octo/core" }
|
||||
y-sync = { version = "0.4" }
|
||||
yrs = "0.23.0"
|
||||
[workspace.dependencies]
|
||||
affine_common = { path = "./packages/common/native" }
|
||||
affine_nbstore = { path = "./packages/frontend/native/nbstore" }
|
||||
ahash = "0.8"
|
||||
anyhow = "1"
|
||||
arbitrary = { version = "1.3", features = ["derive"] }
|
||||
assert-json-diff = "2.0"
|
||||
async-lock = { version = "3.4.0", features = ["loom"] }
|
||||
base64-simd = "0.8"
|
||||
bitvec = "1.0"
|
||||
block2 = "0.6"
|
||||
byteorder = "1.5"
|
||||
chrono = "0.4"
|
||||
clap = { version = "4.4", features = ["derive"] }
|
||||
core-foundation = "0.10"
|
||||
coreaudio-rs = "0.12"
|
||||
criterion = { version = "0.5", features = ["html_reports"] }
|
||||
criterion2 = { version = "3", default-features = false }
|
||||
dispatch2 = "0.3"
|
||||
docx-parser = { git = "https://github.com/toeverything/docx-parser" }
|
||||
dotenvy = "0.15"
|
||||
file-format = { version = "0.26", features = ["reader"] }
|
||||
homedir = "0.3"
|
||||
infer = { version = "0.19.0" }
|
||||
lasso = { version = "0.7", features = ["multi-threaded"] }
|
||||
lib0 = { version = "0.16", features = ["lib0-serde"] }
|
||||
libc = "0.2"
|
||||
log = "0.4"
|
||||
loom = { version = "0.7", features = ["checkpoint"] }
|
||||
mimalloc = "0.1"
|
||||
nanoid = "0.4"
|
||||
napi = { version = "3.0.0-beta.3", features = ["async", "chrono_date", "error_anyhow", "napi9", "serde"] }
|
||||
napi-build = { version = "2" }
|
||||
napi-derive = { version = "3.0.0-beta.3" }
|
||||
nom = "8"
|
||||
notify = { version = "8", features = ["serde"] }
|
||||
objc2 = "0.6"
|
||||
objc2-foundation = "0.3"
|
||||
once_cell = "1"
|
||||
ordered-float = "5"
|
||||
parking_lot = "0.12"
|
||||
path-ext = "0.1.2"
|
||||
pdf-extract = { git = "https://github.com/toeverything/pdf-extract", branch = "darksky/improve-font-decoding" }
|
||||
phf = { version = "0.11", features = ["macros"] }
|
||||
proptest = "1.3"
|
||||
proptest-derive = "0.5"
|
||||
rand = "0.9"
|
||||
rand_chacha = "0.9"
|
||||
rand_distr = "0.5"
|
||||
rayon = "1.10"
|
||||
readability = { version = "0.3.0", default-features = false }
|
||||
regex = "1.10"
|
||||
rubato = "0.16"
|
||||
screencapturekit = "0.3"
|
||||
serde = "1"
|
||||
serde_json = "1"
|
||||
sha3 = "0.10"
|
||||
smol_str = "0.3"
|
||||
sqlx = { version = "0.8", default-features = false, features = ["chrono", "macros", "migrate", "runtime-tokio", "sqlite", "tls-rustls"] }
|
||||
strum_macros = "0.27.0"
|
||||
symphonia = { version = "0.5", features = ["all", "opt-simd"] }
|
||||
text-splitter = "0.27"
|
||||
thiserror = "2"
|
||||
tiktoken-rs = "0.7"
|
||||
tokio = "1.45"
|
||||
tree-sitter = { version = "0.25" }
|
||||
tree-sitter-c = { version = "0.24" }
|
||||
tree-sitter-c-sharp = { version = "0.23" }
|
||||
tree-sitter-cpp = { version = "0.23" }
|
||||
tree-sitter-go = { version = "0.23" }
|
||||
tree-sitter-java = { version = "0.23" }
|
||||
tree-sitter-javascript = { version = "0.23" }
|
||||
tree-sitter-kotlin-ng = { version = "1.1" }
|
||||
tree-sitter-python = { version = "0.23" }
|
||||
tree-sitter-rust = { version = "0.24" }
|
||||
tree-sitter-scala = { version = "0.23" }
|
||||
tree-sitter-typescript = { version = "0.23" }
|
||||
uniffi = "0.29"
|
||||
url = { version = "2.5" }
|
||||
uuid = "1.8"
|
||||
v_htmlescape = "0.15"
|
||||
y-octo = { path = "./packages/common/y-octo/core" }
|
||||
y-sync = { version = "0.4" }
|
||||
yrs = "0.23.0"
|
||||
|
||||
[profile.dev.package.sqlx-macros]
|
||||
opt-level = 3
|
||||
@@ -140,6 +110,6 @@ lto = true
|
||||
opt-level = 3
|
||||
strip = "symbols"
|
||||
|
||||
# android uniffi bindgen requires symbols
|
||||
[profile.release.package.affine_mobile_native]
|
||||
strip = "none"
|
||||
# android uniffi bindgen requires symbols
|
||||
[profile.release.package.affine_mobile_native]
|
||||
strip = "none"
|
||||
2
LICENSE
2
LICENSE
@@ -2,7 +2,7 @@ Copyright (c) 2022-present TOEVERYTHING PTE. LTD. and its affiliates.
|
||||
|
||||
Portions of this software are licensed as follows:
|
||||
|
||||
- All content that resides under the "packages/backend" and "packages/common/native" directory of this repository, if that directory exists, is licensed under the license defined in "packages/backend/server/LICENSE".
|
||||
- All content that resides under the "packages/backend/server" directory of this repository, if that directory exists, is licensed under the license defined in "packages/backend/server/LICENSE".
|
||||
- All third party components incorporated into the AFFiNE Software are licensed under the original license provided by the owner of the applicable component.
|
||||
- Content outside of the above mentioned directories or restrictions above is available under the "MIT" license as defined in "LICENSE-MIT".
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<br>
|
||||
</h1>
|
||||
<a href="https://affine.pro/download">
|
||||
<img alt="affine logo" src="https://cdn.affine.pro/Github_hero_image2.png" style="width: 100%">
|
||||
<img alt="affine logo" src="https://cdn.affine.pro/Github_hero_image1.png" style="width: 100%">
|
||||
</a>
|
||||
<br/>
|
||||
<p align="center">
|
||||
@@ -81,7 +81,7 @@ Star us, and you will receive all release notifications from GitHub without any
|
||||
|
||||
**Multimodal AI partner ready to kick in any work**
|
||||
|
||||
- Write up professional work report? Turn an outline into expressive and presentable slides? Summary an article into a well-structured mindmap? Sorting your job plan and backlog for tasks? Or... draw and code prototype apps and web pages directly all with one prompt? With you, [AFFiNE AI](https://affine.pro/ai) pushes your creativity to the edge of your imagination, just like [Canvas AI](https://affine.pro/blog/best-canvas-ai) to generate mind map for brainstorming.
|
||||
- Write up professional work report? Turn an outline into expressive and presentable slides? Summary an article into a well-structured mindmap? Sorting your job plan and backlog for tasks? Or... draw and code prototype apps and web pages directly all with one prompt? With you, [AFFiNE AI](https://affine.pro/ai) pushes your creativity to the edge of your imagination,just like [Canvas AI](https://affine.pro/blog/best-canvas-ai) to generate mind map for brainstorming.
|
||||
|
||||
**Local-first & Real-time collaborative**
|
||||
|
||||
@@ -193,8 +193,6 @@ We would like to express our gratitude to all the individuals who have already c
|
||||
|
||||
Begin with Docker to deploy your own feature-rich, unrestricted version of AFFiNE. Our team is diligently updating to the latest version. For more information on how to self-host AFFiNE, please refer to our [documentation](https://docs.affine.pro/self-host-affine).
|
||||
|
||||
[](https://sealos.io/products/app-store/affine)
|
||||
|
||||
[](https://template.run.claw.cloud/?openapp=system-fastdeploy%3FtemplateName%3Daffine)
|
||||
|
||||
## Hiring
|
||||
|
||||
11
SECURITY.md
11
SECURITY.md
@@ -6,14 +6,15 @@ We recommend users to always use the latest major version. Security updates will
|
||||
|
||||
| Version | Supported |
|
||||
| --------------- | ------------------ |
|
||||
| 0.25.x (stable) | :white_check_mark: |
|
||||
| < 0.25.x | :x: |
|
||||
| 0.17.x (stable) | :white_check_mark: |
|
||||
| < 0.17.x | :x: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
We welcome you to provide us with bug reports via and email at [security@toeverything.info](mailto:security@toeverything.info) or submit directly on [GitHub](https://github.com/toeverything/AFFiNE/security), **we encourage you to submit the relevant information directly via GitHub**. We expect your report to contain at least the following for us to evaluate and reproduce:
|
||||
We welcome you to provide us with bug reports via and email at [security@toeverything.info](mailto:security@toeverything.info). We expect your report to contain at least the following for us to evaluate and reproduce:
|
||||
|
||||
1. Using platform and version, for example:
|
||||
|
||||
- macos arm64 0.12.0-canary-202402220729-0868ac6
|
||||
- app.affine.pro 0.12.0-canary-202402220729-0868ac6
|
||||
|
||||
@@ -21,6 +22,8 @@ We welcome you to provide us with bug reports via and email at [security@toevery
|
||||
|
||||
3. Your classification or analysis of the vulnerability (optional)
|
||||
|
||||
Since we are an open source project, we also welcome you to provide corresponding fix PRs, we will determine specific rewards based on the evaluation results.
|
||||
Since we are an open source project, we also welcome you to provide corresponding fix PRs.
|
||||
|
||||
We will provide bounties for vulnerabilities involving user information leakage, permission leakage, and unauthorized code execution. For other types of vulnerabilities, we will determine specific rewards based on the evaluation results.
|
||||
|
||||
If the vulnerability is caused by a library we depend on, we encourage you to submit a security report to the corresponding dependent library at the same time to benefit more users.
|
||||
|
||||
@@ -48,7 +48,6 @@
|
||||
"@blocksuite/affine-gfx-template": "workspace:*",
|
||||
"@blocksuite/affine-gfx-text": "workspace:*",
|
||||
"@blocksuite/affine-gfx-turbo-renderer": "workspace:*",
|
||||
"@blocksuite/affine-inline-comment": "workspace:*",
|
||||
"@blocksuite/affine-inline-footnote": "workspace:*",
|
||||
"@blocksuite/affine-inline-latex": "workspace:*",
|
||||
"@blocksuite/affine-inline-link": "workspace:*",
|
||||
@@ -79,7 +78,7 @@
|
||||
"@blocksuite/std": "workspace:*",
|
||||
"@blocksuite/store": "workspace:*",
|
||||
"@blocksuite/sync": "workspace:*",
|
||||
"rxjs": "^7.8.2"
|
||||
"rxjs": "^7.8.1"
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
@@ -174,7 +173,6 @@
|
||||
"./inlines/footnote": "./src/inlines/footnote/index.ts",
|
||||
"./inlines/footnote/view": "./src/inlines/footnote/view.ts",
|
||||
"./inlines/footnote/store": "./src/inlines/footnote/store.ts",
|
||||
"./inlines/comment": "./src/inlines/comment/index.ts",
|
||||
"./inlines/latex": "./src/inlines/latex/index.ts",
|
||||
"./inlines/latex/store": "./src/inlines/latex/store.ts",
|
||||
"./inlines/latex/view": "./src/inlines/latex/view.ts",
|
||||
@@ -266,7 +264,6 @@
|
||||
"./components/toggle-button": "./src/components/toggle-button.ts",
|
||||
"./components/toggle-switch": "./src/components/toggle-switch.ts",
|
||||
"./components/toolbar": "./src/components/toolbar.ts",
|
||||
"./components/tooltip": "./src/components/tooltip.ts",
|
||||
"./components/view-dropdown-menu": "./src/components/view-dropdown-menu.ts",
|
||||
"./components/tooltip-content-with-shortcut": "./src/components/tooltip-content-with-shortcut.ts",
|
||||
"./components/resource": "./src/components/resource.ts",
|
||||
@@ -286,7 +283,6 @@
|
||||
"./sync": "./src/sync/index.ts",
|
||||
"./extensions/store": "./src/extensions/store.ts",
|
||||
"./extensions/view": "./src/extensions/view.ts",
|
||||
"./foundation/clipboard": "./src/foundation/clipboard.ts",
|
||||
"./foundation/store": "./src/foundation/store.ts",
|
||||
"./foundation/view": "./src/foundation/view.ts"
|
||||
},
|
||||
@@ -296,10 +292,10 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.26.0",
|
||||
"version": "0.21.0",
|
||||
"devDependencies": {
|
||||
"@vanilla-extract/vite-plugin": "^5.0.0",
|
||||
"msw": "^2.12.4",
|
||||
"vitest": "^3.2.4"
|
||||
"msw": "^2.8.4",
|
||||
"vitest": "3.1.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2214,7 +2214,7 @@ describe('html to snapshot', () => {
|
||||
|
||||
test('iframe', async () => {
|
||||
const html = template(
|
||||
`<iframe width="560" height="315" src="https://www.youtube.com/embed/QDsd0nyzwz0?start=&end=" title="YouTube video player" frameborder="0" allow="fullscreen; autoplay; clipboard-write; encrypted-media; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin"></iframe>`
|
||||
`<iframe width="560" height="315" src="https://www.youtube.com/embed/QDsd0nyzwz0?start=&end=" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>`
|
||||
);
|
||||
|
||||
const blockSnapshot: BlockSnapshot = {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { MarkdownTransformer } from '@blocksuite/affine/widgets/linked-doc';
|
||||
import {
|
||||
DefaultTheme,
|
||||
NoteDisplayMode,
|
||||
@@ -17,15 +16,12 @@ import type {
|
||||
SliceSnapshot,
|
||||
TransformerMiddleware,
|
||||
} from '@blocksuite/store';
|
||||
import { AssetsManager, MemoryBlobCRUD, Schema } from '@blocksuite/store';
|
||||
import { TestWorkspace } from '@blocksuite/store/test';
|
||||
import { AssetsManager, MemoryBlobCRUD } from '@blocksuite/store';
|
||||
import { describe, expect, test } from 'vitest';
|
||||
|
||||
import { AffineSchemas } from '../../schemas.js';
|
||||
import { createJob } from '../utils/create-job.js';
|
||||
import { getProvider } from '../utils/get-provider.js';
|
||||
import { nanoidReplacement } from '../utils/nanoid-replacement.js';
|
||||
import { testStoreExtensions } from '../utils/store.js';
|
||||
|
||||
const provider = getProvider();
|
||||
|
||||
@@ -94,39 +90,6 @@ describe('snapshot to markdown', () => {
|
||||
expect(target.file).toBe(markdown);
|
||||
});
|
||||
|
||||
test('imports frontmatter metadata into doc meta', async () => {
|
||||
const schema = new Schema().register(AffineSchemas);
|
||||
const collection = new TestWorkspace();
|
||||
collection.storeExtensions = testStoreExtensions;
|
||||
collection.meta.initialize();
|
||||
|
||||
const markdown = `---
|
||||
title: Web developer
|
||||
created: 2018-04-12T09:51:00
|
||||
updated: 2018-04-12T10:00:00
|
||||
tags: [a, b]
|
||||
favorite: true
|
||||
---
|
||||
Hello world
|
||||
`;
|
||||
|
||||
const docId = await MarkdownTransformer.importMarkdownToDoc({
|
||||
collection,
|
||||
schema,
|
||||
markdown,
|
||||
fileName: 'fallback-title',
|
||||
extensions: testStoreExtensions,
|
||||
});
|
||||
|
||||
expect(docId).toBeTruthy();
|
||||
const meta = collection.meta.getDocMeta(docId!);
|
||||
expect(meta?.title).toBe('Web developer');
|
||||
expect(meta?.createDate).toBe(Date.parse('2018-04-12T09:51:00'));
|
||||
expect(meta?.updatedDate).toBe(Date.parse('2018-04-12T10:00:00'));
|
||||
expect(meta?.favorite).toBe(true);
|
||||
expect(meta?.tags).toEqual(['a', 'b']);
|
||||
});
|
||||
|
||||
test('paragraph', async () => {
|
||||
const blockSnapshot: BlockSnapshot = {
|
||||
type: 'block',
|
||||
@@ -3033,50 +2996,6 @@ describe('markdown to snapshot', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('html inline color span imports to nearest supported text color', async () => {
|
||||
const markdown = `<span style="color: #00afde;">Hello</span>`;
|
||||
const blockSnapshot: BlockSnapshot = {
|
||||
type: 'block',
|
||||
id: 'matchesReplaceMap[0]',
|
||||
flavour: 'affine:note',
|
||||
props: {
|
||||
xywh: '[0,0,800,95]',
|
||||
background: DefaultTheme.noteBackgrounColor,
|
||||
index: 'a0',
|
||||
hidden: false,
|
||||
displayMode: NoteDisplayMode.DocAndEdgeless,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
type: 'block',
|
||||
id: 'matchesReplaceMap[1]',
|
||||
flavour: 'affine:paragraph',
|
||||
props: {
|
||||
type: 'text',
|
||||
text: {
|
||||
'$blocksuite:internal:text$': true,
|
||||
delta: [
|
||||
{
|
||||
insert: 'Hello',
|
||||
attributes: {
|
||||
color: 'var(--affine-v2-text-highlight-fg-blue)',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const mdAdapter = new MarkdownAdapter(createJob(), provider);
|
||||
const rawBlockSnapshot = await mdAdapter.toBlockSnapshot({
|
||||
file: markdown,
|
||||
});
|
||||
expect(nanoidReplacement(rawBlockSnapshot)).toEqual(blockSnapshot);
|
||||
});
|
||||
|
||||
test('paragraph', async () => {
|
||||
const markdown = `aaa
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1 +0,0 @@
|
||||
export * from '@blocksuite/affine-components/tooltip';
|
||||
@@ -33,7 +33,6 @@ import { PointerViewExtension } from '@blocksuite/affine-gfx-pointer/view';
|
||||
import { ShapeViewExtension } from '@blocksuite/affine-gfx-shape/view';
|
||||
import { TemplateViewExtension } from '@blocksuite/affine-gfx-template/view';
|
||||
import { TextViewExtension } from '@blocksuite/affine-gfx-text/view';
|
||||
import { InlineCommentViewExtension } from '@blocksuite/affine-inline-comment/view';
|
||||
import { FootnoteViewExtension } from '@blocksuite/affine-inline-footnote/view';
|
||||
import { LatexViewExtension as InlineLatexViewExtension } from '@blocksuite/affine-inline-latex/view';
|
||||
import { LinkViewExtension } from '@blocksuite/affine-inline-link/view';
|
||||
@@ -96,7 +95,6 @@ export function getInternalViewExtensions() {
|
||||
RootViewExtension,
|
||||
|
||||
// Inline
|
||||
InlineCommentViewExtension,
|
||||
FootnoteViewExtension,
|
||||
LinkViewExtension,
|
||||
ReferenceViewExtension,
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export * from '@blocksuite/affine-foundation/clipboard';
|
||||
@@ -1 +0,0 @@
|
||||
export * from '@blocksuite/affine-inline-comment';
|
||||
@@ -45,7 +45,6 @@
|
||||
{ "path": "../gfx/template" },
|
||||
{ "path": "../gfx/text" },
|
||||
{ "path": "../gfx/turbo-renderer" },
|
||||
{ "path": "../inlines/comment" },
|
||||
{ "path": "../inlines/footnote" },
|
||||
{ "path": "../inlines/latex" },
|
||||
{ "path": "../inlines/link" },
|
||||
|
||||
@@ -17,18 +17,18 @@
|
||||
"@blocksuite/affine-shared": "workspace:*",
|
||||
"@blocksuite/affine-widget-slash-menu": "workspace:*",
|
||||
"@blocksuite/global": "workspace:*",
|
||||
"@blocksuite/icons": "^2.2.17",
|
||||
"@blocksuite/icons": "^2.2.12",
|
||||
"@blocksuite/std": "workspace:*",
|
||||
"@blocksuite/store": "workspace:*",
|
||||
"@floating-ui/dom": "^1.6.13",
|
||||
"@lit/context": "^1.1.2",
|
||||
"@preact/signals-core": "^1.8.0",
|
||||
"@toeverything/theme": "^1.1.23",
|
||||
"@toeverything/theme": "^1.1.15",
|
||||
"file-type": "^21.0.0",
|
||||
"lit": "^3.2.0",
|
||||
"minimatch": "^10.1.1",
|
||||
"rxjs": "^7.8.2",
|
||||
"zod": "^3.25.76"
|
||||
"minimatch": "^10.0.1",
|
||||
"rxjs": "^7.8.1",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
@@ -41,5 +41,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.26.0"
|
||||
"version": "0.21.0"
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ import {
|
||||
AttachmentBlockStyles,
|
||||
} from '@blocksuite/affine-model';
|
||||
import {
|
||||
BlockElementCommentManager,
|
||||
CitationProvider,
|
||||
DocModeProvider,
|
||||
FileSizeLimitProvider,
|
||||
@@ -93,14 +92,6 @@ export class AttachmentBlockComponent extends CaptionedBlockComponent<Attachment
|
||||
return this.citationService.isCitationModel(this.model);
|
||||
}
|
||||
|
||||
get isCommentHighlighted() {
|
||||
return (
|
||||
this.std
|
||||
.getOptional(BlockElementCommentManager)
|
||||
?.isBlockCommentHighlighted(this.model) ?? false
|
||||
);
|
||||
}
|
||||
|
||||
convertTo = () => {
|
||||
return this.std
|
||||
.get(AttachmentEmbedProvider)
|
||||
@@ -508,7 +499,6 @@ export class AttachmentBlockComponent extends CaptionedBlockComponent<Attachment
|
||||
class=${classMap({
|
||||
'affine-attachment-container': true,
|
||||
focused: this.selected$.value,
|
||||
'comment-highlighted': this.isCommentHighlighted,
|
||||
})}
|
||||
style=${this.containerStyleMap}
|
||||
>
|
||||
|
||||
@@ -15,10 +15,6 @@ export const styles = css`
|
||||
}
|
||||
}
|
||||
|
||||
.affine-attachment-container.comment-highlighted {
|
||||
outline: 2px solid ${unsafeCSSVarV2('block/comment/highlightUnderline')};
|
||||
}
|
||||
|
||||
.affine-attachment-card {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
|
||||
@@ -19,20 +19,20 @@
|
||||
"@blocksuite/affine-shared": "workspace:*",
|
||||
"@blocksuite/affine-widget-slash-menu": "workspace:*",
|
||||
"@blocksuite/global": "workspace:*",
|
||||
"@blocksuite/icons": "^2.2.17",
|
||||
"@blocksuite/icons": "^2.2.12",
|
||||
"@blocksuite/std": "workspace:*",
|
||||
"@blocksuite/store": "workspace:*",
|
||||
"@lit/context": "^1.1.2",
|
||||
"@preact/signals-core": "^1.8.0",
|
||||
"@toeverything/theme": "^1.1.23",
|
||||
"@toeverything/theme": "^1.1.15",
|
||||
"lit": "^3.2.0",
|
||||
"minimatch": "^10.1.1",
|
||||
"rxjs": "^7.8.2",
|
||||
"yjs": "^13.6.27",
|
||||
"zod": "^3.25.76"
|
||||
"minimatch": "^10.0.1",
|
||||
"rxjs": "^7.8.1",
|
||||
"yjs": "^13.6.23",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vitest": "^3.2.4"
|
||||
"vitest": "3.1.3"
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
@@ -45,5 +45,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.26.0"
|
||||
"version": "0.21.0"
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import type {
|
||||
} from '@blocksuite/affine-model';
|
||||
import { ImageProxyService } from '@blocksuite/affine-shared/adapters';
|
||||
import {
|
||||
BlockElementCommentManager,
|
||||
CitationProvider,
|
||||
DocModeProvider,
|
||||
LinkPreviewServiceIdentifier,
|
||||
@@ -129,14 +128,6 @@ export class BookmarkBlockComponent extends CaptionedBlockComponent<BookmarkBloc
|
||||
return this.std.get(ImageProxyService);
|
||||
}
|
||||
|
||||
get isCommentHighlighted() {
|
||||
return (
|
||||
this.std
|
||||
.getOptional(BlockElementCommentManager)
|
||||
?.isBlockCommentHighlighted(this.model) ?? false
|
||||
);
|
||||
}
|
||||
|
||||
handleClick = (event: MouseEvent) => {
|
||||
event.stopPropagation();
|
||||
|
||||
|
||||
@@ -45,7 +45,6 @@ export class BookmarkCard extends SignalWatcher(
|
||||
[style]: true,
|
||||
selected: this.bookmark.selected$.value,
|
||||
edgeless: isGfxBlockComponent(this.bookmark),
|
||||
'comment-highlighted': this.bookmark.isCommentHighlighted,
|
||||
});
|
||||
|
||||
const domainName = url.match(
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import {
|
||||
canEmbedAsEmbedBlock,
|
||||
canEmbedAsIframe,
|
||||
EMBED_IFRAME_DEFAULT_HEIGHT_IN_SURFACE,
|
||||
EMBED_IFRAME_DEFAULT_WIDTH_IN_SURFACE,
|
||||
@@ -150,10 +149,13 @@ const builtinToolbarConfig = {
|
||||
if (!model) return true;
|
||||
|
||||
const url = model.props.url;
|
||||
// check if the url can be embedded as iframe block or other embed blocks
|
||||
const options = ctx.std
|
||||
.get(EmbedOptionProvider)
|
||||
.getEmbedBlockOptions(url);
|
||||
|
||||
return (
|
||||
!canEmbedAsIframe(ctx.std, url) &&
|
||||
!canEmbedAsEmbedBlock(ctx.std, url)
|
||||
!canEmbedAsIframe(ctx.std, url) && options?.viewType !== 'embed'
|
||||
);
|
||||
},
|
||||
run(ctx) {
|
||||
@@ -167,8 +169,15 @@ const builtinToolbarConfig = {
|
||||
|
||||
let blockId: string | undefined;
|
||||
|
||||
// first try to embed as a custom embed block
|
||||
if (canEmbedAsEmbedBlock(ctx.std, url)) {
|
||||
// first try to embed as iframe block
|
||||
if (canEmbedAsIframe(ctx.std, url)) {
|
||||
const embedIframeService = ctx.std.get(EmbedIframeService);
|
||||
blockId = embedIframeService.addEmbedIframeBlock(
|
||||
{ url, caption, title, description },
|
||||
parent.id,
|
||||
index
|
||||
);
|
||||
} else {
|
||||
const options = ctx.std
|
||||
.get(EmbedOptionProvider)
|
||||
.getEmbedBlockOptions(url);
|
||||
@@ -193,13 +202,6 @@ const builtinToolbarConfig = {
|
||||
parent,
|
||||
index
|
||||
);
|
||||
} else if (canEmbedAsIframe(ctx.std, url)) {
|
||||
const embedIframeService = ctx.std.get(EmbedIframeService);
|
||||
blockId = embedIframeService.addEmbedIframeBlock(
|
||||
{ url, caption, title, description },
|
||||
parent.id,
|
||||
index
|
||||
);
|
||||
}
|
||||
|
||||
if (!blockId) return;
|
||||
@@ -377,8 +379,27 @@ const builtinSurfaceToolbarConfig = {
|
||||
|
||||
let newId: string | undefined;
|
||||
|
||||
// first try to embed as a custom embed block
|
||||
if (canEmbedAsEmbedBlock(ctx.std, url)) {
|
||||
// first try to embed as iframe block
|
||||
if (canEmbedAsIframe(ctx.std, url)) {
|
||||
const embedIframeService = ctx.std.get(EmbedIframeService);
|
||||
const config = embedIframeService.getConfig(url);
|
||||
if (!config) {
|
||||
return;
|
||||
}
|
||||
|
||||
const bound = Bound.deserialize(xywh);
|
||||
const options = config.options;
|
||||
const { widthInSurface, heightInSurface } = options ?? {};
|
||||
bound.w = widthInSurface ?? EMBED_IFRAME_DEFAULT_WIDTH_IN_SURFACE;
|
||||
bound.h =
|
||||
heightInSurface ?? EMBED_IFRAME_DEFAULT_HEIGHT_IN_SURFACE;
|
||||
|
||||
newId = ctx.store.addBlock(
|
||||
'affine:embed-iframe',
|
||||
{ url, caption, title, description, xywh: bound.serialize() },
|
||||
parent
|
||||
);
|
||||
} else {
|
||||
const options = ctx.std
|
||||
.get(EmbedOptionProvider)
|
||||
.getEmbedBlockOptions(url);
|
||||
@@ -408,29 +429,8 @@ const builtinSurfaceToolbarConfig = {
|
||||
},
|
||||
parent
|
||||
);
|
||||
} else if (canEmbedAsIframe(ctx.std, url)) {
|
||||
const embedIframeService = ctx.std.get(EmbedIframeService);
|
||||
const config = embedIframeService.getConfig(url);
|
||||
if (!config) {
|
||||
return;
|
||||
}
|
||||
|
||||
const bound = Bound.deserialize(xywh);
|
||||
const options = config.options;
|
||||
const { widthInSurface, heightInSurface } = options ?? {};
|
||||
bound.w = widthInSurface ?? EMBED_IFRAME_DEFAULT_WIDTH_IN_SURFACE;
|
||||
bound.h =
|
||||
heightInSurface ?? EMBED_IFRAME_DEFAULT_HEIGHT_IN_SURFACE;
|
||||
|
||||
newId = ctx.store.addBlock(
|
||||
'affine:embed-iframe',
|
||||
{ url, caption, title, description, xywh: bound.serialize() },
|
||||
parent
|
||||
);
|
||||
}
|
||||
|
||||
if (!newId) return;
|
||||
|
||||
ctx.command.exec(reassociateConnectorsCommand, { oldId, newId });
|
||||
|
||||
ctx.store.deleteBlock(model);
|
||||
@@ -449,10 +449,13 @@ const builtinSurfaceToolbarConfig = {
|
||||
when(ctx) {
|
||||
const model = ctx.getCurrentModelByType(BookmarkBlockModel);
|
||||
if (!model) return false;
|
||||
|
||||
const { url } = model.props;
|
||||
return (
|
||||
canEmbedAsIframe(ctx.std, url) || canEmbedAsEmbedBlock(ctx.std, url)
|
||||
);
|
||||
const options = ctx.std
|
||||
.get(EmbedOptionProvider)
|
||||
.getEmbedBlockOptions(url);
|
||||
|
||||
return canEmbedAsIframe(ctx.std, url) || options?.viewType === 'embed';
|
||||
},
|
||||
content(ctx) {
|
||||
const model = ctx.getCurrentModelByType(BookmarkBlockModel);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { unsafeCSSVar, unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme';
|
||||
import { unsafeCSSVar } from '@blocksuite/affine-shared/theme';
|
||||
import { baseTheme } from '@toeverything/theme';
|
||||
import { css, unsafeCSS } from 'lit';
|
||||
|
||||
@@ -17,9 +17,9 @@ export const styles = css`
|
||||
width: 100%;
|
||||
|
||||
border-radius: 8px;
|
||||
border: 1px solid ${unsafeCSSVarV2('layer/background/tertiary')};
|
||||
border: 1px solid var(--affine-background-tertiary-color);
|
||||
|
||||
background: ${unsafeCSSVarV2('layer/background/primary')};
|
||||
background: var(--affine-background-primary-color);
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
@@ -158,10 +158,6 @@ export const styles = css`
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.affine-bookmark-card.comment-highlighted {
|
||||
outline: 2px solid ${unsafeCSSVarV2('block/comment/highlightUnderline')};
|
||||
}
|
||||
|
||||
.affine-bookmark-card.loading {
|
||||
.affine-bookmark-content-title-text {
|
||||
color: var(--affine-placeholder-color);
|
||||
|
||||
@@ -18,21 +18,20 @@
|
||||
"@blocksuite/affine-shared": "workspace:*",
|
||||
"@blocksuite/affine-widget-slash-menu": "workspace:*",
|
||||
"@blocksuite/global": "workspace:*",
|
||||
"@blocksuite/icons": "^2.2.17",
|
||||
"@blocksuite/icons": "^2.2.12",
|
||||
"@blocksuite/std": "workspace:*",
|
||||
"@blocksuite/store": "workspace:*",
|
||||
"@emoji-mart/data": "^1.2.1",
|
||||
"@emotion/css": "^11.13.5",
|
||||
"@floating-ui/dom": "^1.6.10",
|
||||
"@lit/context": "^1.1.2",
|
||||
"@preact/signals-core": "^1.8.0",
|
||||
"@toeverything/theme": "^1.1.23",
|
||||
"@toeverything/theme": "^1.1.15",
|
||||
"@types/mdast": "^4.0.4",
|
||||
"emoji-mart": "^5.6.0",
|
||||
"lit": "^3.2.0",
|
||||
"minimatch": "^10.1.1",
|
||||
"rxjs": "^7.8.2",
|
||||
"zod": "^3.25.76"
|
||||
"minimatch": "^10.0.1",
|
||||
"rxjs": "^7.8.1",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
@@ -45,5 +44,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.26.0"
|
||||
"version": "0.21.0"
|
||||
}
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
import { css } from '@emotion/css';
|
||||
|
||||
export const calloutHostStyles = css({
|
||||
display: 'block',
|
||||
margin: '8px 0',
|
||||
});
|
||||
|
||||
export const calloutBlockContainerStyles = css({
|
||||
display: 'flex',
|
||||
alignItems: 'flex-start',
|
||||
padding: '5px 10px',
|
||||
borderRadius: '8px',
|
||||
});
|
||||
|
||||
export const calloutEmojiContainerStyles = css({
|
||||
userSelect: 'none',
|
||||
fontSize: '1.2em',
|
||||
width: '24px',
|
||||
height: '24px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
// marginTop is dynamically set by JavaScript based on first child's height
|
||||
marginBottom: '10px',
|
||||
flexShrink: 0,
|
||||
position: 'relative',
|
||||
});
|
||||
|
||||
export const calloutEmojiStyles = css({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
':hover': {
|
||||
cursor: 'pointer',
|
||||
opacity: 0.7,
|
||||
},
|
||||
});
|
||||
|
||||
export const calloutChildrenStyles = css({
|
||||
flex: 1,
|
||||
minWidth: 0,
|
||||
paddingLeft: '10px',
|
||||
});
|
||||
|
||||
export const iconPickerContainerStyles = css({
|
||||
position: 'absolute',
|
||||
top: '100%',
|
||||
left: 0,
|
||||
zIndex: 1000,
|
||||
background: 'white',
|
||||
border: '1px solid #ccc',
|
||||
borderRadius: '8px',
|
||||
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)',
|
||||
width: '390px',
|
||||
height: '400px',
|
||||
});
|
||||
@@ -1,191 +1,84 @@
|
||||
import { CaptionedBlockComponent } from '@blocksuite/affine-components/caption';
|
||||
import {
|
||||
createPopup,
|
||||
popupTargetFromElement,
|
||||
} from '@blocksuite/affine-components/context-menu';
|
||||
import { createLitPortal } from '@blocksuite/affine-components/portal';
|
||||
import { DefaultInlineManagerExtension } from '@blocksuite/affine-inline-preset';
|
||||
import {
|
||||
type CalloutBlockModel,
|
||||
type ParagraphBlockModel,
|
||||
} from '@blocksuite/affine-model';
|
||||
import { focusTextModel } from '@blocksuite/affine-rich-text';
|
||||
import { type CalloutBlockModel } from '@blocksuite/affine-model';
|
||||
import { EDGELESS_TOP_CONTENTEDITABLE_SELECTOR } from '@blocksuite/affine-shared/consts';
|
||||
import {
|
||||
DocModeProvider,
|
||||
type IconData,
|
||||
IconPickerServiceIdentifier,
|
||||
IconType,
|
||||
ThemeProvider,
|
||||
} from '@blocksuite/affine-shared/services';
|
||||
import type { UniComponent } from '@blocksuite/affine-shared/types';
|
||||
import * as icons from '@blocksuite/icons/lit';
|
||||
import { unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme';
|
||||
import type { BlockComponent } from '@blocksuite/std';
|
||||
import { type Signal } from '@preact/signals-core';
|
||||
import { cssVarV2 } from '@toeverything/theme/v2';
|
||||
import type { TemplateResult } from 'lit';
|
||||
import { html } from 'lit';
|
||||
import { type StyleInfo, styleMap } from 'lit/directives/style-map.js';
|
||||
|
||||
import {
|
||||
calloutBlockContainerStyles,
|
||||
calloutChildrenStyles,
|
||||
calloutEmojiContainerStyles,
|
||||
calloutEmojiStyles,
|
||||
calloutHostStyles,
|
||||
} from './callout-block-styles.js';
|
||||
import { IconPickerWrapper } from './icon-picker-wrapper.js';
|
||||
// Copy of renderUniLit and UniLit from affine-data-view
|
||||
export const renderUniLit = <Props, Expose extends NonNullable<unknown>>(
|
||||
uni: UniComponent<Props, Expose> | undefined,
|
||||
props?: Props,
|
||||
options?: {
|
||||
ref?: Signal<Expose | undefined>;
|
||||
style?: Readonly<StyleInfo>;
|
||||
class?: string;
|
||||
}
|
||||
): TemplateResult => {
|
||||
return html` <uni-lit
|
||||
.uni="${uni}"
|
||||
.props="${props}"
|
||||
.ref="${options?.ref}"
|
||||
style=${options?.style ? styleMap(options?.style) : ''}
|
||||
></uni-lit>`;
|
||||
};
|
||||
const getIcon = (icon?: IconData) => {
|
||||
if (!icon) {
|
||||
return null;
|
||||
}
|
||||
if (icon.type === IconType.Emoji) {
|
||||
return icon.unicode;
|
||||
}
|
||||
if (icon.type === IconType.AffineIcon) {
|
||||
return (
|
||||
icons as Record<string, (props: { style: string }) => TemplateResult>
|
||||
)[`${icon.name}Icon`]?.({ style: `color:${icon.color}` });
|
||||
}
|
||||
return null;
|
||||
};
|
||||
import { flip, offset } from '@floating-ui/dom';
|
||||
import { css, html } from 'lit';
|
||||
import { query } from 'lit/decorators.js';
|
||||
import { styleMap } from 'lit/directives/style-map.js';
|
||||
export class CalloutBlockComponent extends CaptionedBlockComponent<CalloutBlockModel> {
|
||||
private _popupCloseHandler: (() => void) | null = null;
|
||||
|
||||
override connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.classList.add(calloutHostStyles);
|
||||
}
|
||||
|
||||
private _getEmojiMarginTop(): string {
|
||||
if (this.model.children.length === 0) {
|
||||
return '10px';
|
||||
static override styles = css`
|
||||
:host {
|
||||
display: block;
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
const firstChild = this.model.children[0];
|
||||
const flavour = firstChild.flavour;
|
||||
|
||||
const marginTopMap: Record<string, string> = {
|
||||
'affine:paragraph:h1': '23px',
|
||||
'affine:paragraph:h2': '20px',
|
||||
'affine:paragraph:h3': '16px',
|
||||
'affine:paragraph:h4': '15px',
|
||||
'affine:paragraph:h5': '14px',
|
||||
'affine:paragraph:h6': '13px',
|
||||
};
|
||||
|
||||
// For heading blocks, use the type to determine margin
|
||||
if (flavour === 'affine:paragraph') {
|
||||
const paragraph = firstChild as ParagraphBlockModel;
|
||||
const type = paragraph.props.type$.value;
|
||||
const key = `${flavour}:${type}`;
|
||||
return marginTopMap[key] || '10px';
|
||||
.affine-callout-block-container {
|
||||
display: flex;
|
||||
padding: 5px 10px;
|
||||
border-radius: 8px;
|
||||
background-color: ${unsafeCSSVarV2('block/callout/background/grey')};
|
||||
}
|
||||
|
||||
// Default for all other block types
|
||||
return '10px';
|
||||
}
|
||||
|
||||
private _closeIconPicker() {
|
||||
if (this._popupCloseHandler) {
|
||||
this._popupCloseHandler();
|
||||
this._popupCloseHandler = null;
|
||||
.affine-callout-emoji-container {
|
||||
margin-right: 10px;
|
||||
margin-top: 14px;
|
||||
user-select: none;
|
||||
font-size: 1.2em;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
private _toggleIconPicker(event: MouseEvent) {
|
||||
// If popup is already open, close it
|
||||
if (this._popupCloseHandler) {
|
||||
this._closeIconPicker();
|
||||
return;
|
||||
.affine-callout-emoji:hover {
|
||||
cursor: pointer;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
// Get IconPickerService from the framework
|
||||
const iconPickerService = this.std.getOptional(IconPickerServiceIdentifier);
|
||||
if (!iconPickerService) {
|
||||
console.warn('IconPickerService not found');
|
||||
return;
|
||||
.affine-callout-children {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
padding-left: 10px;
|
||||
}
|
||||
`;
|
||||
|
||||
// Get the uni-component from the service
|
||||
const iconPickerComponent = iconPickerService.iconPickerComponent;
|
||||
private _emojiMenuAbortController: AbortController | null = null;
|
||||
private readonly _toggleEmojiMenu = () => {
|
||||
if (this._emojiMenuAbortController) {
|
||||
this._emojiMenuAbortController.abort();
|
||||
}
|
||||
this._emojiMenuAbortController = new AbortController();
|
||||
|
||||
// Create props for the icon picker
|
||||
const props = {
|
||||
onSelect: (iconData?: IconData) => {
|
||||
this.model.props.icon$.value = iconData;
|
||||
this._closeIconPicker(); // Close the picker after selection
|
||||
const theme = this.std.get(ThemeProvider).theme$.value;
|
||||
|
||||
createLitPortal({
|
||||
template: html`<affine-emoji-menu
|
||||
.theme=${theme}
|
||||
.onEmojiSelect=${(data: any) => {
|
||||
this.model.props.emoji = data.native;
|
||||
}}
|
||||
></affine-emoji-menu>`,
|
||||
portalStyles: {
|
||||
zIndex: 'var(--affine-z-index-popover)',
|
||||
},
|
||||
onClose: () => {
|
||||
this._closeIconPicker();
|
||||
},
|
||||
};
|
||||
|
||||
// Create IconPickerWrapper instance
|
||||
const wrapper = new IconPickerWrapper();
|
||||
wrapper.iconPickerComponent = iconPickerComponent;
|
||||
wrapper.props = props;
|
||||
wrapper.style.position = 'absolute';
|
||||
wrapper.style.backgroundColor = cssVarV2.layer.background.overlayPanel;
|
||||
wrapper.style.boxShadow = 'var(--affine-menu-shadow)';
|
||||
wrapper.style.borderRadius = '8px';
|
||||
|
||||
// Create popup target from the clicked element
|
||||
const target = popupTargetFromElement(event.currentTarget as HTMLElement);
|
||||
|
||||
// Create popup
|
||||
this._popupCloseHandler = createPopup(target, wrapper, {
|
||||
onClose: () => {
|
||||
this._popupCloseHandler = null;
|
||||
container: this.host,
|
||||
computePosition: {
|
||||
referenceElement: this._emojiButton,
|
||||
placement: 'bottom-start',
|
||||
middleware: [flip(), offset(4)],
|
||||
autoUpdate: { animationFrame: true },
|
||||
},
|
||||
abortController: this._emojiMenuAbortController,
|
||||
closeOnClickAway: true,
|
||||
});
|
||||
}
|
||||
|
||||
private readonly _handleBlockClick = (event: MouseEvent) => {
|
||||
// Check if the click target is emoji related element
|
||||
const target = event.target as HTMLElement;
|
||||
if (
|
||||
target.closest('.affine-callout-emoji-container') ||
|
||||
target.classList.contains('affine-callout-emoji')
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If there's no icon, open icon picker on click
|
||||
const icon = this.model.props.icon$.value;
|
||||
if (!icon) {
|
||||
this._toggleIconPicker(event);
|
||||
return;
|
||||
}
|
||||
|
||||
// Only handle clicks when there are no children
|
||||
if (this.model.children.length > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Prevent event bubbling
|
||||
event.stopPropagation();
|
||||
|
||||
// Create a new paragraph block
|
||||
const paragraphId = this.store.addBlock('affine:paragraph', {}, this.model);
|
||||
|
||||
// Focus the new paragraph
|
||||
focusTextModel(this.std, paragraphId);
|
||||
};
|
||||
|
||||
get attributeRenderer() {
|
||||
@@ -204,6 +97,9 @@ export class CalloutBlockComponent extends CaptionedBlockComponent<CalloutBlockM
|
||||
return this.std.get(DefaultInlineManagerExtension.identifier);
|
||||
}
|
||||
|
||||
@query('.affine-callout-emoji')
|
||||
private accessor _emojiButton!: HTMLElement;
|
||||
|
||||
override get topContenteditableElement() {
|
||||
if (this.std.get(DocModeProvider).getEditorMode() === 'edgeless') {
|
||||
return this.closest<BlockComponent>(
|
||||
@@ -214,39 +110,20 @@ export class CalloutBlockComponent extends CaptionedBlockComponent<CalloutBlockM
|
||||
}
|
||||
|
||||
override renderBlock() {
|
||||
const icon = this.model.props.icon$.value;
|
||||
const backgroundColorName = this.model.props.backgroundColorName$.value;
|
||||
const backgroundColor = (
|
||||
cssVarV2.block.callout.background as Record<string, string>
|
||||
)[backgroundColorName ?? ''];
|
||||
|
||||
const iconContent = getIcon(icon);
|
||||
|
||||
const emoji = this.model.props.emoji$.value;
|
||||
return html`
|
||||
<div
|
||||
class="${calloutBlockContainerStyles}"
|
||||
@click=${this._handleBlockClick}
|
||||
style=${styleMap({
|
||||
backgroundColor: backgroundColor ?? 'transparent',
|
||||
})}
|
||||
>
|
||||
${iconContent
|
||||
? html`
|
||||
<div
|
||||
@click=${this._toggleIconPicker}
|
||||
contenteditable="false"
|
||||
class="${calloutEmojiContainerStyles}"
|
||||
style=${styleMap({
|
||||
marginTop: this._getEmojiMarginTop(),
|
||||
})}
|
||||
>
|
||||
<span class="${calloutEmojiStyles}" data-testid="callout-emoji"
|
||||
>${iconContent}</span
|
||||
>
|
||||
</div>
|
||||
`
|
||||
: ''}
|
||||
<div class="${calloutChildrenStyles}">
|
||||
<div class="affine-callout-block-container">
|
||||
<div
|
||||
@click=${this._toggleEmojiMenu}
|
||||
contenteditable="false"
|
||||
class="affine-callout-emoji-container"
|
||||
style=${styleMap({
|
||||
display: emoji.length === 0 ? 'none' : undefined,
|
||||
})}
|
||||
>
|
||||
<span class="affine-callout-emoji">${emoji}</span>
|
||||
</div>
|
||||
<div class="affine-callout-children">
|
||||
${this.renderChildren(this.model)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
import {
|
||||
CalloutBlockModel,
|
||||
ParagraphBlockModel,
|
||||
} from '@blocksuite/affine-model';
|
||||
import { CalloutBlockModel } from '@blocksuite/affine-model';
|
||||
import { matchModels } from '@blocksuite/affine-shared/utils';
|
||||
import {
|
||||
BlockSelection,
|
||||
@@ -9,46 +6,13 @@ import {
|
||||
TextSelection,
|
||||
} from '@blocksuite/std';
|
||||
|
||||
import { calloutToParagraphCommand } from './commands/callout-to-paragraph.js';
|
||||
import { splitCalloutCommand } from './commands/split-callout.js';
|
||||
|
||||
export const CalloutKeymapExtension = KeymapExtension(std => {
|
||||
return {
|
||||
Enter: ctx => {
|
||||
const text = std.selection.find(TextSelection);
|
||||
if (!text) return false;
|
||||
|
||||
const currentBlock = std.store.getBlock(text.from.blockId);
|
||||
if (!currentBlock) return false;
|
||||
|
||||
// Check if current block is a callout block
|
||||
let calloutBlock = currentBlock;
|
||||
if (!matchModels(currentBlock.model, [CalloutBlockModel])) {
|
||||
// If not, check if the parent is a callout block
|
||||
const parent = std.store.getParent(currentBlock.model);
|
||||
if (!parent || !matchModels(parent, [CalloutBlockModel])) {
|
||||
return false;
|
||||
}
|
||||
const parentBlock = std.store.getBlock(parent.id);
|
||||
if (!parentBlock) return false;
|
||||
calloutBlock = parentBlock;
|
||||
}
|
||||
|
||||
ctx.get('keyboardState').raw.preventDefault();
|
||||
std.command
|
||||
.chain()
|
||||
.pipe(splitCalloutCommand, {
|
||||
blockId: calloutBlock.model.id,
|
||||
inlineIndex: text.from.index,
|
||||
currentBlockId: text.from.blockId,
|
||||
})
|
||||
.run();
|
||||
return true;
|
||||
},
|
||||
Backspace: ctx => {
|
||||
const text = std.selection.find(TextSelection);
|
||||
if (text && text.isCollapsed() && text.from.index === 0) {
|
||||
const event = ctx.get('defaultState').event;
|
||||
event.preventDefault();
|
||||
|
||||
const block = std.store.getBlock(text.from.blockId);
|
||||
if (!block) return false;
|
||||
@@ -56,22 +20,6 @@ export const CalloutKeymapExtension = KeymapExtension(std => {
|
||||
if (!parent) return false;
|
||||
if (!matchModels(parent, [CalloutBlockModel])) return false;
|
||||
|
||||
// Check if current block is a paragraph inside callout
|
||||
if (matchModels(block.model, [ParagraphBlockModel])) {
|
||||
event.preventDefault();
|
||||
|
||||
std.command
|
||||
.chain()
|
||||
.pipe(calloutToParagraphCommand, {
|
||||
id: block.model.id,
|
||||
})
|
||||
.run();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Fallback to selecting the callout block
|
||||
event.preventDefault();
|
||||
std.selection.setGroup('note', [
|
||||
std.selection.create(BlockSelection, {
|
||||
blockId: parent.id,
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
import {
|
||||
CalloutBlockModel,
|
||||
ParagraphBlockModel,
|
||||
} from '@blocksuite/affine-model';
|
||||
import { focusTextModel } from '@blocksuite/affine-rich-text';
|
||||
import { matchModels } from '@blocksuite/affine-shared/utils';
|
||||
import type { Command } from '@blocksuite/std';
|
||||
import { BlockSelection } from '@blocksuite/std';
|
||||
import { Text } from '@blocksuite/store';
|
||||
|
||||
export const calloutToParagraphCommand: Command<
|
||||
{
|
||||
id: string;
|
||||
stopCapturing?: boolean;
|
||||
},
|
||||
{
|
||||
success: boolean;
|
||||
}
|
||||
> = (ctx, next) => {
|
||||
const { id, stopCapturing = true } = ctx;
|
||||
const std = ctx.std;
|
||||
const doc = std.store;
|
||||
const model = doc.getBlock(id)?.model;
|
||||
|
||||
if (!model || !matchModels(model, [ParagraphBlockModel])) return false;
|
||||
|
||||
const parent = doc.getParent(model);
|
||||
if (!parent || !matchModels(parent, [CalloutBlockModel])) return false;
|
||||
|
||||
if (stopCapturing) std.store.captureSync();
|
||||
|
||||
// Get current block index in callout
|
||||
const currentIndex = parent.children.indexOf(model);
|
||||
const hasText = model.text && model.text.length > 0;
|
||||
|
||||
// Find previous paragraph block in callout
|
||||
let previousBlock = null;
|
||||
for (let i = currentIndex - 1; i >= 0; i--) {
|
||||
const sibling = parent.children[i];
|
||||
if (matchModels(sibling, [ParagraphBlockModel])) {
|
||||
previousBlock = sibling;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (previousBlock && hasText) {
|
||||
// Clone current text content before any operations to prevent data loss
|
||||
const currentText = model.text || new Text();
|
||||
|
||||
// Get previous block text and merge index
|
||||
const previousText = previousBlock.text || new Text();
|
||||
const mergeIndex = previousText.length;
|
||||
|
||||
// Apply each delta from cloned current text to previous block to preserve formatting
|
||||
previousText.join(currentText);
|
||||
|
||||
// Remove current block after text has been merged
|
||||
doc.deleteBlock(model, {
|
||||
deleteChildren: false,
|
||||
});
|
||||
|
||||
// Focus at merge point in previous block
|
||||
focusTextModel(std, previousBlock.id, mergeIndex);
|
||||
} else if (previousBlock && !hasText) {
|
||||
// Move cursor to end of previous block
|
||||
doc.deleteBlock(model, {
|
||||
deleteChildren: false,
|
||||
});
|
||||
|
||||
const previousText = previousBlock.text || new Text();
|
||||
focusTextModel(std, previousBlock.id, previousText.length);
|
||||
} else {
|
||||
// No previous block, select the entire callout
|
||||
doc.deleteBlock(model, {
|
||||
deleteChildren: false,
|
||||
});
|
||||
|
||||
std.selection.setGroup('note', [
|
||||
std.selection.create(BlockSelection, {
|
||||
blockId: parent.id,
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
return next({ success: true });
|
||||
};
|
||||
@@ -1,85 +0,0 @@
|
||||
import {
|
||||
CalloutBlockModel,
|
||||
ParagraphBlockModel,
|
||||
} from '@blocksuite/affine-model';
|
||||
import { focusTextModel } from '@blocksuite/affine-rich-text';
|
||||
import { matchModels } from '@blocksuite/affine-shared/utils';
|
||||
import type { Command, EditorHost } from '@blocksuite/std';
|
||||
|
||||
export const splitCalloutCommand: Command<{
|
||||
blockId: string;
|
||||
inlineIndex: number;
|
||||
currentBlockId: string;
|
||||
}> = (ctx, next) => {
|
||||
const { blockId, inlineIndex, currentBlockId, std } = ctx;
|
||||
const host = std.host as EditorHost;
|
||||
const doc = host.store;
|
||||
|
||||
const calloutModel = doc.getBlock(blockId)?.model;
|
||||
if (!calloutModel || !matchModels(calloutModel, [CalloutBlockModel])) {
|
||||
console.error(`block ${blockId} is not a callout block`);
|
||||
return;
|
||||
}
|
||||
|
||||
const currentModel = doc.getBlock(currentBlockId)?.model;
|
||||
if (!currentModel) {
|
||||
console.error(`current block ${currentBlockId} not found`);
|
||||
return;
|
||||
}
|
||||
|
||||
doc.captureSync();
|
||||
|
||||
if (matchModels(currentModel, [ParagraphBlockModel])) {
|
||||
// User is in a paragraph within the callout's children
|
||||
const afterText = currentModel.props.text.split(inlineIndex);
|
||||
|
||||
// Update the current paragraph's text to keep only the part before cursor
|
||||
doc.transact(() => {
|
||||
currentModel.props.text.delete(
|
||||
inlineIndex,
|
||||
currentModel.props.text.length - inlineIndex
|
||||
);
|
||||
});
|
||||
|
||||
// Create a new paragraph block after the current one
|
||||
const parent = doc.getParent(currentModel);
|
||||
if (parent) {
|
||||
const currentIndex = parent.children.indexOf(currentModel);
|
||||
const newParagraphId = doc.addBlock(
|
||||
'affine:paragraph',
|
||||
{
|
||||
text: afterText,
|
||||
},
|
||||
parent,
|
||||
currentIndex + 1
|
||||
);
|
||||
|
||||
if (newParagraphId) {
|
||||
host.updateComplete
|
||||
.then(() => {
|
||||
focusTextModel(std, newParagraphId);
|
||||
})
|
||||
.catch(console.error);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If current block is not a paragraph, create a new paragraph in callout
|
||||
const newParagraphId = doc.addBlock(
|
||||
'affine:paragraph',
|
||||
{
|
||||
text: new Text(),
|
||||
},
|
||||
calloutModel
|
||||
);
|
||||
|
||||
if (newParagraphId) {
|
||||
host.updateComplete
|
||||
.then(() => {
|
||||
focusTextModel(std, newParagraphId);
|
||||
})
|
||||
.catch(console.error);
|
||||
}
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
@@ -1,11 +1,24 @@
|
||||
import { CalloutBlockModel } from '@blocksuite/affine-model';
|
||||
import { focusBlockEnd } from '@blocksuite/affine-shared/commands';
|
||||
import { isInsideBlockByFlavour } from '@blocksuite/affine-shared/utils';
|
||||
import { FeatureFlagService } from '@blocksuite/affine-shared/services';
|
||||
import {
|
||||
findAncestorModel,
|
||||
isInsideBlockByFlavour,
|
||||
matchModels,
|
||||
} from '@blocksuite/affine-shared/utils';
|
||||
import { type SlashMenuConfig } from '@blocksuite/affine-widget-slash-menu';
|
||||
import { FontIcon } from '@blocksuite/icons/lit';
|
||||
|
||||
import { calloutTooltip } from './tooltips';
|
||||
|
||||
export const calloutSlashMenuConfig: SlashMenuConfig = {
|
||||
disableWhen: ({ model }) => {
|
||||
return (
|
||||
findAncestorModel(model, ancestor =>
|
||||
matchModels(ancestor, [CalloutBlockModel])
|
||||
) !== null
|
||||
);
|
||||
},
|
||||
items: [
|
||||
{
|
||||
name: 'Callout',
|
||||
@@ -17,11 +30,10 @@ export const calloutSlashMenuConfig: SlashMenuConfig = {
|
||||
},
|
||||
searchAlias: ['callout'],
|
||||
group: '0_Basic@9',
|
||||
when: ({ model }) => {
|
||||
return !isInsideBlockByFlavour(
|
||||
model.store,
|
||||
model,
|
||||
'affine:edgeless-text'
|
||||
when: ({ std, model }) => {
|
||||
return (
|
||||
std.get(FeatureFlagService).getFlag('enable_callout') &&
|
||||
!isInsideBlockByFlavour(model.store, model, 'affine:edgeless-text')
|
||||
);
|
||||
},
|
||||
action: ({ model, std }) => {
|
||||
|
||||
@@ -1,204 +0,0 @@
|
||||
import {
|
||||
createPopup,
|
||||
popupTargetFromElement,
|
||||
} from '@blocksuite/affine-components/context-menu';
|
||||
import { EditorChevronDown } from '@blocksuite/affine-components/toolbar';
|
||||
import { CalloutBlockModel } from '@blocksuite/affine-model';
|
||||
import {
|
||||
ActionPlacement,
|
||||
type IconData,
|
||||
IconPickerServiceIdentifier,
|
||||
type ToolbarAction,
|
||||
type ToolbarActionGroup,
|
||||
type ToolbarModuleConfig,
|
||||
ToolbarModuleExtension,
|
||||
} from '@blocksuite/affine-shared/services';
|
||||
import { DeleteIcon, PaletteIcon, SmileIcon } from '@blocksuite/icons/lit';
|
||||
import { BlockFlavourIdentifier } from '@blocksuite/std';
|
||||
import type { ExtensionType } from '@blocksuite/store';
|
||||
import { cssVarV2 } from '@toeverything/theme/v2';
|
||||
import { html } from 'lit';
|
||||
import { repeat } from 'lit/directives/repeat.js';
|
||||
import { styleMap } from 'lit/directives/style-map.js';
|
||||
|
||||
import { IconPickerWrapper } from '../icon-picker-wrapper.js';
|
||||
|
||||
const colors = [
|
||||
'default',
|
||||
'red',
|
||||
'orange',
|
||||
'yellow',
|
||||
'green',
|
||||
'teal',
|
||||
'blue',
|
||||
'purple',
|
||||
'grey',
|
||||
] as const;
|
||||
|
||||
const backgroundColorAction = {
|
||||
id: 'background-color',
|
||||
label: 'Background Color',
|
||||
tooltip: 'Change background color',
|
||||
icon: PaletteIcon(),
|
||||
run() {
|
||||
// This will be handled by the content function
|
||||
},
|
||||
content(ctx) {
|
||||
const model = ctx.getCurrentModelByType(CalloutBlockModel);
|
||||
if (!model) return null;
|
||||
|
||||
const updateBackground = (color: string) => {
|
||||
ctx.store.updateBlock(model, { backgroundColorName: color });
|
||||
};
|
||||
|
||||
return html`
|
||||
<editor-menu-button
|
||||
.contentPadding=${'8px'}
|
||||
.button=${html`
|
||||
<editor-icon-button
|
||||
aria-label="background"
|
||||
.tooltip=${'Background Color'}
|
||||
>
|
||||
${PaletteIcon()} ${EditorChevronDown}
|
||||
</editor-icon-button>
|
||||
`}
|
||||
>
|
||||
<div data-size="large" data-orientation="vertical">
|
||||
<div class="highlight-heading">Background</div>
|
||||
${repeat(colors, color => {
|
||||
const isDefault = color === 'default';
|
||||
const value = isDefault
|
||||
? null
|
||||
: `var(--affine-text-highlight-${color})`;
|
||||
const displayName = `${color} Background`;
|
||||
|
||||
return html`
|
||||
<editor-menu-action
|
||||
data-testid="background-${color}"
|
||||
@click=${() => updateBackground(color)}
|
||||
>
|
||||
<affine-text-duotone-icon
|
||||
style=${styleMap({
|
||||
'--color': 'var(--affine-text-primary-color)',
|
||||
'--background': value ?? 'transparent',
|
||||
})}
|
||||
></affine-text-duotone-icon>
|
||||
<span class="label capitalize">${displayName}</span>
|
||||
</editor-menu-action>
|
||||
`;
|
||||
})}
|
||||
</div>
|
||||
</editor-menu-button>
|
||||
`;
|
||||
},
|
||||
} satisfies ToolbarAction;
|
||||
|
||||
const iconPickerAction = {
|
||||
id: 'icon-picker',
|
||||
label: 'Icon Picker',
|
||||
tooltip: 'Change icon',
|
||||
icon: SmileIcon(),
|
||||
run() {
|
||||
// This will be handled by the content function
|
||||
},
|
||||
content(ctx) {
|
||||
const model = ctx.getCurrentModelByType(CalloutBlockModel);
|
||||
if (!model) return null;
|
||||
|
||||
const handleIconPickerClick = (event: MouseEvent) => {
|
||||
// Get IconPickerService from the framework
|
||||
const iconPickerService = ctx.std.getOptional(
|
||||
IconPickerServiceIdentifier
|
||||
);
|
||||
if (!iconPickerService) {
|
||||
console.warn('IconPickerService not found');
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the uni-component from the service
|
||||
const iconPickerComponent = iconPickerService.iconPickerComponent;
|
||||
|
||||
// Create props for the icon picker
|
||||
const props = {
|
||||
onSelect: (iconData?: IconData) => {
|
||||
// When iconData is undefined (delete icon), set icon to undefined
|
||||
ctx.store.updateBlock(model, { icon: iconData });
|
||||
closeHandler(); // Close the picker after selection
|
||||
},
|
||||
onClose: () => {
|
||||
closeHandler();
|
||||
},
|
||||
};
|
||||
|
||||
// Create IconPickerWrapper instance
|
||||
const wrapper = new IconPickerWrapper();
|
||||
wrapper.iconPickerComponent = iconPickerComponent;
|
||||
wrapper.props = props;
|
||||
wrapper.style.position = 'absolute';
|
||||
wrapper.style.backgroundColor = cssVarV2.layer.background.overlayPanel;
|
||||
wrapper.style.boxShadow = 'var(--affine-menu-shadow)';
|
||||
wrapper.style.borderRadius = '8px';
|
||||
|
||||
// Create popup target from the clicked element
|
||||
const target = popupTargetFromElement(event.currentTarget as HTMLElement);
|
||||
|
||||
// Create popup
|
||||
const closeHandler = createPopup(target, wrapper, {
|
||||
onClose: () => {
|
||||
// Cleanup if needed
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return html`
|
||||
<editor-icon-button
|
||||
aria-label="icon-picker"
|
||||
.tooltip=${'Change Icon'}
|
||||
@click=${handleIconPickerClick}
|
||||
>
|
||||
${SmileIcon()} ${EditorChevronDown}
|
||||
</editor-icon-button>
|
||||
`;
|
||||
},
|
||||
} satisfies ToolbarAction;
|
||||
|
||||
const builtinToolbarConfig = {
|
||||
actions: [
|
||||
{
|
||||
id: 'style',
|
||||
actions: [backgroundColorAction],
|
||||
} satisfies ToolbarActionGroup<ToolbarAction>,
|
||||
{
|
||||
id: 'icon',
|
||||
actions: [iconPickerAction],
|
||||
} satisfies ToolbarActionGroup<ToolbarAction>,
|
||||
{
|
||||
placement: ActionPlacement.More,
|
||||
id: 'c.delete',
|
||||
label: 'Delete',
|
||||
icon: DeleteIcon(),
|
||||
variant: 'destructive',
|
||||
run(ctx) {
|
||||
const model = ctx.getCurrentModelByType(CalloutBlockModel);
|
||||
if (!model) return;
|
||||
|
||||
ctx.store.deleteBlock(model);
|
||||
|
||||
// Clears
|
||||
ctx.select('note');
|
||||
ctx.reset();
|
||||
},
|
||||
} satisfies ToolbarAction,
|
||||
],
|
||||
} as const satisfies ToolbarModuleConfig;
|
||||
|
||||
export const createBuiltinToolbarConfigExtension = (
|
||||
flavour: string
|
||||
): ExtensionType[] => {
|
||||
return [
|
||||
ToolbarModuleExtension({
|
||||
id: BlockFlavourIdentifier(flavour),
|
||||
config: builtinToolbarConfig,
|
||||
}),
|
||||
];
|
||||
};
|
||||
@@ -1,14 +1,14 @@
|
||||
import { CalloutBlockComponent } from './callout-block';
|
||||
import { IconPickerWrapper } from './icon-picker-wrapper';
|
||||
import { EmojiMenu } from './emoji-menu';
|
||||
|
||||
export function effects() {
|
||||
customElements.define('affine-callout', CalloutBlockComponent);
|
||||
customElements.define('icon-picker-wrapper', IconPickerWrapper);
|
||||
customElements.define('affine-emoji-menu', EmojiMenu);
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'affine-callout': CalloutBlockComponent;
|
||||
'icon-picker-wrapper': IconPickerWrapper;
|
||||
'affine-emoji-menu': EmojiMenu;
|
||||
}
|
||||
}
|
||||
|
||||
34
blocksuite/affine/blocks/callout/src/emoji-menu.ts
Normal file
34
blocksuite/affine/blocks/callout/src/emoji-menu.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { WithDisposable } from '@blocksuite/global/lit';
|
||||
import data from '@emoji-mart/data';
|
||||
import { Picker } from 'emoji-mart';
|
||||
import { html, LitElement, type PropertyValues } from 'lit';
|
||||
import { property, query } from 'lit/decorators.js';
|
||||
|
||||
export class EmojiMenu extends WithDisposable(LitElement) {
|
||||
override firstUpdated(props: PropertyValues) {
|
||||
const result = super.firstUpdated(props);
|
||||
|
||||
const picker = new Picker({
|
||||
data,
|
||||
onEmojiSelect: this.onEmojiSelect,
|
||||
autoFocus: true,
|
||||
theme: this.theme,
|
||||
});
|
||||
this.emojiMenu.append(picker as unknown as Node);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor onEmojiSelect: (data: any) => void = () => {};
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor theme: 'light' | 'dark' = 'light';
|
||||
|
||||
@query('.affine-emoji-menu')
|
||||
accessor emojiMenu!: HTMLElement;
|
||||
|
||||
override render() {
|
||||
return html`<div class="affine-emoji-menu"></div>`;
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
import type { IconData } from '@blocksuite/affine-shared/services';
|
||||
import type { UniComponent } from '@blocksuite/affine-shared/types';
|
||||
import { ShadowlessElement } from '@blocksuite/std';
|
||||
import { type Signal } from '@preact/signals-core';
|
||||
import { html, type TemplateResult } from 'lit';
|
||||
import { type StyleInfo, styleMap } from 'lit/directives/style-map.js';
|
||||
|
||||
// Copy of renderUniLit from callout-block.ts
|
||||
const renderUniLit = <Props, Expose extends NonNullable<unknown>>(
|
||||
uni: UniComponent<Props, Expose> | undefined,
|
||||
props?: Props,
|
||||
options?: {
|
||||
ref?: Signal<Expose | undefined>;
|
||||
style?: Readonly<StyleInfo>;
|
||||
class?: string;
|
||||
}
|
||||
): TemplateResult => {
|
||||
return html` <uni-lit
|
||||
.uni="${uni}"
|
||||
.props="${props}"
|
||||
.ref="${options?.ref}"
|
||||
style=${options?.style ? styleMap(options?.style) : ''}
|
||||
></uni-lit>`;
|
||||
};
|
||||
|
||||
export interface IconPickerWrapperProps {
|
||||
onSelect?: (iconData?: IconData) => void;
|
||||
onClose?: () => void;
|
||||
}
|
||||
|
||||
export class IconPickerWrapper extends ShadowlessElement {
|
||||
iconPickerComponent?: UniComponent<IconPickerWrapperProps, any>;
|
||||
props?: IconPickerWrapperProps;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
override render() {
|
||||
if (!this.iconPickerComponent) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
return renderUniLit(this.iconPickerComponent, this.props);
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'icon-picker-wrapper': IconPickerWrapper;
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,6 @@ import { literal } from 'lit/static-html.js';
|
||||
|
||||
import { CalloutKeymapExtension } from './callout-keymap';
|
||||
import { calloutSlashMenuConfig } from './configs/slash-menu';
|
||||
import { createBuiltinToolbarConfigExtension } from './configs/toolbar';
|
||||
import { effects } from './effects';
|
||||
|
||||
export class CalloutViewExtension extends ViewExtensionProvider {
|
||||
@@ -26,7 +25,6 @@ export class CalloutViewExtension extends ViewExtensionProvider {
|
||||
BlockViewExtension('affine:callout', literal`affine-callout`),
|
||||
CalloutKeymapExtension,
|
||||
SlashMenuConfigExtension('affine:callout', calloutSlashMenuConfig),
|
||||
...createBuiltinToolbarConfigExtension('affine:callout'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
"@blocksuite/affine-components": "workspace:*",
|
||||
"@blocksuite/affine-ext-loader": "workspace:*",
|
||||
"@blocksuite/affine-gfx-turbo-renderer": "workspace:*",
|
||||
"@blocksuite/affine-inline-comment": "workspace:*",
|
||||
"@blocksuite/affine-inline-latex": "workspace:*",
|
||||
"@blocksuite/affine-inline-link": "workspace:*",
|
||||
"@blocksuite/affine-inline-preset": "workspace:*",
|
||||
@@ -22,19 +21,19 @@
|
||||
"@blocksuite/affine-shared": "workspace:*",
|
||||
"@blocksuite/affine-widget-slash-menu": "workspace:*",
|
||||
"@blocksuite/global": "workspace:*",
|
||||
"@blocksuite/icons": "^2.2.17",
|
||||
"@blocksuite/icons": "^2.2.12",
|
||||
"@blocksuite/std": "workspace:*",
|
||||
"@blocksuite/store": "workspace:*",
|
||||
"@floating-ui/dom": "^1.6.13",
|
||||
"@lit/context": "^1.1.2",
|
||||
"@preact/signals-core": "^1.8.0",
|
||||
"@toeverything/theme": "^1.1.23",
|
||||
"@toeverything/theme": "^1.1.15",
|
||||
"@types/mdast": "^4.0.4",
|
||||
"lit": "^3.2.0",
|
||||
"minimatch": "^10.1.1",
|
||||
"rxjs": "^7.8.2",
|
||||
"shiki": "^3.19.0",
|
||||
"zod": "^3.25.76"
|
||||
"minimatch": "^10.0.1",
|
||||
"rxjs": "^7.8.1",
|
||||
"shiki": "^3.0.0",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
@@ -48,5 +47,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.26.0"
|
||||
"version": "0.21.0"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { CommentInlineSpecExtension } from '@blocksuite/affine-inline-comment';
|
||||
import { LatexInlineSpecExtension } from '@blocksuite/affine-inline-latex';
|
||||
import { LinkInlineSpecExtension } from '@blocksuite/affine-inline-link';
|
||||
import {
|
||||
@@ -21,9 +20,7 @@ import { z } from 'zod';
|
||||
export const CodeBlockUnitSpecExtension =
|
||||
InlineSpecExtension<AffineTextAttributes>({
|
||||
name: 'code-block-unit',
|
||||
schema: z.object({
|
||||
'code-block-uint': z.undefined(),
|
||||
}),
|
||||
schema: z.undefined(),
|
||||
match: () => true,
|
||||
renderer: ({ delta }) => {
|
||||
return html`<affine-code-unit .delta=${delta}></affine-code-unit>`;
|
||||
@@ -45,6 +42,5 @@ export const CodeBlockInlineManagerExtension =
|
||||
LatexInlineSpecExtension.identifier,
|
||||
LinkInlineSpecExtension.identifier,
|
||||
CodeBlockUnitSpecExtension.identifier,
|
||||
CommentInlineSpecExtension.identifier,
|
||||
],
|
||||
});
|
||||
|
||||
@@ -19,12 +19,8 @@ import {
|
||||
export class CodeBlockHighlighter extends LifeCycleWatcher {
|
||||
static override key = 'code-block-highlighter';
|
||||
|
||||
// Singleton highlighter instance
|
||||
private static _sharedHighlighter: HighlighterCore | null = null;
|
||||
private static _highlighterPromise: Promise<HighlighterCore> | null = null;
|
||||
private static _refCount = 0;
|
||||
|
||||
private _darkThemeKey: string | undefined;
|
||||
|
||||
private _lightThemeKey: string | undefined;
|
||||
|
||||
highlighter$: Signal<HighlighterCore | null> = signal(null);
|
||||
@@ -39,13 +35,6 @@ export class CodeBlockHighlighter extends LifeCycleWatcher {
|
||||
private readonly _loadTheme = async (
|
||||
highlighter: HighlighterCore
|
||||
): Promise<void> => {
|
||||
// It is possible that by the time the highlighter is ready all instances
|
||||
// have already been unmounted. In that case there is no need to load
|
||||
// themes or update state.
|
||||
if (CodeBlockHighlighter._refCount === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const config = this.std.getOptional(CodeBlockConfigExtension.identifier);
|
||||
const darkTheme = config?.theme?.dark ?? CODE_BLOCK_DEFAULT_DARK_THEME;
|
||||
const lightTheme = config?.theme?.light ?? CODE_BLOCK_DEFAULT_LIGHT_THEME;
|
||||
@@ -55,58 +44,18 @@ export class CodeBlockHighlighter extends LifeCycleWatcher {
|
||||
this.highlighter$.value = highlighter;
|
||||
};
|
||||
|
||||
private static async _getOrCreateHighlighter(): Promise<HighlighterCore> {
|
||||
if (CodeBlockHighlighter._sharedHighlighter) {
|
||||
return CodeBlockHighlighter._sharedHighlighter;
|
||||
}
|
||||
|
||||
if (!CodeBlockHighlighter._highlighterPromise) {
|
||||
CodeBlockHighlighter._highlighterPromise = createHighlighterCore({
|
||||
engine: createOnigurumaEngine(() => getWasm),
|
||||
}).then(highlighter => {
|
||||
CodeBlockHighlighter._sharedHighlighter = highlighter;
|
||||
return highlighter;
|
||||
});
|
||||
}
|
||||
|
||||
return CodeBlockHighlighter._highlighterPromise;
|
||||
}
|
||||
|
||||
override mounted(): void {
|
||||
super.mounted();
|
||||
|
||||
CodeBlockHighlighter._refCount++;
|
||||
|
||||
CodeBlockHighlighter._getOrCreateHighlighter()
|
||||
createHighlighterCore({
|
||||
engine: createOnigurumaEngine(() => getWasm),
|
||||
})
|
||||
.then(this._loadTheme)
|
||||
.catch(console.error);
|
||||
}
|
||||
|
||||
override unmounted(): void {
|
||||
CodeBlockHighlighter._refCount--;
|
||||
|
||||
// Dispose the shared highlighter **after** any in-flight creation finishes.
|
||||
if (CodeBlockHighlighter._refCount !== 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const doDispose = (highlighter: HighlighterCore | null) => {
|
||||
if (highlighter) {
|
||||
highlighter.dispose();
|
||||
}
|
||||
CodeBlockHighlighter._sharedHighlighter = null;
|
||||
CodeBlockHighlighter._highlighterPromise = null;
|
||||
};
|
||||
|
||||
if (CodeBlockHighlighter._sharedHighlighter) {
|
||||
// Highlighter already created – dispose immediately.
|
||||
doDispose(CodeBlockHighlighter._sharedHighlighter);
|
||||
} else if (CodeBlockHighlighter._highlighterPromise) {
|
||||
// Highlighter still being created – wait for it, then dispose.
|
||||
CodeBlockHighlighter._highlighterPromise
|
||||
.then(doDispose)
|
||||
.catch(console.error);
|
||||
}
|
||||
this.highlighter$.value?.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
EDGELESS_TOP_CONTENTEDITABLE_SELECTOR,
|
||||
} from '@blocksuite/affine-shared/consts';
|
||||
import {
|
||||
BlockElementCommentManager,
|
||||
DocModeProvider,
|
||||
NotificationProvider,
|
||||
} from '@blocksuite/affine-shared/services';
|
||||
@@ -391,14 +390,6 @@ export class CodeBlockComponent extends CaptionedBlockComponent<CodeBlockModel>
|
||||
});
|
||||
}
|
||||
|
||||
get isCommentHighlighted() {
|
||||
return (
|
||||
this.std
|
||||
.getOptional(BlockElementCommentManager)
|
||||
?.isBlockCommentHighlighted(this.model) ?? false
|
||||
);
|
||||
}
|
||||
|
||||
override async getUpdateComplete() {
|
||||
const result = await super.getUpdateComplete();
|
||||
await this._richTextElement?.updateComplete;
|
||||
@@ -422,7 +413,6 @@ export class CodeBlockComponent extends CaptionedBlockComponent<CodeBlockModel>
|
||||
<div
|
||||
class=${classMap({
|
||||
'affine-code-block-container': true,
|
||||
'highlight-comment': this.isCommentHighlighted,
|
||||
mobile: IS_MOBILE,
|
||||
wrap: this.model.props.wrap,
|
||||
'disable-line-numbers': !showLineNumbers,
|
||||
@@ -460,7 +450,7 @@ export class CodeBlockComponent extends CaptionedBlockComponent<CodeBlockModel>
|
||||
contenteditable="false"
|
||||
class="affine-code-block-preview"
|
||||
>
|
||||
${shouldRenderPreview && previewContext?.renderer(this.model)}
|
||||
${previewContext?.renderer(this.model)}
|
||||
</div>
|
||||
${this.renderChildren(this.model)} ${Object.values(this.widgets)}
|
||||
</div>
|
||||
|
||||
@@ -7,10 +7,9 @@ import {
|
||||
WrapIcon,
|
||||
} from '@blocksuite/affine-components/icons';
|
||||
import type { MenuItemGroup } from '@blocksuite/affine-components/toolbar';
|
||||
import { CommentProviderIdentifier } from '@blocksuite/affine-shared/services';
|
||||
import { isInsidePageEditor } from '@blocksuite/affine-shared/utils';
|
||||
import { noop, sleep } from '@blocksuite/global/utils';
|
||||
import { CommentIcon, NumberedListIcon } from '@blocksuite/icons/lit';
|
||||
import { NumberedListIcon } from '@blocksuite/icons/lit';
|
||||
import { BlockSelection } from '@blocksuite/std';
|
||||
import { html } from 'lit';
|
||||
import { ifDefined } from 'lit/directives/if-defined.js';
|
||||
@@ -114,47 +113,6 @@ export const PRIMARY_GROUPS: MenuItemGroup<CodeBlockToolbarContext>[] = [
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'comment',
|
||||
label: 'Comment',
|
||||
tooltip: 'Comment',
|
||||
icon: CommentIcon({
|
||||
width: '20',
|
||||
height: '20',
|
||||
}),
|
||||
when: ({ std }) => !!std.getOptional(CommentProviderIdentifier),
|
||||
generate: ({ blockComponent }) => {
|
||||
return {
|
||||
action: () => {
|
||||
const commentProvider = blockComponent.std.getOptional(
|
||||
CommentProviderIdentifier
|
||||
);
|
||||
if (!commentProvider) return;
|
||||
|
||||
commentProvider.addComment([
|
||||
new BlockSelection({
|
||||
blockId: blockComponent.model.id,
|
||||
}),
|
||||
]);
|
||||
},
|
||||
render: item =>
|
||||
html`<editor-icon-button
|
||||
class="code-toolbar-button comment"
|
||||
aria-label=${ifDefined(item.label)}
|
||||
.tooltip=${item.label}
|
||||
.tooltipOffset=${4}
|
||||
.iconSize=${'16px'}
|
||||
.iconContainerPadding=${4}
|
||||
@click=${(e: MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
item.action();
|
||||
}}
|
||||
>
|
||||
${item.icon}
|
||||
</editor-icon-button>`,
|
||||
};
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
export const CODE_BLOCK_DEFAULT_DARK_THEME =
|
||||
import('shiki/themes/dark-plus.mjs');
|
||||
export const CODE_BLOCK_DEFAULT_LIGHT_THEME =
|
||||
import('shiki/themes/light-plus.mjs');
|
||||
export const CODE_BLOCK_DEFAULT_DARK_THEME = import(
|
||||
'shiki/themes/dark-plus.mjs'
|
||||
);
|
||||
export const CODE_BLOCK_DEFAULT_LIGHT_THEME = import(
|
||||
'shiki/themes/light-plus.mjs'
|
||||
);
|
||||
|
||||
@@ -2,9 +2,7 @@ export * from './adapters';
|
||||
export * from './clipboard';
|
||||
export * from './code-block';
|
||||
export * from './code-block-config';
|
||||
export * from './code-block-service';
|
||||
export * from './code-preview-extension';
|
||||
export * from './code-toolbar';
|
||||
export * from './highlight/const';
|
||||
export * from './turbo/code-layout-handler';
|
||||
export * from './turbo/code-painter.worker';
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { scrollbarStyle } from '@blocksuite/affine-shared/styles';
|
||||
import { unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme';
|
||||
import { css } from 'lit';
|
||||
|
||||
export const codeBlockStyles = css`
|
||||
@@ -21,10 +20,6 @@ export const codeBlockStyles = css`
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.affine-code-block-container.highlight-comment {
|
||||
outline: 2px solid ${unsafeCSSVarV2('block/comment/highlightUnderline')};
|
||||
}
|
||||
|
||||
${scrollbarStyle('.affine-code-block-container rich-text')}
|
||||
|
||||
.affine-code-block-container .inline-editor {
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
{ "path": "../../components" },
|
||||
{ "path": "../../ext-loader" },
|
||||
{ "path": "../../gfx/turbo-renderer" },
|
||||
{ "path": "../../inlines/comment" },
|
||||
{ "path": "../../inlines/latex" },
|
||||
{ "path": "../../inlines/link" },
|
||||
{ "path": "../../inlines/preset" },
|
||||
|
||||
@@ -18,18 +18,18 @@
|
||||
"@blocksuite/affine-widget-slash-menu": "workspace:*",
|
||||
"@blocksuite/data-view": "workspace:*",
|
||||
"@blocksuite/global": "workspace:*",
|
||||
"@blocksuite/icons": "^2.2.17",
|
||||
"@blocksuite/icons": "^2.2.12",
|
||||
"@blocksuite/std": "workspace:*",
|
||||
"@blocksuite/store": "workspace:*",
|
||||
"@floating-ui/dom": "^1.6.13",
|
||||
"@lit/context": "^1.1.2",
|
||||
"@preact/signals-core": "^1.8.0",
|
||||
"@toeverything/theme": "^1.1.23",
|
||||
"@toeverything/theme": "^1.1.15",
|
||||
"@types/mdast": "^4.0.4",
|
||||
"lit": "^3.2.0",
|
||||
"minimatch": "^10.1.1",
|
||||
"rxjs": "^7.8.2",
|
||||
"zod": "^3.25.76"
|
||||
"minimatch": "^10.0.1",
|
||||
"rxjs": "^7.8.1",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
@@ -42,5 +42,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.26.0"
|
||||
"version": "0.21.0"
|
||||
}
|
||||
|
||||
@@ -40,7 +40,6 @@ import { RANGE_SYNC_EXCLUDE_ATTR } from '@blocksuite/std/inline';
|
||||
import { Slice } from '@blocksuite/store';
|
||||
import { computed, signal } from '@preact/signals-core';
|
||||
import { css, nothing, unsafeCSS } from 'lit';
|
||||
import { repeat } from 'lit/directives/repeat.js';
|
||||
import { html } from 'lit/static-html.js';
|
||||
|
||||
import { BlockQueryDataSource } from './data-source.js';
|
||||
@@ -304,15 +303,9 @@ export class DataViewBlockComponent extends CaptionedBlockComponent<DataViewBloc
|
||||
},
|
||||
});
|
||||
override renderBlock() {
|
||||
const widgets = html`${repeat(
|
||||
Object.entries(this.widgets),
|
||||
([id]) => id,
|
||||
([_, widget]) => widget
|
||||
)}`;
|
||||
|
||||
return html`
|
||||
<div contenteditable="false" style="position: relative">
|
||||
${this.dataViewRootLogic.render()} ${widgets}
|
||||
${this.dataViewRootLogic.render()}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -21,21 +21,21 @@
|
||||
"@blocksuite/affine-widget-slash-menu": "workspace:*",
|
||||
"@blocksuite/data-view": "workspace:*",
|
||||
"@blocksuite/global": "workspace:*",
|
||||
"@blocksuite/icons": "^2.2.17",
|
||||
"@blocksuite/icons": "^2.2.12",
|
||||
"@blocksuite/std": "workspace:*",
|
||||
"@blocksuite/store": "workspace:*",
|
||||
"@emotion/css": "^11.13.5",
|
||||
"@floating-ui/dom": "^1.6.13",
|
||||
"@lit/context": "^1.1.2",
|
||||
"@preact/signals-core": "^1.8.0",
|
||||
"@toeverything/theme": "^1.1.23",
|
||||
"@toeverything/theme": "^1.1.15",
|
||||
"@types/mdast": "^4.0.4",
|
||||
"date-fns": "^4.0.0",
|
||||
"lit": "^3.2.0",
|
||||
"minimatch": "^10.1.1",
|
||||
"rxjs": "^7.8.2",
|
||||
"yjs": "^13.6.27",
|
||||
"zod": "^3.25.76"
|
||||
"minimatch": "^10.0.1",
|
||||
"rxjs": "^7.8.1",
|
||||
"yjs": "^13.6.21",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
@@ -48,5 +48,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.26.0"
|
||||
"version": "0.21.0"
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ const ColumnClassMap: Record<string, string> = {
|
||||
typesCheckbox: 'checkbox',
|
||||
typesText: 'rich-text',
|
||||
typesTitle: 'title',
|
||||
typesDate: 'date',
|
||||
};
|
||||
|
||||
const NotionDatabaseToken = '.collection-content';
|
||||
@@ -166,36 +165,7 @@ export const databaseBlockNotionHtmlAdapterMatcher: BlockNotionHtmlAdapterMatche
|
||||
if (!column) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for <time> element to find date field from Notion.
|
||||
if (HastUtils.querySelector(child, 'time')) {
|
||||
const timeElement = HastUtils.querySelector(child, 'time');
|
||||
let rawColumnData =
|
||||
HastUtils.getTextContent(timeElement).trim();
|
||||
|
||||
if (rawColumnData.startsWith('@')) {
|
||||
rawColumnData = rawColumnData.slice(1);
|
||||
}
|
||||
|
||||
const columnDate = new Date(rawColumnData);
|
||||
const timestamp = columnDate.getTime();
|
||||
|
||||
if (!Number.isNaN(timestamp)) {
|
||||
column.data = {};
|
||||
if (column.type !== 'date') {
|
||||
column.type = 'date';
|
||||
}
|
||||
row[column.id] = {
|
||||
columnId: column.id,
|
||||
value: timestamp,
|
||||
};
|
||||
} else {
|
||||
row[column.id] = {
|
||||
columnId: column.id,
|
||||
value: HastUtils.getTextContent(child),
|
||||
};
|
||||
}
|
||||
} else if (HastUtils.querySelector(child, '.selected-value')) {
|
||||
if (HastUtils.querySelector(child, '.selected-value')) {
|
||||
if (!('options' in column.data)) {
|
||||
column.data.options = [];
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user