Compare commits

..

5 Commits

Author SHA1 Message Date
eyhn
b14f1cdb7c chore: update i18n metadata 2025-01-04 11:28:57 +08:00
github-actions[bot]
22a8694972 chore(i18n): sync translations (#9513)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2025-01-04 11:07:12 +08:00
doodlewind
bbe88c57b7 fix(editor): blur in edgeless content zooming (#9496)
Fix [BS-2294](https://linear.app/affine-design/issue/BS-2294/edgeless-%E7%BC%A9%E6%94%BE%E5%AD%97%E5%8F%B7%E6%A8%A1%E7%B3%8A)
2025-01-04 11:03:09 +08:00
github-actions[bot]
a3b502fad7 chore(i18n): sync translations (#9499)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2025-01-04 11:01:55 +08:00
forehalo
28f0dad3f9 fix(server): fail to load custom config (#9485)
closed #9237 #9417
2025-01-04 11:01:27 +08:00
3667 changed files with 68753 additions and 133068 deletions

View File

@@ -1,6 +1,5 @@
FROM mcr.microsoft.com/devcontainers/base:bookworm
USER vscode
# Install Homebrew For Linux
RUN /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" && \
eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" && \

View File

@@ -1,3 +1,4 @@
DATABASE_LOCATION=./postgres
DB_PASSWORD=affine
DB_USERNAME=affine
DB_DATABASE_NAME=affine

1
.github/CODEOWNERS vendored
View File

@@ -1,2 +1 @@
/blocksuite/ @toeverything/blocksuite-core
/packages/frontend/core/src/blocksuite @toeverything/blocksuite-core

View File

@@ -26,7 +26,6 @@ runs:
DEV_SERVER_URL: http://localhost:8080
COPILOT_OPENAI_API_KEY: ${{ inputs.openai-key }}
COPILOT_FAL_API_KEY: ${{ inputs.fal-key }}
COPILOT_PERPLEXITY_API_KEY: ${{ inputs.perplexity-key }}
- name: Upload test results
if: ${{ failure() }}

View File

@@ -17,7 +17,6 @@ const {
METRICS_CUSTOMER_IO_TOKEN,
COPILOT_OPENAI_API_KEY,
COPILOT_FAL_API_KEY,
COPILOT_PERPLEXITY_API_KEY,
COPILOT_UNSPLASH_API_KEY,
MAILER_SENDER,
MAILER_USER,
@@ -47,21 +46,18 @@ const replicaConfig = {
graphql: Number(process.env.PRODUCTION_GRAPHQL_REPLICA) || 3,
sync: Number(process.env.PRODUCTION_SYNC_REPLICA) || 3,
renderer: Number(process.env.PRODUCTION_RENDERER_REPLICA) || 3,
doc: Number(process.env.PRODUCTION_DOC_REPLICA) || 3,
},
beta: {
web: 2,
graphql: Number(process.env.BETA_GRAPHQL_REPLICA) || 2,
sync: Number(process.env.BETA_SYNC_REPLICA) || 2,
renderer: Number(process.env.BETA_RENDERER_REPLICA) || 2,
doc: Number(process.env.BETA_DOC_REPLICA) || 2,
},
canary: {
web: 2,
graphql: 2,
sync: 2,
renderer: 2,
doc: 2,
},
};
@@ -70,14 +66,12 @@ const cpuConfig = {
web: '300m',
graphql: '1',
sync: '1',
doc: '1',
renderer: '300m',
},
canary: {
web: '300m',
graphql: '1',
sync: '1',
doc: '1',
renderer: '300m',
},
};
@@ -116,7 +110,6 @@ const createHelmCommand = ({ isDryRun }) => {
`--set web.resources.requests.cpu="${cpu.web}"`,
`--set graphql.resources.requests.cpu="${cpu.graphql}"`,
`--set sync.resources.requests.cpu="${cpu.sync}"`,
`--set doc.resources.requests.cpu="${cpu.doc}"`,
]
: [];
@@ -154,7 +147,6 @@ const createHelmCommand = ({ isDryRun }) => {
`--set graphql.app.copilot.enabled=true`,
`--set-string graphql.app.copilot.openai.key="${COPILOT_OPENAI_API_KEY}"`,
`--set-string graphql.app.copilot.fal.key="${COPILOT_FAL_API_KEY}"`,
`--set-string graphql.app.copilot.perplexity.key="${COPILOT_PERPLEXITY_API_KEY}"`,
`--set-string graphql.app.copilot.unsplash.key="${COPILOT_UNSPLASH_API_KEY}"`,
`--set-string graphql.app.mailer.sender="${MAILER_SENDER}"`,
`--set-string graphql.app.mailer.user="${MAILER_USER}"`,
@@ -174,9 +166,6 @@ const createHelmCommand = ({ isDryRun }) => {
`--set-string renderer.image.tag="${imageTag}"`,
`--set renderer.app.host=${host}`,
`--set renderer.replicaCount=${replica.renderer}`,
`--set-string doc.image.tag="${imageTag}"`,
`--set doc.app.host=${host}`,
`--set doc.replicaCount=${replica.doc}`,
...serviceAnnotations,
...resources,
`--timeout 10m`,

View File

@@ -1,4 +1,4 @@
FROM node:22-bookworm-slim
FROM node:20-bookworm-slim
COPY ./packages/backend/server /app
COPY ./packages/frontend/apps/web/dist /app/static

View File

@@ -3,4 +3,4 @@ name: affine
description: AFFiNE cloud chart
type: application
version: 0.0.0
appVersion: "0.20.0"
appVersion: "0.19.0"

View File

@@ -1,11 +0,0 @@
apiVersion: v2
name: doc
description: AFFiNE doc server
type: application
version: 0.0.0
appVersion: "0.20.0"
dependencies:
- name: gcloud-sql-proxy
version: 0.0.0
repository: "file://../gcloud-sql-proxy"
condition: .global.database.gcloud.enabled

View File

@@ -1,16 +0,0 @@
1. Get the application URL by running these commands:
{{- if contains "NodePort" .Values.service.type }}
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "doc.fullname" . }})
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
echo http://$NODE_IP:$NODE_PORT
{{- else if contains "LoadBalancer" .Values.service.type }}
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "doc.fullname" . }}'
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "doc.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
echo http://$SERVICE_IP:{{ .Values.service.port }}
{{- else if contains "ClusterIP" .Values.service.type }}
export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "doc.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
echo "Visit http://127.0.0.1:8080 to use your application"
kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT
{{- end }}

View File

@@ -1,63 +0,0 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "doc.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "doc.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "doc.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "doc.labels" -}}
helm.sh/chart: {{ include "doc.chart" . }}
{{ include "doc.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
monitoring: enabled
{{- end }}
{{/*
Selector labels
*/}}
{{- define "doc.selectorLabels" -}}
app.kubernetes.io/name: {{ include "doc.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{/*
Create the name of the service account to use
*/}}
{{- define "doc.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "doc.fullname" .) .Values.global.docService.name }}
{{- else }}
{{- default "default" .Values.global.docService.name }}
{{- end }}
{{- end }}

View File

@@ -1,105 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "doc.fullname" . }}
labels:
{{- include "doc.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
{{- include "doc.selectorLabels" . | nindent 6 }}
template:
metadata:
{{- with .Values.podAnnotations }}
annotations:
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "doc.selectorLabels" . | nindent 8 }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "doc.serviceAccountName" . }}
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
env:
- name: AFFINE_PRIVATE_KEY
valueFrom:
secretKeyRef:
name: "{{ .Values.global.secret.secretName }}"
key: key
- name: NODE_ENV
value: "{{ .Values.env }}"
- name: NODE_OPTIONS
value: "--max-old-space-size=4096"
- name: NO_COLOR
value: "1"
- name: DEPLOYMENT_TYPE
value: "affine"
- name: SERVER_FLAVOR
value: "doc"
- name: AFFINE_ENV
value: "{{ .Release.Namespace }}"
- name: DATABASE_PASSWORD
valueFrom:
secretKeyRef:
name: pg-postgresql
key: postgres-password
- name: DATABASE_URL
value: postgres://{{ .Values.global.database.user }}:$(DATABASE_PASSWORD)@{{ .Values.global.database.url }}:{{ .Values.global.database.port }}/{{ .Values.global.database.name }}
- name: REDIS_SERVER_ENABLED
value: "true"
- name: REDIS_SERVER_HOST
value: "{{ .Values.global.redis.host }}"
- name: REDIS_SERVER_PORT
value: "{{ .Values.global.redis.port }}"
- name: REDIS_SERVER_USER
value: "{{ .Values.global.redis.username }}"
- name: REDIS_SERVER_PASSWORD
valueFrom:
secretKeyRef:
name: redis
key: redis-password
- name: REDIS_SERVER_DATABASE
value: "{{ .Values.global.redis.database }}"
- name: AFFINE_SERVER_PORT
value: "{{ .Values.global.docService.port }}"
- name: AFFINE_SERVER_SUB_PATH
value: "{{ .Values.app.path }}"
- name: AFFINE_SERVER_HOST
value: "{{ .Values.app.host }}"
- name: AFFINE_SERVER_HTTPS
value: "{{ .Values.app.https }}"
ports:
- name: http
containerPort: {{ .Values.global.docService.port }}
protocol: TCP
livenessProbe:
httpGet:
path: /info
port: http
initialDelaySeconds: {{ .Values.probe.initialDelaySeconds }}
readinessProbe:
httpGet:
path: /info
port: http
initialDelaySeconds: {{ .Values.probe.initialDelaySeconds }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}

View File

@@ -1,19 +0,0 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "doc.fullname" . }}
labels:
{{- include "doc.labels" . | nindent 4 }}
{{- with .Values.service.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.global.docService.port }}
targetPort: http
protocol: TCP
name: http
selector:
{{- include "doc.selectorLabels" . | nindent 4 }}

View File

@@ -1,12 +0,0 @@
{{- if .Values.serviceAccount.create -}}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "doc.serviceAccountName" . }}
labels:
{{- include "doc.labels" . | nindent 4 }}
{{- with .Values.serviceAccount.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- end }}

View File

@@ -1,15 +0,0 @@
apiVersion: v1
kind: Pod
metadata:
name: "{{ include "doc.fullname" . }}-test-connection"
labels:
{{- include "doc.labels" . | nindent 4 }}
annotations:
"helm.sh/hook": test
spec:
containers:
- name: wget
image: busybox
command: ['wget']
args: ['{{ include "doc.fullname" . }}:{{ .Values.global.docService.port }}']
restartPolicy: Never

View File

@@ -1,37 +0,0 @@
replicaCount: 1
image:
repository: ghcr.io/toeverything/affine-graphql
pullPolicy: IfNotPresent
tag: ''
imagePullSecrets: []
nameOverride: ''
fullnameOverride: ''
# map to NODE_ENV environment variable
env: 'production'
app:
# AFFINE_SERVER_SUB_PATH
path: ''
# AFFINE_SERVER_HOST
host: '0.0.0.0'
https: true
serviceAccount:
create: true
annotations: {}
podAnnotations: {}
podSecurityContext:
fsGroup: 2000
resources:
requests:
cpu: '2'
memory: 4Gi
probe:
initialDelaySeconds: 20
nodeSelector: {}
tolerations: []
affinity: {}

View File

@@ -3,7 +3,7 @@ name: graphql
description: AFFiNE GraphQL server
type: application
version: 0.0.0
appVersion: "0.20.0"
appVersion: "0.19.0"
dependencies:
- name: gcloud-sql-proxy
version: 0.0.0

View File

@@ -7,6 +7,5 @@ type: Opaque
data:
openaiSecret: {{ .Values.app.copilot.openai.key | b64enc }}
falSecret: {{ .Values.app.copilot.fal.key | b64enc }}
perplexitySecret: {{ .Values.app.copilot.perplexity.key | b64enc }}
unsplashSecret: {{ .Values.app.copilot.unsplash.key | b64enc }}
{{- end }}

View File

@@ -116,8 +116,8 @@ spec:
secretKeyRef:
name: "{{ .Values.app.payment.stripe.secretName }}"
key: stripeWebhookKey
- name: DOC_SERVICE_ENDPOINT
value: "http://{{ .Values.global.docService.name }}:{{ .Values.global.docService.port }}"
- name: DOC_MERGE_INTERVAL
value: "{{ .Values.app.doc.mergeInterval }}"
{{ if .Values.app.experimental.enableJwstCodec }}
- name: DOC_MERGE_USE_JWST_CODEC
value: "true"
@@ -157,11 +157,6 @@ spec:
secretKeyRef:
name: "{{ .Values.app.copilot.secretName }}"
key: falSecret
- name: COPILOT_PERPLEXITY_API_KEY
valueFrom:
secretKeyRef:
name: "{{ .Values.app.copilot.secretName }}"
key: perplexitySecret
- name: COPILOT_UNSPLASH_API_KEY
valueFrom:
secretKeyRef:

View File

@@ -17,6 +17,8 @@ app:
# AFFINE_SERVER_HOST
host: '0.0.0.0'
https: true
doc:
mergeInterval: "3000"
captcha:
enabled: false
secretName: captcha

View File

@@ -94,8 +94,6 @@ spec:
name: "{{ .Values.global.objectStorage.r2.secretName }}"
key: secretAccessKey
{{ end }}
- name: DOC_SERVICE_ENDPOINT
value: "http://{{ .Values.global.docService.name }}:{{ .Values.global.docService.port }}"
ports:
- name: http
containerPort: {{ .Values.service.port }}

View File

@@ -3,7 +3,7 @@ name: sync
description: AFFiNE Sync Server
type: application
version: 0.0.0
appVersion: "0.20.0"
appVersion: "0.19.0"
dependencies:
- name: gcloud-sql-proxy
version: 0.0.0

View File

@@ -73,8 +73,6 @@ spec:
value: "{{ .Values.service.port }}"
- name: AFFINE_SERVER_HOST
value: "{{ .Values.app.host }}"
- name: DOC_SERVICE_ENDPOINT
value: "http://{{ .Values.global.docService.name }}:{{ .Values.global.docService.port }}"
ports:
- name: http
containerPort: {{ .Values.service.port }}

View File

@@ -39,9 +39,6 @@ global:
secretAccessKey: ''
gke:
enabled: true
docService:
name: 'affine-doc'
port: 3020
graphql:
service:
@@ -64,12 +61,6 @@ renderer:
annotations:
cloud.google.com/backend-config: '{"default": "affine-api-backendconfig"}'
doc:
service:
type: ClusterIP
annotations:
cloud.google.com/backend-config: '{"default": "affine-api-backendconfig"}'
web:
service:
type: ClusterIP

View File

@@ -27,9 +27,7 @@ jobs:
electron-install: false
extra-flags: workspaces focus @affine/server
- name: Build Server
run: |
find packages/backend/server -type d -name "__tests__" -exec rm -rf {} +
yarn workspace @affine/server build
run: yarn workspace @affine/server build
- name: Upload server dist
uses: actions/upload-artifact@v4
with:

View File

@@ -118,26 +118,6 @@ jobs:
- name: Run Type Check
run: yarn typecheck
lint-rust:
name: Lint Rust
runs-on: ubuntu-latest
needs: optimize_ci
if: needs.optimize_ci.outputs.skip == 'false'
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/build-rust
with:
no-build: 'true'
- name: fmt check
run: |
rustup toolchain add nightly
rustup component add --toolchain nightly-x86_64-unknown-linux-gnu rustfmt
cargo +nightly fmt --all -- --check
- name: Clippy
run: |
rustup component add clippy
cargo clippy --all-targets --all-features -- -D warnings
check-yarn-binary:
name: Check yarn binary
runs-on: ubuntu-latest
@@ -175,7 +155,7 @@ jobs:
run: yarn workspace @blocksuite/legacy-e2e test --forbid-only --shard=${{ matrix.shard }}/${{ strategy.job-total }}
- name: Upload test results
if: always()
if: ${{ failure() }}
uses: actions/upload-artifact@v4
with:
name: test-results-e2e-legacy-bs-${{ matrix.shard }}
@@ -207,7 +187,7 @@ jobs:
run: yarn affine @affine-test/affine-local e2e --forbid-only --shard=${{ matrix.shard }}/${{ strategy.job-total }}
- name: Upload test results
if: always()
if: ${{ failure() }}
uses: actions/upload-artifact@v4
with:
name: test-results-e2e-${{ matrix.shard }}
@@ -239,7 +219,7 @@ jobs:
run: yarn affine @affine-test/affine-mobile e2e --forbid-only --shard=${{ matrix.shard }}/${{ strategy.job-total }}
- name: Upload test results
if: always()
if: ${{ failure() }}
uses: actions/upload-artifact@v4
with:
name: test-results-e2e-mobile-${{ matrix.shard }}
@@ -328,7 +308,6 @@ jobs:
package: '@affine/native'
- name: Upload ${{ steps.filename.outputs.filename }}
uses: actions/upload-artifact@v4
if: always()
with:
name: ${{ steps.filename.outputs.filename }}
path: ${{ env.DEV_DRIVE_WORKSPACE || github.workspace }}/packages/frontend/native/${{ steps.filename.outputs.filename }}
@@ -355,7 +334,6 @@ jobs:
package: '@affine/server-native'
- name: Upload server-native.node
uses: actions/upload-artifact@v4
if: always()
with:
name: server-native.node
path: ./packages/backend/native/server-native.node
@@ -381,7 +359,6 @@ jobs:
run: tar -czf dist.tar.gz --directory=packages/frontend/apps/electron-renderer/dist .
- name: Upload web artifact
uses: actions/upload-artifact@v4
if: always()
with:
name: web
path: dist.tar.gz
@@ -394,11 +371,6 @@ jobs:
- optimize_ci
- build-server-native
if: needs.optimize_ci.outputs.skip == 'false'
strategy:
fail-fast: false
matrix:
node_index: [0, 1, 2]
total_nodes: [3]
env:
NODE_ENV: test
DISTRIBUTION: web
@@ -448,8 +420,6 @@ jobs:
env:
CARGO_TARGET_DIR: '${{ github.workspace }}/target'
COPILOT_OPENAI_API_KEY: 'use_fake_openai_api_key'
CI_NODE_INDEX: ${{ matrix.node_index }}
CI_NODE_TOTAL: ${{ matrix.total_nodes }}
- name: Upload server test coverage results
uses: codecov/codecov-action@v5
@@ -561,7 +531,6 @@ jobs:
CARGO_TARGET_DIR: '${{ github.workspace }}/target'
COPILOT_OPENAI_API_KEY: ${{ secrets.COPILOT_OPENAI_API_KEY }}
COPILOT_FAL_API_KEY: ${{ secrets.COPILOT_FAL_API_KEY }}
COPILOT_PERPLEXITY_API_KEY: ${{ secrets.COPILOT_PERPLEXITY_API_KEY }}
- name: Upload server test coverage results
if: ${{ steps.check-blocksuite-update.outputs.skip != 'true' || steps.apifilter.outputs.changed == 'true' }}
@@ -624,7 +593,8 @@ jobs:
with:
filters: |
changed:
- 'packages/frontend/core/src/blocksuite/ai/**'
- 'packages/frontend/core/src/blocksuite/presets/ai/**'
- 'packages/frontend/core/src/components/blocksuite/block-suite-editor/ai/**'
- 'tests/affine-cloud-copilot/**'
- name: Setup Node.js
@@ -649,7 +619,6 @@ jobs:
script: yarn affine @affine-test/affine-cloud-copilot e2e --forbid-only --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
openai-key: ${{ secrets.COPILOT_OPENAI_API_KEY }}
fal-key: ${{ secrets.COPILOT_FAL_API_KEY }}
perplexity-key: ${{ secrets.COPILOT_PERPLEXITY_API_KEY }}
server-e2e-test:
name: ${{ matrix.tests.name }}
@@ -669,16 +638,12 @@ jobs:
matrix:
tests:
- name: 'Server E2E Test 1/3'
shard: 1
script: yarn affine @affine-test/affine-cloud e2e --forbid-only --shard=1/3
- name: 'Server E2E Test 2/3'
shard: 2
script: yarn affine @affine-test/affine-cloud e2e --forbid-only --shard=2/3
- name: 'Server E2E Test 3/3'
shard: 3
script: yarn affine @affine-test/affine-cloud e2e --forbid-only --shard=3/3
- name: 'Server Desktop E2E Test'
shard: desktop
script: |
yarn affine @affine/electron build:dev
# Workaround for Electron apps failing to initialize on Ubuntu 24.04 due to AppArmor restrictions
@@ -738,13 +703,12 @@ jobs:
DEV_SERVER_URL: http://localhost:8080
COPILOT_OPENAI_API_KEY: 1
COPILOT_FAL_API_KEY: 1
COPILOT_PERPLEXITY_API_KEY: 1
- name: Upload test results
if: always()
if: ${{ failure() }}
uses: actions/upload-artifact@v4
with:
name: test-results-e2e-server-${{ matrix.tests.shard }}
name: test-results-e2e-server
path: ./test-results
if-no-files-found: ignore
@@ -866,7 +830,7 @@ jobs:
yarn affine @affine/electron node ./scripts/macos-arm64-output-check.ts
- name: Upload test results
if: always()
if: ${{ failure() }}
uses: actions/upload-artifact@v4
with:
name: test-results-e2e-${{ matrix.spec.os }}-${{ matrix.spec.arch }}
@@ -888,7 +852,6 @@ jobs:
needs:
- analyze
- lint
- lint-rust
- check-yarn-binary
- e2e-test
- e2e-legacy-blocksuite-test

View File

@@ -84,7 +84,6 @@ jobs:
CARGO_TARGET_DIR: '${{ github.workspace }}/target'
COPILOT_OPENAI_API_KEY: ${{ secrets.COPILOT_OPENAI_API_KEY }}
COPILOT_FAL_API_KEY: ${{ secrets.COPILOT_FAL_API_KEY }}
COPILOT_PERPLEXITY_API_KEY: ${{ secrets.COPILOT_PERPLEXITY_API_KEY }}
- name: Upload server test coverage results
uses: codecov/codecov-action@v5
@@ -148,7 +147,6 @@ jobs:
script: yarn affine @affine-test/affine-cloud-copilot e2e --forbid-only --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
openai-key: ${{ secrets.COPILOT_OPENAI_API_KEY }}
fal-key: ${{ secrets.COPILOT_FAL_API_KEY }}
perplexity-key: ${{ secrets.COPILOT_PERPLEXITY_API_KEY }}
test-done:
needs:

View File

@@ -98,7 +98,6 @@ jobs:
CAPTCHA_TURNSTILE_SECRET: ${{ secrets.CAPTCHA_TURNSTILE_SECRET }}
COPILOT_OPENAI_API_KEY: ${{ secrets.COPILOT_OPENAI_API_KEY }}
COPILOT_FAL_API_KEY: ${{ secrets.COPILOT_FAL_API_KEY }}
COPILOT_PERPLEXITY_API_KEY: ${{ secrets.COPILOT_PERPLEXITY_API_KEY }}
COPILOT_UNSPLASH_API_KEY: ${{ secrets.COPILOT_UNSPLASH_API_KEY }}
METRICS_CUSTOMER_IO_TOKEN: ${{ secrets.METRICS_CUSTOMER_IO_TOKEN }}
MAILER_SENDER: ${{ secrets.OAUTH_EMAIL_SENDER }}

View File

@@ -139,7 +139,7 @@ jobs:
enableScripts: false
- uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: 16.1
xcode-version: latest-stable
- name: Cap sync
run: yarn workspace @affine/ios cap sync
- name: Signing By Apple Developer ID
@@ -191,7 +191,7 @@ jobs:
uses: ./.github/actions/setup-node
timeout-minutes: 10
with:
extra-flags: workspaces focus @affine/monorepo @affine-tools/cli @affine/android @affine/playstore-auto-bump
extra-flags: workspaces focus @affine/android @affine/playstore-auto-bump
playwright-install: false
electron-install: false
hard-link-nm: false
@@ -220,7 +220,7 @@ jobs:
- uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '21'
java-version: '17'
cache: 'gradle'
- name: Auto increment version code
id: bump
@@ -231,7 +231,7 @@ jobs:
- name: Build
run: |
echo -n "${{ env.AFFINE_ANDROID_SIGN_KEYSTORE }}" | base64 --decode > packages/frontend/apps/android/affine.keystore
yarn workspace @affine/android cap build android --flavor ${{ env.BUILD_TYPE }} --androidreleasetype AAB
yarn workspace @affine/android cap build android
env:
AFFINE_ANDROID_KEYSTORE_PASSWORD: ${{ secrets.AFFINE_ANDROID_KEYSTORE_PASSWORD }}
AFFINE_ANDROID_KEYSTORE_ALIAS_PASSWORD: ${{ secrets.AFFINE_ANDROID_KEYSTORE_ALIAS_PASSWORD }}
@@ -243,7 +243,7 @@ jobs:
with:
serviceAccountJson: ${{ steps.auth.outputs.credentials_file_path }}
packageName: app.affine.pro
releaseFiles: packages/frontend/apps/android/App/app/build/outputs/bundle/${{ env.BUILD_TYPE }}Release/app-${{ env.BUILD_TYPE }}-release-signed.aab
releaseFiles: packages/frontend/apps/android/App/app/build/outputs/bundle/release/app-release-signed.aab
track: internal
status: draft
existingEditId: ${{ steps.bump.outputs.EDIT_ID }}

View File

@@ -11,6 +11,7 @@ on:
jobs:
synchronize-with-crowdin:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
@@ -20,11 +21,10 @@ jobs:
uses: actions/checkout@v4
- name: Crowdin action
id: crowdin
uses: crowdin/github-action@v2
with:
upload_sources: true
upload_translations: false
upload_translations: true
download_translations: true
auto_approve_imported: true
import_eq_suggestions: true
@@ -40,33 +40,3 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
i18n-codegen:
needs: synchronize-with-crowdin
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: l10n_crowdin_translations
- name: Setup Node.js
uses: ./.github/actions/setup-node
with:
electron-install: false
full-cache: true
- name: Run i18n codegen
run: yarn affine @affine/i18n build
- name: Commit changes
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add .
git commit -m "chore(i18n): i18n codegen"
git push origin l10n_crowdin_translations

23
.github/workflows/workers.yml vendored Normal file
View File

@@ -0,0 +1,23 @@
name: Deploy Cloudflare Worker
on:
push:
branches:
- canary
paths:
- tools/workers/**
jobs:
deploy:
runs-on: ubuntu-latest
name: Deploy
environment: stable
steps:
- uses: actions/checkout@v4
- name: Publish
uses: cloudflare/wrangler-action@v3.13.0
with:
apiToken: ${{ secrets.CF_API_TOKEN }}
accountId: ${{ secrets.CF_ACCOUNT_ID }}
workingDirectory: 'tools/workers'
packageManager: 'yarn'

2
.nvmrc
View File

@@ -1 +1 @@
22.14.0
20.18.1

View File

@@ -28,8 +28,6 @@ test-results
# per files
tools/cli/src/webpack/error-handler.js
packages/backend/native/index.d.ts
packages/backend/server/src/__tests__/__snapshots__
packages/common/native/fixtures/**
packages/frontend/native/index.d.ts
packages/frontend/native/index.js
packages/frontend/graphql/src/graphql/index.ts

View File

@@ -7,11 +7,7 @@
"request": "launch",
"runtimeExecutable": "yarn",
"cwd": "${workspaceFolder}",
"runtimeArgs": [
"affine",
"@affine/server",
"dev"
]
"runtimeArgs": ["affine", "@affine/server", "dev"]
},
{
"name": "Lanuch AFFiNE Web",
@@ -19,20 +15,7 @@
"request": "launch",
"runtimeExecutable": "yarn",
"cwd": "${workspaceFolder}",
"runtimeArgs": [
"affine",
"@affine/web",
"dev"
]
},
{
"type": "chrome",
"request": "launch",
"name": "Debug AFFiNE Web",
"url": "http://localhost:8080",
"sourceMapPathOverrides": {
"webpack://affine/blocksuite/*": "${workspaceFolder}/blocksuite/*"
}
"runtimeArgs": ["affine", "@affine/web", "dev"]
}
]
}
}

1931
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -15,29 +15,23 @@ affine_common = { path = "./packages/common/native" }
affine_nbstore = { path = "./packages/frontend/native/nbstore" }
anyhow = "1"
base64-simd = "0.8"
block2 = "0.6"
chrono = "0.4"
core-foundation = "0.10"
coreaudio-rs = "0.12"
criterion2 = { version = "2", default-features = false }
dispatch2 = "0.2"
dashmap = "6"
dotenvy = "0.15"
file-format = { version = "0.26", features = ["reader"] }
homedir = "0.3"
libc = "0.2"
mimalloc = "0.1"
napi = { version = "3.0.0-alpha.12", features = ["async", "chrono_date", "error_anyhow", "napi9", "serde"] }
napi-build = { version = "2" }
napi-derive = { version = "3.0.0-alpha.12" }
notify = { version = "8", features = ["serde"] }
objc2 = "0.6"
objc2-foundation = "0.3"
notify = { version = "7", features = ["serde"] }
objc2 = "0.5.2"
objc2-foundation = "0.2.2"
once_cell = "1"
parking_lot = "0.12"
rand = "0.9"
homedir = "0.3"
rand = "0.8"
rayon = "1.10"
rubato = "0.16"
screencapturekit = "0.3"
serde = "1"
serde_json = "1"
sha3 = "0.10"
@@ -45,7 +39,7 @@ sqlx = { version = "0.8", default-features = false, features = ["chr
thiserror = "2"
tiktoken-rs = "0.6"
tokio = "1.37"
uniffi = "0.29"
uniffi = "0.28"
uuid = "1.8"
v_htmlescape = "0.15"
y-octo = { git = "https://github.com/y-crdt/y-octo.git", branch = "main" }

View File

@@ -127,8 +127,6 @@ AFFiNE now provides pre-built [templates](https://affine.pro/templates) from our
Welcome to the AFFiNE blog section! Here, youll find the latest insights, tips, and guides on how to maximize your experience with AFFiNE and AFFiNE AI, the leading Canvas AI tool for flexible note-taking and creative organization.
- [vision board template](https://affine.pro/blog/8-free-printable-vision-board-templates-examples-2023)
- [ai homework helper](https://affine.pro/blog/ai-homework-helper)
- [vision board maker](https://affine.pro/blog/vision-board-maker)
- [itinerary template](https://affine.pro/blog/free-customized-travel-itinerary-planner-templates)
- [one pager template](https://affine.pro/blog/top-12-one-pager-examples-how-to-create-your-own)
- [cornell notes template](https://affine.pro/blog/the-cornell-notes-template-and-system-learning-tips)

View File

@@ -17,8 +17,8 @@
"@blocksuite/blocks": "workspace:*",
"@blocksuite/global": "workspace:*",
"@blocksuite/inline": "workspace:*",
"@blocksuite/store": "workspace:*",
"@blocksuite/sync": "workspace:*"
"@blocksuite/presets": "workspace:*",
"@blocksuite/store": "workspace:*"
},
"exports": {
".": "./src/index.ts",
@@ -32,13 +32,12 @@
"./global/di": "./src/global/di.ts",
"./global/types": "./src/global/types.ts",
"./store": "./src/store/index.ts",
"./store/test": "./src/store/test.ts",
"./inline": "./src/inline/index.ts",
"./inline/consts": "./src/inline/consts.ts",
"./inline/types": "./src/inline/types.ts",
"./presets": "./src/presets/index.ts",
"./blocks": "./src/blocks/index.ts",
"./blocks/schemas": "./src/blocks/schemas.ts",
"./sync": "./src/sync/index.ts"
"./blocks/schemas": "./src/blocks/schemas.ts"
},
"typesVersions": {
"*": {
@@ -81,14 +80,14 @@
"inline/types": [
"dist/inline/types.d.ts"
],
"presets": [
"dist/presets/index.d.ts"
],
"blocks": [
"dist/blocks/index.d.ts"
],
"blocks/schemas": [
"dist/blocks/schemas.d.ts"
],
"sync": [
"dist/sync/index.d.ts"
]
}
},
@@ -98,5 +97,5 @@
"!src/__tests__",
"!dist/__tests__"
],
"version": "0.20.0"
"version": "0.19.0"
}

View File

@@ -1,5 +1,7 @@
import { effects as blocksEffects } from '@blocksuite/blocks/effects';
import { effects as presetsEffects } from '@blocksuite/presets/effects';
export function effects() {
blocksEffects();
presetsEffects();
}

View File

@@ -0,0 +1 @@
export * from '@blocksuite/presets';

View File

@@ -1,3 +1,5 @@
/* eslint-disable @typescript-eslint/no-restricted-imports */
// oxlint-disable-next-line
// @ts-ignore FIXME: typecheck error
export * from '@blocksuite/store';

View File

@@ -1,6 +0,0 @@
export {
createAutoIncrementIdGenerator,
TestDoc,
TestMeta,
TestWorkspace,
} from '@blocksuite/store/test';

View File

@@ -1 +0,0 @@
export * from '@blocksuite/sync';

View File

@@ -1,17 +1,20 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./dist",
"tsBuildInfoFile": "./dist/tsconfig.tsbuildinfo"
"rootDir": "./src/",
"outDir": "./dist/",
"noEmit": false
},
"include": ["./src"],
"references": [
{ "path": "../../framework/block-std" },
{ "path": "../../blocks" },
{ "path": "../../framework/global" },
{ "path": "../../framework/inline" },
{ "path": "../../framework/store" },
{ "path": "../../framework/sync" }
{
"path": "../../framework"
},
{
"path": "../../blocks"
},
{
"path": "../../presets"
}
]
}

View File

@@ -0,0 +1,30 @@
import { defineConfig } from 'vitest/config';
export default defineConfig({
esbuild: {
target: 'es2018',
},
test: {
globalSetup: '../../../scripts/vitest-global.js',
include: ['src/__tests__/**/*.unit.spec.ts'],
testTimeout: 1000,
coverage: {
provider: 'istanbul', // or 'c8'
reporter: ['lcov'],
reportsDirectory: '../../../.coverage/affine',
},
/**
* Custom handler for console.log in tests.
*
* Return `false` to ignore the log.
*/
onConsoleLog(log, type) {
if (log.includes('https://lit.dev/msg/dev-mode')) {
return false;
}
console.warn(`Unexpected ${type} log`, log);
throw new Error(log);
},
environment: 'happy-dom',
},
});

View File

@@ -20,14 +20,14 @@
"@blocksuite/affine-shared": "workspace:*",
"@blocksuite/block-std": "workspace:*",
"@blocksuite/global": "workspace:*",
"@blocksuite/icons": "^2.2.1",
"@blocksuite/icons": "^2.1.75",
"@blocksuite/inline": "workspace:*",
"@blocksuite/store": "workspace:*",
"@floating-ui/dom": "^1.6.13",
"@floating-ui/dom": "^1.6.10",
"@lit/context": "^1.1.2",
"@preact/signals-core": "^1.8.0",
"@toeverything/theme": "^1.1.12",
"file-type": "^20.0.0",
"@toeverything/theme": "^1.1.3",
"file-type": "^19.5.0",
"lit": "^3.2.0",
"minimatch": "^10.0.1",
"zod": "^3.23.8"
@@ -42,5 +42,5 @@
"!src/__tests__",
"!dist/__tests__"
],
"version": "0.20.0"
"version": "0.19.0"
}

View File

@@ -3,7 +3,7 @@ import { CaptionedBlockComponent } from '@blocksuite/affine-components/caption';
import { HoverController } from '@blocksuite/affine-components/hover';
import {
AttachmentIcon16,
getAttachmentFileIcon,
getAttachmentFileIcons,
} from '@blocksuite/affine-components/icons';
import { Peekable } from '@blocksuite/affine-components/peek';
import { toast } from '@blocksuite/affine-components/toast';
@@ -11,12 +11,8 @@ import {
type AttachmentBlockModel,
AttachmentBlockStyles,
} from '@blocksuite/affine-model';
import {
FileSizeLimitService,
ThemeProvider,
} from '@blocksuite/affine-shared/services';
import { ThemeProvider } from '@blocksuite/affine-shared/services';
import { humanFileSize } from '@blocksuite/affine-shared/utils';
import { BlockSelection, TextSelection } from '@blocksuite/block-std';
import { Slice } from '@blocksuite/store';
import { flip, offset } from '@floating-ui/dom';
import { html, nothing } from 'lit';
@@ -25,24 +21,30 @@ import { classMap } from 'lit/directives/class-map.js';
import { ref } from 'lit/directives/ref.js';
import { styleMap } from 'lit/directives/style-map.js';
import type { AttachmentBlockService } from './attachment-service.js';
import { AttachmentOptionsTemplate } from './components/options.js';
import { AttachmentEmbedProvider } from './embed.js';
import { styles } from './styles.js';
import { checkAttachmentBlob, downloadAttachmentBlob } from './utils.js';
@Peekable()
export class AttachmentBlockComponent extends CaptionedBlockComponent<AttachmentBlockModel> {
export class AttachmentBlockComponent extends CaptionedBlockComponent<
AttachmentBlockModel,
AttachmentBlockService
> {
static override styles = styles;
protected _isDragging = false;
protected _isResizing = false;
protected _isSelected = false;
protected _whenHover: HoverController | null = new HoverController(
this,
({ abortController }) => {
const selection = this.host.selection;
const textSelection = selection.find(TextSelection);
const textSelection = selection.find('text');
if (
!!textSelection &&
(!!textSelection.to || !!textSelection.from.length)
@@ -50,7 +52,7 @@ export class AttachmentBlockComponent extends CaptionedBlockComponent<Attachment
return null;
}
const blockSelections = selection.filter(BlockSelection);
const blockSelections = selection.filter('block');
if (
blockSelections.length > 1 ||
(blockSelections.length === 1 &&
@@ -83,14 +85,10 @@ export class AttachmentBlockComponent extends CaptionedBlockComponent<Attachment
margin: '18px 0px',
});
private get _maxFileSize() {
return this.std.store.get(FileSizeLimitService).maxFileSize;
}
convertTo = () => {
return this.std
.get(AttachmentEmbedProvider)
.convertTo(this.model, this._maxFileSize);
.convertTo(this.model, this.service.maxFileSize);
};
copy = () => {
@@ -106,7 +104,7 @@ export class AttachmentBlockComponent extends CaptionedBlockComponent<Attachment
embedded = () => {
return this.std
.get(AttachmentEmbedProvider)
.embedded(this.model, this._maxFileSize);
.embedded(this.model, this.service.maxFileSize);
};
open = () => {
@@ -123,12 +121,12 @@ export class AttachmentBlockComponent extends CaptionedBlockComponent<Attachment
protected get embedView() {
return this.std
.get(AttachmentEmbedProvider)
.render(this.model, this.blobUrl, this._maxFileSize);
.render(this.model, this.blobUrl, this.service.maxFileSize);
}
private _selectBlock() {
const selectionManager = this.host.selection;
const blockSelection = selectionManager.create(BlockSelection, {
const blockSelection = selectionManager.create('block', {
blockId: this.blockId,
});
selectionManager.setGroup('note', [blockSelection]);
@@ -167,21 +165,25 @@ export class AttachmentBlockComponent extends CaptionedBlockComponent<Attachment
// this is required to prevent iframe from capturing pointer events
this.disposables.add(
this.selected$.subscribe(selected => {
this._showOverlay = this._isResizing || this._isDragging || !selected;
this.std.selection.slots.changed.on(() => {
this._isSelected =
!!this.selected?.is('block') || !!this.selected?.is('surface');
this._showOverlay =
this._isResizing || this._isDragging || !this._isSelected;
})
);
// this is required to prevent iframe from capturing pointer events
this.handleEvent('dragStart', () => {
this._isDragging = true;
this._showOverlay =
this._isResizing || this._isDragging || !this.selected$.peek();
this._isResizing || this._isDragging || !this._isSelected;
});
this.handleEvent('dragEnd', () => {
this._isDragging = false;
this._showOverlay =
this._isResizing || this._isDragging || !this.selected$.peek();
this._isResizing || this._isDragging || !this._isSelected;
});
}
@@ -218,7 +220,7 @@ export class AttachmentBlockComponent extends CaptionedBlockComponent<Attachment
const infoText = this.error ? 'File loading failed.' : humanFileSize(size);
const fileType = name.split('.').pop() ?? '';
const FileTypeIcon = getAttachmentFileIcon(fileType);
const FileTypeIcon = getAttachmentFileIcons(fileType);
const embedView = this.embedView;
@@ -226,6 +228,7 @@ export class AttachmentBlockComponent extends CaptionedBlockComponent<Attachment
<div
${this._whenHover ? ref(this._whenHover.setReference) : nothing}
class="affine-attachment-container"
draggable="${this.blockDraggable ? 'true' : 'false'}"
style=${this.containerStyleMap}
>
${embedView

View File

@@ -35,7 +35,7 @@ export class AttachmentEdgelessBlockComponent extends toGfxBlockComponent(
this.slots.elementResizeEnd.on(() => {
this._isResizing = false;
this._showOverlay =
this._isResizing || this._isDragging || !this.selected$.peek();
this._isResizing || this._isDragging || !this._isSelected;
})
);
}

View File

@@ -1,36 +1,42 @@
import { SurfaceBlockModel } from '@blocksuite/affine-block-surface';
import { FileDropConfigExtension } from '@blocksuite/affine-components/drop-indicator';
import { FileDropConfigExtension } from '@blocksuite/affine-components/drag-indicator';
import { AttachmentBlockSchema } from '@blocksuite/affine-model';
import {
FileSizeLimitService,
TelemetryProvider,
} from '@blocksuite/affine-shared/services';
import { TelemetryProvider } from '@blocksuite/affine-shared/services';
import {
isInsideEdgelessEditor,
matchModels,
matchFlavours,
} from '@blocksuite/affine-shared/utils';
import { BlockService } from '@blocksuite/block-std';
import { GfxControllerIdentifier } from '@blocksuite/block-std/gfx';
import { addAttachments, addSiblingAttachmentBlocks } from './utils.js';
// bytes.parse('2GB')
const maxFileSize = 2147483648;
export class AttachmentBlockService extends BlockService {
static override readonly flavour = AttachmentBlockSchema.model.flavour;
maxFileSize = maxFileSize;
}
export const AttachmentDropOption = FileDropConfigExtension({
flavour: AttachmentBlockSchema.model.flavour,
onDrop: ({ files, targetModel, placement, point, std }) => {
onDrop: ({ files, targetModel, place, point, std }) => {
// generic attachment block for all files except images
const attachmentFiles = files.filter(
file => !file.type.startsWith('image/')
);
if (!attachmentFiles.length) return false;
const maxFileSize = std.store.get(FileSizeLimitService).maxFileSize;
if (targetModel && !matchModels(targetModel, [SurfaceBlockModel])) {
if (targetModel && !matchFlavours(targetModel, ['affine:surface'])) {
addSiblingAttachmentBlocks(
std.host,
attachmentFiles,
// TODO: use max file size from service
maxFileSize,
targetModel,
placement
place
).catch(console.error);
return true;

View File

@@ -1,9 +1,15 @@
import { BlockViewExtension, FlavourExtension } from '@blocksuite/block-std';
import type { ExtensionType } from '@blocksuite/store';
import {
BlockViewExtension,
type ExtensionType,
FlavourExtension,
} from '@blocksuite/block-std';
import { literal } from 'lit/static-html.js';
import { AttachmentBlockNotionHtmlAdapterExtension } from './adapters/notion-html.js';
import { AttachmentDropOption } from './attachment-service.js';
import {
AttachmentBlockService,
AttachmentDropOption,
} from './attachment-service.js';
import {
AttachmentEmbedConfigExtension,
AttachmentEmbedService,
@@ -11,6 +17,7 @@ import {
export const AttachmentBlockSpec: ExtensionType[] = [
FlavourExtension('affine:attachment'),
AttachmentBlockService,
BlockViewExtension('affine:attachment', model => {
return model.parent?.flavour === 'affine:surface'
? literal`affine-edgeless-attachment`

View File

@@ -2,6 +2,8 @@ import {
CaptionIcon,
DownloadIcon,
EditIcon,
MoreVerticalIcon,
SmallArrowDownIcon,
} from '@blocksuite/affine-components/icons';
import { createLitPortal } from '@blocksuite/affine-components/portal';
import {
@@ -19,7 +21,6 @@ import {
EMBED_CARD_WIDTH,
} from '@blocksuite/affine-shared/consts';
import { Bound } from '@blocksuite/global/utils';
import { ArrowDownSmallIcon, MoreVerticalIcon } from '@blocksuite/icons/lit';
import { flip, offset } from '@floating-ui/dom';
import { html, nothing } from 'lit';
import { join } from 'lit/directives/join.js';
@@ -87,7 +88,7 @@ export function attachmentViewToggleMenu({
<span style="text-transform: capitalize">${viewType}</span>
view
</div>
${ArrowDownSmallIcon({ width: '16px', height: '16px' })}
${SmallArrowDownIcon}
</editor-icon-button>
`}
>
@@ -199,12 +200,8 @@ export function AttachmentOptionsTemplate({
<editor-menu-button
.contentPadding=${'8px'}
.button=${html`
<editor-icon-button
aria-label="More"
.tooltip=${'More'}
.iconSize=${'20px'}
>
${MoreVerticalIcon()}
<editor-icon-button aria-label="More" .tooltip=${'More'}>
${MoreVerticalIcon}
</editor-icon-button>
`}
>

View File

@@ -1,5 +1,6 @@
import { AttachmentBlockComponent } from './attachment-block';
import { AttachmentEdgelessBlockComponent } from './attachment-edgeless-block';
import type { AttachmentBlockService } from './attachment-service';
export function effects() {
customElements.define(
@@ -8,3 +9,11 @@ export function effects() {
);
customElements.define('affine-attachment', AttachmentBlockComponent);
}
declare global {
namespace BlockSuite {
interface BlockServices {
'affine:attachment': AttachmentBlockService;
}
}
}

View File

@@ -1,25 +1,18 @@
import {
type AttachmentBlockModel,
type ImageBlockProps,
MAX_IMAGE_WIDTH,
import type {
AttachmentBlockModel,
ImageBlockProps,
} from '@blocksuite/affine-model';
import { FileSizeLimitService } from '@blocksuite/affine-shared/services';
import {
readImageSize,
transformModel,
withTempBlobData,
} from '@blocksuite/affine-shared/utils';
import { type BlockStdScope, StdIdentifier } from '@blocksuite/block-std';
import type { ExtensionType } from '@blocksuite/block-std';
import { Extension } from '@blocksuite/block-std';
import type { Container } from '@blocksuite/global/di';
import { createIdentifier } from '@blocksuite/global/di';
import { Bound } from '@blocksuite/global/utils';
import type { ExtensionType } from '@blocksuite/store';
import { Extension } from '@blocksuite/store';
import type { TemplateResult } from 'lit';
import { html } from 'lit';
import { getAttachmentBlob } from './utils';
export type AttachmentEmbedConfig = {
name: string;
/**
@@ -29,10 +22,7 @@ export type AttachmentEmbedConfig = {
/**
* The action will be executed when the 「Turn into embed view」 button is clicked.
*/
action?: (
model: AttachmentBlockModel,
std: BlockStdScope
) => Promise<void> | void;
action?: (model: AttachmentBlockModel) => Promise<void> | void;
/**
* The template will be used to render the embed view.
*/
@@ -67,9 +57,8 @@ export const AttachmentEmbedProvider = createIdentifier<AttachmentEmbedService>(
);
export class AttachmentEmbedService extends Extension {
private get _maxFileSize() {
return this.std.store.get(FileSizeLimitService).maxFileSize;
}
// 10MB
static MAX_EMBED_SIZE = 10 * 1024 * 1024;
get keys() {
return this.configs.keys();
@@ -79,11 +68,7 @@ export class AttachmentEmbedService extends Extension {
return this.configs.values();
}
get configs(): Map<string, AttachmentEmbedConfig> {
return this.std.get(AttachmentEmbedConfigMapIdentifier);
}
constructor(private readonly std: BlockStdScope) {
constructor(private readonly configs: Map<string, AttachmentEmbedConfig>) {
super();
}
@@ -91,27 +76,35 @@ export class AttachmentEmbedService extends Extension {
di.addImpl(AttachmentEmbedConfigMapIdentifier, provider =>
provider.getAll(AttachmentEmbedConfigIdentifier)
);
di.addImpl(AttachmentEmbedProvider, this, [StdIdentifier]);
di.addImpl(AttachmentEmbedProvider, AttachmentEmbedService, [
AttachmentEmbedConfigMapIdentifier,
]);
}
// Converts to embed view.
convertTo(model: AttachmentBlockModel, maxFileSize = this._maxFileSize) {
convertTo(
model: AttachmentBlockModel,
maxFileSize = AttachmentEmbedService.MAX_EMBED_SIZE
) {
const config = this.values.find(config => config.check(model, maxFileSize));
if (!config?.action) {
if (!config || !config.action) {
model.doc.updateBlock(model, { embed: true });
return;
}
config.action(model, this.std)?.catch(console.error);
config.action(model)?.catch(console.error);
}
embedded(model: AttachmentBlockModel, maxFileSize = this._maxFileSize) {
embedded(
model: AttachmentBlockModel,
maxFileSize = AttachmentEmbedService.MAX_EMBED_SIZE
) {
return this.values.some(config => config.check(model, maxFileSize));
}
render(
model: AttachmentBlockModel,
blobUrl?: string,
maxFileSize = this._maxFileSize
maxFileSize = AttachmentEmbedService.MAX_EMBED_SIZE
) {
if (!model.embed || !blobUrl) return;
@@ -131,12 +124,7 @@ const embedConfig: AttachmentEmbedConfig[] = [
check: model =>
model.doc.schema.flavourSchemaMap.has('affine:image') &&
model.type.startsWith('image/'),
async action(model, std) {
const component = std.view.getBlock(model.id);
if (!component) return;
await turnIntoImageBlock(model);
},
action: model => turnIntoImageBlock(model),
},
{
name: 'pdf',
@@ -164,13 +152,7 @@ const embedConfig: AttachmentEmbedConfig[] = [
check: (model, maxFileSize) =>
model.type.startsWith('video/') && model.size <= maxFileSize,
template: (_, blobUrl) =>
html`<video
style="max-height: max-content;"
width="100%;"
height="480"
controls
src=${blobUrl}
></video>`,
html`<video width="100%;" height="480" controls src=${blobUrl}></video>`,
},
{
name: 'audio',
@@ -184,7 +166,7 @@ const embedConfig: AttachmentEmbedConfig[] = [
/**
* Turn the attachment block into an image block.
*/
export async function turnIntoImageBlock(model: AttachmentBlockModel) {
export function turnIntoImageBlock(model: AttachmentBlockModel) {
if (!model.doc.schema.flavourSchemaMap.has('affine:image')) {
console.error('The image flavour is not supported!');
return;
@@ -196,37 +178,15 @@ export async function turnIntoImageBlock(model: AttachmentBlockModel) {
const { saveAttachmentData, getImageData } = withTempBlobData();
saveAttachmentData(sourceId, { name: model.name });
let imageSize = model.sourceId ? getImageData(model.sourceId) : undefined;
const bounds = model.xywh
? Bound.fromXYWH(model.deserializedXYWH)
const imageConvertData = model.sourceId
? getImageData(model.sourceId)
: undefined;
if (bounds) {
if (!imageSize?.width || !imageSize?.height) {
const blob = await getAttachmentBlob(model);
if (blob) {
imageSize = await readImageSize(blob);
}
}
if (imageSize?.width && imageSize?.height) {
const p = imageSize.height / imageSize.width;
imageSize.width = Math.min(imageSize.width, MAX_IMAGE_WIDTH);
imageSize.height = imageSize.width * p;
bounds.w = imageSize.width;
bounds.h = imageSize.height;
}
}
const others = bounds ? { xywh: bounds.serialize() } : undefined;
const imageProp: Partial<ImageBlockProps> = {
sourceId,
caption: model.caption,
size: model.size,
...imageSize,
...others,
...imageConvertData,
};
transformModel(model, 'affine:image', imageProp);
}

View File

@@ -1,3 +1,7 @@
import type * as SurfaceEffects from '@blocksuite/affine-block-surface/effects';
declare type _GLOBAL_ = typeof SurfaceEffects;
export * from './adapters/notion-html';
export * from './attachment-block';
export * from './attachment-service';

View File

@@ -1,3 +1,7 @@
import {
EMBED_CARD_HEIGHT,
EMBED_CARD_WIDTH,
} from '@blocksuite/affine-shared/consts';
import { css } from 'lit';
export const styles = css`
@@ -8,7 +12,7 @@ export const styles = css`
gap: 12px;
width: 100%;
height: 100%;
height: ${EMBED_CARD_HEIGHT.horizontalThin}px;
padding: 12px;
border-radius: 8px;
@@ -117,6 +121,9 @@ export const styles = css`
}
.affine-attachment-card.cubeThick {
width: ${EMBED_CARD_WIDTH.cubeThick}px;
height: ${EMBED_CARD_HEIGHT.cubeThick}px;
flex-direction: column-reverse;
.affine-attachment-content {

View File

@@ -8,10 +8,7 @@ import {
EMBED_CARD_HEIGHT,
EMBED_CARD_WIDTH,
} from '@blocksuite/affine-shared/consts';
import {
FileSizeLimitService,
TelemetryProvider,
} from '@blocksuite/affine-shared/services';
import { TelemetryProvider } from '@blocksuite/affine-shared/services';
import { humanFileSize } from '@blocksuite/affine-shared/utils';
import type { BlockStdScope, EditorHost } from '@blocksuite/block-std';
import { GfxControllerIdentifier } from '@blocksuite/block-std/gfx';
@@ -97,7 +94,7 @@ export async function uploadAttachmentBlob(
}
}
export async function getAttachmentBlob(model: AttachmentBlockModel) {
async function getAttachmentBlob(model: AttachmentBlockModel) {
const sourceId = model.sourceId;
if (!sourceId) {
return null;
@@ -215,8 +212,7 @@ export async function addSiblingAttachmentBlocks(
files: File[],
maxFileSize: number,
targetModel: BlockModel,
place: 'before' | 'after' = 'after',
isEmbed?: boolean
place: 'before' | 'after' = 'after'
) {
if (!files.length) {
return;
@@ -246,7 +242,6 @@ export async function addSiblingAttachmentBlocks(
name: file.name,
size: file.size,
type: types[index],
embed: isEmbed,
}));
const blockIds = doc.addSiblingBlocks(
@@ -266,13 +261,18 @@ export async function addSiblingAttachmentBlocks(
export async function addAttachments(
std: BlockStdScope,
files: File[],
point?: IVec,
transformPoint?: boolean // determines whether we should use `toModelCoord` to convert the point
point?: IVec
): Promise<string[]> {
if (!files.length) return [];
const attachmentService = std.getService('affine:attachment');
const gfx = std.get(GfxControllerIdentifier);
const maxFileSize = std.store.get(FileSizeLimitService).maxFileSize;
if (!attachmentService) {
console.error('Attachment service not found');
return [];
}
const maxFileSize = attachmentService.maxFileSize;
const isSizeExceeded = files.some(file => file.size > maxFileSize);
if (isSizeExceeded) {
toast(
@@ -287,14 +287,7 @@ export async function addAttachments(
}
let { x, y } = gfx.viewport.center;
if (point) {
let transform = transformPoint ?? true;
if (transform) {
[x, y] = gfx.viewport.toModelCoord(...point);
} else {
[x, y] = point;
}
}
if (point) [x, y] = gfx.viewport.toModelCoord(...point);
const CARD_STACK_GAP = 32;
@@ -310,7 +303,7 @@ export async function addAttachments(
EMBED_CARD_WIDTH.cubeThick,
EMBED_CARD_HEIGHT.cubeThick
);
const blockId = std.store.addBlock(
const blockId = std.doc.addBlock(
'affine:attachment',
{
name: file.name,

View File

@@ -1,20 +1,29 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./dist",
"tsBuildInfoFile": "./dist/tsconfig.tsbuildinfo"
"rootDir": "./src/",
"outDir": "./dist/",
"noEmit": false
},
"include": ["./src"],
"references": [
{ "path": "../block-embed" },
{ "path": "../block-surface" },
{ "path": "../components" },
{ "path": "../model" },
{ "path": "../shared" },
{ "path": "../../framework/block-std" },
{ "path": "../../framework/global" },
{ "path": "../../framework/inline" },
{ "path": "../../framework/store" }
{
"path": "../../framework"
},
{
"path": "../model"
},
{
"path": "../components"
},
{
"path": "../shared"
},
{
"path": "../block-embed"
},
{
"path": "../block-surface"
}
]
}

View File

@@ -19,13 +19,13 @@
"@blocksuite/affine-shared": "workspace:*",
"@blocksuite/block-std": "workspace:*",
"@blocksuite/global": "workspace:*",
"@blocksuite/icons": "^2.2.1",
"@blocksuite/icons": "^2.1.75",
"@blocksuite/inline": "workspace:*",
"@blocksuite/store": "workspace:*",
"@floating-ui/dom": "^1.6.13",
"@floating-ui/dom": "^1.6.10",
"@lit/context": "^1.1.2",
"@preact/signals-core": "^1.8.0",
"@toeverything/theme": "^1.1.12",
"@toeverything/theme": "^1.1.3",
"lit": "^3.2.0",
"minimatch": "^10.0.1",
"zod": "^3.23.8"
@@ -40,5 +40,5 @@
"!src/__tests__",
"!dist/__tests__"
],
"version": "0.20.0"
"version": "0.19.0"
}

View File

@@ -1,4 +1,4 @@
import type { ExtensionType } from '@blocksuite/store';
import type { ExtensionType } from '@blocksuite/block-std';
import { BookmarkBlockHtmlAdapterExtension } from './html.js';
import { BookmarkBlockMarkdownAdapterExtension } from './markdown.js';

View File

@@ -4,23 +4,20 @@ import {
} from '@blocksuite/affine-components/caption';
import type { BookmarkBlockModel } from '@blocksuite/affine-model';
import { DocModeProvider } from '@blocksuite/affine-shared/services';
import { computed, type ReadonlySignal } from '@preact/signals-core';
import { html } from 'lit';
import { property, query } from 'lit/decorators.js';
import { type ClassInfo, classMap } from 'lit/directives/class-map.js';
import { classMap } from 'lit/directives/class-map.js';
import { type StyleInfo, styleMap } from 'lit/directives/style-map.js';
import type { BookmarkBlockService } from './bookmark-service.js';
import { refreshBookmarkUrlData } from './utils.js';
export const BOOKMARK_MIN_WIDTH = 450;
export class BookmarkBlockComponent extends CaptionedBlockComponent<BookmarkBlockModel> {
selectedStyle$: ReadonlySignal<ClassInfo> | null = computed<ClassInfo>(
() => ({
'selected-style': this.selected$.value,
})
);
export class BookmarkBlockComponent extends CaptionedBlockComponent<
BookmarkBlockModel,
BookmarkBlockService
> {
private _fetchAbortController?: AbortController;
blockDraggable = true;
@@ -76,12 +73,13 @@ export class BookmarkBlockComponent extends CaptionedBlockComponent<BookmarkBloc
}
override renderBlock() {
const selected = !!this.selected?.is('block');
return html`
<div
draggable="${this.blockDraggable ? 'true' : 'false'}"
class=${classMap({
'affine-bookmark-container': true,
...this.selectedStyle$?.value,
'selected-style': selected,
})}
style=${this.containerStyleMap}
>

View File

@@ -3,15 +3,13 @@ import {
EMBED_CARD_WIDTH,
} from '@blocksuite/affine-shared/consts';
import { toGfxBlockComponent } from '@blocksuite/block-std';
import { type StyleInfo, styleMap } from 'lit/directives/style-map.js';
import { styleMap } from 'lit/directives/style-map.js';
import { BookmarkBlockComponent } from './bookmark-block.js';
export class BookmarkEdgelessBlockComponent extends toGfxBlockComponent(
BookmarkBlockComponent
) {
override selectedStyle$ = null;
override blockDraggable = false;
override getRenderingRect() {
@@ -45,9 +43,7 @@ export class BookmarkEdgelessBlockComponent extends toGfxBlockComponent(
return this.renderPageContent();
}
protected override accessor blockContainerStyles: StyleInfo = {
height: '100%',
};
protected override accessor blockContainerStyles = {};
}
declare global {

View File

@@ -0,0 +1,16 @@
import { LinkPreviewer } from '@blocksuite/affine-block-embed';
import { BookmarkBlockSchema } from '@blocksuite/affine-model';
import { BlockService } from '@blocksuite/block-std';
export class BookmarkBlockService extends BlockService {
static override readonly flavour = BookmarkBlockSchema.model.flavour;
private static readonly linkPreviewer = new LinkPreviewer();
static setLinkPreviewEndpoint =
BookmarkBlockService.linkPreviewer.setEndpoint;
queryUrlData = (url: string, signal?: AbortSignal) => {
return BookmarkBlockService.linkPreviewer.query(url, signal);
};
}

View File

@@ -1,11 +1,19 @@
import { BlockViewExtension, FlavourExtension } from '@blocksuite/block-std';
import type { ExtensionType } from '@blocksuite/store';
import {
BlockViewExtension,
CommandExtension,
type ExtensionType,
FlavourExtension,
} from '@blocksuite/block-std';
import { literal } from 'lit/static-html.js';
import { BookmarkBlockAdapterExtensions } from './adapters/extension.js';
import { BookmarkBlockService } from './bookmark-service.js';
import { commands } from './commands/index.js';
export const BookmarkBlockSpec: ExtensionType[] = [
FlavourExtension('affine:bookmark'),
BookmarkBlockService,
CommandExtension(commands),
BlockViewExtension('affine:bookmark', model => {
return model.parent?.flavour === 'affine:surface'
? literal`affine-edgeless-bookmark`

View File

@@ -1,2 +1,9 @@
export { insertBookmarkCommand } from './insert-bookmark.js';
export { insertLinkByQuickSearchCommand } from './insert-link-by-quick-search.js';
import type { BlockCommands } from '@blocksuite/block-std';
import { insertBookmarkCommand } from './insert-bookmark.js';
import { insertLinkByQuickSearchCommand } from './insert-link-by-quick-search.js';
export const commands: BlockCommands = {
insertBookmark: insertBookmarkCommand,
insertLinkByQuickSearch: insertLinkByQuickSearchCommand,
};

View File

@@ -5,7 +5,11 @@ import type { EmbedCardStyle } from '@blocksuite/affine-model';
import { EmbedOptionProvider } from '@blocksuite/affine-shared/services';
import type { Command } from '@blocksuite/block-std';
export const insertBookmarkCommand: Command<{ url: string }> = (ctx, next) => {
export const insertBookmarkCommand: Command<
never,
'insertedLinkType',
{ url: string }
> = (ctx, next) => {
const { url, std } = ctx;
const embedOptions = std.get(EmbedOptionProvider).getEmbedBlockOptions(url);

View File

@@ -1,15 +1,10 @@
import {
type InsertedLinkType,
insertEmbedLinkedDocCommand,
} from '@blocksuite/affine-block-embed';
import type { InsertedLinkType } from '@blocksuite/affine-block-embed';
import { QuickSearchProvider } from '@blocksuite/affine-shared/services';
import type { Command } from '@blocksuite/block-std';
import { insertBookmarkCommand } from './insert-bookmark';
export const insertLinkByQuickSearchCommand: Command<
{},
{ insertedLinkType: Promise<InsertedLinkType> }
never,
'insertedLinkType'
> = (ctx, next) => {
const { std } = ctx;
const quickSearchService = std.getOptional(QuickSearchProvider);
@@ -25,7 +20,7 @@ export const insertLinkByQuickSearchCommand: Command<
// add linked doc
if ('docId' in result) {
std.command.exec(insertEmbedLinkedDocCommand, {
std.command.exec('insertEmbedLinkedDoc', {
docId: result.docId,
params: result.params,
});
@@ -36,7 +31,7 @@ export const insertLinkByQuickSearchCommand: Command<
// add normal link;
if ('externalUrl' in result) {
std.command.exec(insertBookmarkCommand, { url: result.externalUrl });
std.command.exec('insertBookmark', { url: result.externalUrl });
return {
flavour: 'affine:bookmark',
};

View File

@@ -2,11 +2,11 @@ import { getEmbedCardIcons } from '@blocksuite/affine-block-embed';
import { WebIcon16 } from '@blocksuite/affine-components/icons';
import { ThemeProvider } from '@blocksuite/affine-shared/services';
import { getHostName } from '@blocksuite/affine-shared/utils';
import { BlockSelection, ShadowlessElement } from '@blocksuite/block-std';
import { ShadowlessElement } from '@blocksuite/block-std';
import { WithDisposable } from '@blocksuite/global/utils';
import { OpenInNewIcon } from '@blocksuite/icons/lit';
import { html } from 'lit';
import { property } from 'lit/decorators.js';
import { property, state } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js';
import type { BookmarkBlockComponent } from '../bookmark-block.js';
@@ -31,7 +31,7 @@ export class BookmarkCard extends WithDisposable(ShadowlessElement) {
private _selectBlock() {
const selectionManager = this.bookmark.host.selection;
const blockSelection = selectionManager.create(BlockSelection, {
const blockSelection = selectionManager.create('block', {
blockId: this.bookmark.blockId,
});
selectionManager.setGroup('note', [blockSelection]);
@@ -51,6 +51,14 @@ export class BookmarkCard extends WithDisposable(ShadowlessElement) {
.get(ThemeProvider)
.theme$.subscribe(() => this.requestUpdate())
);
this.disposables.add(
this.bookmark.selection.slots.changed.on(() => {
this._isSelected =
!!this.bookmark.selected?.is('block') ||
!!this.bookmark.selected?.is('surface');
})
);
}
override render() {
@@ -60,7 +68,7 @@ export class BookmarkCard extends WithDisposable(ShadowlessElement) {
loading: this.loading,
error: this.error,
[style]: true,
selected: this.bookmark.selected$.value,
selected: this._isSelected,
});
const domainName = url.match(
@@ -136,6 +144,9 @@ export class BookmarkCard extends WithDisposable(ShadowlessElement) {
`;
}
@state()
private accessor _isSelected = false;
@property({ attribute: false })
accessor bookmark!: BookmarkBlockComponent;

View File

@@ -1,4 +1,3 @@
import { stopPropagation } from '@blocksuite/affine-shared/utils';
import { type BlockComponent, ShadowlessElement } from '@blocksuite/block-std';
import { WithDisposable } from '@blocksuite/global/utils';
import type { BlockModel } from '@blocksuite/store';
@@ -49,9 +48,6 @@ export class EmbedCardEditCaptionEditModal extends WithDisposable(
.catch(console.error);
this.disposables.addFromEvent(this, 'keydown', this._onKeydown);
this.disposables.addFromEvent(this, 'cut', stopPropagation);
this.disposables.addFromEvent(this, 'copy', stopPropagation);
this.disposables.addFromEvent(this, 'paste', stopPropagation);
}
override render() {

View File

@@ -1,15 +1,21 @@
import { EdgelessCRUDIdentifier } from '@blocksuite/affine-block-surface';
import { toast } from '@blocksuite/affine-components/toast';
import type { EmbedCardStyle } from '@blocksuite/affine-model';
import {
EMBED_CARD_HEIGHT,
EMBED_CARD_WIDTH,
} from '@blocksuite/affine-shared/consts';
import { EmbedOptionProvider } from '@blocksuite/affine-shared/services';
import { isValidUrl, stopPropagation } from '@blocksuite/affine-shared/utils';
import { isValidUrl } from '@blocksuite/affine-shared/utils';
import type { EditorHost } from '@blocksuite/block-std';
import { ShadowlessElement } from '@blocksuite/block-std';
import { GfxControllerIdentifier } from '@blocksuite/block-std/gfx';
import { WithDisposable } from '@blocksuite/global/utils';
import { Bound, Vec, WithDisposable } from '@blocksuite/global/utils';
import type { BlockModel } from '@blocksuite/store';
import { html } from 'lit';
import { property, query, state } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js';
import { toast } from '../toast';
import { embedCardModalStyles } from './styles.js';
export class EmbedCardCreateModal extends WithDisposable(ShadowlessElement) {
@@ -49,13 +55,37 @@ export class EmbedCardCreateModal extends WithDisposable(ShadowlessElement) {
index
);
} else if (mode === 'edgeless') {
let flavour = 'affine:bookmark',
targetStyle: EmbedCardStyle = 'vertical';
if (embedOptions) {
flavour = embedOptions.flavour;
targetStyle = embedOptions.styles[0];
}
const gfx = this.host.std.get(GfxControllerIdentifier);
const crud = this.host.std.get(EdgelessCRUDIdentifier);
const viewport = gfx.viewport;
const surfaceModel = gfx.surface;
if (!surfaceModel) {
return;
}
this.createOptions.onSave(url);
const center = Vec.toVec(viewport.center);
crud.addBlock(
flavour,
{
url,
xywh: Bound.fromCenter(
center,
EMBED_CARD_WIDTH[targetStyle],
EMBED_CARD_HEIGHT[targetStyle]
).serialize(),
style: targetStyle,
},
surfaceModel
);
gfx.tool.setTool(
// @ts-expect-error FIXME: resolve after gfx tool refactor
@@ -92,9 +122,6 @@ export class EmbedCardCreateModal extends WithDisposable(ShadowlessElement) {
})
.catch(console.error);
this.disposables.addFromEvent(this, 'keydown', this._onDocumentKeydown);
this.disposables.addFromEvent(this, 'cut', stopPropagation);
this.disposables.addFromEvent(this, 'copy', stopPropagation);
this.disposables.addFromEvent(this, 'paste', stopPropagation);
}
override render() {
@@ -150,7 +177,6 @@ export class EmbedCardCreateModal extends WithDisposable(ShadowlessElement) {
}
| {
mode: 'edgeless';
onSave: (url: string) => void;
};
@property({ attribute: false })
@@ -181,7 +207,6 @@ export async function toggleEmbedCardCreateModal(
}
| {
mode: 'edgeless';
onSave: (url: string) => void;
}
): Promise<void> {
host.selection.clear();

View File

@@ -1,8 +1,16 @@
import type { AliasInfo, LinkableEmbedModel } from '@blocksuite/affine-model';
import {
EmbedLinkedDocBlockComponent,
EmbedSyncedDocBlockComponent,
} from '@blocksuite/affine-block-embed';
import {
notifyLinkedDocClearedAliases,
notifyLinkedDocSwitchedToCard,
} from '@blocksuite/affine-components/notification';
import { toast } from '@blocksuite/affine-components/toast';
import type { AliasInfo } from '@blocksuite/affine-model';
import {
EmbedLinkedDocModel,
EmbedSyncedDocModel,
isInternalEmbedModel,
} from '@blocksuite/affine-model';
import {
type LinkEventType,
@@ -29,7 +37,8 @@ import { choose } from 'lit/directives/choose.js';
import { classMap } from 'lit/directives/class-map.js';
import { live } from 'lit/directives/live.js';
import { toast } from '../toast';
import type { LinkableEmbedModel } from './type.js';
import { isInternalEmbedModel } from './type.js';
export class EmbedCardEditModal extends SignalWatcher(
WithDisposable(LitElement)
@@ -162,8 +171,14 @@ export class EmbedCardEditModal extends SignalWatcher(
this.model.doc.updateBlock(this.model, { title: null, description: null });
this.onReset?.(std, blockComponent);
if (
this.isEmbedLinkedDocModel &&
blockComponent instanceof EmbedLinkedDocBlockComponent
) {
blockComponent.refreshData();
notifyLinkedDocClearedAliases(std);
}
blockComponent.requestUpdate();
track(std, this.model, this.viewType, 'ResetedAlias', { control: 'reset' });
@@ -192,7 +207,17 @@ export class EmbedCardEditModal extends SignalWatcher(
const props: AliasInfo = { title };
if (description) props.description = description;
this.onSave?.(std, blockComponent, props);
if (
this.isEmbedSyncedDocModel &&
blockComponent instanceof EmbedSyncedDocBlockComponent
) {
blockComponent.convertToCard(props);
notifyLinkedDocSwitchedToCard(std);
} else {
this.model.doc.updateBlock(this.model, props);
blockComponent.requestUpdate();
}
track(std, this.model, this.viewType, 'SavedAlias', { control: 'save' });
@@ -254,7 +279,6 @@ export class EmbedCardEditModal extends SignalWatcher(
override connectedCallback() {
super.connectedCallback();
this.disposables.add(this.host.slots.unmounted.on(this._hide));
this._updateInfo();
}
@@ -281,9 +305,6 @@ export class EmbedCardEditModal extends SignalWatcher(
this.disposables.add(listenClickAway(this, this._hide));
this.disposables.addFromEvent(this, 'keydown', this._onKeydown);
this.disposables.addFromEvent(this, 'pointerdown', stopPropagation);
this.disposables.addFromEvent(this, 'cut', stopPropagation);
this.disposables.addFromEvent(this, 'copy', stopPropagation);
this.disposables.addFromEvent(this, 'paste', stopPropagation);
this.titleInput.focus();
this.titleInput.select();
@@ -368,20 +389,6 @@ export class EmbedCardEditModal extends SignalWatcher(
@property({ attribute: false })
accessor originalDocInfo: AliasInfo | undefined = undefined;
@property({ attribute: false })
accessor onReset:
| ((std: BlockStdScope, component: BlockComponent) => void)
| undefined = undefined;
@property({ attribute: false })
accessor onSave:
| ((
std: BlockStdScope,
component: BlockComponent,
props: AliasInfo
) => void)
| undefined = undefined;
accessor resetButtonDisabled$ = computed<boolean>(
() =>
!(
@@ -407,13 +414,7 @@ export function toggleEmbedCardEditModal(
host: EditorHost,
embedCardModel: LinkableEmbedModel,
viewType: string,
originalDocInfo?: AliasInfo,
onReset?: (std: BlockStdScope, component: BlockComponent) => void,
onSave?: (
std: BlockStdScope,
component: BlockComponent,
props: AliasInfo
) => void
originalDocInfo?: AliasInfo
) {
document.body.querySelector('embed-card-edit-modal')?.remove();
@@ -422,8 +423,6 @@ export function toggleEmbedCardEditModal(
embedCardEditModal.host = host;
embedCardEditModal.viewType = viewType;
embedCardEditModal.originalDocInfo = originalDocInfo;
embedCardEditModal.onReset = onReset;
embedCardEditModal.onSave = onSave;
document.body.append(embedCardEditModal);
}

View File

@@ -0,0 +1,4 @@
export * from './embed-card-caption-edit-modal';
export * from './embed-card-create-modal';
export * from './embed-card-edit-modal';
export * from './type';

View File

@@ -1,4 +1,3 @@
import { BookmarkBlockComponent } from '@blocksuite/affine-block-bookmark';
import {
EmbedFigmaBlockComponent,
EmbedGithubBlockComponent,
@@ -8,8 +7,22 @@ import {
EmbedSyncedDocBlockComponent,
EmbedYoutubeBlockComponent,
} from '@blocksuite/affine-block-embed';
import type {
BookmarkBlockModel,
EmbedFigmaModel,
EmbedGithubModel,
EmbedHtmlModel,
EmbedLoomModel,
EmbedYoutubeModel,
} from '@blocksuite/affine-model';
import {
EmbedLinkedDocModel,
EmbedSyncedDocModel,
} from '@blocksuite/affine-model';
import type { BlockComponent } from '@blocksuite/block-std';
import { BookmarkBlockComponent } from '../../bookmark-block';
export type ExternalEmbedBlockComponent =
| BookmarkBlockComponent
| EmbedFigmaBlockComponent
@@ -29,6 +42,19 @@ export type BuiltInEmbedBlockComponent =
| LinkableEmbedBlockComponent
| EmbedHtmlBlockComponent;
export type ExternalEmbedModel =
| BookmarkBlockModel
| EmbedFigmaModel
| EmbedGithubModel
| EmbedLoomModel
| EmbedYoutubeModel;
export type InternalEmbedModel = EmbedLinkedDocModel | EmbedSyncedDocModel;
export type LinkableEmbedModel = ExternalEmbedModel | InternalEmbedModel;
export type BuiltInEmbedModel = LinkableEmbedModel | EmbedHtmlModel;
export function isEmbedCardBlockComponent(
block: BlockComponent
): block is BuiltInEmbedBlockComponent {
@@ -43,3 +69,11 @@ export function isEmbedCardBlockComponent(
block instanceof EmbedSyncedDocBlockComponent
);
}
export function isInternalEmbedModel(
model: BuiltInEmbedModel
): model is InternalEmbedModel {
return (
model instanceof EmbedLinkedDocModel || model instanceof EmbedSyncedDocModel
);
}

View File

@@ -1 +1,2 @@
export * from './bookmark-card';
export * from './embed-card-modal';

View File

@@ -1,6 +1,14 @@
import { BookmarkBlockComponent } from './bookmark-block';
import { BookmarkEdgelessBlockComponent } from './bookmark-edgeless-block';
import type { BookmarkBlockService } from './bookmark-service';
import type { insertBookmarkCommand } from './commands/insert-bookmark';
import type { insertLinkByQuickSearchCommand } from './commands/insert-link-by-quick-search';
import { BookmarkCard } from './components/bookmark-card';
import {
EmbedCardCreateModal,
EmbedCardEditCaptionEditModal,
EmbedCardEditModal,
} from './components/embed-card-modal';
export function effects() {
customElements.define(
@@ -9,4 +17,23 @@ export function effects() {
);
customElements.define('affine-bookmark', BookmarkBlockComponent);
customElements.define('bookmark-card', BookmarkCard);
customElements.define('embed-card-create-modal', EmbedCardCreateModal);
customElements.define('embed-card-edit-modal', EmbedCardEditModal);
customElements.define(
'embed-card-caption-edit-modal',
EmbedCardEditCaptionEditModal
);
}
declare global {
namespace BlockSuite {
interface Commands {
insertBookmark: typeof insertBookmarkCommand;
insertLinkByQuickSearch: typeof insertLinkByQuickSearchCommand;
}
interface BlockServices {
'affine:bookmark': BookmarkBlockService;
}
}
}

View File

@@ -1,5 +1,5 @@
export * from './adapters';
export * from './bookmark-block';
export * from './bookmark-service';
export * from './bookmark-spec';
export * from './commands';
export * from './components';

View File

@@ -1,20 +1,19 @@
import {
EMBED_CARD_HEIGHT,
EMBED_CARD_WIDTH,
} from '@blocksuite/affine-shared/consts';
import { unsafeCSSVar } from '@blocksuite/affine-shared/theme';
import { baseTheme } from '@toeverything/theme';
import { css, unsafeCSS } from 'lit';
export const styles = css`
bookmark-card {
display: block;
height: 100%;
width: 100%;
}
.affine-bookmark-card {
container: affine-bookmark-card / inline-size;
margin: 0 auto;
box-sizing: border-box;
display: flex;
width: 100%;
height: ${EMBED_CARD_HEIGHT.horizontal}px;
border-radius: 8px;
border: 1px solid var(--affine-background-tertiary-color);
@@ -188,6 +187,8 @@ export const styles = css`
}
.affine-bookmark-card.list {
height: ${EMBED_CARD_HEIGHT.list}px;
.affine-bookmark-content {
width: 100%;
flex-direction: row;
@@ -214,8 +215,9 @@ export const styles = css`
}
.affine-bookmark-card.vertical {
width: ${EMBED_CARD_WIDTH.vertical}px;
height: ${EMBED_CARD_HEIGHT.vertical}px;
flex-direction: column-reverse;
height: 100%;
.affine-bookmark-content {
width: 100%;
@@ -246,6 +248,9 @@ export const styles = css`
}
.affine-bookmark-card.cube {
width: ${EMBED_CARD_WIDTH.cube}px;
height: ${EMBED_CARD_HEIGHT.cube}px;
.affine-bookmark-content {
width: 100%;
flex-direction: column;

View File

@@ -1,5 +1,5 @@
import { LinkPreviewerService } from '@blocksuite/affine-shared/services';
import { isAbortError } from '@blocksuite/affine-shared/utils';
import { assertExists } from '@blocksuite/global/utils';
import type { BookmarkBlockComponent } from './bookmark-block.js';
@@ -15,8 +15,10 @@ export async function refreshBookmarkUrlData(
try {
bookmarkElement.loading = true;
const linkPreviewer = bookmarkElement.doc.get(LinkPreviewerService);
const bookmarkUrlData = await linkPreviewer.query(
const queryUrlData = bookmarkElement.service?.queryUrlData;
assertExists(queryUrlData);
const bookmarkUrlData = await queryUrlData(
bookmarkElement.model.url,
signal
);

View File

@@ -1,19 +1,26 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./dist",
"tsBuildInfoFile": "./dist/tsconfig.tsbuildinfo"
"rootDir": "./src/",
"outDir": "./dist/",
"noEmit": false
},
"include": ["./src"],
"references": [
{ "path": "../block-embed" },
{ "path": "../components" },
{ "path": "../model" },
{ "path": "../shared" },
{ "path": "../../framework/block-std" },
{ "path": "../../framework/global" },
{ "path": "../../framework/inline" },
{ "path": "../../framework/store" }
{
"path": "../../framework"
},
{
"path": "../model"
},
{
"path": "../components"
},
{
"path": "../shared"
},
{
"path": "../block-embed"
}
]
}

View File

@@ -18,17 +18,16 @@
"@blocksuite/affine-shared": "workspace:*",
"@blocksuite/block-std": "workspace:*",
"@blocksuite/global": "workspace:*",
"@blocksuite/icons": "^2.2.3",
"@blocksuite/inline": "workspace:*",
"@blocksuite/store": "workspace:*",
"@floating-ui/dom": "^1.6.13",
"@floating-ui/dom": "^1.6.10",
"@lit/context": "^1.1.2",
"@preact/signals-core": "^1.8.0",
"@toeverything/theme": "^1.1.12",
"@toeverything/theme": "^1.1.3",
"@types/mdast": "^4.0.4",
"lit": "^3.2.0",
"minimatch": "^10.0.1",
"shiki": "^2.0.0",
"shiki": "^1.14.1",
"zod": "^3.23.8"
},
"exports": {
@@ -41,5 +40,5 @@
"!src/__tests__",
"!dist/__tests__"
],
"version": "0.20.0"
"version": "0.19.0"
}

View File

@@ -1,4 +1,4 @@
import type { ExtensionType } from '@blocksuite/store';
import type { ExtensionType } from '@blocksuite/block-std';
import { CodeBlockHtmlAdapterExtension } from './html.js';
import { CodeBlockMarkdownAdapterExtension } from './markdown.js';

View File

@@ -2,7 +2,6 @@ import { CodeBlockSchema } from '@blocksuite/affine-model';
import {
BlockHtmlAdapterExtension,
type BlockHtmlAdapterMatcher,
CODE_BLOCK_WRAP_KEY,
HastUtils,
} from '@blocksuite/affine-shared/adapters';
import type { DeltaInsert } from '@blocksuite/inline';
@@ -38,8 +37,7 @@ export const codeBlockHtmlAdapterMatcher: BlockHtmlAdapterMatcher = {
? codeLang.replace('code-', '')
: undefined;
const { walkerContext, deltaConverter, configs } = context;
const wrap = configs.get(CODE_BLOCK_WRAP_KEY) === 'true';
const { walkerContext, deltaConverter } = context;
walkerContext
.openNode(
{
@@ -48,7 +46,6 @@ export const codeBlockHtmlAdapterMatcher: BlockHtmlAdapterMatcher = {
flavour: 'affine:code',
props: {
language: codeLang ?? 'Plain Text',
wrap,
text: {
'$blocksuite:internal:text$': true,
delta: deltaConverter.astToDelta(codeText, {

View File

@@ -2,7 +2,6 @@ import { CodeBlockSchema } from '@blocksuite/affine-model';
import {
BlockMarkdownAdapterExtension,
type BlockMarkdownAdapterMatcher,
CODE_BLOCK_WRAP_KEY,
type MarkdownAST,
} from '@blocksuite/affine-shared/adapters';
import type { DeltaInsert } from '@blocksuite/inline';
@@ -20,8 +19,7 @@ export const codeBlockMarkdownAdapterMatcher: BlockMarkdownAdapterMatcher = {
if (!isCodeNode(o.node)) {
return;
}
const { walkerContext, configs } = context;
const wrap = configs.get(CODE_BLOCK_WRAP_KEY) === 'true';
const { walkerContext } = context;
walkerContext
.openNode(
{
@@ -30,7 +28,6 @@ export const codeBlockMarkdownAdapterMatcher: BlockMarkdownAdapterMatcher = {
flavour: 'affine:code',
props: {
language: o.node.lang ?? 'Plain Text',
wrap,
text: {
'$blocksuite:internal:text$': true,
delta: [

View File

@@ -2,7 +2,6 @@ import { CodeBlockSchema } from '@blocksuite/affine-model';
import {
BlockNotionHtmlAdapterExtension,
type BlockNotionHtmlAdapterMatcher,
CODE_BLOCK_WRAP_KEY,
HastUtils,
} from '@blocksuite/affine-shared/adapters';
import { nanoid } from '@blocksuite/store';
@@ -21,8 +20,7 @@ export const codeBlockNotionHtmlAdapterMatcher: BlockNotionHtmlAdapterMatcher =
if (!code) {
return;
}
const { walkerContext, deltaConverter, configs } = context;
const wrap = configs.get(CODE_BLOCK_WRAP_KEY) === 'true';
const { walkerContext, deltaConverter } = context;
const codeText =
code.children.length === 1 && code.children[0].type === 'text'
? code.children[0]
@@ -35,7 +33,6 @@ export const codeBlockNotionHtmlAdapterMatcher: BlockNotionHtmlAdapterMatcher =
flavour: CodeBlockSchema.model.flavour,
props: {
language: 'Plain Text',
wrap,
text: {
'$blocksuite:internal:text$': true,
delta: deltaConverter.astToDelta(codeText, {

View File

@@ -1,14 +1,8 @@
import { deleteTextCommand } from '@blocksuite/affine-components/rich-text';
import {
HtmlAdapter,
pasteMiddleware,
PlainTextAdapter,
} from '@blocksuite/affine-shared/adapters';
import {
getBlockIndexCommand,
getBlockSelectionsCommand,
getTextSelectionCommand,
} from '@blocksuite/affine-shared/commands';
import {
type BlockComponent,
Clipboard,
@@ -42,17 +36,17 @@ export class CodeClipboardController {
const e = ctx.get('clipboardState').raw;
e.preventDefault();
this._std.store.captureSync();
this._std.doc.captureSync();
this._std.command
.chain()
.try(cmd => [
cmd.pipe(getTextSelectionCommand).pipe((ctx, next) => {
cmd.getTextSelection().inline<'currentSelectionPath'>((ctx, next) => {
const textSelection = ctx.currentTextSelection;
if (!textSelection) return;
const end = textSelection.to ?? textSelection.from;
next({ currentSelectionPath: end.blockId });
}),
cmd.pipe(getBlockSelectionsCommand).pipe((ctx, next) => {
cmd.getBlockSelections().inline<'currentSelectionPath'>((ctx, next) => {
const currentBlockSelections = ctx.currentBlockSelections;
if (!currentBlockSelections) return;
const blockSelection = currentBlockSelections.at(-1);
@@ -60,16 +54,16 @@ export class CodeClipboardController {
next({ currentSelectionPath: blockSelection.blockId });
}),
])
.pipe(getBlockIndexCommand)
.try(cmd => [cmd.pipe(getTextSelectionCommand).pipe(deleteTextCommand)])
.pipe((ctx, next) => {
.getBlockIndex()
.try(cmd => [cmd.getTextSelection().deleteText()])
.inline((ctx, next) => {
if (!ctx.parentBlock) {
return;
}
this._clipboard
.paste(
e,
this._std.store,
this._std.doc,
ctx.parentBlock.model.id,
ctx.blockIndex ? ctx.blockIndex + 1 : 1
)

View File

@@ -1,4 +1,3 @@
import { ConfigExtensionFactory } from '@blocksuite/block-std';
import type { BundledLanguageInfo, ThemeInput } from 'shiki';
export interface CodeBlockConfig {
@@ -14,6 +13,3 @@ export interface CodeBlockConfig {
*/
showLineNumbers?: boolean;
}
export const CodeBlockConfigExtension =
ConfigExtensionFactory<CodeBlockConfig>('affine:code');

View File

@@ -6,13 +6,11 @@ import { type Signal, signal } from '@preact/signals-core';
import {
bundledLanguagesInfo,
createHighlighterCore,
createOnigurumaEngine,
type HighlighterCore,
type MaybeGetter,
} from 'shiki';
import getWasm from 'shiki/wasm';
import { CodeBlockConfigExtension } from './code-block-config.js';
import {
CODE_BLOCK_DEFAULT_DARK_THEME,
CODE_BLOCK_DEFAULT_LIGHT_THEME,
@@ -28,10 +26,7 @@ export class CodeBlockService extends BlockService {
highlighter$: Signal<HighlighterCore | null> = signal(null);
get langs() {
return (
this.std.getOptional(CodeBlockConfigExtension.identifier)?.langs ??
bundledLanguagesInfo
);
return this.std.getConfig('affine:code')?.langs ?? bundledLanguagesInfo;
}
get themeKey() {
@@ -47,12 +42,10 @@ export class CodeBlockService extends BlockService {
this.bindHotKey(textKeymap(this.std));
createHighlighterCore({
engine: createOnigurumaEngine(() => getWasm),
loadWasm: getWasm,
})
.then(async highlighter => {
const config = this.std.getOptional(
CodeBlockConfigExtension.identifier
);
const config = this.std.getConfig('affine:code');
const darkTheme = config?.theme?.dark ?? CODE_BLOCK_DEFAULT_DARK_THEME;
const lightTheme =
config?.theme?.light ?? CODE_BLOCK_DEFAULT_LIGHT_THEME;

View File

@@ -1,9 +1,9 @@
import {
BlockViewExtension,
type ExtensionType,
FlavourExtension,
WidgetViewExtension,
WidgetViewMapExtension,
} from '@blocksuite/block-std';
import type { ExtensionType } from '@blocksuite/store';
import { literal, unsafeStatic } from 'lit/static-html.js';
import { CodeBlockAdapterExtensions } from './adapters/extension.js';
@@ -14,17 +14,13 @@ import {
import { CodeBlockService } from './code-block-service.js';
import { AFFINE_CODE_TOOLBAR_WIDGET } from './code-toolbar/index.js';
export const codeToolbarWidget = WidgetViewExtension(
'affine:code',
AFFINE_CODE_TOOLBAR_WIDGET,
literal`${unsafeStatic(AFFINE_CODE_TOOLBAR_WIDGET)}`
);
export const CodeBlockSpec: ExtensionType[] = [
FlavourExtension('affine:code'),
CodeBlockService,
BlockViewExtension('affine:code', literal`affine-code`),
codeToolbarWidget,
WidgetViewMapExtension('affine:code', {
codeToolbar: literal`${unsafeStatic(AFFINE_CODE_TOOLBAR_WIDGET)}`,
}),
CodeBlockInlineManagerExtension,
CodeBlockUnitSpecExtension,
CodeBlockAdapterExtensions,

View File

@@ -11,12 +11,8 @@ import {
} from '@blocksuite/affine-shared/services';
import { getViewportElement } from '@blocksuite/affine-shared/utils';
import type { BlockComponent } from '@blocksuite/block-std';
import {
BlockSelection,
getInlineRangeProvider,
TextSelection,
} from '@blocksuite/block-std';
import { IS_MAC, IS_MOBILE } from '@blocksuite/global/env';
import { getInlineRangeProvider } from '@blocksuite/block-std';
import { IS_MAC } from '@blocksuite/global/env';
import { noop } from '@blocksuite/global/utils';
import {
INLINE_ROOT_ATTR,
@@ -32,7 +28,6 @@ import { classMap } from 'lit/directives/class-map.js';
import type { ThemedToken } from 'shiki';
import { CodeClipboardController } from './clipboard/index.js';
import { CodeBlockConfigExtension } from './code-block-config.js';
import { CodeBlockInlineManagerExtension } from './code-block-inline.js';
import type { CodeBlockService } from './code-block-service.js';
import { codeBlockStyles } from './styles.js';
@@ -183,7 +178,7 @@ export class CodeBlockComponent extends CaptionedBlockComponent<
this.bindHotKey({
Backspace: ctx => {
const state = ctx.get('keyboardState');
const textSelection = selectionManager.find(TextSelection);
const textSelection = selectionManager.find('text');
if (!textSelection) {
state.raw.preventDefault();
return;
@@ -194,7 +189,7 @@ export class CodeBlockComponent extends CaptionedBlockComponent<
if (from.index === 0 && from.length === 0) {
state.raw.preventDefault();
selectionManager.setGroup('note', [
selectionManager.create(BlockSelection, { blockId: this.blockId }),
selectionManager.create('block', { blockId: this.blockId }),
]);
return true;
}
@@ -384,14 +379,12 @@ export class CodeBlockComponent extends CaptionedBlockComponent<
override renderBlock(): TemplateResult<1> {
const showLineNumbers =
this.std.getOptional(CodeBlockConfigExtension.identifier)
?.showLineNumbers ?? true;
this.std.getConfig('affine:code')?.showLineNumbers ?? true;
return html`
<div
class=${classMap({
'affine-code-block-container': true,
mobile: IS_MOBILE,
wrap: this.model.wrap,
})}
>

View File

@@ -1,3 +1,4 @@
import { MoreVerticalIcon } from '@blocksuite/affine-components/icons';
import { createLitPortal } from '@blocksuite/affine-components/portal';
import type {
EditorIconButton,
@@ -5,7 +6,6 @@ import type {
} from '@blocksuite/affine-components/toolbar';
import { renderGroups } from '@blocksuite/affine-components/toolbar';
import { noop, WithDisposable } from '@blocksuite/global/utils';
import { MoreVerticalIcon } from '@blocksuite/icons/lit';
import { flip, offset } from '@floating-ui/dom';
import { css, html, LitElement } from 'lit';
import { property, query, state } from 'lit/decorators.js';
@@ -17,19 +17,15 @@ export class AffineCodeToolbar extends WithDisposable(LitElement) {
static override styles = css`
:host {
position: absolute;
width: 100%;
top: 0;
left: 0;
right: 0;
}
.code-toolbar-container {
width: auto;
height: 24px;
gap: 4px;
padding: 4px;
margin: 0;
display: flex;
justify-content: flex-end;
}
.code-toolbar-button {
@@ -132,7 +128,7 @@ export class AffineCodeToolbar extends WithDisposable(LitElement) {
?disabled=${this.context.doc.readonly}
@click=${() => this._toggleMoreMenu()}
>
${MoreVerticalIcon()}
${MoreVerticalIcon}
</editor-icon-button>
</editor-toolbar>
`;

View File

@@ -17,10 +17,6 @@ export class LanguageListButton extends WithDisposable(
SignalWatcher(LitElement)
) {
static override styles = css`
:host {
margin-right: auto;
}
.lang-button {
background-color: var(--affine-background-primary-color);
box-shadow: var(--affine-shadow-1);

View File

@@ -8,7 +8,6 @@ import {
} from '@blocksuite/affine-components/icons';
import type { MenuItemGroup } from '@blocksuite/affine-components/toolbar';
import { isInsidePageEditor } from '@blocksuite/affine-shared/utils';
import { BlockSelection } from '@blocksuite/block-std';
import { noop, sleep } from '@blocksuite/global/utils';
import { html } from 'lit';
import { ifDefined } from 'lit/directives/if-defined.js';
@@ -135,7 +134,7 @@ export const clipboardGroup: MenuItemGroup<CodeBlockToolbarContext> = {
host.updateComplete
.then(() => {
host.selection.setGroup('note', [
host.selection.create(BlockSelection, {
host.selection.create('block', {
blockId: codeId,
}),
]);

View File

@@ -9,12 +9,8 @@ import {
} from '@blocksuite/affine-components/toolbar';
import type { CodeBlockModel } from '@blocksuite/affine-model';
import { PAGE_HEADER_HEIGHT } from '@blocksuite/affine-shared/consts';
import {
BlockSelection,
TextSelection,
WidgetComponent,
} from '@blocksuite/block-std';
import { limitShift, shift, size } from '@floating-ui/dom';
import { WidgetComponent } from '@blocksuite/block-std';
import { limitShift, shift } from '@floating-ui/dom';
import { html } from 'lit';
import type { CodeBlockComponent } from '../code-block.js';
@@ -38,7 +34,7 @@ export class AffineCodeToolbarWidget extends WidgetComponent<
const codeBlock = this.block;
const selection = this.host.selection;
const textSelection = selection.find(TextSelection);
const textSelection = selection.find('text');
if (
!!textSelection &&
(!!textSelection.to || !!textSelection.from.length)
@@ -46,7 +42,7 @@ export class AffineCodeToolbarWidget extends WidgetComponent<
return null;
}
const blockSelections = selection.filter(BlockSelection);
const blockSelections = selection.filter('block');
if (
blockSelections.length > 1 ||
(blockSelections.length === 1 &&
@@ -82,13 +78,8 @@ export class AffineCodeToolbarWidget extends WidgetComponent<
},
computePosition: {
referenceElement: codeBlock,
placement: 'top',
placement: 'right-start',
middleware: [
size({
apply({ rects, elements }) {
elements.floating.style.width = `${rects.reference.width}px`;
},
}),
shift({
crossAxis: true,
padding: {

View File

@@ -1,4 +1,7 @@
import type * as CommandsType from '@blocksuite/affine-shared/commands';
import { CodeBlockComponent } from './code-block';
import type { CodeBlockConfig } from './code-block-config';
import {
AFFINE_CODE_TOOLBAR_WIDGET,
AffineCodeToolbarWidget,
@@ -15,7 +18,15 @@ export function effects() {
customElements.define('affine-code', CodeBlockComponent);
}
declare type _GLOBAL_ = typeof CommandsType;
declare global {
namespace BlockSuite {
interface BlockConfigs {
'affine:code': CodeBlockConfig;
}
}
interface HTMLElementTagNameMap {
'language-list-button': LanguageListButton;
'affine-code-toolbar': AffineCodeToolbar;

View File

@@ -9,16 +9,12 @@ export const codeBlockStyles = css`
font-size: var(--affine-font-xs);
line-height: var(--affine-line-height);
position: relative;
padding: 28px 24px;
padding: 12px;
background: var(--affine-background-code-block);
border-radius: 10px;
box-sizing: border-box;
}
.affine-code-block-container.mobile {
padding: 12px;
}
.affine-code-block-container .inline-editor {
font-family: var(--affine-font-code-family);
font-variant-ligatures: none;

View File

@@ -1,18 +1,23 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./dist",
"tsBuildInfoFile": "./dist/tsconfig.tsbuildinfo"
"rootDir": "./src/",
"outDir": "./dist/",
"noEmit": false
},
"include": ["./src"],
"references": [
{ "path": "../components" },
{ "path": "../model" },
{ "path": "../shared" },
{ "path": "../../framework/block-std" },
{ "path": "../../framework/global" },
{ "path": "../../framework/inline" },
{ "path": "../../framework/store" }
{
"path": "../../framework"
},
{
"path": "../model"
},
{
"path": "../components"
},
{
"path": "../shared"
}
]
}

View File

@@ -20,13 +20,12 @@
"@blocksuite/block-std": "workspace:*",
"@blocksuite/data-view": "workspace:*",
"@blocksuite/global": "workspace:*",
"@blocksuite/icons": "^2.2.3",
"@blocksuite/inline": "workspace:*",
"@blocksuite/store": "workspace:*",
"@floating-ui/dom": "^1.6.13",
"@floating-ui/dom": "^1.6.10",
"@lit/context": "^1.1.2",
"@preact/signals-core": "^1.8.0",
"@toeverything/theme": "^1.1.12",
"@toeverything/theme": "^1.1.3",
"@types/mdast": "^4.0.4",
"lit": "^3.2.0",
"minimatch": "^10.0.1",
@@ -42,5 +41,5 @@
"!src/__tests__",
"!dist/__tests__"
],
"version": "0.20.0"
"version": "0.19.0"
}

View File

@@ -53,7 +53,7 @@ todoMeta.addProperty({
metaConfig: propertyPresets.textPropertyConfig,
get: block => block.doc.meta?.title ?? '',
updated: (block, callback) => {
return block.doc.workspace.slots.docListUpdated.on(() => {
return block.doc.collection.meta.docMetaUpdated.on(() => {
callback();
});
},

View File

@@ -11,7 +11,7 @@ import type { EditorHost } from '@blocksuite/block-std';
import { DataSourceBase, type PropertyMetaConfig } from '@blocksuite/data-view';
import { propertyPresets } from '@blocksuite/data-view/property-presets';
import { assertExists, Slot } from '@blocksuite/global/utils';
import type { Block, Store } from '@blocksuite/store';
import type { Block, Doc } from '@blocksuite/store';
import type { BlockMeta } from './block-meta/base.js';
import { blockMetaMap } from './block-meta/index.js';
@@ -59,7 +59,7 @@ export class BlockQueryDataSource extends DataSourceBase {
}
get workspace() {
return this.host.doc.workspace;
return this.host.doc.collection;
}
constructor(
@@ -73,16 +73,16 @@ export class BlockQueryDataSource extends DataSourceBase {
this.columnMetaMap.set(property.metaConfig.type, property.metaConfig);
}
for (const collection of this.workspace.docs.values()) {
for (const block of Object.values(collection.getStore().blocks.peek())) {
for (const block of Object.values(collection.getDoc().blocks.peek())) {
if (this.meta.selector(block)) {
this.blockMap.set(block.id, block);
}
}
}
this.workspace.docs.forEach(doc => {
this.listenToDoc(doc.getStore());
this.listenToDoc(doc.getDoc());
});
this.workspace.slots.docCreated.on(id => {
this.workspace.slots.docAdded.on(id => {
const doc = this.workspace.getDoc(id);
if (doc) {
this.listenToDoc(doc);
@@ -140,7 +140,7 @@ export class BlockQueryDataSource extends DataSourceBase {
return this.block.columns.find(v => v.id === id);
}
listenToDoc(doc: Store) {
listenToDoc(doc: Doc) {
this.docDisposeMap.set(
doc.id,
doc.slots.blockUpdated.on(v => {
@@ -167,7 +167,7 @@ export class BlockQueryDataSource extends DataSourceBase {
type ?? propertyPresets.multiSelectPropertyConfig.type
].create(this.newColumnName());
const id = doc.workspace.idGenerator();
const id = doc.generateBlockId();
if (this.block.columns.some(v => v.id === id)) {
return id;
}
@@ -211,7 +211,7 @@ export class BlockQueryDataSource extends DataSourceBase {
}
}
propertyDuplicate(_columnId: string): string | undefined {
propertyDuplicate(_columnId: string): string {
throw new Error('Method not implemented.');
}

View File

@@ -1,15 +1,15 @@
import {
BlockRenderer,
DatabaseSelection,
NoteRenderer,
} from '@blocksuite/affine-block-database';
import { BlockRenderer, NoteRenderer } from '@blocksuite/affine-block-database';
import { CaptionedBlockComponent } from '@blocksuite/affine-components/caption';
import {
menu,
popMenu,
popupTargetFromElement,
} from '@blocksuite/affine-components/context-menu';
import { CopyIcon, DeleteIcon } from '@blocksuite/affine-components/icons';
import {
CopyIcon,
DeleteIcon,
MoreHorizontalIcon,
} from '@blocksuite/affine-components/icons';
import { PeekViewProvider } from '@blocksuite/affine-components/peek';
import { toast } from '@blocksuite/affine-components/toast';
import { NOTE_SELECTOR } from '@blocksuite/affine-shared/consts';
@@ -26,6 +26,7 @@ import {
import {
createRecordDetail,
createUniComponentFromWebComponent,
DatabaseSelection,
type DataSource,
DataView,
dataViewCommonStyle,
@@ -38,7 +39,6 @@ import {
uniMap,
} from '@blocksuite/data-view';
import { widgetPresets } from '@blocksuite/data-view/widget-presets';
import { MoreHorizontalIcon } from '@blocksuite/icons/lit';
import { Slice } from '@blocksuite/store';
import { computed, signal } from '@preact/signals-core';
import { css, nothing, unsafeCSS } from 'lit';
@@ -241,7 +241,7 @@ export class DataViewBlockComponent extends CaptionedBlockComponent<DataViewBloc
return nothing;
}
return html` <div class="database-ops" @click="${this._clickDatabaseOps}">
${MoreHorizontalIcon()}
${MoreHorizontalIcon}
</div>`;
}

View File

@@ -5,11 +5,7 @@ import {
type InsertToPosition,
} from '@blocksuite/affine-shared/utils';
import type { DataViewDataType } from '@blocksuite/data-view';
import {
BlockModel,
BlockSchemaExtension,
defineBlockSchema,
} from '@blocksuite/store';
import { BlockModel, defineBlockSchema } from '@blocksuite/store';
type Props = {
title: string;
@@ -37,7 +33,7 @@ export class DataViewBlockModel extends BlockModel<Props> {
}
duplicateView(id: string): string {
const newId = this.doc.workspace.idGenerator();
const newId = this.doc.generateBlockId();
this.doc.transact(() => {
const index = this.views.findIndex(v => v.id === id);
const view = this.views[index];
@@ -97,6 +93,3 @@ export const DataViewBlockSchema = defineBlockSchema({
return new DataViewBlockModel();
},
});
export const DataViewBlockSchemaExtension =
BlockSchemaExtension(DataViewBlockSchema);

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