mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-05 11:35:34 +08:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 68b57cdee7 | |||
| afa108d517 |
@@ -6,7 +6,6 @@ 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
|
||||
|
||||
@@ -10,7 +10,6 @@ services:
|
||||
environment:
|
||||
DATABASE_URL: postgresql://affine:affine@db:5432/affine
|
||||
REDIS_SERVER_HOST: redis
|
||||
AFFINE_INDEXER_SEARCH_ENDPOINT: http://indexer:9308
|
||||
|
||||
db:
|
||||
image: pgvector/pgvector:pg16
|
||||
@@ -24,19 +23,5 @@ services:
|
||||
redis:
|
||||
image: redis
|
||||
|
||||
indexer:
|
||||
image: manticoresearch/manticore:${MANTICORE_VERSION:-9.3.2}
|
||||
ulimits:
|
||||
nproc: 65535
|
||||
nofile:
|
||||
soft: 65535
|
||||
hard: 65535
|
||||
memlock:
|
||||
soft: -1
|
||||
hard: -1
|
||||
volumes:
|
||||
- manticoresearch_data:/var/lib/manticore
|
||||
|
||||
volumes:
|
||||
postgres-data:
|
||||
manticoresearch_data:
|
||||
|
||||
@@ -12,4 +12,4 @@ DB_DATABASE_NAME=affine
|
||||
# ELASTIC_PLATFORM=linux/arm64
|
||||
|
||||
# manticoresearch
|
||||
MANTICORE_VERSION=9.3.2
|
||||
MANTICORE_VERSION=9.2.14
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
postgres
|
||||
.env
|
||||
compose.yml
|
||||
certs/*
|
||||
!certs/.gitkeep
|
||||
nginx/conf.d/*
|
||||
compose.yml
|
||||
@@ -1,27 +0,0 @@
|
||||
# Dev containers
|
||||
|
||||
## Develop with domain
|
||||
|
||||
> MacOs only, OrbStack only
|
||||
|
||||
### 1. Generate and install Root CA
|
||||
|
||||
```bash
|
||||
# the root ca file will be located at `./.docker/dev/certs/ca`
|
||||
yarn affine cert --install
|
||||
```
|
||||
|
||||
### 2. Generate domain certs
|
||||
|
||||
```bash
|
||||
# certificates will be located at `./.docker/dev/certs/${domain}`
|
||||
yarn affine cert --domain dev.affine.fail
|
||||
```
|
||||
|
||||
### 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
|
||||
```
|
||||
@@ -0,0 +1,65 @@
|
||||
name: affine_dev_services
|
||||
services:
|
||||
postgres:
|
||||
env_file:
|
||||
- .env
|
||||
image: pgvector/pgvector:pg${DB_VERSION:-16}
|
||||
ports:
|
||||
- 5432:5432
|
||||
environment:
|
||||
POSTGRES_PASSWORD: ${DB_PASSWORD}
|
||||
POSTGRES_USER: ${DB_USERNAME}
|
||||
POSTGRES_DB: ${DB_DATABASE_NAME}
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
|
||||
redis:
|
||||
image: redis:latest
|
||||
ports:
|
||||
- 6379:6379
|
||||
|
||||
mailhog:
|
||||
image: mailhog/mailhog:latest
|
||||
ports:
|
||||
- 1025:1025
|
||||
- 8025:8025
|
||||
|
||||
elasticsearch:
|
||||
image: docker.elastic.co/elasticsearch/elasticsearch:${ELASTIC_VERSION:-9.0.1}${ELASTIC_VERSION_ARM64}
|
||||
platform: ${ELASTIC_PLATFORM}
|
||||
labels:
|
||||
co.elastic.logs/module: elasticsearch
|
||||
volumes:
|
||||
- elasticsearch_data:/usr/share/elasticsearch/data
|
||||
ports:
|
||||
- ${ES_PORT:-9200}:9200
|
||||
environment:
|
||||
- node.name=es01
|
||||
- cluster.name=affine-dev
|
||||
- discovery.type=single-node
|
||||
- bootstrap.memory_lock=true
|
||||
- xpack.security.enabled=false
|
||||
- xpack.security.http.ssl.enabled=false
|
||||
- xpack.security.transport.ssl.enabled=false
|
||||
- xpack.license.self_generated.type=basic
|
||||
mem_limit: ${ES_MEM_LIMIT:-1073741824}
|
||||
ulimits:
|
||||
memlock:
|
||||
soft: -1
|
||||
hard: -1
|
||||
healthcheck:
|
||||
test:
|
||||
[
|
||||
"CMD-SHELL",
|
||||
"curl -s http://localhost:9200 | grep -q 'affine-dev'",
|
||||
]
|
||||
interval: 10s
|
||||
timeout: 10s
|
||||
retries: 120
|
||||
|
||||
networks:
|
||||
dev:
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
elasticsearch_data:
|
||||
@@ -26,7 +26,8 @@ services:
|
||||
|
||||
# https://manual.manticoresearch.com/Starting_the_server/Docker
|
||||
manticoresearch:
|
||||
image: manticoresearch/manticore:${MANTICORE_VERSION:-9.3.2}
|
||||
image: manticoresearch/manticore:${MANTICORE_VERSION:-9.2.14}
|
||||
restart: always
|
||||
ports:
|
||||
- 9308:9308
|
||||
ulimits:
|
||||
@@ -39,58 +40,6 @@ services:
|
||||
hard: -1
|
||||
volumes:
|
||||
- manticoresearch_data:/var/lib/manticore
|
||||
|
||||
# elasticsearch:
|
||||
# image: docker.elastic.co/elasticsearch/elasticsearch:${ELASTIC_VERSION:-9.0.1}${ELASTIC_VERSION_ARM64}
|
||||
# platform: ${ELASTIC_PLATFORM}
|
||||
# labels:
|
||||
# co.elastic.logs/module: elasticsearch
|
||||
# volumes:
|
||||
# - elasticsearch_data:/usr/share/elasticsearch/data
|
||||
# ports:
|
||||
# - ${ES_PORT:-9200}:9200
|
||||
# environment:
|
||||
# - node.name=es01
|
||||
# - cluster.name=affine-dev
|
||||
# - discovery.type=single-node
|
||||
# - bootstrap.memory_lock=true
|
||||
# - xpack.security.enabled=false
|
||||
# - xpack.security.http.ssl.enabled=false
|
||||
# - xpack.security.transport.ssl.enabled=false
|
||||
# - xpack.license.self_generated.type=basic
|
||||
# mem_limit: ${ES_MEM_LIMIT:-1073741824}
|
||||
# ulimits:
|
||||
# memlock:
|
||||
# soft: -1
|
||||
# hard: -1
|
||||
# healthcheck:
|
||||
# test:
|
||||
# [
|
||||
# "CMD-SHELL",
|
||||
# "curl -s http://localhost:9200 | grep -q 'affine-dev'",
|
||||
# ]
|
||||
# interval: 10s
|
||||
# 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:
|
||||
# - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
|
||||
# - ./nginx/conf.d:/etc/nginx/conf.d:ro
|
||||
# - ./certs:/etc/nginx/certs:ro
|
||||
# network_mode: host
|
||||
|
||||
networks:
|
||||
dev:
|
||||
@@ -98,4 +47,3 @@ networks:
|
||||
volumes:
|
||||
postgres_data:
|
||||
manticoresearch_data:
|
||||
elasticsearch_data:
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
log-queries
|
||||
address=/dev.affine.fail/127.0.0.1
|
||||
@@ -1,28 +0,0 @@
|
||||
user nginx;
|
||||
worker_processes auto;
|
||||
|
||||
error_log /var/log/nginx/error.log;
|
||||
pid /var/run/nginx.pid;
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
http {
|
||||
include mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||
'$status $body_bytes_sent "$http_referer" '
|
||||
'"$http_user_agent" "$http_x_forwarded_for"';
|
||||
access_log /var/log/nginx/access.log main;
|
||||
|
||||
sendfile on;
|
||||
keepalive_timeout 65;
|
||||
types_hash_max_size 2048;
|
||||
client_max_body_size 512M;
|
||||
server_names_hash_bucket_size 128;
|
||||
ssi on;
|
||||
gzip on;
|
||||
include "/etc/nginx/conf.d/*";
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name DEV_DOMAIN;
|
||||
|
||||
location / {
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
http2 on;
|
||||
ssl_certificate /etc/nginx/certs/$host/crt;
|
||||
ssl_certificate_key /etc/nginx/certs/$host/key;
|
||||
server_name DEV_DOMAIN;
|
||||
|
||||
location / {
|
||||
proxy_pass http://localhost:8080;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
resolver 127.0.0.1;
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
[req]
|
||||
distinguished_name = req_distinguished_name
|
||||
req_extensions = v3_req
|
||||
|
||||
[req_distinguished_name]
|
||||
countryName = Country Name (2 letter code)
|
||||
countryName_default = US
|
||||
stateOrProvinceName = State or Province Name (full name)
|
||||
stateOrProvinceName_default = MN
|
||||
localityName = Locality Name (eg, city)
|
||||
localityName_default = Minneapolis
|
||||
organizationalUnitName = Organizational Unit Name (eg, section)
|
||||
organizationalUnitName_default = Domain Control Validated
|
||||
commonName = Internet Widgits Ltd
|
||||
commonName_max = 64
|
||||
|
||||
[ v3_req ]
|
||||
# Extensions to add to a certificate request
|
||||
basicConstraints = CA:FALSE
|
||||
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
|
||||
subjectAltName = @alt_names
|
||||
|
||||
[alt_names]
|
||||
DNS.1 = DEV_DOMAIN
|
||||
DNS.2 = *.DEV_DOMAIN
|
||||
@@ -21,3 +21,8 @@ CONFIG_LOCATION=~/.affine/self-host/config
|
||||
DB_USERNAME=affine
|
||||
DB_PASSWORD=
|
||||
DB_DATABASE=affine
|
||||
|
||||
# indexer search provider manticoresearch version
|
||||
MANTICORE_VERSION=9.2.14
|
||||
# position of the manticoresearch data to persist
|
||||
MANTICORE_DATA_LOCATION=~/.affine/self-host/manticore
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
.env
|
||||
@@ -10,6 +10,8 @@ services:
|
||||
condition: service_healthy
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
indexer:
|
||||
condition: service_healthy
|
||||
affine_migration:
|
||||
condition: service_completed_successfully
|
||||
volumes:
|
||||
@@ -21,7 +23,6 @@ services:
|
||||
environment:
|
||||
- REDIS_SERVER_HOST=redis
|
||||
- DATABASE_URL=postgresql://${DB_USERNAME}:${DB_PASSWORD}@postgres:5432/${DB_DATABASE:-affine}
|
||||
- AFFINE_INDEXER_ENABLED=false
|
||||
restart: unless-stopped
|
||||
|
||||
affine_migration:
|
||||
@@ -37,12 +38,13 @@ services:
|
||||
environment:
|
||||
- REDIS_SERVER_HOST=redis
|
||||
- DATABASE_URL=postgresql://${DB_USERNAME}:${DB_PASSWORD}@postgres:5432/${DB_DATABASE:-affine}
|
||||
- AFFINE_INDEXER_ENABLED=false
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
indexer:
|
||||
condition: service_healthy
|
||||
|
||||
redis:
|
||||
image: redis
|
||||
@@ -55,7 +57,7 @@ services:
|
||||
restart: unless-stopped
|
||||
|
||||
postgres:
|
||||
image: pgvector/pgvector:pg16
|
||||
image: postgres:16
|
||||
container_name: affine_postgres
|
||||
volumes:
|
||||
- ${DB_DATA_LOCATION}:/var/lib/postgresql/data
|
||||
@@ -74,3 +76,24 @@ services:
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
restart: unless-stopped
|
||||
|
||||
indexer:
|
||||
image: manticoresearch/manticore:${MANTICORE_VERSION:-9.2.14}
|
||||
container_name: affine_indexer
|
||||
volumes:
|
||||
- ${MANTICORE_DATA_LOCATION}:/var/lib/manticore
|
||||
ulimits:
|
||||
nproc: 65535
|
||||
nofile:
|
||||
soft: 65535
|
||||
hard: 65535
|
||||
memlock:
|
||||
soft: -1
|
||||
hard: -1
|
||||
healthcheck:
|
||||
test:
|
||||
['CMD', 'wget', '-O-', 'http://127.0.0.1:9308']
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
restart: unless-stopped
|
||||
|
||||
@@ -48,14 +48,14 @@
|
||||
},
|
||||
"queues.copilot": {
|
||||
"type": "object",
|
||||
"description": "The config for copilot job queue\n@default {\"concurrency\":5}",
|
||||
"description": "The config for copilot job queue\n@default {\"concurrency\":1}",
|
||||
"properties": {
|
||||
"concurrency": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"concurrency": 5
|
||||
"concurrency": 1
|
||||
}
|
||||
},
|
||||
"queues.doc": {
|
||||
@@ -812,7 +812,7 @@
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean",
|
||||
"description": "Enable indexer plugin\n@default true\n@environment `AFFINE_INDEXER_ENABLED`",
|
||||
"description": "Enable indexer plugin\n@default true",
|
||||
"default": true
|
||||
},
|
||||
"provider.type": {
|
||||
@@ -825,11 +825,6 @@
|
||||
"description": "Indexer search service endpoint\n@default \"http://localhost:9308\"\n@environment `AFFINE_INDEXER_SEARCH_ENDPOINT`",
|
||||
"default": "http://localhost:9308"
|
||||
},
|
||||
"provider.apiKey": {
|
||||
"type": "string",
|
||||
"description": "Indexer search service api key. Optional for elasticsearch\n@default \"\"\n@environment `AFFINE_INDEXER_SEARCH_API_KEY`\n@link https://www.elastic.co/guide/server/current/api-key.html",
|
||||
"default": ""
|
||||
},
|
||||
"provider.username": {
|
||||
"type": "string",
|
||||
"description": "Indexer search service auth username, if not set, basic auth will be disabled. Optional for elasticsearch\n@default \"\"\n@environment `AFFINE_INDEXER_SEARCH_USERNAME`\n@link https://www.elastic.co/guide/en/elasticsearch/reference/current/http-clients.html",
|
||||
@@ -839,11 +834,6 @@
|
||||
"type": "string",
|
||||
"description": "Indexer search service auth password, if not set, basic auth will be disabled. Optional for elasticsearch\n@default \"\"\n@environment `AFFINE_INDEXER_SEARCH_PASSWORD`",
|
||||
"default": ""
|
||||
},
|
||||
"autoIndex.batchSize": {
|
||||
"type": "number",
|
||||
"description": "Number of workspaces automatically indexed per batch\n@default 10",
|
||||
"default": 10
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -891,43 +881,13 @@
|
||||
},
|
||||
"providers.oidc": {
|
||||
"type": "object",
|
||||
"description": "OIDC OAuth provider config\n@default {\"clientId\":\"\",\"clientSecret\":\"\",\"issuer\":\"\",\"args\":{}}\n@link https://openid.net/specs/openid-connect-core-1_0.html",
|
||||
"properties": {
|
||||
"clientId": {
|
||||
"type": "string"
|
||||
},
|
||||
"clientSecret": {
|
||||
"type": "string"
|
||||
},
|
||||
"args": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"description": "OIDC OAuth provider config\n@default {\"clientId\":\"\",\"clientSecret\":\"\",\"issuer\":\"\",\"args\":{}}",
|
||||
"default": {
|
||||
"clientId": "",
|
||||
"clientSecret": "",
|
||||
"issuer": "",
|
||||
"args": {}
|
||||
}
|
||||
},
|
||||
"providers.apple": {
|
||||
"type": "object",
|
||||
"description": "Apple OAuth provider config\n@default {\"clientId\":\"\",\"clientSecret\":\"\"}\n@link https://developer.apple.com/documentation/sign_in_with_apple/sign_in_with_apple_js/implementing_sign_in_with_apple_in_your_app",
|
||||
"properties": {
|
||||
"clientId": {
|
||||
"type": "string"
|
||||
},
|
||||
"clientSecret": {
|
||||
"type": "string"
|
||||
},
|
||||
"args": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"clientId": "",
|
||||
"clientSecret": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -18,7 +18,8 @@ const {
|
||||
STATIC_IP_NAME,
|
||||
AFFINE_INDEXER_SEARCH_PROVIDER,
|
||||
AFFINE_INDEXER_SEARCH_ENDPOINT,
|
||||
AFFINE_INDEXER_SEARCH_API_KEY,
|
||||
AFFINE_INDEXER_SEARCH_USERNAME,
|
||||
AFFINE_INDEXER_SEARCH_PASSWORD,
|
||||
} = process.env;
|
||||
|
||||
const buildType = BUILD_TYPE || 'canary';
|
||||
@@ -87,7 +88,8 @@ const createHelmCommand = ({ isDryRun }) => {
|
||||
const indexerOptions = [
|
||||
`--set-string global.indexer.provider="${AFFINE_INDEXER_SEARCH_PROVIDER}"`,
|
||||
`--set-string global.indexer.endpoint="${AFFINE_INDEXER_SEARCH_ENDPOINT}"`,
|
||||
`--set-string global.indexer.apiKey="${AFFINE_INDEXER_SEARCH_API_KEY}"`,
|
||||
`--set-string global.indexer.username="${AFFINE_INDEXER_SEARCH_USERNAME}"`,
|
||||
`--set-string global.indexer.password="${AFFINE_INDEXER_SEARCH_PASSWORD}"`,
|
||||
];
|
||||
const serviceAnnotations = [
|
||||
`--set-json web.serviceAccount.annotations="{ \\"iam.gke.io/gcp-service-account\\": \\"${APP_IAM_ACCOUNT}\\" }"`,
|
||||
|
||||
@@ -73,11 +73,13 @@ spec:
|
||||
value: "{{ .Values.global.indexer.provider }}"
|
||||
- name: AFFINE_INDEXER_SEARCH_ENDPOINT
|
||||
value: "{{ .Values.global.indexer.endpoint }}"
|
||||
- name: AFFINE_INDEXER_SEARCH_API_KEY
|
||||
- name: AFFINE_INDEXER_SEARCH_USERNAME
|
||||
value: "{{ .Values.global.indexer.username }}"
|
||||
- name: AFFINE_INDEXER_SEARCH_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: indexer
|
||||
key: indexer-apiKey
|
||||
key: indexer-password
|
||||
- name: AFFINE_SERVER_PORT
|
||||
value: "{{ .Values.global.docService.port }}"
|
||||
- name: AFFINE_SERVER_SUB_PATH
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
{{- if eq .Values.global.deployment.platform "gcp" -}}
|
||||
apiVersion: monitoring.googleapis.com/v1
|
||||
kind: ClusterPodMonitoring
|
||||
metadata:
|
||||
name: "{{ include "doc.fullname" . }}"
|
||||
spec:
|
||||
selector:
|
||||
{{- include "doc.selectorLabels" . | nindent 4 }}
|
||||
endpoints:
|
||||
- port: 9464
|
||||
interval: 30s
|
||||
{{- end }}
|
||||
@@ -71,11 +71,13 @@ spec:
|
||||
value: "{{ .Values.global.indexer.provider }}"
|
||||
- name: AFFINE_INDEXER_SEARCH_ENDPOINT
|
||||
value: "{{ .Values.global.indexer.endpoint }}"
|
||||
- name: AFFINE_INDEXER_SEARCH_API_KEY
|
||||
- name: AFFINE_INDEXER_SEARCH_USERNAME
|
||||
value: "{{ .Values.global.indexer.username }}"
|
||||
- name: AFFINE_INDEXER_SEARCH_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: indexer
|
||||
key: indexer-apiKey
|
||||
key: indexer-password
|
||||
- name: AFFINE_SERVER_PORT
|
||||
value: "{{ .Values.service.port }}"
|
||||
- name: AFFINE_SERVER_SUB_PATH
|
||||
|
||||
@@ -48,11 +48,13 @@ spec:
|
||||
value: "{{ .Values.global.indexer.provider }}"
|
||||
- name: AFFINE_INDEXER_SEARCH_ENDPOINT
|
||||
value: "{{ .Values.global.indexer.endpoint }}"
|
||||
- name: AFFINE_INDEXER_SEARCH_API_KEY
|
||||
- name: AFFINE_INDEXER_SEARCH_USERNAME
|
||||
value: "{{ .Values.global.indexer.username }}"
|
||||
- name: AFFINE_INDEXER_SEARCH_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: indexer
|
||||
key: indexer-apiKey
|
||||
key: indexer-password
|
||||
resources:
|
||||
requests:
|
||||
cpu: '100m'
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
{{- if eq .Values.global.deployment.platform "gcp" -}}
|
||||
apiVersion: monitoring.googleapis.com/v1
|
||||
kind: ClusterPodMonitoring
|
||||
metadata:
|
||||
name: "{{ include "graphql.fullname" . }}"
|
||||
spec:
|
||||
selector:
|
||||
{{- include "graphql.selectorLabels" . | nindent 4 }}
|
||||
endpoints:
|
||||
- port: 9464
|
||||
interval: 30s
|
||||
{{- end }}
|
||||
@@ -73,11 +73,13 @@ spec:
|
||||
value: "{{ .Values.global.indexer.provider }}"
|
||||
- name: AFFINE_INDEXER_SEARCH_ENDPOINT
|
||||
value: "{{ .Values.global.indexer.endpoint }}"
|
||||
- name: AFFINE_INDEXER_SEARCH_API_KEY
|
||||
- name: AFFINE_INDEXER_SEARCH_USERNAME
|
||||
value: "{{ .Values.global.indexer.username }}"
|
||||
- name: AFFINE_INDEXER_SEARCH_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: indexer
|
||||
key: indexer-apiKey
|
||||
key: indexer-password
|
||||
- name: AFFINE_SERVER_PORT
|
||||
value: "{{ .Values.service.port }}"
|
||||
- name: AFFINE_SERVER_SUB_PATH
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
{{- if eq .Values.global.deployment.platform "gcp" -}}
|
||||
apiVersion: monitoring.googleapis.com/v1
|
||||
kind: ClusterPodMonitoring
|
||||
metadata:
|
||||
name: "{{ include "renderer.fullname" . }}"
|
||||
spec:
|
||||
selector:
|
||||
{{- include "renderer.selectorLabels" . | nindent 4 }}
|
||||
endpoints:
|
||||
- port: 9464
|
||||
interval: 30s
|
||||
{{- end }}
|
||||
@@ -73,11 +73,13 @@ spec:
|
||||
value: "{{ .Values.global.indexer.provider }}"
|
||||
- name: AFFINE_INDEXER_SEARCH_ENDPOINT
|
||||
value: "{{ .Values.global.indexer.endpoint }}"
|
||||
- name: AFFINE_INDEXER_SEARCH_API_KEY
|
||||
- name: AFFINE_INDEXER_SEARCH_USERNAME
|
||||
value: "{{ .Values.global.indexer.username }}"
|
||||
- name: AFFINE_INDEXER_SEARCH_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: indexer
|
||||
key: indexer-apiKey
|
||||
key: indexer-password
|
||||
- name: AFFINE_SERVER_PORT
|
||||
value: "{{ .Values.service.port }}"
|
||||
- name: AFFINE_SERVER_HOST
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
{{- if eq .Values.global.deployment.platform "gcp" -}}
|
||||
apiVersion: monitoring.googleapis.com/v1
|
||||
kind: ClusterPodMonitoring
|
||||
metadata:
|
||||
name: "{{ include "sync.fullname" . }}"
|
||||
spec:
|
||||
selector:
|
||||
{{- include "sync.selectorLabels" . | nindent 4 }}
|
||||
endpoints:
|
||||
- port: 9464
|
||||
interval: 30s
|
||||
{{- end }}
|
||||
@@ -1,4 +1,4 @@
|
||||
{{- if .Values.global.indexer.apiKey -}}
|
||||
{{- if .Values.global.indexer.password -}}
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
@@ -9,5 +9,5 @@ metadata:
|
||||
"helm.sh/hook-delete-policy": before-hook-creation
|
||||
type: Opaque
|
||||
data:
|
||||
indexer-apiKey: {{ .Values.global.indexer.apiKey | b64enc }}
|
||||
indexer-password: {{ .Values.global.indexer.password | b64enc }}
|
||||
{{- end }}
|
||||
|
||||
@@ -125,7 +125,6 @@ jobs:
|
||||
- name: Run BS Docs Build
|
||||
run: |
|
||||
yarn affine bs-docs build
|
||||
git checkout packages/frontend/i18n/src/i18n-completenesses.json
|
||||
git status --porcelain | grep . && {
|
||||
echo "Run 'yarn typecheck && yarn affine bs-docs build' and make sure all changes are submitted"
|
||||
exit 1
|
||||
@@ -184,7 +183,6 @@ jobs:
|
||||
yarn affine gql build
|
||||
yarn affine i18n build
|
||||
yarn affine server genconfig
|
||||
git checkout packages/frontend/i18n/src/i18n-completenesses.json
|
||||
git status --porcelain | grep . && {
|
||||
echo "Run 'yarn affine init && yarn affine gql build && yarn affine i18n build && yarn affine server genconfig' and make sure all changes are submitted"
|
||||
exit 1
|
||||
@@ -584,80 +582,10 @@ jobs:
|
||||
ports:
|
||||
- 1025:1025
|
||||
- 8025:8025
|
||||
indexer:
|
||||
image: manticoresearch/manticore:9.3.2
|
||||
manticoresearch:
|
||||
image: manticoresearch/manticore:9.2.14
|
||||
ports:
|
||||
- 9308:9308
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
electron-install: false
|
||||
full-cache: true
|
||||
|
||||
- name: Download server-native.node
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: server-native.node
|
||||
path: ./packages/backend/native
|
||||
|
||||
- name: Prepare Server Test Environment
|
||||
uses: ./.github/actions/server-test-env
|
||||
|
||||
- name: Run server tests
|
||||
run: yarn affine @affine/server test:coverage --forbid-only
|
||||
env:
|
||||
CARGO_TARGET_DIR: '${{ github.workspace }}/target'
|
||||
CI_NODE_INDEX: ${{ matrix.node_index }}
|
||||
CI_NODE_TOTAL: ${{ matrix.total_nodes }}
|
||||
|
||||
- name: Upload server test coverage results
|
||||
uses: codecov/codecov-action@v5
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
files: ./packages/backend/server/.coverage/lcov.info
|
||||
flags: server-test
|
||||
name: affine
|
||||
fail_ci_if_error: false
|
||||
|
||||
server-test-elasticsearch:
|
||||
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:
|
||||
NODE_ENV: test
|
||||
DATABASE_URL: postgresql://affine:affine@localhost:5432/affine
|
||||
REDIS_SERVER_HOST: localhost
|
||||
AFFINE_INDEXER_SEARCH_PROVIDER: elasticsearch
|
||||
AFFINE_INDEXER_SEARCH_ENDPOINT: http://localhost:9200
|
||||
services:
|
||||
postgres:
|
||||
image: pgvector/pgvector:pg16
|
||||
env:
|
||||
POSTGRES_PASSWORD: affine
|
||||
options: >-
|
||||
--health-cmd pg_isready
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
ports:
|
||||
- 5432:5432
|
||||
redis:
|
||||
image: redis
|
||||
ports:
|
||||
- 6379:6379
|
||||
mailer:
|
||||
image: mailhog/mailhog
|
||||
ports:
|
||||
- 1025:1025
|
||||
- 8025:8025
|
||||
steps:
|
||||
# https://github.com/elastic/elastic-github-actions/blob/master/elasticsearch/README.md
|
||||
- name: Configure sysctl limits for Elasticsearch
|
||||
@@ -690,8 +618,8 @@ jobs:
|
||||
- name: Prepare Server Test Environment
|
||||
uses: ./.github/actions/server-test-env
|
||||
|
||||
- name: Run server tests with elasticsearch only
|
||||
run: yarn affine @affine/server test:coverage "**/*/*elasticsearch.spec.ts" --forbid-only
|
||||
- name: Run server tests
|
||||
run: yarn affine @affine/server test:coverage --forbid-only
|
||||
env:
|
||||
CARGO_TARGET_DIR: '${{ github.workspace }}/target'
|
||||
CI_NODE_INDEX: ${{ matrix.node_index }}
|
||||
@@ -735,7 +663,7 @@ jobs:
|
||||
ports:
|
||||
- 6379:6379
|
||||
indexer:
|
||||
image: manticoresearch/manticore:9.3.2
|
||||
image: manticoresearch/manticore:9.2.14
|
||||
ports:
|
||||
- 9308:9308
|
||||
steps:
|
||||
@@ -958,10 +886,6 @@ jobs:
|
||||
ports:
|
||||
- 1025:1025
|
||||
- 8025:8025
|
||||
indexer:
|
||||
image: manticoresearch/manticore:9.3.2
|
||||
ports:
|
||||
- 9308:9308
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
@@ -1057,10 +981,6 @@ jobs:
|
||||
image: redis
|
||||
ports:
|
||||
- 6379:6379
|
||||
indexer:
|
||||
image: manticoresearch/manticore:9.3.2
|
||||
ports:
|
||||
- 9308:9308
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
@@ -1083,7 +1003,6 @@ jobs:
|
||||
- 'packages/backend/server/src/plugins/copilot/**'
|
||||
- 'packages/backend/server/tests/copilot.*'
|
||||
- 'packages/frontend/core/src/blocksuite/ai/**'
|
||||
- 'packages/frontend/core/src/modules/workspace-indexer-embedding/**'
|
||||
- 'tests/affine-cloud-copilot/**'
|
||||
|
||||
- name: Setup Node.js
|
||||
@@ -1185,7 +1104,7 @@ jobs:
|
||||
- 1025:1025
|
||||
- 8025:8025
|
||||
indexer:
|
||||
image: manticoresearch/manticore:9.3.2
|
||||
image: manticoresearch/manticore:9.2.14
|
||||
ports:
|
||||
- 9308:9308
|
||||
steps:
|
||||
@@ -1357,13 +1276,6 @@ jobs:
|
||||
target: x86_64-unknown-linux-gnu,
|
||||
test: true,
|
||||
}
|
||||
- {
|
||||
os: windows-latest,
|
||||
platform: windows,
|
||||
arch: x64,
|
||||
target: x86_64-pc-windows-msvc,
|
||||
test: true,
|
||||
}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Node.js
|
||||
@@ -1404,18 +1316,6 @@ jobs:
|
||||
HOIST_NODE_MODULES: 1
|
||||
run: yarn affine @affine/electron package --platform=darwin --arch=arm64
|
||||
|
||||
- name: Make Bundle (Windows)
|
||||
if: ${{ matrix.spec.target == 'x86_64-pc-windows-msvc' }}
|
||||
shell: bash
|
||||
env:
|
||||
SKIP_BUNDLE: true
|
||||
SKIP_WEB_BUILD: true
|
||||
HOIST_NODE_MODULES: 1
|
||||
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
|
||||
yarn affine @affine/electron package --platform=win32 --arch=x64
|
||||
|
||||
- name: Make Bundle (Linux)
|
||||
run: |
|
||||
sudo add-apt-repository universe
|
||||
|
||||
@@ -59,10 +59,6 @@ jobs:
|
||||
ports:
|
||||
- 1025:1025
|
||||
- 8025:8025
|
||||
indexer:
|
||||
image: manticoresearch/manticore:9.3.2
|
||||
ports:
|
||||
- 9308:9308
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
@@ -134,10 +130,6 @@ jobs:
|
||||
image: redis
|
||||
ports:
|
||||
- 6379:6379
|
||||
indexer:
|
||||
image: manticoresearch/manticore:9.3.2
|
||||
ports:
|
||||
- 9308:9308
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
|
||||
@@ -105,7 +105,8 @@ jobs:
|
||||
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 }}
|
||||
AFFINE_INDEXER_SEARCH_USERNAME: ${{ secrets.AFFINE_INDEXER_SEARCH_USERNAME }}
|
||||
AFFINE_INDEXER_SEARCH_PASSWORD: ${{ secrets.AFFINE_INDEXER_SEARCH_PASSWORD }}
|
||||
|
||||
deploy-done:
|
||||
needs:
|
||||
|
||||
@@ -252,7 +252,7 @@ jobs:
|
||||
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/nbstore/node_modules/@blocksuite
|
||||
rm -rf packages/frontend/apps/electron/node_modules/@affine/native/node_modules
|
||||
|
||||
- name: package
|
||||
|
||||
@@ -33,7 +33,6 @@
|
||||
"@blocksuite/affine-components": "workspace:*",
|
||||
"@blocksuite/affine-ext-loader": "workspace:*",
|
||||
"@blocksuite/affine-foundation": "workspace:*",
|
||||
"@blocksuite/affine-fragment-adapter-panel": "workspace:*",
|
||||
"@blocksuite/affine-fragment-doc-title": "workspace:*",
|
||||
"@blocksuite/affine-fragment-frame-panel": "workspace:*",
|
||||
"@blocksuite/affine-fragment-outline": "workspace:*",
|
||||
@@ -59,14 +58,11 @@
|
||||
"@blocksuite/affine-shared": "workspace:*",
|
||||
"@blocksuite/affine-widget-drag-handle": "workspace:*",
|
||||
"@blocksuite/affine-widget-edgeless-auto-connect": "workspace:*",
|
||||
"@blocksuite/affine-widget-edgeless-dragging-area": "workspace:*",
|
||||
"@blocksuite/affine-widget-edgeless-selected-rect": "workspace:*",
|
||||
"@blocksuite/affine-widget-edgeless-toolbar": "workspace:*",
|
||||
"@blocksuite/affine-widget-edgeless-zoom-toolbar": "workspace:*",
|
||||
"@blocksuite/affine-widget-frame-title": "workspace:*",
|
||||
"@blocksuite/affine-widget-keyboard-toolbar": "workspace:*",
|
||||
"@blocksuite/affine-widget-linked-doc": "workspace:*",
|
||||
"@blocksuite/affine-widget-note-slicer": "workspace:*",
|
||||
"@blocksuite/affine-widget-page-dragging-area": "workspace:*",
|
||||
"@blocksuite/affine-widget-remote-selection": "workspace:*",
|
||||
"@blocksuite/affine-widget-scroll-anchoring": "workspace:*",
|
||||
@@ -182,8 +178,6 @@
|
||||
"./widgets/drag-handle/view": "./src/widgets/drag-handle/view.ts",
|
||||
"./widgets/edgeless-auto-connect": "./src/widgets/edgeless-auto-connect/index.ts",
|
||||
"./widgets/edgeless-auto-connect/view": "./src/widgets/edgeless-auto-connect/view.ts",
|
||||
"./widgets/edgeless-dragging-area": "./src/widgets/edgeless-dragging-area/index.ts",
|
||||
"./widgets/edgeless-dragging-area/view": "./src/widgets/edgeless-dragging-area/view.ts",
|
||||
"./widgets/edgeless-toolbar": "./src/widgets/edgeless-toolbar/index.ts",
|
||||
"./widgets/edgeless-toolbar/view": "./src/widgets/edgeless-toolbar/view.ts",
|
||||
"./widgets/frame-title": "./src/widgets/frame-title/index.ts",
|
||||
@@ -210,8 +204,6 @@
|
||||
"./fragments/frame-panel/view": "./src/fragments/frame-panel/view.ts",
|
||||
"./fragments/outline": "./src/fragments/outline/index.ts",
|
||||
"./fragments/outline/view": "./src/fragments/outline/view.ts",
|
||||
"./fragments/adapter-panel": "./src/fragments/adapter-panel/index.ts",
|
||||
"./fragments/adapter-panel/view": "./src/fragments/adapter-panel/view.ts",
|
||||
"./gfx/text": "./src/gfx/text/index.ts",
|
||||
"./gfx/text/store": "./src/gfx/text/store.ts",
|
||||
"./gfx/text/view": "./src/gfx/text/view.ts",
|
||||
@@ -295,7 +287,6 @@
|
||||
"version": "0.21.0",
|
||||
"devDependencies": {
|
||||
"@vanilla-extract/vite-plugin": "^5.0.0",
|
||||
"msw": "^2.8.4",
|
||||
"vitest": "3.1.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2697,335 +2697,4 @@ describe('html to snapshot', () => {
|
||||
});
|
||||
expect(nanoidReplacement(rawBlockSnapshot)).toEqual(blockSnapshot);
|
||||
});
|
||||
|
||||
test('block level element in b should not be treated as inline', async () => {
|
||||
const html = template(`<b><p><span>aaa</span></p></b>`);
|
||||
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: 'aaa',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const htmlAdapter = new HtmlAdapter(createJob(), provider);
|
||||
const rawBlockSnapshot = await htmlAdapter.toBlockSnapshot({
|
||||
file: html,
|
||||
});
|
||||
expect(nanoidReplacement(rawBlockSnapshot)).toEqual(blockSnapshot);
|
||||
});
|
||||
|
||||
describe('strong element', () => {
|
||||
test('should not be bold when font-weight is normal', async () => {
|
||||
const html = template(`<span style="font-weight: normal;">aaa</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: 'aaa',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const htmlAdapter = new HtmlAdapter(createJob(), provider);
|
||||
const rawBlockSnapshot = await htmlAdapter.toBlockSnapshot({
|
||||
file: html,
|
||||
});
|
||||
expect(nanoidReplacement(rawBlockSnapshot)).toEqual(blockSnapshot);
|
||||
});
|
||||
|
||||
test('should be bold when font-weight is bold or 500-900 ', async () => {
|
||||
const html = template(
|
||||
`<p><span style="font-weight: bold;">aaa</span><span style="font-weight: 100;">aaa</span><span style="font-weight: 500;">bbb</span><span style="font-weight: 200;">bbb</span><span style="font-weight: 600;">ccc</span><span style="font-weight: 300;">ccc</span><span style="font-weight: 700;">ddd</span></p>`
|
||||
);
|
||||
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: [
|
||||
{
|
||||
attributes: {
|
||||
bold: true,
|
||||
},
|
||||
insert: 'aaa',
|
||||
},
|
||||
{
|
||||
insert: 'aaa',
|
||||
},
|
||||
{
|
||||
attributes: {
|
||||
bold: true,
|
||||
},
|
||||
insert: 'bbb',
|
||||
},
|
||||
{
|
||||
insert: 'bbb',
|
||||
},
|
||||
{
|
||||
attributes: {
|
||||
bold: true,
|
||||
},
|
||||
insert: 'ccc',
|
||||
},
|
||||
{
|
||||
insert: 'ccc',
|
||||
},
|
||||
{
|
||||
attributes: {
|
||||
bold: true,
|
||||
},
|
||||
insert: 'ddd',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const htmlAdapter = new HtmlAdapter(createJob(), provider);
|
||||
const rawBlockSnapshot = await htmlAdapter.toBlockSnapshot({
|
||||
file: html,
|
||||
});
|
||||
expect(nanoidReplacement(rawBlockSnapshot)).toEqual(blockSnapshot);
|
||||
});
|
||||
});
|
||||
|
||||
test('should be italic when tag is i or em or span with style font-style: italic', async () => {
|
||||
const html = template(
|
||||
`<p><i>aaa</i><span>aaa</span><em>bbb</em><span>bbb</span><span style="font-style: italic;">ccc</span></p>`
|
||||
);
|
||||
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: [
|
||||
{
|
||||
attributes: {
|
||||
italic: true,
|
||||
},
|
||||
insert: 'aaa',
|
||||
},
|
||||
{
|
||||
insert: 'aaa',
|
||||
},
|
||||
{
|
||||
attributes: {
|
||||
italic: true,
|
||||
},
|
||||
insert: 'bbb',
|
||||
},
|
||||
{
|
||||
insert: 'bbb',
|
||||
},
|
||||
{
|
||||
attributes: {
|
||||
italic: true,
|
||||
},
|
||||
insert: 'ccc',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const htmlAdapter = new HtmlAdapter(createJob(), provider);
|
||||
const rawBlockSnapshot = await htmlAdapter.toBlockSnapshot({
|
||||
file: html,
|
||||
});
|
||||
expect(nanoidReplacement(rawBlockSnapshot)).toEqual(blockSnapshot);
|
||||
});
|
||||
|
||||
test('should be underline when tag is u or span with style text-decoration: underline', async () => {
|
||||
const html = template(
|
||||
`<p><u>aaa</u><span>aaa</span><span style="text-decoration: underline;">bbb</span></p>`
|
||||
);
|
||||
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: [
|
||||
{
|
||||
attributes: {
|
||||
underline: true,
|
||||
},
|
||||
insert: 'aaa',
|
||||
},
|
||||
{
|
||||
insert: 'aaa',
|
||||
},
|
||||
{
|
||||
attributes: {
|
||||
underline: true,
|
||||
},
|
||||
insert: 'bbb',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const htmlAdapter = new HtmlAdapter(createJob(), provider);
|
||||
const rawBlockSnapshot = await htmlAdapter.toBlockSnapshot({
|
||||
file: html,
|
||||
});
|
||||
expect(nanoidReplacement(rawBlockSnapshot)).toEqual(blockSnapshot);
|
||||
});
|
||||
|
||||
test('should be strike when tag is del or span with style text-decoration: line-through', async () => {
|
||||
const html = template(
|
||||
`<p><del>aaa</del><span>aaa</span><span style="text-decoration: line-through;">bbb</span></p>`
|
||||
);
|
||||
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: [
|
||||
{
|
||||
attributes: {
|
||||
strike: true,
|
||||
},
|
||||
insert: 'aaa',
|
||||
},
|
||||
{
|
||||
insert: 'aaa',
|
||||
},
|
||||
{
|
||||
attributes: {
|
||||
strike: true,
|
||||
},
|
||||
insert: 'bbb',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const htmlAdapter = new HtmlAdapter(createJob(), provider);
|
||||
const rawBlockSnapshot = await htmlAdapter.toBlockSnapshot({
|
||||
file: html,
|
||||
});
|
||||
expect(nanoidReplacement(rawBlockSnapshot)).toEqual(blockSnapshot);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4417,69 +4417,6 @@ hhh
|
||||
});
|
||||
expect(nanoidReplacement(rawBlockSnapshot)).toEqual(blockSnapshot);
|
||||
});
|
||||
|
||||
test('should handle footnote reference with url prefix', async () => {
|
||||
const 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: 'https://example.com',
|
||||
attributes: {
|
||||
link: 'https://example.com',
|
||||
},
|
||||
},
|
||||
{
|
||||
insert: ' ',
|
||||
},
|
||||
{
|
||||
insert: ' ',
|
||||
attributes: {
|
||||
footnote: {
|
||||
label: '1',
|
||||
reference: {
|
||||
type: 'url',
|
||||
url,
|
||||
favicon,
|
||||
title,
|
||||
description,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const markdown = `https://example.com[^1]\n\n[^1]: {"type":"url","url":"${url}","favicon":"${favicon}","title":"${title}","description":"${description}"}\n`;
|
||||
|
||||
const mdAdapter = new MarkdownAdapter(createJob(), provider);
|
||||
const rawBlockSnapshot = await mdAdapter.toBlockSnapshot({
|
||||
file: markdown,
|
||||
});
|
||||
expect(nanoidReplacement(rawBlockSnapshot)).toEqual(blockSnapshot);
|
||||
});
|
||||
});
|
||||
|
||||
test('should not wrap url with angle brackets if it is not a url', async () => {
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
import { DefaultTheme, NoteDisplayMode } from '@blocksuite/affine-model';
|
||||
import { NotionHtmlAdapter } from '@blocksuite/affine-shared/adapters';
|
||||
import { DEFAULT_IMAGE_PROXY_ENDPOINT } from '@blocksuite/affine-shared/consts';
|
||||
import {
|
||||
AssetsManager,
|
||||
type BlockSnapshot,
|
||||
MemoryBlobCRUD,
|
||||
} from '@blocksuite/store';
|
||||
import { http, HttpResponse } from 'msw';
|
||||
import { setupServer } from 'msw/node';
|
||||
import { afterAll, afterEach, beforeAll, describe, expect, test } from 'vitest';
|
||||
import { describe, expect, test } from 'vitest';
|
||||
|
||||
import { createJob } from '../utils/create-job.js';
|
||||
import { getProvider } from '../utils/get-provider.js';
|
||||
@@ -1198,71 +1195,43 @@ describe('notion html to snapshot', () => {
|
||||
expect(nanoidReplacement(rawBlockSnapshot)).toEqual(blockSnapshot);
|
||||
});
|
||||
|
||||
describe('image', () => {
|
||||
const originalUrl =
|
||||
'https://raw.githubusercontent.com/toeverything/blocksuite/master/assets/logo.svg';
|
||||
test('image', async () => {
|
||||
const html = `<div class="page-body">
|
||||
<figure id="ed3d2ae9-62f5-433a-9049-9ddbd1c81ac5" class="image"><a
|
||||
href="https://raw.githubusercontent.com/toeverything/blocksuite/master/assets/logo.svg"><img src="https://raw.githubusercontent.com/toeverything/blocksuite/master/assets/logo.svg" /></a>
|
||||
</figure>
|
||||
</div>`;
|
||||
|
||||
const imageProxy = DEFAULT_IMAGE_PROXY_ENDPOINT;
|
||||
const imageUrl = `${imageProxy}?url=${encodeURIComponent(originalUrl)}`;
|
||||
|
||||
// Mock the image request
|
||||
const imageRequestHandlers = [
|
||||
http.get(imageUrl.toString(), async () => {
|
||||
// Return a mock image blob
|
||||
const mockImageBlob = new Blob(['mock image data'], {
|
||||
type: 'image/svg+xml',
|
||||
});
|
||||
return new HttpResponse(mockImageBlob, {
|
||||
headers: {
|
||||
'Content-Type': 'image/svg+xml',
|
||||
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:image',
|
||||
props: {
|
||||
sourceId: 'matchesReplaceMap[2]',
|
||||
},
|
||||
});
|
||||
}),
|
||||
];
|
||||
|
||||
const server = setupServer(...imageRequestHandlers);
|
||||
beforeAll(() => server.listen({ onUnhandledRequest: 'error' }));
|
||||
afterAll(() => server.close());
|
||||
afterEach(() => server.resetHandlers());
|
||||
|
||||
test('network image resource', async () => {
|
||||
const html = `<div class="page-body">
|
||||
<figure id="ed3d2ae9-62f5-433a-9049-9ddbd1c81ac5" class="image"><a
|
||||
href="${originalUrl}"><img src="${originalUrl}" /></a>
|
||||
</figure>
|
||||
</div>`;
|
||||
|
||||
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: [],
|
||||
},
|
||||
children: [
|
||||
{
|
||||
type: 'block',
|
||||
id: 'matchesReplaceMap[1]',
|
||||
flavour: 'affine:image',
|
||||
props: {
|
||||
sourceId: 'matchesReplaceMap[2]',
|
||||
},
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
};
|
||||
],
|
||||
};
|
||||
|
||||
const adapter = new NotionHtmlAdapter(createJob(), provider);
|
||||
const rawBlockSnapshot = await adapter.toBlockSnapshot({
|
||||
file: html,
|
||||
assets: new AssetsManager({ blob: new MemoryBlobCRUD() }),
|
||||
});
|
||||
expect(nanoidReplacement(rawBlockSnapshot)).toEqual(blockSnapshot);
|
||||
const adapter = new NotionHtmlAdapter(createJob(), provider);
|
||||
const rawBlockSnapshot = await adapter.toBlockSnapshot({
|
||||
file: html,
|
||||
assets: new AssetsManager({ blob: new MemoryBlobCRUD() }),
|
||||
});
|
||||
expect(nanoidReplacement(rawBlockSnapshot)).toEqual(blockSnapshot);
|
||||
});
|
||||
|
||||
test('bookmark', async () => {
|
||||
|
||||
@@ -19,7 +19,6 @@ import { SurfaceViewExtension } from '@blocksuite/affine-block-surface/view';
|
||||
import { SurfaceRefViewExtension } from '@blocksuite/affine-block-surface-ref/view';
|
||||
import { TableViewExtension } from '@blocksuite/affine-block-table/view';
|
||||
import { FoundationViewExtension } from '@blocksuite/affine-foundation/view';
|
||||
import { AdapterPanelViewExtension } from '@blocksuite/affine-fragment-adapter-panel/view';
|
||||
import { DocTitleViewExtension } from '@blocksuite/affine-fragment-doc-title/view';
|
||||
import { FramePanelViewExtension } from '@blocksuite/affine-fragment-frame-panel/view';
|
||||
import { OutlineViewExtension } from '@blocksuite/affine-fragment-outline/view';
|
||||
@@ -41,14 +40,11 @@ import { InlinePresetViewExtension } from '@blocksuite/affine-inline-preset/view
|
||||
import { ReferenceViewExtension } from '@blocksuite/affine-inline-reference/view';
|
||||
import { DragHandleViewExtension } from '@blocksuite/affine-widget-drag-handle/view';
|
||||
import { EdgelessAutoConnectViewExtension } from '@blocksuite/affine-widget-edgeless-auto-connect/view';
|
||||
import { EdgelessDraggingAreaViewExtension } from '@blocksuite/affine-widget-edgeless-dragging-area/view';
|
||||
import { EdgelessSelectedRectViewExtension } from '@blocksuite/affine-widget-edgeless-selected-rect/view';
|
||||
import { EdgelessToolbarViewExtension } from '@blocksuite/affine-widget-edgeless-toolbar/view';
|
||||
import { EdgelessZoomToolbarViewExtension } from '@blocksuite/affine-widget-edgeless-zoom-toolbar/view';
|
||||
import { FrameTitleViewExtension } from '@blocksuite/affine-widget-frame-title/view';
|
||||
import { KeyboardToolbarViewExtension } from '@blocksuite/affine-widget-keyboard-toolbar/view';
|
||||
import { LinkedDocViewExtension } from '@blocksuite/affine-widget-linked-doc/view';
|
||||
import { NoteSlicerViewExtension } from '@blocksuite/affine-widget-note-slicer/view';
|
||||
import { PageDraggingAreaViewExtension } from '@blocksuite/affine-widget-page-dragging-area/view';
|
||||
import { RemoteSelectionViewExtension } from '@blocksuite/affine-widget-remote-selection/view';
|
||||
import { ScrollAnchoringViewExtension } from '@blocksuite/affine-widget-scroll-anchoring/view';
|
||||
@@ -103,9 +99,9 @@ export function getInternalViewExtensions() {
|
||||
InlinePresetViewExtension,
|
||||
|
||||
// Widget
|
||||
// order will affect the z-index of the widget
|
||||
DragHandleViewExtension,
|
||||
EdgelessAutoConnectViewExtension,
|
||||
EdgelessToolbarViewExtension,
|
||||
FrameTitleViewExtension,
|
||||
KeyboardToolbarViewExtension,
|
||||
LinkedDocViewExtension,
|
||||
@@ -116,15 +112,10 @@ export function getInternalViewExtensions() {
|
||||
ViewportOverlayViewExtension,
|
||||
EdgelessZoomToolbarViewExtension,
|
||||
PageDraggingAreaViewExtension,
|
||||
EdgelessSelectedRectViewExtension,
|
||||
EdgelessDraggingAreaViewExtension,
|
||||
NoteSlicerViewExtension,
|
||||
EdgelessToolbarViewExtension,
|
||||
|
||||
// Fragment
|
||||
DocTitleViewExtension,
|
||||
FramePanelViewExtension,
|
||||
OutlineViewExtension,
|
||||
AdapterPanelViewExtension,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export * from '@blocksuite/affine-fragment-adapter-panel';
|
||||
@@ -1 +0,0 @@
|
||||
export * from '@blocksuite/affine-fragment-adapter-panel/view';
|
||||
@@ -1 +0,0 @@
|
||||
export * from '@blocksuite/affine-widget-edgeless-dragging-area';
|
||||
@@ -1 +0,0 @@
|
||||
export * from '@blocksuite/affine-widget-edgeless-dragging-area/view';
|
||||
@@ -1 +0,0 @@
|
||||
export * from '@blocksuite/affine-widget-edgeless-selected-rect';
|
||||
@@ -1 +0,0 @@
|
||||
export * from '@blocksuite/affine-widget-edgeless-selected-rect/view';
|
||||
@@ -1 +0,0 @@
|
||||
export * from '@blocksuite/affine-widget-note-slicer';
|
||||
@@ -1 +0,0 @@
|
||||
export * from '@blocksuite/affine-widget-note-slicer/view';
|
||||
@@ -30,7 +30,6 @@
|
||||
{ "path": "../components" },
|
||||
{ "path": "../ext-loader" },
|
||||
{ "path": "../foundation" },
|
||||
{ "path": "../fragments/adapter-panel" },
|
||||
{ "path": "../fragments/doc-title" },
|
||||
{ "path": "../fragments/frame-panel" },
|
||||
{ "path": "../fragments/outline" },
|
||||
@@ -56,14 +55,11 @@
|
||||
{ "path": "../shared" },
|
||||
{ "path": "../widgets/drag-handle" },
|
||||
{ "path": "../widgets/edgeless-auto-connect" },
|
||||
{ "path": "../widgets/edgeless-dragging-area" },
|
||||
{ "path": "../widgets/edgeless-selected-rect" },
|
||||
{ "path": "../widgets/edgeless-toolbar" },
|
||||
{ "path": "../widgets/edgeless-zoom-toolbar" },
|
||||
{ "path": "../widgets/frame-title" },
|
||||
{ "path": "../widgets/keyboard-toolbar" },
|
||||
{ "path": "../widgets/linked-doc" },
|
||||
{ "path": "../widgets/note-slicer" },
|
||||
{ "path": "../widgets/page-dragging-area" },
|
||||
{ "path": "../widgets/remote-selection" },
|
||||
{ "path": "../widgets/scroll-anchoring" },
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
"@lit/context": "^1.1.2",
|
||||
"@preact/signals-core": "^1.8.0",
|
||||
"@toeverything/theme": "^1.1.14",
|
||||
"file-type": "^21.0.0",
|
||||
"file-type": "^20.0.0",
|
||||
"lit": "^3.2.0",
|
||||
"minimatch": "^10.0.1",
|
||||
"rxjs": "^7.8.1",
|
||||
@@ -32,6 +32,7 @@
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./effects": "./src/effects.ts",
|
||||
"./store": "./src/store.ts",
|
||||
"./view": "./src/view.ts"
|
||||
},
|
||||
|
||||
@@ -17,12 +17,10 @@ import {
|
||||
AttachmentBlockStyles,
|
||||
} from '@blocksuite/affine-model';
|
||||
import {
|
||||
DocModeProvider,
|
||||
FileSizeLimitProvider,
|
||||
TelemetryProvider,
|
||||
ThemeProvider,
|
||||
} from '@blocksuite/affine-shared/services';
|
||||
import { formatSize } from '@blocksuite/affine-shared/utils';
|
||||
import { humanFileSize } from '@blocksuite/affine-shared/utils';
|
||||
import {
|
||||
AttachmentIcon,
|
||||
ResetIcon,
|
||||
@@ -145,11 +143,7 @@ export class AttachmentBlockComponent extends CaptionedBlockComponent<Attachment
|
||||
this.disposables.add(this.resourceController.subscribe());
|
||||
this.disposables.add(this.resourceController);
|
||||
|
||||
this.disposables.add(
|
||||
this.model.props.sourceId$.subscribe(() => {
|
||||
this.refreshData();
|
||||
})
|
||||
);
|
||||
this.refreshData();
|
||||
|
||||
if (!this.model.props.style && !this.store.readonly) {
|
||||
this.store.withoutTransact(() => {
|
||||
@@ -189,22 +183,6 @@ export class AttachmentBlockComponent extends CaptionedBlockComponent<Attachment
|
||||
@click=${(event: MouseEvent) => {
|
||||
event.stopPropagation();
|
||||
onOverFileSize?.();
|
||||
|
||||
{
|
||||
const mode =
|
||||
this.std.get(DocModeProvider).getEditorMode() ?? 'page';
|
||||
const segment = mode === 'page' ? 'doc' : 'whiteboard';
|
||||
this.std
|
||||
.getOptional(TelemetryProvider)
|
||||
?.track('AttachmentUpgradedEvent', {
|
||||
segment,
|
||||
page: `${segment} editor`,
|
||||
module: 'attachment',
|
||||
control: 'upgrade',
|
||||
category: 'card',
|
||||
type: this.model.props.name.split('.').pop() ?? '',
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
${UpgradeIcon()} Upgrade
|
||||
@@ -220,22 +198,6 @@ export class AttachmentBlockComponent extends CaptionedBlockComponent<Attachment
|
||||
@click=${(event: MouseEvent) => {
|
||||
event.stopPropagation();
|
||||
this.refreshData();
|
||||
|
||||
{
|
||||
const mode =
|
||||
this.std.get(DocModeProvider).getEditorMode() ?? 'page';
|
||||
const segment = mode === 'page' ? 'doc' : 'whiteboard';
|
||||
this.std
|
||||
.getOptional(TelemetryProvider)
|
||||
?.track('AttachmentReloadedEvent', {
|
||||
segment,
|
||||
page: `${segment} editor`,
|
||||
module: 'attachment',
|
||||
control: 'reload',
|
||||
category: 'card',
|
||||
type: this.model.props.name.split('.').pop() ?? '',
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
${ResetIcon()} Reload
|
||||
@@ -316,7 +278,7 @@ export class AttachmentBlockComponent extends CaptionedBlockComponent<Attachment
|
||||
errorIcon: WarningIcon(),
|
||||
icon: AttachmentIcon(),
|
||||
title: name,
|
||||
description: formatSize(size),
|
||||
description: humanFileSize(size),
|
||||
});
|
||||
|
||||
return { ...resolvedState, kind };
|
||||
|
||||
@@ -264,12 +264,6 @@ const builtinToolbarConfig = {
|
||||
run(ctx) {
|
||||
const block = ctx.getCurrentBlockByType(AttachmentBlockComponent);
|
||||
block?.reload();
|
||||
|
||||
ctx.track('AttachmentReloadedEvent', {
|
||||
...trackBaseProps,
|
||||
control: 'reload',
|
||||
type: block?.model.props.name.split('.').pop() ?? '',
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
FileSizeLimitProvider,
|
||||
TelemetryProvider,
|
||||
} from '@blocksuite/affine-shared/services';
|
||||
import { formatSize } from '@blocksuite/affine-shared/utils';
|
||||
import { humanFileSize } from '@blocksuite/affine-shared/utils';
|
||||
import { Bound, type IVec, Vec } from '@blocksuite/global/gfx';
|
||||
import type { BlockStdScope } from '@blocksuite/std';
|
||||
import { GfxControllerIdentifier } from '@blocksuite/std/gfx';
|
||||
@@ -93,7 +93,7 @@ function hasExceeded(
|
||||
const exceeded = files.some(file => file.size > maxFileSize);
|
||||
|
||||
if (exceeded) {
|
||||
const size = formatSize(maxFileSize);
|
||||
const size = humanFileSize(maxFileSize, true, 0);
|
||||
toast(std.host, `You can only upload files less than ${size}`);
|
||||
}
|
||||
|
||||
@@ -130,7 +130,7 @@ async function buildPropsWith(
|
||||
std.getOptional(TelemetryProvider)?.track('AttachmentUploadedEvent', {
|
||||
page: `${mode} editor`,
|
||||
module: 'attachment',
|
||||
segment: mode,
|
||||
segment: 'attachment',
|
||||
control: 'uploader',
|
||||
type,
|
||||
category,
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./effects": "./src/effects.ts",
|
||||
"./store": "./src/store.ts",
|
||||
"./view": "./src/view.ts"
|
||||
},
|
||||
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
DocModeProvider,
|
||||
LinkPreviewServiceIdentifier,
|
||||
} from '@blocksuite/affine-shared/services';
|
||||
import { normalizeUrl } from '@blocksuite/affine-shared/utils';
|
||||
import { BlockSelection } from '@blocksuite/std';
|
||||
import { computed, type ReadonlySignal, signal } from '@preact/signals-core';
|
||||
import { html } from 'lit';
|
||||
@@ -100,12 +99,12 @@ export class BookmarkBlockComponent extends CaptionedBlockComponent<BookmarkBloc
|
||||
selectionManager.setGroup('note', [blockSelection]);
|
||||
};
|
||||
|
||||
get link() {
|
||||
return normalizeUrl(this.model.props.url);
|
||||
}
|
||||
|
||||
open = () => {
|
||||
window.open(this.link, '_blank');
|
||||
let link = this.model.props.url;
|
||||
if (!link.match(/^[a-zA-Z]+:\/\//)) {
|
||||
link = 'https://' + link;
|
||||
}
|
||||
window.open(link, '_blank');
|
||||
};
|
||||
|
||||
refreshData = () => {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import '@blocksuite/affine-block-embed/effects';
|
||||
|
||||
import { insertEmbedCard } from '@blocksuite/affine-block-embed';
|
||||
import type { EmbedCardStyle } from '@blocksuite/affine-model';
|
||||
import { EmbedOptionProvider } from '@blocksuite/affine-shared/services';
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./effects": "./src/effects.ts",
|
||||
"./view": "./src/view.ts",
|
||||
"./store": "./src/store.ts"
|
||||
},
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./effects": "./src/effects.ts",
|
||||
"./turbo-painter": "./src/turbo/code-painter.worker.ts",
|
||||
"./view": "./src/view.ts",
|
||||
"./store": "./src/store.ts"
|
||||
|
||||
@@ -48,11 +48,7 @@ const codePreprocessor: MarkdownAdapterPreprocessor = {
|
||||
}
|
||||
|
||||
trimmedLine = trimmedLine.trimEnd();
|
||||
if (
|
||||
!trimmedLine.startsWith('<') &&
|
||||
!trimmedLine.endsWith('>') &&
|
||||
!trimmedLine.includes(' ')
|
||||
) {
|
||||
if (!trimmedLine.startsWith('<') && !trimmedLine.endsWith('>')) {
|
||||
// check if it is a url link and wrap it with the angle brackets
|
||||
// sometimes the url includes emphasis `_` that will break URL parsing
|
||||
//
|
||||
|
||||
@@ -388,10 +388,8 @@ export class CodeBlockComponent extends CaptionedBlockComponent<CodeBlockModel>
|
||||
|
||||
override renderBlock(): TemplateResult<1> {
|
||||
const showLineNumbers =
|
||||
(this.std.getOptional(CodeBlockConfigExtension.identifier)
|
||||
?.showLineNumbers ??
|
||||
true) &&
|
||||
(this.model.props.lineNumber ?? true);
|
||||
this.std.getOptional(CodeBlockConfigExtension.identifier)
|
||||
?.showLineNumbers ?? true;
|
||||
|
||||
const preview = !!this.model.props.preview;
|
||||
const previewContext = this.std.getOptional(
|
||||
@@ -405,7 +403,6 @@ export class CodeBlockComponent extends CaptionedBlockComponent<CodeBlockModel>
|
||||
'affine-code-block-container': true,
|
||||
mobile: IS_MOBILE,
|
||||
wrap: this.model.props.wrap,
|
||||
'disable-line-numbers': !showLineNumbers,
|
||||
})}
|
||||
>
|
||||
<rich-text
|
||||
@@ -423,14 +420,16 @@ export class CodeBlockComponent extends CaptionedBlockComponent<CodeBlockModel>
|
||||
.enableUndoRedo=${false}
|
||||
.wrapText=${this.model.props.wrap}
|
||||
.verticalScrollContainerGetter=${() => getViewportElement(this.host)}
|
||||
.vLineRenderer=${(vLine: VLine) => {
|
||||
return html`
|
||||
<span contenteditable="false" class="line-number"
|
||||
>${vLine.index + 1}</span
|
||||
>
|
||||
${vLine.renderVElements()}
|
||||
`;
|
||||
}}
|
||||
.vLineRenderer=${showLineNumbers
|
||||
? (vLine: VLine) => {
|
||||
return html`
|
||||
<span contenteditable="false" class="line-number"
|
||||
>${vLine.index + 1}</span
|
||||
>
|
||||
${vLine.renderVElements()}
|
||||
`;
|
||||
}
|
||||
: undefined}
|
||||
>
|
||||
</rich-text>
|
||||
<div
|
||||
|
||||
@@ -4,7 +4,6 @@ import type {
|
||||
MenuItemGroup,
|
||||
} from '@blocksuite/affine-components/toolbar';
|
||||
import { renderGroups } from '@blocksuite/affine-components/toolbar';
|
||||
import { unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme';
|
||||
import { WithDisposable } from '@blocksuite/global/lit';
|
||||
import { noop } from '@blocksuite/global/utils';
|
||||
import { MoreVerticalIcon } from '@blocksuite/icons/lit';
|
||||
@@ -34,8 +33,8 @@ export class AffineCodeToolbar extends WithDisposable(LitElement) {
|
||||
}
|
||||
|
||||
.code-toolbar-button {
|
||||
color: ${unsafeCSSVarV2('icon/primary')};
|
||||
background-color: ${unsafeCSSVarV2('segment/background')};
|
||||
color: var(--affine-icon-color);
|
||||
background-color: var(--affine-background-primary-color);
|
||||
box-shadow: var(--affine-shadow-1);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@ export class LanguageListButton extends WithDisposable(
|
||||
) {
|
||||
static override styles = css`
|
||||
.lang-button {
|
||||
background-color: var(--affine-background-primary-color);
|
||||
box-shadow: var(--affine-shadow-1);
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
padding: 2px 4px;
|
||||
@@ -26,11 +28,11 @@ export class LanguageListButton extends WithDisposable(
|
||||
}
|
||||
|
||||
.lang-button:hover {
|
||||
background: ${unsafeCSSVarV2('layer/background/hoverOverlay')};
|
||||
background: var(--affine-hover-color-filled);
|
||||
}
|
||||
|
||||
.lang-button[hover] {
|
||||
background: ${unsafeCSSVarV2('layer/background/hoverOverlay')};
|
||||
background: var(--affine-hover-color-filled);
|
||||
}
|
||||
|
||||
.lang-button-icon {
|
||||
|
||||
@@ -9,12 +9,10 @@ import {
|
||||
import type { MenuItemGroup } from '@blocksuite/affine-components/toolbar';
|
||||
import { isInsidePageEditor } from '@blocksuite/affine-shared/utils';
|
||||
import { noop, sleep } from '@blocksuite/global/utils';
|
||||
import { NumberedListIcon } from '@blocksuite/icons/lit';
|
||||
import { BlockSelection } from '@blocksuite/std';
|
||||
import { html } from 'lit';
|
||||
import { ifDefined } from 'lit/directives/if-defined.js';
|
||||
|
||||
import { CodeBlockConfigExtension } from '../code-block-config.js';
|
||||
import type { CodeBlockToolbarContext } from './context.js';
|
||||
import { duplicateCodeBlock } from './utils.js';
|
||||
|
||||
@@ -150,40 +148,6 @@ export const clipboardGroup: MenuItemGroup<CodeBlockToolbarContext> = {
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'line-number',
|
||||
when: ({ std }) =>
|
||||
std.getOptional(CodeBlockConfigExtension.identifier)?.showLineNumbers ??
|
||||
true,
|
||||
generate: ({ blockComponent, close }) => {
|
||||
return {
|
||||
action: () => {},
|
||||
render: () => {
|
||||
const lineNumber = blockComponent.model.props.lineNumber ?? true;
|
||||
const label = lineNumber ? 'Cancel line number' : 'Line number';
|
||||
return html`
|
||||
<editor-menu-action
|
||||
@click=${() => {
|
||||
blockComponent.store.updateBlock(blockComponent.model, {
|
||||
lineNumber: !lineNumber,
|
||||
});
|
||||
|
||||
close();
|
||||
}}
|
||||
aria-label=${label}
|
||||
>
|
||||
${NumberedListIcon()}
|
||||
<span class="label">${label}</span>
|
||||
<toggle-switch
|
||||
style="margin-left: auto;"
|
||||
.on="${lineNumber}"
|
||||
></toggle-switch>
|
||||
</editor-menu-action>
|
||||
`;
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'duplicate',
|
||||
label: 'Duplicate',
|
||||
|
||||
@@ -2,10 +2,6 @@ import { scrollbarStyle } from '@blocksuite/affine-shared/styles';
|
||||
import { css } from 'lit';
|
||||
|
||||
export const codeBlockStyles = css`
|
||||
affine-code {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.affine-code-block-container {
|
||||
font-size: var(--affine-font-xs);
|
||||
line-height: var(--affine-line-height);
|
||||
@@ -54,10 +50,6 @@ export const codeBlockStyles = css`
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.affine-code-block-container.disable-line-numbers .line-number {
|
||||
display: none;
|
||||
}
|
||||
|
||||
affine-code .affine-code-block-preview {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./effects": "./src/effects.ts",
|
||||
"./view": "./src/view.ts",
|
||||
"./store": "./src/store.ts"
|
||||
},
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./effects": "./src/effects.ts",
|
||||
"./store": "./src/store.ts",
|
||||
"./view": "./src/view.ts"
|
||||
},
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./effects": "./src/effects.ts",
|
||||
"./view": "./src/view.ts",
|
||||
"./store": "./src/store.ts"
|
||||
},
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./effects": "./src/effects.ts",
|
||||
"./view": "./src/view.ts",
|
||||
"./store": "./src/store.ts"
|
||||
},
|
||||
|
||||
@@ -26,7 +26,6 @@ import {
|
||||
GfxViewInteractionExtension,
|
||||
type SelectedContext,
|
||||
} from '@blocksuite/std/gfx';
|
||||
import { computed } from '@preact/signals-core';
|
||||
import { css, html } from 'lit';
|
||||
import { query, state } from 'lit/decorators.js';
|
||||
import { type StyleInfo, styleMap } from 'lit/directives/style-map.js';
|
||||
@@ -83,23 +82,6 @@ export class EdgelessTextBlockComponent extends GfxBlockComponent<EdgelessTextBl
|
||||
});
|
||||
}
|
||||
|
||||
private readonly _style$ = computed(() => {
|
||||
const {
|
||||
color$: { value: color },
|
||||
fontFamily$: { value: fontFamily },
|
||||
fontStyle$: { value: fontStyle },
|
||||
fontWeight$: { value: fontWeight },
|
||||
textAlign$: { value: textAlign },
|
||||
} = this.model.props;
|
||||
return {
|
||||
color,
|
||||
fontFamily,
|
||||
fontStyle,
|
||||
fontWeight,
|
||||
textAlign,
|
||||
};
|
||||
});
|
||||
|
||||
checkWidthOverflow(width: number) {
|
||||
let wValid = true;
|
||||
|
||||
@@ -383,7 +365,7 @@ export class EdgelessTextBlockComponent extends GfxBlockComponent<EdgelessTextBl
|
||||
|
||||
override renderPageContent() {
|
||||
const { color, fontFamily, fontStyle, fontWeight, textAlign } =
|
||||
this._style$.value;
|
||||
this.model.props;
|
||||
const themeProvider = this.std.get(ThemeProvider);
|
||||
const textColor = themeProvider.generateColorProperty(
|
||||
color,
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./effects": "./src/effects.ts",
|
||||
"./view": "./src/view.ts",
|
||||
"./store": "./src/store.ts"
|
||||
},
|
||||
|
||||
+2
-3
@@ -259,7 +259,7 @@ export class EmbedLinkedDocBlockComponent extends EmbedBlockComponent<EmbedLinke
|
||||
);
|
||||
}
|
||||
|
||||
private readonly _handleDoubleClick = (event: MouseEvent) => {
|
||||
private _handleDoubleClick(event: MouseEvent) {
|
||||
event.stopPropagation();
|
||||
const openDocService = this.std.get(OpenDocExtensionIdentifier);
|
||||
const shouldOpenInPeek =
|
||||
@@ -270,7 +270,7 @@ export class EmbedLinkedDocBlockComponent extends EmbedBlockComponent<EmbedLinke
|
||||
: 'open-in-active-view',
|
||||
event,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
private _isDocEmpty() {
|
||||
const linkedDoc = this.linkedDoc;
|
||||
@@ -311,7 +311,6 @@ export class EmbedLinkedDocBlockComponent extends EmbedBlockComponent<EmbedLinke
|
||||
.citationIdentifier=${footnoteIdentifier}
|
||||
.active=${this.selected$.value}
|
||||
.onClickCallback=${this._handleClick}
|
||||
.onDoubleClickCallback=${this._handleDoubleClick}
|
||||
></affine-citation-card>
|
||||
</div> `;
|
||||
};
|
||||
|
||||
+12
-14
@@ -52,25 +52,23 @@ export const EmbedSyncedDocInteraction =
|
||||
scale = newBound.w / realWidth;
|
||||
}
|
||||
|
||||
const newWidth = newBound.w / scale;
|
||||
|
||||
newBound.w =
|
||||
clamp(
|
||||
newBound.w / scale,
|
||||
constraint.minWidth,
|
||||
constraint.maxWidth
|
||||
) * scale;
|
||||
clamp(newWidth, constraint.minWidth, constraint.maxWidth) * scale;
|
||||
newBound.h =
|
||||
clamp(
|
||||
newBound.h / scale,
|
||||
constraint.minHeight,
|
||||
constraint.maxHeight
|
||||
) * scale;
|
||||
clamp(newBound.h, constraint.minHeight, constraint.maxHeight) *
|
||||
scale;
|
||||
|
||||
const newHeight = newBound.h / scale;
|
||||
|
||||
if (model.isFolded && newHeight > constraint.minHeight) {
|
||||
model.props.preFoldHeight = 0;
|
||||
} else if (!model.isFolded && newHeight <= constraint.minHeight) {
|
||||
model.props.preFoldHeight = initHeight;
|
||||
// only adjust height check the fold state
|
||||
if (originalBound.w === newBound.w) {
|
||||
let preFoldHeight = 0;
|
||||
if (newHeight === constraint.minHeight) {
|
||||
preFoldHeight = initHeight;
|
||||
}
|
||||
model.props.preFoldHeight = preFoldHeight;
|
||||
}
|
||||
|
||||
model.props.scale = scale;
|
||||
|
||||
+17
-12
@@ -20,6 +20,7 @@ import { choose } from 'lit/directives/choose.js';
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
import { guard } from 'lit/directives/guard.js';
|
||||
import { styleMap } from 'lit/directives/style-map.js';
|
||||
import { when } from 'lit/directives/when.js';
|
||||
|
||||
import { EmbedSyncedDocConfigExtension } from './configs';
|
||||
import { EmbedSyncedDocBlockComponent } from './embed-synced-doc-block';
|
||||
@@ -122,18 +123,22 @@ export class EmbedEdgelessSyncedDocBlockComponent extends toEdgelessEmbedBlock(
|
||||
<div class="affine-embed-synced-doc-edgeless-header-wrapper">
|
||||
${header}
|
||||
</div>
|
||||
<div class="affine-embed-synced-doc-editor">
|
||||
${this.isPageMode && this._isEmptySyncedDoc
|
||||
? html`
|
||||
<div class="affine-embed-synced-doc-editor-empty">
|
||||
<span>
|
||||
This is a linked doc, you can add content here.
|
||||
</span>
|
||||
</div>
|
||||
`
|
||||
: guard([editorMode, syncedDoc], renderEditor)}
|
||||
</div>
|
||||
<div class="affine-embed-synced-doc-editor-overlay"></div>
|
||||
${when(
|
||||
!this.model.isFolded,
|
||||
() =>
|
||||
html`<div class="affine-embed-synced-doc-editor">
|
||||
${this.isPageMode && this._isEmptySyncedDoc
|
||||
? html`
|
||||
<div class="affine-embed-synced-doc-editor-empty">
|
||||
<span>
|
||||
This is a linked doc, you can add content here.
|
||||
</span>
|
||||
</div>
|
||||
`
|
||||
: guard([editorMode, syncedDoc], renderEditor)}
|
||||
</div>
|
||||
<div class="affine-embed-synced-doc-editor-overlay"></div>`
|
||||
)}
|
||||
</div>
|
||||
`
|
||||
);
|
||||
|
||||
@@ -76,8 +76,6 @@ export function calcSyncedDocFullHeight(block: BlockComponent) {
|
||||
const bottomPadding = 8;
|
||||
|
||||
return (
|
||||
(headerHeight + contentHeight + bottomPadding) /
|
||||
block.gfx.viewport.zoom /
|
||||
(block.model.props.scale ?? 1)
|
||||
(headerHeight + contentHeight + bottomPadding) / block.gfx.viewport.zoom
|
||||
);
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./effects": "./src/effects.ts",
|
||||
"./view": "./src/view.ts",
|
||||
"./store": "./src/store.ts"
|
||||
},
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./effects": "./src/effects.ts",
|
||||
"./store": "./src/store.ts",
|
||||
"./view": "./src/view.ts"
|
||||
},
|
||||
|
||||
@@ -185,33 +185,9 @@ export class PresentationToolbar extends EdgelessToolbarToolMixin(
|
||||
if (document.fullscreenElement) {
|
||||
document.exitFullscreen().catch(console.error);
|
||||
}
|
||||
|
||||
// Reset the flag when fully exiting presentation mode
|
||||
this.edgeless.std
|
||||
.get(EditPropsStore)
|
||||
.setStorage('presentNoFrameToastShown', false);
|
||||
}
|
||||
|
||||
private _moveToCurrentFrame(forceMove = false) {
|
||||
const currentToolOption = this.gfx.tool.currentToolOption$.value;
|
||||
const toolOptions = currentToolOption?.options;
|
||||
|
||||
// If PresentTool is being activated after a temporary pan (indicated by restoredAfterPan)
|
||||
// and a forced move isn't explicitly requested, skip moving to the current frame.
|
||||
// This preserves the user's panned position instead of resetting to the frame's default view.
|
||||
if (
|
||||
currentToolOption?.toolType === PresentTool &&
|
||||
toolOptions?.restoredAfterPan &&
|
||||
!forceMove
|
||||
) {
|
||||
// Clear the flag so future navigations behave normally
|
||||
this.gfx.tool.setTool(PresentTool, {
|
||||
...toolOptions,
|
||||
restoredAfterPan: false,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
private _moveToCurrentFrame() {
|
||||
const current = this._currentFrameIndex;
|
||||
const viewport = this.gfx.viewport;
|
||||
const frame = this._frames[current];
|
||||
@@ -287,56 +263,28 @@ export class PresentationToolbar extends EdgelessToolbarToolMixin(
|
||||
|
||||
_disposables.add(
|
||||
effect(() => {
|
||||
const currentToolOption = this.gfx.tool.currentToolOption$.value;
|
||||
|
||||
if (currentToolOption?.toolType === PresentTool) {
|
||||
const opts = currentToolOption.options as
|
||||
| ToolOptions<PresentTool>
|
||||
| undefined;
|
||||
|
||||
const isAlreadyFullscreen = !!document.fullscreenElement;
|
||||
|
||||
if (!isAlreadyFullscreen) {
|
||||
this._toggleFullScreen();
|
||||
} else {
|
||||
this._fullScreenMode = true;
|
||||
}
|
||||
const currentTool = this.gfx.tool.currentToolOption$.value;
|
||||
const selection = this.gfx.selection;
|
||||
|
||||
if (currentTool?.toolType === PresentTool) {
|
||||
this._cachedIndex = this._currentFrameIndex;
|
||||
this._navigatorMode = opts?.mode ?? this._navigatorMode;
|
||||
|
||||
const selection = this.gfx.selection;
|
||||
if (
|
||||
selection.selectedElements.length > 0 &&
|
||||
isFrameBlock(selection.selectedElements[0])
|
||||
) {
|
||||
const selectedFrameId = selection.selectedElements[0].id;
|
||||
const indexOfSelectedFrame = this._frames.findIndex(
|
||||
frame => frame.id === selectedFrameId
|
||||
this._navigatorMode =
|
||||
(currentTool.options as ToolOptions<PresentTool>)?.mode ??
|
||||
this._navigatorMode;
|
||||
if (isFrameBlock(selection.selectedElements[0])) {
|
||||
this._cachedIndex = this._frames.findIndex(
|
||||
frame => frame.id === selection.selectedElements[0].id
|
||||
);
|
||||
if (indexOfSelectedFrame !== -1) {
|
||||
this._cachedIndex = indexOfSelectedFrame;
|
||||
}
|
||||
}
|
||||
|
||||
const store = this.edgeless.std.get(EditPropsStore);
|
||||
if (this._frames.length === 0) {
|
||||
if (!store.getStorage('presentNoFrameToastShown')) {
|
||||
toast(
|
||||
this.host,
|
||||
'The presentation requires at least 1 frame. You can firstly create a frame.',
|
||||
5000
|
||||
);
|
||||
store.setStorage('presentNoFrameToastShown', true);
|
||||
}
|
||||
} else {
|
||||
// If frames exist, and the flag was set, reset it.
|
||||
// This allows the toast to show again if all frames are subsequently deleted.
|
||||
if (store.getStorage('presentNoFrameToastShown')) {
|
||||
store.setStorage('presentNoFrameToastShown', false);
|
||||
}
|
||||
}
|
||||
if (this._frames.length === 0)
|
||||
toast(
|
||||
this.host,
|
||||
'The presentation requires at least 1 frame. You can firstly create a frame.',
|
||||
5000
|
||||
);
|
||||
this._toggleFullScreen();
|
||||
}
|
||||
|
||||
this.requestUpdate();
|
||||
})
|
||||
);
|
||||
@@ -357,10 +305,12 @@ export class PresentationToolbar extends EdgelessToolbarToolMixin(
|
||||
|
||||
_disposables.addFromEvent(document, 'fullscreenchange', () => {
|
||||
if (document.fullscreenElement) {
|
||||
// When enter fullscreen, we need to set current frame to the cached index
|
||||
this._timer = setTimeout(() => {
|
||||
this._currentFrameIndex = this._cachedIndex;
|
||||
}, 400);
|
||||
} else {
|
||||
// When exit fullscreen, we need to clear the timer
|
||||
clearTimeout(this._timer);
|
||||
if (
|
||||
this.edgelessTool.toolType === PresentTool &&
|
||||
@@ -374,7 +324,7 @@ export class PresentationToolbar extends EdgelessToolbarToolMixin(
|
||||
}
|
||||
}
|
||||
|
||||
setTimeout(() => this._moveToCurrentFrame(true), 400);
|
||||
setTimeout(() => this._moveToCurrentFrame(), 400);
|
||||
this.slots.fullScreenToggled.next();
|
||||
});
|
||||
|
||||
@@ -480,29 +430,11 @@ export class PresentationToolbar extends EdgelessToolbarToolMixin(
|
||||
}
|
||||
|
||||
protected override updated(changedProperties: PropertyValues) {
|
||||
const currentToolOption = this.gfx.tool.currentToolOption$.value;
|
||||
const isPresentToolActive = currentToolOption?.toolType === PresentTool;
|
||||
const toolOptions = currentToolOption?.options;
|
||||
const isRestoredAfterPan = !!(
|
||||
isPresentToolActive && toolOptions?.restoredAfterPan
|
||||
);
|
||||
|
||||
if (changedProperties.has('_currentFrameIndex') && isPresentToolActive) {
|
||||
// When the current frame index changes (e.g., user navigates), a viewport update is needed.
|
||||
// However, if PresentTool is merely being restored after a pan (isRestoredAfterPan = true)
|
||||
// without an explicit index change in this update cycle, we avoid forcing a move to preserve the panned position.
|
||||
// Thus, `forceMove` is true unless it's a pan restoration.
|
||||
const shouldForceMove = !isRestoredAfterPan;
|
||||
this._moveToCurrentFrame(shouldForceMove);
|
||||
} else if (isPresentToolActive && changedProperties.has('edgelessTool')) {
|
||||
// Handles cases where the tool is set/switched to PresentTool (e.g., initial activation or returning from another tool).
|
||||
// Similar to frame index changes, avoid forcing a viewport move if restoring after a pan.
|
||||
const currentToolIsPresentTool =
|
||||
this.edgelessTool.toolType === PresentTool;
|
||||
if (currentToolIsPresentTool) {
|
||||
const shouldForceMoveOnToolChange = !isRestoredAfterPan;
|
||||
this._moveToCurrentFrame(shouldForceMoveOnToolChange);
|
||||
}
|
||||
if (
|
||||
changedProperties.has('_currentFrameIndex') &&
|
||||
this.edgelessTool.toolType === PresentTool
|
||||
) {
|
||||
this._moveToCurrentFrame();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -160,30 +160,19 @@ const builtinSurfaceToolbarConfig = {
|
||||
background => resolveColor(background, theme)
|
||||
) ?? DefaultTheme.transparent;
|
||||
const onPick = (e: PickColorEvent) => {
|
||||
switch (e.type) {
|
||||
case 'pick':
|
||||
{
|
||||
const color = e.detail.value;
|
||||
const props = packColor(field, color);
|
||||
const crud = ctx.std.get(EdgelessCRUDIdentifier);
|
||||
models.forEach(model => {
|
||||
crud.updateElement(model.id, props);
|
||||
});
|
||||
}
|
||||
break;
|
||||
case 'start':
|
||||
ctx.store.captureSync();
|
||||
models.forEach(model => {
|
||||
model.stash(field);
|
||||
});
|
||||
break;
|
||||
case 'end':
|
||||
ctx.store.transact(() => {
|
||||
models.forEach(model => {
|
||||
model.pop(field);
|
||||
});
|
||||
});
|
||||
break;
|
||||
if (e.type === 'pick') {
|
||||
const color = e.detail.value;
|
||||
for (const model of models) {
|
||||
const props = packColor(field, color);
|
||||
ctx.std
|
||||
.get(EdgelessCRUDIdentifier)
|
||||
.updateElement(model.id, props);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
for (const model of models) {
|
||||
model[e.type === 'start' ? 'stash' : 'pop'](field);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import type { NavigatorMode } from './frame-manager';
|
||||
|
||||
export type PresentToolOption = {
|
||||
mode?: NavigatorMode;
|
||||
restoredAfterPan?: boolean;
|
||||
};
|
||||
|
||||
export class PresentTool extends BaseTool<PresentToolOption> {
|
||||
|
||||
@@ -77,16 +77,18 @@ export class EdgelessNavigatorSettingButton extends WithDisposable(LitElement) {
|
||||
slots.navigatorSettingUpdated.next({
|
||||
blackBackground: this.blackBackground,
|
||||
});
|
||||
this.edgeless.std
|
||||
.get(EditPropsStore)
|
||||
.setStorage('presentBlackBackground', checked);
|
||||
};
|
||||
|
||||
private _tryRestoreSettings() {
|
||||
const blackBackground = this.edgeless.std
|
||||
.get(EditPropsStore)
|
||||
.getStorage('presentBlackBackground');
|
||||
this.blackBackground = blackBackground ?? false;
|
||||
this.blackBackground = blackBackground ?? true;
|
||||
}
|
||||
|
||||
override connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this._tryRestoreSettings();
|
||||
}
|
||||
|
||||
override disconnectedCallback(): void {
|
||||
@@ -95,7 +97,6 @@ export class EdgelessNavigatorSettingButton extends WithDisposable(LitElement) {
|
||||
}
|
||||
|
||||
override firstUpdated() {
|
||||
if (this.edgeless) this._tryRestoreSettings();
|
||||
this._navigatorSettingPopper = createButtonPopper({
|
||||
reference: this._navigatorSettingButton,
|
||||
popperElement: this._navigatorSettingMenu,
|
||||
@@ -132,7 +133,7 @@ export class EdgelessNavigatorSettingButton extends WithDisposable(LitElement) {
|
||||
<div class="text">Black background</div>
|
||||
|
||||
<toggle-switch
|
||||
.on=${this.blackBackground}
|
||||
.subscribe=${this.blackBackground}
|
||||
.onChange=${this._onBlackBackgroundChange}
|
||||
>
|
||||
</toggle-switch>
|
||||
@@ -142,7 +143,7 @@ export class EdgelessNavigatorSettingButton extends WithDisposable(LitElement) {
|
||||
<div class="text">Hide toolbar</div>
|
||||
|
||||
<toggle-switch
|
||||
.on=${this.hideToolbar}
|
||||
.subscribe=${this.hideToolbar}
|
||||
.onChange=${(checked: boolean) => {
|
||||
this.onHideToolbarChange && this.onHideToolbarChange(checked);
|
||||
}}
|
||||
@@ -172,7 +173,7 @@ export class EdgelessNavigatorSettingButton extends WithDisposable(LitElement) {
|
||||
private accessor _navigatorSettingMenu!: HTMLElement;
|
||||
|
||||
@state()
|
||||
accessor blackBackground = false;
|
||||
accessor blackBackground = true;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor edgeless!: BlockComponent;
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"@lit/context": "^1.1.2",
|
||||
"@preact/signals-core": "^1.8.0",
|
||||
"@toeverything/theme": "^1.1.14",
|
||||
"file-type": "^21.0.0",
|
||||
"file-type": "^20.0.0",
|
||||
"lit": "^3.2.0",
|
||||
"minimatch": "^10.0.1",
|
||||
"rxjs": "^7.8.1",
|
||||
@@ -34,6 +34,7 @@
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./effects": "./src/effects.ts",
|
||||
"./turbo-painter": "./src/turbo/image-painter.worker.ts",
|
||||
"./store": "./src/store.ts",
|
||||
"./view": "./src/view.ts"
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
} from '@blocksuite/affine-shared/commands';
|
||||
import { ImageSelection } from '@blocksuite/affine-shared/selection';
|
||||
import { unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme';
|
||||
import { SignalWatcher, WithDisposable } from '@blocksuite/global/lit';
|
||||
import { WithDisposable } from '@blocksuite/global/lit';
|
||||
import type { BlockComponent, UIEventStateContext } from '@blocksuite/std';
|
||||
import {
|
||||
BlockSelection,
|
||||
@@ -15,9 +15,8 @@ import {
|
||||
TextSelection,
|
||||
} from '@blocksuite/std';
|
||||
import type { BaseSelection } from '@blocksuite/store';
|
||||
import { computed } from '@preact/signals-core';
|
||||
import { css, html, type PropertyValues } from 'lit';
|
||||
import { property, query } from 'lit/decorators.js';
|
||||
import { property, query, state } from 'lit/decorators.js';
|
||||
import { styleMap } from 'lit/directives/style-map.js';
|
||||
import { when } from 'lit/directives/when.js';
|
||||
|
||||
@@ -26,9 +25,7 @@ import { ImageResizeManager } from '../image-resize-manager';
|
||||
import { shouldResizeImage } from '../utils';
|
||||
import { ImageSelectedRect } from './image-selected-rect';
|
||||
|
||||
export class ImageBlockPageComponent extends SignalWatcher(
|
||||
WithDisposable(ShadowlessElement)
|
||||
) {
|
||||
export class ImageBlockPageComponent extends WithDisposable(ShadowlessElement) {
|
||||
static override styles = css`
|
||||
affine-page-image {
|
||||
position: relative;
|
||||
@@ -71,8 +68,6 @@ export class ImageBlockPageComponent extends SignalWatcher(
|
||||
}
|
||||
`;
|
||||
|
||||
resizeable$ = computed(() => this.block.resizeable$.value);
|
||||
|
||||
private _isDragging = false;
|
||||
|
||||
private get _doc() {
|
||||
@@ -139,21 +134,21 @@ export class ImageBlockPageComponent extends SignalWatcher(
|
||||
return true;
|
||||
},
|
||||
Delete: ctx => {
|
||||
if (this._host.store.readonly || !this.resizeable$.peek()) return;
|
||||
if (this._host.store.readonly || !this._isSelected) return;
|
||||
|
||||
addParagraph(ctx);
|
||||
this._doc.deleteBlock(this._model);
|
||||
return true;
|
||||
},
|
||||
Backspace: ctx => {
|
||||
if (this._host.store.readonly || !this.resizeable$.peek()) return;
|
||||
if (this._host.store.readonly || !this._isSelected) return;
|
||||
|
||||
addParagraph(ctx);
|
||||
this._doc.deleteBlock(this._model);
|
||||
return true;
|
||||
},
|
||||
Enter: ctx => {
|
||||
if (this._host.store.readonly || !this.resizeable$.peek()) return;
|
||||
if (this._host.store.readonly || !this._isSelected) return;
|
||||
|
||||
addParagraph(ctx);
|
||||
return true;
|
||||
@@ -218,6 +213,19 @@ export class ImageBlockPageComponent extends SignalWatcher(
|
||||
|
||||
private _handleSelection() {
|
||||
const selection = this._host.selection;
|
||||
this._disposables.add(
|
||||
selection.slots.changed.subscribe(selList => {
|
||||
this._isSelected = selList.some(
|
||||
sel => sel.blockId === this.block.blockId && sel.is(ImageSelection)
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
this._disposables.add(
|
||||
this._model.propsUpdated.subscribe(() => {
|
||||
this.requestUpdate();
|
||||
})
|
||||
);
|
||||
|
||||
this._disposables.addFromEvent(
|
||||
this.resizeImg,
|
||||
@@ -241,7 +249,7 @@ export class ImageBlockPageComponent extends SignalWatcher(
|
||||
this.block.handleEvent(
|
||||
'click',
|
||||
() => {
|
||||
if (!this.resizeable$.peek()) return;
|
||||
if (!this._isSelected) return;
|
||||
|
||||
selection.update(selList =>
|
||||
selList.filter(
|
||||
@@ -348,7 +356,7 @@ export class ImageBlockPageComponent extends SignalWatcher(
|
||||
override render() {
|
||||
const imageSize = this._normalizeImageSize();
|
||||
|
||||
const imageSelectedRect = this.resizeable$.value
|
||||
const imageSelectedRect = this._isSelected
|
||||
? ImageSelectedRect(this._doc.readonly)
|
||||
: null;
|
||||
|
||||
@@ -381,6 +389,9 @@ export class ImageBlockPageComponent extends SignalWatcher(
|
||||
`;
|
||||
}
|
||||
|
||||
@state()
|
||||
accessor _isSelected = false;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor block!: ImageBlockComponent;
|
||||
|
||||
|
||||
@@ -4,12 +4,11 @@ import { getLoadingIconWith } from '@blocksuite/affine-components/icons';
|
||||
import { Peekable } from '@blocksuite/affine-components/peek';
|
||||
import { ResourceController } from '@blocksuite/affine-components/resource';
|
||||
import type { ImageBlockModel } from '@blocksuite/affine-model';
|
||||
import { ImageSelection } from '@blocksuite/affine-shared/selection';
|
||||
import {
|
||||
ThemeProvider,
|
||||
ToolbarRegistryIdentifier,
|
||||
} from '@blocksuite/affine-shared/services';
|
||||
import { formatSize } from '@blocksuite/affine-shared/utils';
|
||||
import { humanFileSize } from '@blocksuite/affine-shared/utils';
|
||||
import { IS_MOBILE } from '@blocksuite/global/env';
|
||||
import { BrokenImageIcon, ImageIcon } from '@blocksuite/icons/lit';
|
||||
import { BlockSelection } from '@blocksuite/std';
|
||||
@@ -31,13 +30,6 @@ import {
|
||||
enableOn: () => !IS_MOBILE,
|
||||
})
|
||||
export class ImageBlockComponent extends CaptionedBlockComponent<ImageBlockModel> {
|
||||
resizeable$ = computed(() =>
|
||||
this.std.selection.value.some(
|
||||
selection =>
|
||||
selection.is(ImageSelection) && selection.blockId === this.blockId
|
||||
)
|
||||
);
|
||||
|
||||
resourceController = new ResourceController(
|
||||
computed(() => this.model.props.sourceId$.value),
|
||||
'Image'
|
||||
@@ -112,11 +104,7 @@ export class ImageBlockComponent extends CaptionedBlockComponent<ImageBlockModel
|
||||
this.disposables.add(this.resourceController.subscribe());
|
||||
this.disposables.add(this.resourceController);
|
||||
|
||||
this.disposables.add(
|
||||
this.model.props.sourceId$.subscribe(() => {
|
||||
this.refreshData();
|
||||
})
|
||||
);
|
||||
this.refreshData();
|
||||
}
|
||||
|
||||
override firstUpdated() {
|
||||
@@ -142,7 +130,7 @@ export class ImageBlockComponent extends CaptionedBlockComponent<ImageBlockModel
|
||||
errorIcon: BrokenImageIcon(),
|
||||
icon: ImageIcon(),
|
||||
title: 'Image',
|
||||
description: formatSize(size),
|
||||
description: humanFileSize(size),
|
||||
});
|
||||
|
||||
return html`
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
} from '@blocksuite/affine-model';
|
||||
import { ThemeProvider } from '@blocksuite/affine-shared/services';
|
||||
import { unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme';
|
||||
import { formatSize } from '@blocksuite/affine-shared/utils';
|
||||
import { humanFileSize } from '@blocksuite/affine-shared/utils';
|
||||
import { BrokenImageIcon, ImageIcon } from '@blocksuite/icons/lit';
|
||||
import { GfxBlockComponent } from '@blocksuite/std';
|
||||
import { GfxViewInteractionExtension } from '@blocksuite/std/gfx';
|
||||
@@ -100,11 +100,7 @@ export class ImageEdgelessBlockComponent extends GfxBlockComponent<ImageBlockMod
|
||||
this.disposables.add(this.resourceController.subscribe());
|
||||
this.disposables.add(this.resourceController);
|
||||
|
||||
this.disposables.add(
|
||||
this.model.props.sourceId$.subscribe(() => {
|
||||
this.refreshData();
|
||||
})
|
||||
);
|
||||
this.refreshData();
|
||||
}
|
||||
|
||||
override renderGfxBlock() {
|
||||
@@ -128,7 +124,7 @@ export class ImageEdgelessBlockComponent extends GfxBlockComponent<ImageBlockMod
|
||||
errorIcon: BrokenImageIcon(),
|
||||
icon: ImageIcon(),
|
||||
title: 'Image',
|
||||
description: formatSize(size),
|
||||
description: humanFileSize(size),
|
||||
});
|
||||
|
||||
return html`
|
||||
|
||||
@@ -11,8 +11,8 @@ import {
|
||||
NativeClipboardProvider,
|
||||
} from '@blocksuite/affine-shared/services';
|
||||
import {
|
||||
formatSize,
|
||||
getBlockProps,
|
||||
humanFileSize,
|
||||
isInsidePageEditor,
|
||||
readImageSize,
|
||||
transformModel,
|
||||
@@ -241,7 +241,7 @@ function hasExceeded(
|
||||
const exceeded = files.some(file => file.size > maxFileSize);
|
||||
|
||||
if (exceeded) {
|
||||
const size = formatSize(maxFileSize);
|
||||
const size = humanFileSize(maxFileSize, true, 0);
|
||||
toast(std.host, `You can only upload files less than ${size}`);
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./effects": "./src/effects.ts",
|
||||
"./store": "./src/store.ts",
|
||||
"./view": "./src/view.ts"
|
||||
},
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./effects": "./src/effects.ts",
|
||||
"./turbo-painter": "./src/turbo/list-painter.worker.ts",
|
||||
"./view": "./src/view.ts",
|
||||
"./store": "./src/store.ts"
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./effects": "./src/effects.ts",
|
||||
"./turbo-painter": "./src/turbo/note-painter.worker.ts",
|
||||
"./store": "./src/store.ts",
|
||||
"./view": "./src/view.ts"
|
||||
|
||||
@@ -40,12 +40,12 @@ export class EdgelessNoteStylePanel extends SignalWatcher(
|
||||
@property({ attribute: false })
|
||||
accessor std!: BlockStdScope;
|
||||
|
||||
@query('.edgeless-note-style-panel')
|
||||
private accessor _panel!: HTMLDivElement;
|
||||
|
||||
@state()
|
||||
accessor tabType: 'style' | 'customColor' = 'style';
|
||||
|
||||
@query('div.edgeless-note-style-panel-container')
|
||||
accessor container!: HTMLDivElement;
|
||||
|
||||
static override styles = css`
|
||||
.edgeless-note-style-panel {
|
||||
display: flex;
|
||||
@@ -187,32 +187,7 @@ export class EdgelessNoteStylePanel extends SignalWatcher(
|
||||
};
|
||||
|
||||
private readonly _pickColor = (e: PickColorEvent) => {
|
||||
switch (e.type) {
|
||||
case 'pick':
|
||||
{
|
||||
const color = e.detail.value;
|
||||
const crud = this.std.get(EdgelessCRUDIdentifier);
|
||||
this.notes.forEach(note => {
|
||||
crud.updateElement(note.id, {
|
||||
background: color,
|
||||
} satisfies Partial<NoteProps>);
|
||||
});
|
||||
}
|
||||
break;
|
||||
case 'start':
|
||||
this._beforeChange();
|
||||
this.notes.forEach(note => {
|
||||
note.stash('background');
|
||||
});
|
||||
break;
|
||||
case 'end':
|
||||
this.std.store.transact(() => {
|
||||
this.notes.forEach(note => {
|
||||
note.pop('background');
|
||||
});
|
||||
});
|
||||
break;
|
||||
}
|
||||
console.log(e);
|
||||
};
|
||||
|
||||
private readonly _selectShadow = (e: CustomEvent<NoteShadow>) => {
|
||||
@@ -290,7 +265,7 @@ export class EdgelessNoteStylePanel extends SignalWatcher(
|
||||
};
|
||||
|
||||
private _renderStylePanel() {
|
||||
return html`<div class="edgeless-note-style-panel">
|
||||
return html` <div class="edgeless-note-style-panel">
|
||||
<div class="edgeless-note-style-section">
|
||||
<div class="edgeless-note-style-section-title">Fill color</div>
|
||||
<edgeless-color-panel
|
||||
@@ -394,11 +369,9 @@ export class EdgelessNoteStylePanel extends SignalWatcher(
|
||||
}
|
||||
|
||||
override firstUpdated() {
|
||||
if (this.container) {
|
||||
this.disposables.addFromEvent(this.container, 'click', e => {
|
||||
e.stopPropagation();
|
||||
});
|
||||
}
|
||||
this.disposables.addFromEvent(this._panel, 'click', e => {
|
||||
e.stopPropagation();
|
||||
});
|
||||
}
|
||||
|
||||
override render() {
|
||||
@@ -410,18 +383,11 @@ export class EdgelessNoteStylePanel extends SignalWatcher(
|
||||
${PaletteIcon()}
|
||||
</editor-icon-button>
|
||||
`}
|
||||
@toggle=${(e: CustomEvent<boolean>) => {
|
||||
if (!e.detail) {
|
||||
this.tabType = 'style';
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div class="edgeless-note-style-panel-container">
|
||||
${choose(this.tabType, [
|
||||
['style', () => this._renderStylePanel()],
|
||||
['customColor', () => this._renderCustomColorPanel()],
|
||||
])}
|
||||
</div>
|
||||
${choose(this.tabType, [
|
||||
['style', () => this._renderStylePanel()],
|
||||
['customColor', () => this._renderCustomColorPanel()],
|
||||
])}
|
||||
</editor-menu-button>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./effects": "./src/effects.ts",
|
||||
"./turbo-painter": "./src/turbo/paragraph-painter.worker.ts",
|
||||
"./store": "./src/store.ts",
|
||||
"./view": "./src/view.ts"
|
||||
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
EdgelessTextBlockModel,
|
||||
ImageBlockModel,
|
||||
ListBlockModel,
|
||||
NoteBlockModel,
|
||||
ParagraphBlockModel,
|
||||
type RootBlockModel,
|
||||
} from '@blocksuite/affine-model';
|
||||
@@ -20,6 +19,7 @@ import { EMBED_BLOCK_MODEL_LIST } from '@blocksuite/affine-shared/consts';
|
||||
import type { ExtendedModel } from '@blocksuite/affine-shared/types';
|
||||
import {
|
||||
focusTitle,
|
||||
getDocTitleInlineEditor,
|
||||
getPrevContentBlock,
|
||||
matchModels,
|
||||
} from '@blocksuite/affine-shared/utils';
|
||||
@@ -122,39 +122,41 @@ function handleNoPreviousSibling(editorHost: EditorHost, model: ExtendedModel) {
|
||||
const text = model.text;
|
||||
const parent = doc.getParent(model);
|
||||
if (!parent) return false;
|
||||
|
||||
if (matchModels(parent, [NoteBlockModel]) && parent.isPageBlock()) {
|
||||
const rootModel = model.store.root as RootBlockModel;
|
||||
const title = rootModel.props.title;
|
||||
|
||||
doc.captureSync();
|
||||
let textLength = 0;
|
||||
if (text) {
|
||||
textLength = text.length;
|
||||
title.join(text);
|
||||
}
|
||||
|
||||
// Preserve at least one block to be able to focus on container click
|
||||
if (doc.getNext(model) || model.children.length > 0) {
|
||||
const titleEditor = getDocTitleInlineEditor(editorHost);
|
||||
// Probably no title, e.g. in edgeless mode
|
||||
if (!titleEditor) {
|
||||
if (
|
||||
matchModels(parent, [EdgelessTextBlockModel]) ||
|
||||
model.children.length > 0
|
||||
) {
|
||||
doc.deleteBlock(model, {
|
||||
bringChildrenTo: parent,
|
||||
});
|
||||
} else {
|
||||
text?.clear();
|
||||
return true;
|
||||
}
|
||||
focusTitle(editorHost, title.length - textLength);
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
matchModels(parent, [EdgelessTextBlockModel]) ||
|
||||
model.children.length > 0
|
||||
) {
|
||||
const rootModel = model.store.root as RootBlockModel;
|
||||
const title = rootModel.props.title;
|
||||
|
||||
doc.captureSync();
|
||||
let textLength = 0;
|
||||
if (text) {
|
||||
textLength = text.length;
|
||||
title.join(text);
|
||||
}
|
||||
|
||||
// Preserve at least one block to be able to focus on container click
|
||||
if (doc.getNext(model) || model.children.length > 0) {
|
||||
const parent = doc.getParent(model);
|
||||
if (!parent) return false;
|
||||
doc.deleteBlock(model, {
|
||||
bringChildrenTo: parent,
|
||||
});
|
||||
return true;
|
||||
} else {
|
||||
text?.clear();
|
||||
}
|
||||
|
||||
return false;
|
||||
focusTitle(editorHost, title.length - textLength);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
"@blocksuite/affine-block-database": "workspace:*",
|
||||
"@blocksuite/affine-block-edgeless-text": "workspace:*",
|
||||
"@blocksuite/affine-block-embed": "workspace:*",
|
||||
"@blocksuite/affine-block-embed-doc": "workspace:*",
|
||||
"@blocksuite/affine-block-frame": "workspace:*",
|
||||
"@blocksuite/affine-block-image": "workspace:*",
|
||||
"@blocksuite/affine-block-note": "workspace:*",
|
||||
@@ -34,7 +35,6 @@
|
||||
"@blocksuite/affine-model": "workspace:*",
|
||||
"@blocksuite/affine-rich-text": "workspace:*",
|
||||
"@blocksuite/affine-shared": "workspace:*",
|
||||
"@blocksuite/affine-widget-edgeless-selected-rect": "workspace:*",
|
||||
"@blocksuite/affine-widget-edgeless-toolbar": "workspace:*",
|
||||
"@blocksuite/data-view": "workspace:*",
|
||||
"@blocksuite/global": "workspace:*",
|
||||
|
||||
@@ -3,7 +3,6 @@ import {
|
||||
pasteMiddleware,
|
||||
replaceIdMiddleware,
|
||||
surfaceRefToEmbed,
|
||||
uploadMiddleware,
|
||||
} from '@blocksuite/affine-shared/adapters';
|
||||
import {
|
||||
clearAndSelectFirstModelCommand,
|
||||
@@ -35,17 +34,14 @@ export class PageClipboard extends ReadOnlyClipboard {
|
||||
// When pastina a surface-ref block to another doc
|
||||
const surfaceRefToEmbedMiddleware = surfaceRefToEmbed(this.std);
|
||||
const replaceId = replaceIdMiddleware(this.std.store.workspace.idGenerator);
|
||||
const upload = uploadMiddleware(this.std);
|
||||
this.std.clipboard.use(paste);
|
||||
this.std.clipboard.use(surfaceRefToEmbedMiddleware);
|
||||
this.std.clipboard.use(replaceId);
|
||||
this.std.clipboard.use(upload);
|
||||
this._disposables.add({
|
||||
dispose: () => {
|
||||
this.std.clipboard.unuse(paste);
|
||||
this.std.clipboard.unuse(surfaceRefToEmbedMiddleware);
|
||||
this.std.clipboard.unuse(replaceId);
|
||||
this.std.clipboard.unuse(upload);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -209,10 +209,9 @@ export class EdgelessClipboardController extends PageClipboard {
|
||||
await addImages(this.std, imageFiles, {
|
||||
point,
|
||||
maxWidth: MAX_IMAGE_WIDTH,
|
||||
shouldTransformPoint: false,
|
||||
});
|
||||
} else {
|
||||
await addAttachments(this.std, [...files], point, false);
|
||||
await addAttachments(this.std, [...files], point);
|
||||
}
|
||||
|
||||
this.std.getOptional(TelemetryProvider)?.track('CanvasElementAdded', {
|
||||
@@ -228,7 +227,11 @@ export class EdgelessClipboardController extends PageClipboard {
|
||||
|
||||
if (isUrlInClipboard(data)) {
|
||||
const url = data.getData('text/plain');
|
||||
const { x, y } = this.toolManager.lastMousePos$.peek();
|
||||
const lastMousePos = this.toolManager.lastMousePos$.peek();
|
||||
const [x, y] = this.gfx.viewport.toModelCoord(
|
||||
lastMousePos.x,
|
||||
lastMousePos.y
|
||||
);
|
||||
|
||||
// try to interpret url as affine doc url
|
||||
const parseDocUrlService = this.std.getOptional(ParseDocUrlProvider);
|
||||
@@ -559,7 +562,11 @@ export class EdgelessClipboardController extends PageClipboard {
|
||||
}
|
||||
|
||||
private async _pasteTextContentAsNote(content: BlockSnapshot[] | string) {
|
||||
const { x, y } = this.toolManager.lastMousePos$.peek();
|
||||
const lastMousePos = this.toolManager.lastMousePos$.peek();
|
||||
const [x, y] = this.gfx.viewport.toModelCoord(
|
||||
lastMousePos.x,
|
||||
lastMousePos.y
|
||||
);
|
||||
|
||||
const noteProps = {
|
||||
xywh: new Bound(
|
||||
|
||||
@@ -52,7 +52,9 @@ export const createElementsFromClipboardDataCommand: Command<Input, Output> = (
|
||||
let oldCommonBound, pasteX, pasteY;
|
||||
{
|
||||
const lastMousePos = toolManager.lastMousePos$.peek();
|
||||
pasteCenter = pasteCenter ?? [lastMousePos.x, lastMousePos.y];
|
||||
pasteCenter =
|
||||
pasteCenter ??
|
||||
gfx.viewport.toModelCoord(lastMousePos.x, lastMousePos.y);
|
||||
const [modelX, modelY] = pasteCenter;
|
||||
oldCommonBound = edgelessElementsBoundFromRawData(elementsRawData);
|
||||
|
||||
|
||||
+1
-1
@@ -744,7 +744,7 @@ export class EdgelessAutoComplete extends WithDisposable(LitElement) {
|
||||
|
||||
if (
|
||||
this._isMoving ||
|
||||
(this._isHover && !isShape && !this._canAutoComplete())
|
||||
(this._isHover && !isShape && this._canAutoComplete())
|
||||
) {
|
||||
this.removeOverlay();
|
||||
return nothing;
|
||||
+1
-67
@@ -19,13 +19,7 @@ import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
|
||||
import { Bound, normalizeDegAngle, type XYWH } from '@blocksuite/global/gfx';
|
||||
import { assertType } from '@blocksuite/global/utils';
|
||||
import type { BlockComponent } from '@blocksuite/std';
|
||||
import type {
|
||||
CursorType,
|
||||
GfxController,
|
||||
GfxModel,
|
||||
ResizeHandle,
|
||||
StandardCursor,
|
||||
} from '@blocksuite/std/gfx';
|
||||
import type { GfxController, GfxModel } from '@blocksuite/std/gfx';
|
||||
import * as Y from 'yjs';
|
||||
|
||||
export enum Direction {
|
||||
@@ -354,63 +348,3 @@ export function createShapeElement(
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
const rotateCursorMap: {
|
||||
[key in ResizeHandle]: number;
|
||||
} = {
|
||||
'top-right': 0,
|
||||
'bottom-right': 90,
|
||||
'bottom-left': 180,
|
||||
'top-left': 270,
|
||||
|
||||
// not used
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
};
|
||||
|
||||
export function generateCursorUrl(
|
||||
angle = 0,
|
||||
handle: ResizeHandle,
|
||||
fallback: StandardCursor = 'default'
|
||||
): CursorType {
|
||||
angle = ((angle % 360) + 360) % 360;
|
||||
return `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32' height='32' viewBox='0 0 32 32'%3E%3Cg transform='rotate(${rotateCursorMap[handle] + angle} 16 16)'%3E%3Cpath fill='white' d='M13.7,18.5h3.9l0-1.5c0-1.4-1.2-2.6-2.6-2.6h-1.5v3.9l-5.8-5.8l5.8-5.8v3.9h2.3c3.1,0,5.6,2.5,5.6,5.6v2.3h3.9l-5.8,5.8L13.7,18.5z'/%3E%3Cpath d='M20.4,19.4v-3.2c0-2.6-2.1-4.7-4.7-4.7h-3.2l0,0V9L9,12.6l3.6,3.6v-2.6l0,0H15c1.9,0,3.5,1.6,3.5,3.5v2.4l0,0h-2.6l3.6,3.6l3.6-3.6L20.4,19.4L20.4,19.4z'/%3E%3C/g%3E%3C/svg%3E") 16 16, ${fallback}`;
|
||||
}
|
||||
|
||||
const handleToRotateMap: {
|
||||
[key in ResizeHandle]: number;
|
||||
} = {
|
||||
'top-left': 45,
|
||||
'top-right': 135,
|
||||
'bottom-right': 45,
|
||||
'bottom-left': 135,
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 90,
|
||||
bottom: 90,
|
||||
};
|
||||
|
||||
const rotateToHandleMap: {
|
||||
[key: number]: StandardCursor;
|
||||
} = {
|
||||
0: 'ew-resize',
|
||||
45: 'nwse-resize',
|
||||
90: 'ns-resize',
|
||||
135: 'nesw-resize',
|
||||
};
|
||||
|
||||
export function getRotatedResizeCursor(option: {
|
||||
handle: ResizeHandle;
|
||||
angle: number;
|
||||
}) {
|
||||
const angle =
|
||||
(Math.round(
|
||||
(handleToRotateMap[option.handle] + ((option.angle + 360) % 360)) / 45
|
||||
) %
|
||||
4) *
|
||||
45;
|
||||
|
||||
return rotateToHandleMap[angle] || 'default';
|
||||
}
|
||||
+3
-9
@@ -12,17 +12,17 @@ import {
|
||||
import { EDGELESS_BLOCK_CHILD_PADDING } from '@blocksuite/affine-shared/consts';
|
||||
import { TelemetryProvider } from '@blocksuite/affine-shared/services';
|
||||
import { getRectByBlockComponent } from '@blocksuite/affine-shared/utils';
|
||||
import type { EdgelessSelectedRectWidget } from '@blocksuite/affine-widget-edgeless-selected-rect';
|
||||
import { DisposableGroup } from '@blocksuite/global/disposable';
|
||||
import { deserializeXYWH, Point, serializeXYWH } from '@blocksuite/global/gfx';
|
||||
import { ScissorsIcon } from '@blocksuite/icons/lit';
|
||||
import { WidgetComponent, WidgetViewExtension } from '@blocksuite/std';
|
||||
import { WidgetComponent } from '@blocksuite/std';
|
||||
import { GfxControllerIdentifier } from '@blocksuite/std/gfx';
|
||||
import { css, html, nothing, type PropertyValues } from 'lit';
|
||||
import { state } from 'lit/decorators.js';
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
import { styleMap } from 'lit/directives/style-map.js';
|
||||
import { literal, unsafeStatic } from 'lit/static-html.js';
|
||||
|
||||
import type { EdgelessSelectedRectWidget } from '../rects/edgeless-selected-rect';
|
||||
|
||||
const DIVIDING_LINE_OFFSET = 4;
|
||||
const NEW_NOTE_GAP = 40;
|
||||
@@ -443,9 +443,3 @@ export class NoteSlicer extends WidgetComponent<RootBlockModel> {
|
||||
@state()
|
||||
private accessor _isResizing = false;
|
||||
}
|
||||
|
||||
export const noteSlicerWidget = WidgetViewExtension(
|
||||
'affine:page',
|
||||
NOTE_SLICER_WIDGET,
|
||||
literal`${unsafeStatic(NOTE_SLICER_WIDGET)}`
|
||||
);
|
||||
+2
-9
@@ -3,12 +3,11 @@ import {
|
||||
DefaultTool,
|
||||
} from '@blocksuite/affine-block-surface';
|
||||
import type { RootBlockModel } from '@blocksuite/affine-model';
|
||||
import { WidgetComponent, WidgetViewExtension } from '@blocksuite/std';
|
||||
import { WidgetComponent } from '@blocksuite/std';
|
||||
import { GfxControllerIdentifier } from '@blocksuite/std/gfx';
|
||||
import { cssVarV2 } from '@toeverything/theme/v2';
|
||||
import { css, html, nothing, unsafeCSS } from 'lit';
|
||||
import { styleMap } from 'lit/directives/style-map.js';
|
||||
import { literal, unsafeStatic } from 'lit/static-html.js';
|
||||
|
||||
export const EDGELESS_DRAGGING_AREA_WIDGET = 'edgeless-dragging-area-rect';
|
||||
|
||||
@@ -36,7 +35,7 @@ export class EdgelessDraggingAreaRectWidget extends WidgetComponent<RootBlockMod
|
||||
}
|
||||
|
||||
override render() {
|
||||
const rect = this.gfx.tool.draggingViewportArea$.value;
|
||||
const rect = this.gfx.tool.draggingViewArea$.value;
|
||||
const tool = this.gfx.tool.currentTool$.value;
|
||||
|
||||
if (
|
||||
@@ -60,9 +59,3 @@ export class EdgelessDraggingAreaRectWidget extends WidgetComponent<RootBlockMod
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
export const edgelessDraggingAreaWidget = WidgetViewExtension(
|
||||
'affine:page',
|
||||
EDGELESS_DRAGGING_AREA_WIDGET,
|
||||
literal`${unsafeStatic(EDGELESS_DRAGGING_AREA_WIDGET)}`
|
||||
);
|
||||
+23
-20
@@ -1,8 +1,5 @@
|
||||
import { type FrameOverlay } from '@blocksuite/affine-block-frame';
|
||||
import {
|
||||
EdgelessLegacySlotIdentifier,
|
||||
OverlayIdentifier,
|
||||
} from '@blocksuite/affine-block-surface';
|
||||
import { OverlayIdentifier } from '@blocksuite/affine-block-surface';
|
||||
import {
|
||||
ConnectorElementModel,
|
||||
type RootBlockModel,
|
||||
@@ -30,12 +27,16 @@ import { repeat } from 'lit/directives/repeat.js';
|
||||
import { styleMap } from 'lit/directives/style-map.js';
|
||||
import { type Subscription } from 'rxjs';
|
||||
|
||||
import { RenderResizeHandles } from './resize-handles.js';
|
||||
import { generateCursorUrl, getRotatedResizeCursor } from './utils.js';
|
||||
import type { EdgelessRootBlockComponent } from '../../edgeless-root-block.js';
|
||||
import { RenderResizeHandles } from '../resize/resize-handles.js';
|
||||
import { generateCursorUrl, getRotatedResizeCursor } from '../utils.js';
|
||||
|
||||
export const EDGELESS_SELECTED_RECT_WIDGET = 'edgeless-selected-rect';
|
||||
|
||||
export class EdgelessSelectedRectWidget extends WidgetComponent<RootBlockModel> {
|
||||
export class EdgelessSelectedRectWidget extends WidgetComponent<
|
||||
RootBlockModel,
|
||||
EdgelessRootBlockComponent
|
||||
> {
|
||||
// disable change-in-update warning
|
||||
static override enabledWarnings = [];
|
||||
|
||||
@@ -468,6 +469,10 @@ export class EdgelessSelectedRectWidget extends WidgetComponent<RootBlockModel>
|
||||
};
|
||||
}, this);
|
||||
|
||||
get edgelessSlots() {
|
||||
return this.block?.slots;
|
||||
}
|
||||
|
||||
get frameOverlay() {
|
||||
return this.std.get(OverlayIdentifier('frame')) as FrameOverlay;
|
||||
}
|
||||
@@ -499,7 +504,7 @@ export class EdgelessSelectedRectWidget extends WidgetComponent<RootBlockModel>
|
||||
}
|
||||
|
||||
override firstUpdated() {
|
||||
const { _disposables, selection, gfx } = this;
|
||||
const { _disposables, block, selection, gfx } = this;
|
||||
|
||||
_disposables.add(
|
||||
// viewport zooming / scrolling
|
||||
@@ -526,13 +531,15 @@ export class EdgelessSelectedRectWidget extends WidgetComponent<RootBlockModel>
|
||||
selection.slots.updated.subscribe(this._updateOnSelectionChange)
|
||||
);
|
||||
|
||||
_disposables.add(
|
||||
this._slots.readonlyUpdated.subscribe(() => this.requestUpdate())
|
||||
);
|
||||
if (block) {
|
||||
_disposables.add(
|
||||
block.slots.readonlyUpdated.subscribe(() => this.requestUpdate())
|
||||
);
|
||||
|
||||
_disposables.add(
|
||||
this._slots.elementResizeEnd.subscribe(() => (this._isResizing = false))
|
||||
);
|
||||
_disposables.add(
|
||||
block.slots.elementResizeEnd.subscribe(() => (this._isResizing = false))
|
||||
);
|
||||
}
|
||||
|
||||
if (this._interaction) {
|
||||
_disposables.add(
|
||||
@@ -547,9 +554,9 @@ export class EdgelessSelectedRectWidget extends WidgetComponent<RootBlockModel>
|
||||
this._isResizing = newVal;
|
||||
|
||||
if (newVal) {
|
||||
this._slots.elementResizeStart.next();
|
||||
block?.slots.elementResizeStart.next();
|
||||
} else {
|
||||
this._slots.elementResizeEnd.next();
|
||||
block?.slots.elementResizeEnd.next();
|
||||
}
|
||||
})
|
||||
);
|
||||
@@ -564,10 +571,6 @@ export class EdgelessSelectedRectWidget extends WidgetComponent<RootBlockModel>
|
||||
return this.std.getOptional(InteractivityIdentifier);
|
||||
}
|
||||
|
||||
private get _slots() {
|
||||
return this.std.get(EdgelessLegacySlotIdentifier);
|
||||
}
|
||||
|
||||
private _renderHandles() {
|
||||
const { selection, gfx, block, store } = this;
|
||||
const elements = selection.selectedElements;
|
||||
@@ -0,0 +1,65 @@
|
||||
import type {
|
||||
CursorType,
|
||||
ResizeHandle,
|
||||
StandardCursor,
|
||||
} from '@blocksuite/std/gfx';
|
||||
|
||||
const rotateCursorMap: {
|
||||
[key in ResizeHandle]: number;
|
||||
} = {
|
||||
'top-right': 0,
|
||||
'bottom-right': 90,
|
||||
'bottom-left': 180,
|
||||
'top-left': 270,
|
||||
|
||||
// not used
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
};
|
||||
|
||||
export function generateCursorUrl(
|
||||
angle = 0,
|
||||
handle: ResizeHandle,
|
||||
fallback: StandardCursor = 'default'
|
||||
): CursorType {
|
||||
angle = ((angle % 360) + 360) % 360;
|
||||
return `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32' height='32' viewBox='0 0 32 32'%3E%3Cg transform='rotate(${rotateCursorMap[handle] + angle} 16 16)'%3E%3Cpath fill='white' d='M13.7,18.5h3.9l0-1.5c0-1.4-1.2-2.6-2.6-2.6h-1.5v3.9l-5.8-5.8l5.8-5.8v3.9h2.3c3.1,0,5.6,2.5,5.6,5.6v2.3h3.9l-5.8,5.8L13.7,18.5z'/%3E%3Cpath d='M20.4,19.4v-3.2c0-2.6-2.1-4.7-4.7-4.7h-3.2l0,0V9L9,12.6l3.6,3.6v-2.6l0,0H15c1.9,0,3.5,1.6,3.5,3.5v2.4l0,0h-2.6l3.6,3.6l3.6-3.6L20.4,19.4L20.4,19.4z'/%3E%3C/g%3E%3C/svg%3E") 16 16, ${fallback}`;
|
||||
}
|
||||
|
||||
const handleToRotateMap: {
|
||||
[key in ResizeHandle]: number;
|
||||
} = {
|
||||
'top-left': 45,
|
||||
'top-right': 135,
|
||||
'bottom-right': 45,
|
||||
'bottom-left': 135,
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 90,
|
||||
bottom: 90,
|
||||
};
|
||||
|
||||
const rotateToHandleMap: {
|
||||
[key: number]: StandardCursor;
|
||||
} = {
|
||||
0: 'ew-resize',
|
||||
45: 'nwse-resize',
|
||||
90: 'ns-resize',
|
||||
135: 'nesw-resize',
|
||||
};
|
||||
|
||||
export function getRotatedResizeCursor(option: {
|
||||
handle: ResizeHandle;
|
||||
angle: number;
|
||||
}) {
|
||||
const angle =
|
||||
(Math.round(
|
||||
(handleToRotateMap[option.handle] + ((option.angle + 360) % 360)) / 45
|
||||
) %
|
||||
4) *
|
||||
45;
|
||||
|
||||
return rotateToHandleMap[angle] || 'default';
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user