mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-02 18:20:39 +08:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 67f875e32d | |||
| a2b9b89695 | |||
| 8fbc03d631 | |||
| 26a7f1a75d | |||
| 94c45d7ea3 | |||
| 2bb3504b9e | |||
| 6f3f291b18 | |||
| 55fffe0762 | |||
| 96a0650841 | |||
| e96f89c26b |
@@ -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)" && \
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
DATABASE_LOCATION=./postgres
|
||||
DB_PASSWORD=affine
|
||||
DB_USERNAME=affine
|
||||
DB_DATABASE_NAME=affine
|
||||
@@ -1,2 +1 @@
|
||||
/blocksuite/ @toeverything/blocksuite-core
|
||||
/packages/frontend/core/src/blocksuite @toeverything/blocksuite-core
|
||||
|
||||
@@ -19,6 +19,8 @@ const {
|
||||
COPILOT_FAL_API_KEY,
|
||||
COPILOT_PERPLEXITY_API_KEY,
|
||||
COPILOT_UNSPLASH_API_KEY,
|
||||
SLACK_BOT_TOKEN,
|
||||
RELEASE_SLACK_CHANNEL_ID,
|
||||
MAILER_SENDER,
|
||||
MAILER_USER,
|
||||
MAILER_PASSWORD,
|
||||
@@ -47,21 +49,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 +69,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 +113,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}"`,
|
||||
]
|
||||
: [];
|
||||
|
||||
@@ -156,6 +152,8 @@ const createHelmCommand = ({ isDryRun }) => {
|
||||
`--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.copilot.slack.botToken="${SLACK_BOT_TOKEN}"`,
|
||||
`--set-string graphql.app.copilot.slack.channelId="${RELEASE_SLACK_CHANNEL_ID}"`,
|
||||
`--set-string graphql.app.mailer.sender="${MAILER_SENDER}"`,
|
||||
`--set-string graphql.app.mailer.user="${MAILER_USER}"`,
|
||||
`--set-string graphql.app.mailer.password="${MAILER_PASSWORD}"`,
|
||||
@@ -174,9 +172,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`,
|
||||
|
||||
@@ -3,4 +3,4 @@ name: affine
|
||||
description: AFFiNE cloud chart
|
||||
type: application
|
||||
version: 0.0.0
|
||||
appVersion: "0.20.0"
|
||||
appVersion: "0.19.0"
|
||||
|
||||
@@ -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
|
||||
@@ -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 }}
|
||||
@@ -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 }}
|
||||
@@ -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 }}
|
||||
@@ -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 }}
|
||||
@@ -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 }}
|
||||
@@ -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
|
||||
@@ -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: {}
|
||||
@@ -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
|
||||
|
||||
@@ -9,4 +9,6 @@ data:
|
||||
falSecret: {{ .Values.app.copilot.fal.key | b64enc }}
|
||||
perplexitySecret: {{ .Values.app.copilot.perplexity.key | b64enc }}
|
||||
unsplashSecret: {{ .Values.app.copilot.unsplash.key | b64enc }}
|
||||
slackBotToken: {{ .Values.app.copilot.slack.botToken | b64enc }}
|
||||
slackChannelId: {{ .Values.app.copilot.slack.channelId | b64enc }}
|
||||
{{- end }}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
{{ if .Values.app.copilot.enabled }}
|
||||
apiVersion: batch/v1
|
||||
kind: CronJob
|
||||
metadata:
|
||||
name: {{ include "graphql.fullname" . }}-copilot-test
|
||||
labels:
|
||||
{{- include "graphql.labels" . | nindent 4 }}
|
||||
annotations:
|
||||
"helm.sh/hook": post-install,post-upgrade
|
||||
"helm.sh/hook-weight": "1"
|
||||
"helm.sh/hook-delete-policy": before-hook-creation
|
||||
spec:
|
||||
schedule: "0 8 * * *"
|
||||
jobTemplate:
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
serviceAccountName: {{ include "graphql.serviceAccountName" . }}
|
||||
containers:
|
||||
- name: {{ .Chart.Name }}
|
||||
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
|
||||
command: ["yarn", "test:copilot:e2e:cron"]
|
||||
env:
|
||||
- name: AFFINE_ENV
|
||||
value: "{{ .Release.Namespace }}"
|
||||
- name: SLACK_BOT_TOKEN
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: "{{ .Values.app.copilot.secretName }}"
|
||||
key: slackBotToken
|
||||
- name: CHANNEL_ID
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: "{{ .Values.app.copilot.secretName }}"
|
||||
key: slackChannelId
|
||||
- name: COPILOT_E2E_ENDPOINT
|
||||
value: "http://{{ include "graphql.fullname" . }}.{{ .Release.Namespace }}.svc.cluster.local:3000"
|
||||
- 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: COPILOT_OPENAI_API_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: "{{ .Values.app.copilot.secretName }}"
|
||||
key: openaiSecret
|
||||
- name: COPILOT_FAL_API_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: "{{ .Values.app.copilot.secretName }}"
|
||||
key: falSecret
|
||||
- name: COPILOT_UNSPLASH_API_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: "{{ .Values.app.copilot.secretName }}"
|
||||
key: unsplashSecret
|
||||
resources:
|
||||
requests:
|
||||
cpu: '100m'
|
||||
memory: '200Mi'
|
||||
restartPolicy: Never
|
||||
backoffLimit: 1
|
||||
{{ end }}
|
||||
@@ -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"
|
||||
|
||||
@@ -17,6 +17,8 @@ app:
|
||||
# AFFINE_SERVER_HOST
|
||||
host: '0.0.0.0'
|
||||
https: true
|
||||
doc:
|
||||
mergeInterval: "3000"
|
||||
captcha:
|
||||
enabled: false
|
||||
secretName: captcha
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
extra-flags: workspaces focus @affine/server
|
||||
- name: Build Server
|
||||
run: |
|
||||
find packages/backend/server -type d -name "__tests__" -exec rm -rf {} +
|
||||
rm -rf packages/backend/server/src/__tests__
|
||||
yarn workspace @affine/server build
|
||||
- name: Upload server dist
|
||||
uses: actions/upload-artifact@v4
|
||||
|
||||
@@ -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
|
||||
@@ -534,7 +504,8 @@ jobs:
|
||||
filters: |
|
||||
changed:
|
||||
- 'packages/backend/server/src/plugins/copilot/**'
|
||||
- 'packages/backend/server/tests/copilot.*'
|
||||
- 'packages/backend/server/tests/copilot*'
|
||||
- 'tests/affine-cloud-copilot/**'
|
||||
|
||||
- name: Setup Node.js
|
||||
if: ${{ steps.check-blocksuite-update.outputs.skip != 'true' || steps.apifilter.outputs.changed == 'true' }}
|
||||
@@ -556,7 +527,7 @@ jobs:
|
||||
|
||||
- name: Run server tests
|
||||
if: ${{ steps.check-blocksuite-update.outputs.skip != 'true' || steps.apifilter.outputs.changed == 'true' }}
|
||||
run: yarn affine @affine/server test:copilot:coverage --forbid-only
|
||||
run: yarn affine @affine/server test:copilot:spec:coverage --forbid-only
|
||||
env:
|
||||
CARGO_TARGET_DIR: '${{ github.workspace }}/target'
|
||||
COPILOT_OPENAI_API_KEY: ${{ secrets.COPILOT_OPENAI_API_KEY }}
|
||||
@@ -624,7 +595,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
|
||||
@@ -669,16 +641,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
|
||||
@@ -741,10 +709,10 @@ jobs:
|
||||
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 +834,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 +856,6 @@ jobs:
|
||||
needs:
|
||||
- analyze
|
||||
- lint
|
||||
- lint-rust
|
||||
- check-yarn-binary
|
||||
- e2e-test
|
||||
- e2e-legacy-blocksuite-test
|
||||
|
||||
@@ -38,6 +38,16 @@ jobs:
|
||||
DISTRIBUTION: web
|
||||
DATABASE_URL: postgresql://affine:affine@localhost:5432/affine
|
||||
REDIS_SERVER_HOST: localhost
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
spec:
|
||||
- {
|
||||
name: e2e,
|
||||
package: '@affine-test/affine-cloud-copilot',
|
||||
type: e2e,
|
||||
}
|
||||
- { name: spec, package: '@affine/server', type: copilot:spec }
|
||||
services:
|
||||
postgres:
|
||||
image: postgres
|
||||
@@ -78,13 +88,14 @@ jobs:
|
||||
- name: Prepare Server Test Environment
|
||||
uses: ./.github/actions/server-test-env
|
||||
|
||||
- name: Run server tests
|
||||
run: yarn affine @affine/server test:copilot:coverage --forbid-only
|
||||
- name: Run copilot api ${{ matrix.spec.name }} tests
|
||||
run: yarn affine ${{ matrix.spec.package }} test:${{ matrix.spec.type }}:coverage --forbid-only
|
||||
env:
|
||||
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 }}
|
||||
COPILOT_E2E_ENDPOINT: ${{ secrets.COPILOT_E2E_ENDPOINT }}
|
||||
|
||||
- name: Upload server test coverage results
|
||||
uses: codecov/codecov-action@v5
|
||||
@@ -170,7 +181,7 @@ jobs:
|
||||
if: ${{ always() && !contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled') }}
|
||||
run: node ./tools/copilot-result/index.js
|
||||
env:
|
||||
CHANNEL_ID: ${{ secrets.RELEASE_SLACK_CHNNEL_ID }}
|
||||
CHANNEL_ID: ${{ secrets.RELEASE_SLACK_CHANNEL_ID }}
|
||||
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
|
||||
BRANCH_SHA: ${{ github.sha }}
|
||||
BRANCH_NAME: ${{ github.ref }}
|
||||
@@ -180,7 +191,7 @@ jobs:
|
||||
if: ${{ always() && contains(needs.*.result, 'failure') }}
|
||||
run: node ./tools/copilot-result/index.js
|
||||
env:
|
||||
CHANNEL_ID: ${{ secrets.RELEASE_SLACK_CHNNEL_ID }}
|
||||
CHANNEL_ID: ${{ secrets.RELEASE_SLACK_CHANNEL_ID }}
|
||||
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
|
||||
BRANCH_SHA: ${{ github.sha }}
|
||||
BRANCH_NAME: ${{ github.ref }}
|
||||
@@ -190,7 +201,7 @@ jobs:
|
||||
if: ${{ always() && contains(needs.*.result, 'cancelled') && !contains(needs.*.result, 'failure') }}
|
||||
run: node ./tools/copilot-result/index.js
|
||||
env:
|
||||
CHANNEL_ID: ${{ secrets.RELEASE_SLACK_CHNNEL_ID }}
|
||||
CHANNEL_ID: ${{ secrets.RELEASE_SLACK_CHANNEL_ID }}
|
||||
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
|
||||
BRANCH_SHA: ${{ github.sha }}
|
||||
BRANCH_NAME: ${{ github.ref }}
|
||||
|
||||
@@ -100,6 +100,9 @@ jobs:
|
||||
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 }}
|
||||
# used for slack notifications
|
||||
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
|
||||
RELEASE_SLACK_CHANNEL_ID: ${{ secrets.RELEASE_SLACK_CHANNEL_ID }}
|
||||
METRICS_CUSTOMER_IO_TOKEN: ${{ secrets.METRICS_CUSTOMER_IO_TOKEN }}
|
||||
MAILER_SENDER: ${{ secrets.OAUTH_EMAIL_SENDER }}
|
||||
MAILER_USER: ${{ secrets.OAUTH_EMAIL_LOGIN }}
|
||||
@@ -162,7 +165,7 @@ jobs:
|
||||
if: ${{ always() && !contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled') }}
|
||||
run: node ./tools/changelog/index.js
|
||||
env:
|
||||
CHANNEL_ID: ${{ secrets.RELEASE_SLACK_CHNNEL_ID }}
|
||||
CHANNEL_ID: ${{ secrets.RELEASE_SLACK_CHANNEL_ID }}
|
||||
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
|
||||
DEPLOYED_URL: ${{ steps.set_info.outputs.deployed_url }}
|
||||
PREV_VERSION: ${{ needs.output-prev-version.outputs.prev }}
|
||||
@@ -178,7 +181,7 @@ jobs:
|
||||
method: chat.postMessage
|
||||
token: ${{ secrets.SLACK_BOT_TOKEN }}
|
||||
payload: |
|
||||
channel: ${{ secrets.RELEASE_SLACK_CHNNEL_ID }}
|
||||
channel: ${{ secrets.RELEASE_SLACK_CHANNEL_ID }}
|
||||
text: "<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|Backend deploy failed `${{ github.event.inputs.flavor }}`>"
|
||||
blocks:
|
||||
- type: section
|
||||
@@ -193,7 +196,7 @@ jobs:
|
||||
token: ${{ secrets.SLACK_BOT_TOKEN }}
|
||||
method: chat.postMessage
|
||||
payload: |
|
||||
channel: ${{ secrets.RELEASE_SLACK_CHNNEL_ID }}
|
||||
channel: ${{ secrets.RELEASE_SLACK_CHANNEL_ID }}
|
||||
text: "<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|Backend deploy cancelled `${{ github.event.inputs.flavor }}`>"
|
||||
blocks:
|
||||
- type: section
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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.1
|
||||
with:
|
||||
apiToken: ${{ secrets.CF_API_TOKEN }}
|
||||
accountId: ${{ secrets.CF_ACCOUNT_ID }}
|
||||
workingDirectory: 'tools/workers'
|
||||
packageManager: 'yarn'
|
||||
@@ -29,7 +29,6 @@ test-results
|
||||
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
|
||||
|
||||
Generated
+378
-1543
File diff suppressed because it is too large
Load Diff
+5
-11
@@ -15,28 +15,22 @@ 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"
|
||||
dotenvy = "0.15"
|
||||
file-format = { version = "0.26", features = ["reader"] }
|
||||
homedir = "0.3"
|
||||
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"
|
||||
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"
|
||||
@@ -44,7 +38,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" }
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"@blocksuite/blocks": "workspace:*",
|
||||
"@blocksuite/global": "workspace:*",
|
||||
"@blocksuite/inline": "workspace:*",
|
||||
"@blocksuite/presets": "workspace:*",
|
||||
"@blocksuite/store": "workspace:*",
|
||||
"@blocksuite/sync": "workspace:*"
|
||||
},
|
||||
@@ -36,6 +37,7 @@
|
||||
"./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"
|
||||
@@ -81,6 +83,9 @@
|
||||
"inline/types": [
|
||||
"dist/inline/types.d.ts"
|
||||
],
|
||||
"presets": [
|
||||
"dist/presets/index.d.ts"
|
||||
],
|
||||
"blocks": [
|
||||
"dist/blocks/index.d.ts"
|
||||
],
|
||||
@@ -98,5 +103,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.20.0"
|
||||
"version": "0.19.0"
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export * from '@blocksuite/presets';
|
||||
@@ -11,6 +11,7 @@
|
||||
{ "path": "../../blocks" },
|
||||
{ "path": "../../framework/global" },
|
||||
{ "path": "../../framework/inline" },
|
||||
{ "path": "../../presets" },
|
||||
{ "path": "../../framework/store" },
|
||||
{ "path": "../../framework/sync" }
|
||||
]
|
||||
|
||||
@@ -23,10 +23,10 @@
|
||||
"@blocksuite/icons": "^2.2.1",
|
||||
"@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.7",
|
||||
"file-type": "^20.0.0",
|
||||
"lit": "^3.2.0",
|
||||
"minimatch": "^10.0.1",
|
||||
@@ -42,5 +42,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.20.0"
|
||||
"version": "0.19.0"
|
||||
}
|
||||
|
||||
@@ -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,13 @@ 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 {
|
||||
BlockSelection,
|
||||
SurfaceSelection,
|
||||
TextSelection,
|
||||
} from '@blocksuite/block-std';
|
||||
import { Slice } from '@blocksuite/store';
|
||||
import { flip, offset } from '@floating-ui/dom';
|
||||
import { html, nothing } from 'lit';
|
||||
@@ -25,19 +26,25 @@ 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 }) => {
|
||||
@@ -83,14 +90,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 +109,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,7 +126,7 @@ 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() {
|
||||
@@ -167,21 +170,26 @@ 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(BlockSelection) ||
|
||||
!!this.selected?.is(SurfaceSelection);
|
||||
|
||||
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 +226,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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,18 +1,24 @@
|
||||
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 }) => {
|
||||
@@ -22,12 +28,11 @@ export const AttachmentDropOption = FileDropConfigExtension({
|
||||
);
|
||||
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
|
||||
|
||||
@@ -3,7 +3,10 @@ import type { ExtensionType } from '@blocksuite/store';
|
||||
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 +14,7 @@ import {
|
||||
|
||||
export const AttachmentBlockSpec: ExtensionType[] = [
|
||||
FlavourExtension('affine:attachment'),
|
||||
AttachmentBlockService,
|
||||
BlockViewExtension('affine:attachment', model => {
|
||||
return model.parent?.flavour === 'affine:surface'
|
||||
? literal`affine-edgeless-attachment`
|
||||
|
||||
@@ -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>
|
||||
`}
|
||||
>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 { 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);
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@blocksuite/affine-block-embed": "workspace:*",
|
||||
"@blocksuite/affine-block-surface": "workspace:*",
|
||||
"@blocksuite/affine-components": "workspace:*",
|
||||
"@blocksuite/affine-model": "workspace:*",
|
||||
"@blocksuite/affine-shared": "workspace:*",
|
||||
@@ -22,10 +23,10 @@
|
||||
"@blocksuite/icons": "^2.2.1",
|
||||
"@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.7",
|
||||
"lit": "^3.2.0",
|
||||
"minimatch": "^10.0.1",
|
||||
"zod": "^3.23.8"
|
||||
@@ -40,5 +41,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.20.0"
|
||||
"version": "0.19.0"
|
||||
}
|
||||
|
||||
@@ -4,10 +4,10 @@ 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 { BlockSelection } from '@blocksuite/block-std';
|
||||
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 { refreshBookmarkUrlData } from './utils.js';
|
||||
@@ -15,12 +15,6 @@ 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,
|
||||
})
|
||||
);
|
||||
|
||||
private _fetchAbortController?: AbortController;
|
||||
|
||||
blockDraggable = true;
|
||||
@@ -76,12 +70,13 @@ export class BookmarkBlockComponent extends CaptionedBlockComponent<BookmarkBloc
|
||||
}
|
||||
|
||||
override renderBlock() {
|
||||
const selected = !!this.selected?.is(BlockSelection);
|
||||
return html`
|
||||
<div
|
||||
draggable="${this.blockDraggable ? 'true' : 'false'}"
|
||||
class=${classMap({
|
||||
'affine-bookmark-container': true,
|
||||
...this.selectedStyle$?.value,
|
||||
'selected-style': selected,
|
||||
})}
|
||||
style=${this.containerStyleMap}
|
||||
>
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
import { BlockViewExtension, FlavourExtension } from '@blocksuite/block-std';
|
||||
import {
|
||||
BlockViewExtension,
|
||||
CommandExtension,
|
||||
FlavourExtension,
|
||||
} from '@blocksuite/block-std';
|
||||
import type { ExtensionType } from '@blocksuite/store';
|
||||
import { literal } from 'lit/static-html.js';
|
||||
|
||||
import { BookmarkBlockAdapterExtensions } from './adapters/extension.js';
|
||||
import { commands } from './commands/index.js';
|
||||
|
||||
export const BookmarkBlockSpec: ExtensionType[] = [
|
||||
FlavourExtension('affine:bookmark'),
|
||||
CommandExtension(commands),
|
||||
BlockViewExtension('affine:bookmark', model => {
|
||||
return model.parent?.flavour === 'affine:surface'
|
||||
? literal`affine-edgeless-bookmark`
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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',
|
||||
};
|
||||
|
||||
@@ -2,11 +2,15 @@ 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 {
|
||||
BlockSelection,
|
||||
ShadowlessElement,
|
||||
SurfaceSelection,
|
||||
} 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';
|
||||
@@ -51,6 +55,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(BlockSelection) ||
|
||||
!!this.bookmark.selected?.is(SurfaceSelection);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
override render() {
|
||||
@@ -60,7 +72,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 +148,9 @@ export class BookmarkCard extends WithDisposable(ShadowlessElement) {
|
||||
`;
|
||||
}
|
||||
|
||||
@state()
|
||||
private accessor _isSelected = false;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor bookmark!: BookmarkBlockComponent;
|
||||
|
||||
|
||||
+1
-1
@@ -49,7 +49,7 @@ 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, 'cup', stopPropagation);
|
||||
this.disposables.addFromEvent(this, 'copy', stopPropagation);
|
||||
this.disposables.addFromEvent(this, 'paste', stopPropagation);
|
||||
}
|
||||
+34
-6
@@ -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 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,7 +122,7 @@ 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, 'cup', stopPropagation);
|
||||
this.disposables.addFromEvent(this, 'copy', stopPropagation);
|
||||
this.disposables.addFromEvent(this, 'paste', stopPropagation);
|
||||
}
|
||||
@@ -150,7 +180,6 @@ export class EmbedCardCreateModal extends WithDisposable(ShadowlessElement) {
|
||||
}
|
||||
| {
|
||||
mode: 'edgeless';
|
||||
onSave: (url: string) => void;
|
||||
};
|
||||
|
||||
@property({ attribute: false })
|
||||
@@ -181,7 +210,6 @@ export async function toggleEmbedCardCreateModal(
|
||||
}
|
||||
| {
|
||||
mode: 'edgeless';
|
||||
onSave: (url: string) => void;
|
||||
}
|
||||
): Promise<void> {
|
||||
host.selection.clear();
|
||||
+32
-29
@@ -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' });
|
||||
|
||||
@@ -281,7 +306,7 @@ 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, 'cup', stopPropagation);
|
||||
this.disposables.addFromEvent(this, 'copy', stopPropagation);
|
||||
this.disposables.addFromEvent(this, 'paste', stopPropagation);
|
||||
|
||||
@@ -368,20 +393,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 +418,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 +427,6 @@ export function toggleEmbedCardEditModal(
|
||||
embedCardEditModal.host = host;
|
||||
embedCardEditModal.viewType = viewType;
|
||||
embedCardEditModal.originalDocInfo = originalDocInfo;
|
||||
embedCardEditModal.onReset = onReset;
|
||||
embedCardEditModal.onSave = onSave;
|
||||
document.body.append(embedCardEditModal);
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
+35
-1
@@ -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
|
||||
);
|
||||
}
|
||||
@@ -1 +1,2 @@
|
||||
export * from './bookmark-card';
|
||||
export * from './embed-card-modal';
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
import { BookmarkBlockComponent } from './bookmark-block';
|
||||
import { BookmarkEdgelessBlockComponent } from './bookmark-edgeless-block';
|
||||
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 +16,20 @@ 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
export * from './adapters';
|
||||
export * from './bookmark-block';
|
||||
export * from './bookmark-spec';
|
||||
export * from './commands';
|
||||
export * from './components';
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
"include": ["./src"],
|
||||
"references": [
|
||||
{ "path": "../block-embed" },
|
||||
{ "path": "../block-surface" },
|
||||
{ "path": "../components" },
|
||||
{ "path": "../model" },
|
||||
{ "path": "../shared" },
|
||||
|
||||
@@ -18,13 +18,12 @@
|
||||
"@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.7",
|
||||
"@types/mdast": "^4.0.4",
|
||||
"lit": "^3.2.0",
|
||||
"minimatch": "^10.0.1",
|
||||
@@ -41,5 +40,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.20.0"
|
||||
"version": "0.19.0"
|
||||
}
|
||||
|
||||
@@ -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, {
|
||||
|
||||
@@ -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: [
|
||||
|
||||
@@ -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, {
|
||||
|
||||
@@ -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,
|
||||
@@ -46,13 +40,13 @@ export class CodeClipboardController {
|
||||
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,9 +54,9 @@ 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;
|
||||
}
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -12,7 +12,6 @@ import {
|
||||
} 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 +27,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() {
|
||||
@@ -50,9 +46,7 @@ export class CodeBlockService extends BlockService {
|
||||
engine: createOnigurumaEngine(() => 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;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
BlockViewExtension,
|
||||
FlavourExtension,
|
||||
WidgetViewExtension,
|
||||
WidgetViewMapExtension,
|
||||
} from '@blocksuite/block-std';
|
||||
import type { ExtensionType } from '@blocksuite/store';
|
||||
import { literal, unsafeStatic } from 'lit/static-html.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,
|
||||
|
||||
@@ -32,7 +32,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';
|
||||
@@ -384,8 +383,7 @@ 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
|
||||
|
||||
@@ -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';
|
||||
@@ -132,7 +132,7 @@ export class AffineCodeToolbar extends WithDisposable(LitElement) {
|
||||
?disabled=${this.context.doc.readonly}
|
||||
@click=${() => this._toggleMoreMenu()}
|
||||
>
|
||||
${MoreVerticalIcon()}
|
||||
${MoreVerticalIcon}
|
||||
</editor-icon-button>
|
||||
</editor-toolbar>
|
||||
`;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.7",
|
||||
"@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"
|
||||
}
|
||||
|
||||
@@ -9,7 +9,11 @@ import {
|
||||
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';
|
||||
@@ -38,7 +42,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 +244,7 @@ export class DataViewBlockComponent extends CaptionedBlockComponent<DataViewBloc
|
||||
return nothing;
|
||||
}
|
||||
return html` <div class="database-ops" @click="${this._clickDatabaseOps}">
|
||||
${MoreHorizontalIcon()}
|
||||
${MoreHorizontalIcon}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
import { DataViewBlockComponent } from './data-view-block';
|
||||
import type { DataViewBlockModel } from './data-view-model';
|
||||
|
||||
export function effects() {
|
||||
customElements.define('affine-data-view', DataViewBlockComponent);
|
||||
}
|
||||
|
||||
declare global {
|
||||
namespace BlockSuite {
|
||||
interface BlockModels {
|
||||
'affine:data-view': DataViewBlockModel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,10 +23,10 @@
|
||||
"@blocksuite/icons": "^2.2.1",
|
||||
"@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.7",
|
||||
"@types/mdast": "^4.0.4",
|
||||
"date-fns": "^4.0.0",
|
||||
"lit": "^3.2.0",
|
||||
@@ -44,5 +44,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.20.0"
|
||||
"version": "0.19.0"
|
||||
}
|
||||
|
||||
@@ -6,17 +6,162 @@ import {
|
||||
import {
|
||||
BlockHtmlAdapterExtension,
|
||||
type BlockHtmlAdapterMatcher,
|
||||
HastUtils,
|
||||
type InlineHtmlAST,
|
||||
TextUtils,
|
||||
} from '@blocksuite/affine-shared/adapters';
|
||||
import { nanoid } from '@blocksuite/store';
|
||||
import type { Element } from 'hast';
|
||||
|
||||
import { processTable } from './utils';
|
||||
|
||||
const DATABASE_NODE_TYPES = new Set(['table', 'thead', 'tbody', 'th', 'tr']);
|
||||
|
||||
export const databaseBlockHtmlAdapterMatcher: BlockHtmlAdapterMatcher = {
|
||||
flavour: DatabaseBlockSchema.model.flavour,
|
||||
toMatch: () => false,
|
||||
toMatch: o =>
|
||||
HastUtils.isElement(o.node) && DATABASE_NODE_TYPES.has(o.node.tagName),
|
||||
fromMatch: o => o.node.flavour === DatabaseBlockSchema.model.flavour,
|
||||
toBlockSnapshot: {},
|
||||
toBlockSnapshot: {
|
||||
enter: (o, context) => {
|
||||
if (!HastUtils.isElement(o.node)) {
|
||||
return;
|
||||
}
|
||||
const { walkerContext } = context;
|
||||
if (o.node.tagName === 'table') {
|
||||
const tableHeader = HastUtils.querySelector(o.node, 'thead');
|
||||
if (!tableHeader) {
|
||||
return;
|
||||
}
|
||||
const tableHeaderRow = HastUtils.querySelector(tableHeader, 'tr');
|
||||
if (!tableHeaderRow) {
|
||||
return;
|
||||
}
|
||||
// Table header row as database header row
|
||||
const viewsColumns = tableHeaderRow.children.map(() => {
|
||||
return {
|
||||
id: nanoid(),
|
||||
hide: false,
|
||||
width: 180,
|
||||
};
|
||||
});
|
||||
|
||||
// Build database cells from table body rows
|
||||
const cells = Object.create(null);
|
||||
const tableBody = HastUtils.querySelector(o.node, 'tbody');
|
||||
tableBody?.children.forEach(row => {
|
||||
const rowId = nanoid();
|
||||
cells[rowId] = Object.create(null);
|
||||
(row as Element).children.forEach((cell, index) => {
|
||||
const column = viewsColumns[index];
|
||||
if (!column) {
|
||||
return;
|
||||
}
|
||||
cells[rowId][column.id] = {
|
||||
columnId: column.id,
|
||||
value: TextUtils.createText(
|
||||
(cell as Element).children
|
||||
.map(child => ('value' in child ? child.value : ''))
|
||||
.join('')
|
||||
),
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
// Build database columns from table header row
|
||||
const columns = tableHeaderRow.children.flatMap((_child, index) => {
|
||||
const column = viewsColumns[index];
|
||||
if (!column) {
|
||||
return [];
|
||||
}
|
||||
return {
|
||||
type: index === 0 ? 'title' : 'rich-text',
|
||||
name: (_child as Element).children
|
||||
.map(child => ('value' in child ? child.value : ''))
|
||||
.join(''),
|
||||
data: {},
|
||||
id: column.id,
|
||||
};
|
||||
});
|
||||
|
||||
walkerContext.openNode(
|
||||
{
|
||||
type: 'block',
|
||||
id: nanoid(),
|
||||
flavour: 'affine:database',
|
||||
props: {
|
||||
views: [
|
||||
{
|
||||
id: nanoid(),
|
||||
name: 'Table View',
|
||||
mode: 'table',
|
||||
columns: [],
|
||||
filter: {
|
||||
type: 'group',
|
||||
op: 'and',
|
||||
conditions: [],
|
||||
},
|
||||
header: {
|
||||
titleColumn: viewsColumns[0]?.id,
|
||||
iconColumn: 'type',
|
||||
},
|
||||
},
|
||||
],
|
||||
title: {
|
||||
'$blocksuite:internal:text$': true,
|
||||
delta: [],
|
||||
},
|
||||
cells,
|
||||
columns,
|
||||
},
|
||||
children: [],
|
||||
},
|
||||
'children'
|
||||
);
|
||||
walkerContext.setNodeContext('affine:table:rowid', Object.keys(cells));
|
||||
walkerContext.skipChildren(1);
|
||||
}
|
||||
|
||||
// The first child of each table body row is the database title cell
|
||||
if (o.node.tagName === 'tr') {
|
||||
const { deltaConverter } = context;
|
||||
const firstChild = o.node.children[0];
|
||||
if (!firstChild) {
|
||||
return;
|
||||
}
|
||||
walkerContext
|
||||
.openNode({
|
||||
type: 'block',
|
||||
id:
|
||||
(
|
||||
walkerContext.getNodeContext(
|
||||
'affine:table:rowid'
|
||||
) as Array<string>
|
||||
).shift() ?? nanoid(),
|
||||
flavour: 'affine:paragraph',
|
||||
props: {
|
||||
text: {
|
||||
'$blocksuite:internal:text$': true,
|
||||
delta: deltaConverter.astToDelta(firstChild),
|
||||
},
|
||||
type: 'text',
|
||||
},
|
||||
children: [],
|
||||
})
|
||||
.closeNode();
|
||||
walkerContext.skipAllChildren();
|
||||
}
|
||||
},
|
||||
leave: (o, context) => {
|
||||
if (!HastUtils.isElement(o.node)) {
|
||||
return;
|
||||
}
|
||||
const { walkerContext } = context;
|
||||
if (o.node.tagName === 'table') {
|
||||
walkerContext.closeNode();
|
||||
}
|
||||
},
|
||||
},
|
||||
fromBlockSnapshot: {
|
||||
enter: (o, context) => {
|
||||
const { walkerContext } = context;
|
||||
|
||||
@@ -7,7 +7,9 @@ import {
|
||||
BlockMarkdownAdapterExtension,
|
||||
type BlockMarkdownAdapterMatcher,
|
||||
type MarkdownAST,
|
||||
TextUtils,
|
||||
} from '@blocksuite/affine-shared/adapters';
|
||||
import { nanoid } from '@blocksuite/store';
|
||||
import type { TableRow } from 'mdast';
|
||||
|
||||
import { processTable } from './utils';
|
||||
@@ -22,7 +24,123 @@ export const databaseBlockMarkdownAdapterMatcher: BlockMarkdownAdapterMatcher =
|
||||
flavour: DatabaseBlockSchema.model.flavour,
|
||||
toMatch: o => isDatabaseNode(o.node),
|
||||
fromMatch: o => o.node.flavour === DatabaseBlockSchema.model.flavour,
|
||||
toBlockSnapshot: {},
|
||||
toBlockSnapshot: {
|
||||
enter: (o, context) => {
|
||||
const { walkerContext } = context;
|
||||
if (o.node.type === 'table') {
|
||||
const viewsColumns = o.node.children[0]?.children.map(() => {
|
||||
return {
|
||||
id: nanoid(),
|
||||
hide: false,
|
||||
width: 180,
|
||||
};
|
||||
});
|
||||
const cells = Object.create(null);
|
||||
o.node.children.slice(1).forEach(row => {
|
||||
const rowId = nanoid();
|
||||
cells[rowId] = Object.create(null);
|
||||
row.children.slice(1).forEach((cell, index) => {
|
||||
const column = viewsColumns?.[index + 1];
|
||||
if (!column) {
|
||||
return;
|
||||
}
|
||||
cells[rowId][column.id] = {
|
||||
columnId: column.id,
|
||||
value: TextUtils.createText(
|
||||
cell.children
|
||||
.map(child => ('value' in child ? child.value : ''))
|
||||
.join('')
|
||||
),
|
||||
};
|
||||
});
|
||||
});
|
||||
const columns = o.node.children[0]?.children.map((_child, index) => {
|
||||
return {
|
||||
type: index === 0 ? 'title' : 'rich-text',
|
||||
name: _child.children
|
||||
.map(child => ('value' in child ? child.value : ''))
|
||||
.join(''),
|
||||
data: {},
|
||||
id: viewsColumns?.[index]?.id,
|
||||
};
|
||||
});
|
||||
walkerContext.openNode(
|
||||
{
|
||||
type: 'block',
|
||||
id: nanoid(),
|
||||
flavour: 'affine:database',
|
||||
props: {
|
||||
views: [
|
||||
{
|
||||
id: nanoid(),
|
||||
name: 'Table View',
|
||||
mode: 'table',
|
||||
columns: [],
|
||||
filter: {
|
||||
type: 'group',
|
||||
op: 'and',
|
||||
conditions: [],
|
||||
},
|
||||
header: {
|
||||
titleColumn: viewsColumns?.[0]?.id,
|
||||
iconColumn: 'type',
|
||||
},
|
||||
},
|
||||
],
|
||||
title: {
|
||||
'$blocksuite:internal:text$': true,
|
||||
delta: [],
|
||||
},
|
||||
cells,
|
||||
columns,
|
||||
},
|
||||
children: [],
|
||||
},
|
||||
'children'
|
||||
);
|
||||
walkerContext.setNodeContext(
|
||||
'affine:table:rowid',
|
||||
Object.keys(cells)
|
||||
);
|
||||
walkerContext.skipChildren(1);
|
||||
}
|
||||
|
||||
if (o.node.type === 'tableRow') {
|
||||
const { deltaConverter } = context;
|
||||
const firstChild = o.node.children[0];
|
||||
if (!firstChild) {
|
||||
return;
|
||||
}
|
||||
walkerContext
|
||||
.openNode({
|
||||
type: 'block',
|
||||
id:
|
||||
(
|
||||
walkerContext.getNodeContext(
|
||||
'affine:table:rowid'
|
||||
) as Array<string>
|
||||
).shift() ?? nanoid(),
|
||||
flavour: 'affine:paragraph',
|
||||
props: {
|
||||
text: {
|
||||
'$blocksuite:internal:text$': true,
|
||||
delta: deltaConverter.astToDelta(firstChild),
|
||||
},
|
||||
type: 'text',
|
||||
},
|
||||
children: [],
|
||||
})
|
||||
.closeNode();
|
||||
walkerContext.skipAllChildren();
|
||||
}
|
||||
},
|
||||
leave: (o, context) => {
|
||||
const { walkerContext } = context;
|
||||
if (o.node.type === 'table') {
|
||||
walkerContext.closeNode();
|
||||
}
|
||||
},
|
||||
},
|
||||
fromBlockSnapshot: {
|
||||
enter: (o, context) => {
|
||||
const { walkerContext, deltaConverter } = context;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { DatabaseBlockSchema } from '@blocksuite/affine-model';
|
||||
import {
|
||||
AdapterTextUtils,
|
||||
BlockNotionHtmlAdapterExtension,
|
||||
type BlockNotionHtmlAdapterMatcher,
|
||||
HastUtils,
|
||||
TextUtils,
|
||||
} from '@blocksuite/affine-shared/adapters';
|
||||
import { getTagColor } from '@blocksuite/data-view';
|
||||
import { type BlockSnapshot, nanoid } from '@blocksuite/store';
|
||||
@@ -219,7 +219,7 @@ export const databaseBlockNotionHtmlAdapterMatcher: BlockNotionHtmlAdapterMatche
|
||||
column.type = 'rich-text';
|
||||
row[column.id] = {
|
||||
columnId: column.id,
|
||||
value: AdapterTextUtils.createText(text),
|
||||
value: TextUtils.createText(text),
|
||||
};
|
||||
} else {
|
||||
row[column.id] = {
|
||||
@@ -235,11 +235,11 @@ export const databaseBlockNotionHtmlAdapterMatcher: BlockNotionHtmlAdapterMatche
|
||||
}
|
||||
if (
|
||||
column.type === 'rich-text' &&
|
||||
!AdapterTextUtils.isText(row[column.id].value)
|
||||
!TextUtils.isText(row[column.id].value)
|
||||
) {
|
||||
row[column.id] = {
|
||||
columnId: column.id,
|
||||
value: AdapterTextUtils.createText(row[column.id].value),
|
||||
value: TextUtils.createText(row[column.id].value),
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { DatabaseBlockModel } from '@blocksuite/affine-model';
|
||||
import type { Command } from '@blocksuite/block-std';
|
||||
import type { BlockCommands, Command } from '@blocksuite/block-std';
|
||||
import type { BlockModel, Store } from '@blocksuite/store';
|
||||
|
||||
import {
|
||||
@@ -8,14 +8,12 @@ import {
|
||||
} from './data-source';
|
||||
|
||||
export const insertDatabaseBlockCommand: Command<
|
||||
'selectedModels',
|
||||
'insertedDatabaseBlockId',
|
||||
{
|
||||
selectedModels?: BlockModel[];
|
||||
viewType: string;
|
||||
place?: 'after' | 'before';
|
||||
removeEmptyLine?: boolean;
|
||||
},
|
||||
{
|
||||
insertedDatabaseBlockId: string;
|
||||
}
|
||||
> = (ctx, next) => {
|
||||
const { selectedModels, viewType, place, removeEmptyLine, std } = ctx;
|
||||
@@ -67,3 +65,7 @@ export const initDatabaseBlock = (
|
||||
doc.addBlock('affine:paragraph', {}, parent.id);
|
||||
}
|
||||
};
|
||||
|
||||
export const commands: BlockCommands = {
|
||||
insertDatabaseBlock: insertDatabaseBlockCommand,
|
||||
};
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
import type { MenuOptions } from '@blocksuite/affine-components/context-menu';
|
||||
import { type DatabaseBlockModel } from '@blocksuite/affine-model';
|
||||
import { ConfigExtensionFactory } from '@blocksuite/block-std';
|
||||
|
||||
export interface DatabaseOptionsConfig {
|
||||
configure: (model: DatabaseBlockModel, options: MenuOptions) => MenuOptions;
|
||||
}
|
||||
|
||||
export const DatabaseConfigExtension =
|
||||
ConfigExtensionFactory<DatabaseOptionsConfig>('affine:database');
|
||||
|
||||
@@ -3,7 +3,6 @@ import type {
|
||||
ColumnUpdater,
|
||||
DatabaseBlockModel,
|
||||
} from '@blocksuite/affine-model';
|
||||
import { getSelectedModelsCommand } from '@blocksuite/affine-shared/commands';
|
||||
import { FeatureFlagService } from '@blocksuite/affine-shared/services';
|
||||
import {
|
||||
insertPositionToIndex,
|
||||
@@ -34,6 +33,8 @@ import {
|
||||
} from './properties/index.js';
|
||||
import {
|
||||
addProperty,
|
||||
applyCellsUpdate,
|
||||
applyPropertyUpdate,
|
||||
copyCellsByProperty,
|
||||
deleteRows,
|
||||
deleteView,
|
||||
@@ -167,6 +168,7 @@ export class DatabaseBlockDataSource extends DataSourceBase {
|
||||
columnId: propertyId,
|
||||
value: newValue,
|
||||
});
|
||||
applyCellsUpdate(this._model);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,6 +198,7 @@ export class DatabaseBlockDataSource extends DataSourceBase {
|
||||
insertToPosition,
|
||||
property.create(this.newPropertyName())
|
||||
);
|
||||
applyPropertyUpdate(this._model);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -279,6 +282,7 @@ export class DatabaseBlockDataSource extends DataSourceBase {
|
||||
propertyDataSet(propertyId: string, data: Record<string, unknown>): void {
|
||||
this._runCapture();
|
||||
this.updateProperty(propertyId, () => ({ data }));
|
||||
applyPropertyUpdate(this._model);
|
||||
}
|
||||
|
||||
propertyDataTypeGet(propertyId: string): TypeInstance | undefined {
|
||||
@@ -332,6 +336,7 @@ export class DatabaseBlockDataSource extends DataSourceBase {
|
||||
schema
|
||||
);
|
||||
copyCellsByProperty(this._model, copyId, id);
|
||||
applyPropertyUpdate(this._model);
|
||||
return id;
|
||||
}
|
||||
|
||||
@@ -360,6 +365,7 @@ export class DatabaseBlockDataSource extends DataSourceBase {
|
||||
propertyNameSet(propertyId: string, name: string): void {
|
||||
this.doc.captureSync();
|
||||
this.updateProperty(propertyId, () => ({ name }));
|
||||
applyPropertyUpdate(this._model);
|
||||
}
|
||||
|
||||
override propertyReadonlyGet(propertyId: string): boolean {
|
||||
@@ -414,6 +420,7 @@ export class DatabaseBlockDataSource extends DataSourceBase {
|
||||
}
|
||||
});
|
||||
updateCells(this._model, propertyId, cells);
|
||||
applyPropertyUpdate(this._model);
|
||||
}
|
||||
|
||||
rowAdd(insertPosition: InsertToPosition | number): string {
|
||||
@@ -510,9 +517,12 @@ export const databaseViewInitTemplate = (
|
||||
datasource.viewManager.viewAdd(viewType);
|
||||
};
|
||||
export const convertToDatabase = (host: EditorHost, viewType: string) => {
|
||||
const [_, ctx] = host.std.command.exec(getSelectedModelsCommand, {
|
||||
types: ['block', 'text'],
|
||||
});
|
||||
const [_, ctx] = host.std.command
|
||||
.chain()
|
||||
.getSelectedModels({
|
||||
types: ['block', 'text'],
|
||||
})
|
||||
.run();
|
||||
const { selectedModels } = ctx;
|
||||
const firstModel = selectedModels?.[0];
|
||||
if (!firstModel) return;
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
popMenu,
|
||||
popupTargetFromElement,
|
||||
} from '@blocksuite/affine-components/context-menu';
|
||||
import { DropIndicator } from '@blocksuite/affine-components/drop-indicator';
|
||||
import { DragIndicator } from '@blocksuite/affine-components/drag-indicator';
|
||||
import { PeekViewProvider } from '@blocksuite/affine-components/peek';
|
||||
import { toast } from '@blocksuite/affine-components/toast';
|
||||
import type { DatabaseBlockModel } from '@blocksuite/affine-model';
|
||||
@@ -48,10 +48,7 @@ import { computed, signal } from '@preact/signals-core';
|
||||
import { css, html, nothing, unsafeCSS } from 'lit';
|
||||
|
||||
import { popSideDetail } from './components/layout.js';
|
||||
import {
|
||||
DatabaseConfigExtension,
|
||||
type DatabaseOptionsConfig,
|
||||
} from './config.js';
|
||||
import type { DatabaseOptionsConfig } from './config.js';
|
||||
import { HostContextKey } from './context/host-context.js';
|
||||
import { DatabaseBlockDataSource } from './data-source.js';
|
||||
import { BlockRenderer } from './detail-panel/block-renderer.js';
|
||||
@@ -244,7 +241,7 @@ export class DatabaseBlockComponent extends CaptionedBlockComponent<DatabaseBloc
|
||||
}
|
||||
);
|
||||
|
||||
indicator = new DropIndicator();
|
||||
indicator = new DragIndicator();
|
||||
|
||||
onDrag = (evt: MouseEvent, id: string): (() => void) => {
|
||||
const result = getDropResult(evt);
|
||||
@@ -337,6 +334,11 @@ export class DatabaseBlockComponent extends CaptionedBlockComponent<DatabaseBloc
|
||||
this._dataSource.contextSet(HostContextKey, this.host);
|
||||
const id = currentViewStorage.getCurrentView(this.model.id);
|
||||
if (id && this.dataSource.viewManager.viewGet(id)) {
|
||||
console.log(
|
||||
'set current view',
|
||||
id,
|
||||
this._dataSource.viewManager.viewGet(id)
|
||||
);
|
||||
this.dataSource.viewManager.setCurrentView(id);
|
||||
}
|
||||
}
|
||||
@@ -346,7 +348,7 @@ export class DatabaseBlockComponent extends CaptionedBlockComponent<DatabaseBloc
|
||||
get optionsConfig(): DatabaseOptionsConfig {
|
||||
return {
|
||||
configure: (_model, options) => options,
|
||||
...this.std.getOptional(DatabaseConfigExtension.identifier),
|
||||
...this.std.getConfig('affine:database'),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
import type { DatabaseBlockModel } from '@blocksuite/affine-model';
|
||||
import { unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme';
|
||||
import { BlockComponent } from '@blocksuite/block-std';
|
||||
import { DatabaseListViewIcon } from '@blocksuite/icons/lit';
|
||||
import { css, html } from 'lit';
|
||||
|
||||
export class DatabaseDndPreviewBlockComponent extends BlockComponent<DatabaseBlockModel> {
|
||||
static override styles = css`
|
||||
.affine-database-preview-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
padding: 4px;
|
||||
box-sizing: border-box;
|
||||
|
||||
border-radius: 8px;
|
||||
background-color: ${unsafeCSSVarV2(
|
||||
'layer/background/overlayPanel',
|
||||
'#FBFBFC'
|
||||
)};
|
||||
}
|
||||
|
||||
.database-preview-content {
|
||||
padding: 4px 16px;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.database-preview-content > svg {
|
||||
color: ${unsafeCSSVarV2('icon/primary', '#77757D')};
|
||||
}
|
||||
|
||||
.database-preview-content > .text {
|
||||
color: var(--affine-text-primary-color);
|
||||
color: ${unsafeCSSVarV2('text/primary', '#121212')};
|
||||
font-size: 14px;
|
||||
line-height: 24px;
|
||||
}
|
||||
`;
|
||||
|
||||
override renderBlock() {
|
||||
return html`<div
|
||||
class="affine-database-preview-container"
|
||||
contenteditable="false"
|
||||
>
|
||||
<div class="database-preview-content">
|
||||
${DatabaseListViewIcon({ width: '24px', height: '24px' })}
|
||||
<span class="text">Database Block</span>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'affine-dnd-preview-database': DatabaseDndPreviewBlockComponent;
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,17 @@
|
||||
import { BlockViewExtension, FlavourExtension } from '@blocksuite/block-std';
|
||||
import {
|
||||
BlockViewExtension,
|
||||
CommandExtension,
|
||||
FlavourExtension,
|
||||
} from '@blocksuite/block-std';
|
||||
import type { ExtensionType } from '@blocksuite/store';
|
||||
import { literal } from 'lit/static-html.js';
|
||||
|
||||
import { DatabaseBlockAdapterExtensions } from './adapters/extension.js';
|
||||
import { commands } from './commands.js';
|
||||
|
||||
export const DatabaseBlockSpec: ExtensionType[] = [
|
||||
FlavourExtension('affine:database'),
|
||||
CommandExtension(commands),
|
||||
BlockViewExtension('affine:database', literal`affine-database`),
|
||||
DatabaseBlockAdapterExtensions,
|
||||
].flat();
|
||||
|
||||
@@ -76,6 +76,10 @@ export class BlockRenderer
|
||||
return this.host?.doc.getBlock(this.rowId)?.model;
|
||||
}
|
||||
|
||||
get service() {
|
||||
return this.host.std.getService('affine:database');
|
||||
}
|
||||
|
||||
override connectedCallback() {
|
||||
super.connectedCallback();
|
||||
if (this.model && this.model.text) {
|
||||
@@ -127,7 +131,7 @@ export class BlockRenderer
|
||||
.attributesSchema=${this.attributesSchema}
|
||||
.attributeRenderer=${this.attributeRenderer}
|
||||
.embedChecker=${this.inlineManager.embedChecker}
|
||||
.markdownMatches=${this.inlineManager.markdownMatches}
|
||||
.markdownShortcutHandler=${this.inlineManager.markdownShortcutHandler}
|
||||
class="inline-editor"
|
||||
></rich-text>
|
||||
`;
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import {
|
||||
CodeBlockModel,
|
||||
type DatabaseBlockModel,
|
||||
ListBlockModel,
|
||||
ParagraphBlockModel,
|
||||
type RootBlockModel,
|
||||
import type {
|
||||
DatabaseBlockModel,
|
||||
RootBlockModel,
|
||||
} from '@blocksuite/affine-model';
|
||||
import { REFERENCE_NODE } from '@blocksuite/affine-shared/consts';
|
||||
import { TelemetryProvider } from '@blocksuite/affine-shared/services';
|
||||
import type { AffineTextAttributes } from '@blocksuite/affine-shared/types';
|
||||
import { createDefaultDoc, matchModels } from '@blocksuite/affine-shared/utils';
|
||||
import {
|
||||
createDefaultDoc,
|
||||
matchFlavours,
|
||||
} from '@blocksuite/affine-shared/utils';
|
||||
import { type EditorHost, ShadowlessElement } from '@blocksuite/block-std';
|
||||
import type { DetailSlotProps, SingleView } from '@blocksuite/data-view';
|
||||
import { SignalWatcher, WithDisposable } from '@blocksuite/global/utils';
|
||||
@@ -72,10 +72,10 @@ export class NoteRenderer
|
||||
note.root.children
|
||||
.find(child => child.flavour === 'affine:note')
|
||||
?.children.find(block =>
|
||||
matchModels(block, [
|
||||
ParagraphBlockModel,
|
||||
ListBlockModel,
|
||||
CodeBlockModel,
|
||||
matchFlavours(block, [
|
||||
'affine:paragraph',
|
||||
'affine:list',
|
||||
'affine:code',
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import type { insertDatabaseBlockCommand } from './commands';
|
||||
import { CenterPeek } from './components/layout';
|
||||
import { DatabaseTitle } from './components/title';
|
||||
import type { DatabaseOptionsConfig } from './config';
|
||||
import { DatabaseBlockComponent } from './database-block';
|
||||
import { DatabaseDndPreviewBlockComponent } from './database-dnd-preview-block';
|
||||
import { BlockRenderer } from './detail-panel/block-renderer';
|
||||
import { NoteRenderer } from './detail-panel/note-renderer';
|
||||
import { LinkCell, LinkCellEditing } from './properties/link/cell-renderer';
|
||||
@@ -36,9 +37,27 @@ export function effects() {
|
||||
customElements.define('database-datasource-block-renderer', BlockRenderer);
|
||||
customElements.define('affine-database-link-node', LinkNode);
|
||||
customElements.define('affine-database', DatabaseBlockComponent);
|
||||
|
||||
customElements.define(
|
||||
'affine-dnd-preview-database',
|
||||
DatabaseDndPreviewBlockComponent
|
||||
);
|
||||
}
|
||||
|
||||
declare global {
|
||||
namespace BlockSuite {
|
||||
interface BlockConfigs {
|
||||
'affine:database': Partial<DatabaseOptionsConfig>;
|
||||
}
|
||||
|
||||
interface CommandContext {
|
||||
insertedDatabaseBlockId?: string;
|
||||
}
|
||||
|
||||
interface Commands {
|
||||
/**
|
||||
* insert a database block after or before the current block selection
|
||||
* @param latex the LaTeX content. A input dialog will be shown if not provided
|
||||
* @param removeEmptyLine remove the current block if it is empty
|
||||
* @param place where to insert the LaTeX block
|
||||
* @returns the id of the inserted LaTeX block
|
||||
*/
|
||||
insertDatabaseBlock: typeof insertDatabaseBlockCommand;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import type * as CommandType from '@blocksuite/affine-shared/commands';
|
||||
|
||||
declare type _GLOBAL_ = typeof CommandType;
|
||||
|
||||
export * from './adapters';
|
||||
export * from './commands';
|
||||
export * from './config';
|
||||
export type { DatabaseOptionsConfig } from './config';
|
||||
export * from './data-source';
|
||||
export * from './database-block';
|
||||
export * from './database-spec';
|
||||
|
||||
@@ -221,7 +221,7 @@ export class RichTextCell extends BaseRichTextCell {
|
||||
.attributesSchema=${this.attributesSchema}
|
||||
.attributeRenderer=${this.attributeRenderer}
|
||||
.embedChecker=${this.inlineManager?.embedChecker}
|
||||
.markdownMatches=${this.inlineManager?.markdownMatches}
|
||||
.markdownShortcutHandler=${this.inlineManager?.markdownShortcutHandler}
|
||||
.readonly=${true}
|
||||
class="affine-database-rich-text inline-editor"
|
||||
></rich-text>`
|
||||
@@ -518,14 +518,12 @@ export class RichTextCellEditing extends BaseRichTextCell {
|
||||
|
||||
override render() {
|
||||
return html`<rich-text
|
||||
data-disable-ask-ai
|
||||
data-not-block-text
|
||||
.yText=${this.value}
|
||||
.inlineEventSource=${this.topContenteditableElement}
|
||||
.attributesSchema=${this.attributesSchema}
|
||||
.attributeRenderer=${this.attributeRenderer}
|
||||
.embedChecker=${this.inlineManager?.embedChecker}
|
||||
.markdownMatches=${this.inlineManager?.markdownMatches}
|
||||
.markdownShortcutHandler=${this.inlineManager?.markdownShortcutHandler}
|
||||
.verticalScrollContainerGetter=${() =>
|
||||
this.topContenteditableElement?.host
|
||||
? getViewportElement(this.topContenteditableElement.host)
|
||||
|
||||
@@ -123,6 +123,10 @@ abstract class BaseTextCell extends BaseCellRenderer<Text> {
|
||||
return this.host?.std.get(DefaultInlineManagerExtension.identifier);
|
||||
}
|
||||
|
||||
get service() {
|
||||
return this.host?.std.getService('affine:database');
|
||||
}
|
||||
|
||||
get topContenteditableElement() {
|
||||
const databaseBlock =
|
||||
this.closest<DatabaseBlockComponent>('affine-database');
|
||||
@@ -187,7 +191,7 @@ export class HeaderAreaTextCell extends BaseTextCell {
|
||||
.attributesSchema="${this.attributesSchema}"
|
||||
.attributeRenderer="${this.attributeRenderer}"
|
||||
.embedChecker="${this.inlineManager?.embedChecker}"
|
||||
.markdownMatches="${this.inlineManager?.markdownMatches}"
|
||||
.markdownShortcutHandler="${this.inlineManager?.markdownShortcutHandler}"
|
||||
.readonly="${true}"
|
||||
class="data-view-header-area-rich-text"
|
||||
></rich-text>`;
|
||||
@@ -384,14 +388,12 @@ export class HeaderAreaTextCellEditing extends BaseTextCell {
|
||||
|
||||
override renderBlockText() {
|
||||
return html` <rich-text
|
||||
data-disable-ask-ai
|
||||
data-not-block-text
|
||||
.yText="${this.value}"
|
||||
.inlineEventSource="${this.topContenteditableElement}"
|
||||
.attributesSchema="${this.attributesSchema}"
|
||||
.attributeRenderer="${this.attributeRenderer}"
|
||||
.embedChecker="${this.inlineManager?.embedChecker}"
|
||||
.markdownMatches="${this.inlineManager?.markdownMatches}"
|
||||
.markdownShortcutHandler="${this.inlineManager?.markdownShortcutHandler}"
|
||||
.readonly="${this.readonly}"
|
||||
.enableClipboard="${false}"
|
||||
.verticalScrollContainerGetter="${() =>
|
||||
@@ -405,8 +407,6 @@ export class HeaderAreaTextCellEditing extends BaseTextCell {
|
||||
|
||||
override renderLinkedDoc(): TemplateResult {
|
||||
return html` <rich-text
|
||||
data-disable-ask-ai
|
||||
data-not-block-text
|
||||
.yText="${this.linkedDocTitle$.value}"
|
||||
.inlineEventSource="${this.topContenteditableElement}"
|
||||
.readonly="${this.readonly}"
|
||||
|
||||
@@ -65,4 +65,12 @@ export class DatabaseSelection extends BaseSelection {
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
namespace BlockSuite {
|
||||
interface Selection {
|
||||
database: typeof DatabaseSelection;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const DatabaseSelectionExtension = SelectionExtension(DatabaseSelection);
|
||||
|
||||
@@ -37,6 +37,24 @@ export function addProperty(
|
||||
return id;
|
||||
}
|
||||
|
||||
export function applyCellsUpdate(model: DatabaseBlockModel) {
|
||||
model.doc.updateBlock(model, {
|
||||
cells: model.cells,
|
||||
});
|
||||
}
|
||||
|
||||
export function applyPropertyUpdate(model: DatabaseBlockModel) {
|
||||
model.doc.updateBlock(model, {
|
||||
columns: model.columns,
|
||||
});
|
||||
}
|
||||
|
||||
export function applyViewsUpdate(model: DatabaseBlockModel) {
|
||||
model.doc.updateBlock(model, {
|
||||
views: model.views,
|
||||
});
|
||||
}
|
||||
|
||||
export function copyCellsByProperty(
|
||||
model: DatabaseBlockModel,
|
||||
fromId: Column['id'],
|
||||
@@ -138,6 +156,7 @@ export function moveViewTo(
|
||||
arr => insertPositionToIndex(position, arr)
|
||||
);
|
||||
});
|
||||
applyViewsUpdate(model);
|
||||
}
|
||||
|
||||
export function updateCell(
|
||||
@@ -236,5 +255,6 @@ export const updateView = <ViewData extends ViewBasicDataType>(
|
||||
return { ...v, ...update(v as ViewData) };
|
||||
});
|
||||
});
|
||||
applyViewsUpdate(model);
|
||||
};
|
||||
export const DATABASE_CONVERT_WHITE_LIST = ['affine:list', 'affine:paragraph'];
|
||||
|
||||
@@ -20,10 +20,10 @@
|
||||
"@blocksuite/global": "workspace:*",
|
||||
"@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.7",
|
||||
"@types/mdast": "^4.0.4",
|
||||
"lit": "^3.2.0",
|
||||
"minimatch": "^10.0.1",
|
||||
@@ -39,5 +39,5 @@
|
||||
"!src/__tests__",
|
||||
"!dist/__tests__"
|
||||
],
|
||||
"version": "0.20.0"
|
||||
"version": "0.19.0"
|
||||
}
|
||||
|
||||
@@ -22,10 +22,10 @@
|
||||
"@blocksuite/icons": "^2.2.1",
|
||||
"@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.7",
|
||||
"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"
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user